前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >数据结构(3)单链表

数据结构(3)单链表

作者头像
mumumu
发布于 2022-12-26 08:54:07
发布于 2022-12-26 08:54:07
27100
代码可运行
举报
文章被收录于专栏:blog1blog1
运行总次数:0
代码可运行

前前后后看了四天左右吧,一方面把单链表过了几遍,另一方面也补全了一些基础,诸如 &引用,结构体指针,封装 等等内容,感觉难点不是代码怎么敲,而是要想明白每个操作的逻辑以及一些容易忽略的边界条件,为什么要有这些边界条件,没有这个条件会影响到哪些特殊情况?想明白这些很重要。

单链表

建表
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct LNode{
    int data;//数据域
    struct LNode *next;//指向下一个结点的指针域
}LNode,*LinkList;

单链表本质就是一个结点指向下一个结点

  • 关于LNode和LinkList,目前的理解:
    • 首先这个结构体表示的是一个结点,结点内有两个成员——数据域和指针域,LNode是这个结构体的一个别名,所以可以用LNode* L,定义一个指向这个结点的指针。
    • LinkList是一个*结构体指针(详情见C结构体),表示LinkList是指向这个结构的指针
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LNode * L;
LinkList L;

所以上面这两个语句在含义上是相同的,都表示L是指向这个结构的指针

只不过

  • 使用LinkList时强调这是一个单链表
  • 使用LNode *时强调这是一个结点
头插法建立链表
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int HeadInsert(LinkList &L){
    int x;//你要插入的数据
    L = (LNode*) malloc(sizeof (LNode*));
    L->next = NULL;//创建头结点,并初始化为空链表
    LNode *s;
    scanf("%d",&x);
    while(x!=9999) {
        s = (LNode *) malloc(sizeof(LNode *));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scanf("%d",&x);
    }
    return OK;
}

头插,顾名思义就是每次插入新的结点时,都插在头结点的后面,所以对应步骤为

  1. 创建头结点(开空间创建结点,并next指向NULL)
  2. 创建s结点(你要插入元素进去的结点)
  3. 输入插入值,进入循环
    • 为s申请空间
    • s->data = x 插入数据域
    • 通过指针域变换操作把s插到头结点L的后面
    • 输入插入值,开启新一轮循环
插入
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int Insert (LinkList &L,int i,int e){
    if(i<1){
        return ERROR;
    }
    LNode* p;
    int j = 0;//p当前指向第j个结点,0就是指向头结点
    p = L;//p先指向头指针
    while(p!=NULL && j<i-1){
        p = p->next;
        j++;
    }
    if(p==NULL){
        return ERROR;//假设一共有8个结点,要在i=10处插,则循环完一遍后j=9,但是链表中第9位没有结点
    }
    LNode *s = (LNode *) malloc(sizeof (LNode*));
    s->next = p->next;
    p->next = s;
    s->data = e;
    return OK;
}

找到插入位置的前一个节点p,新建s插进去即可

注意边界条件:p == NULL 的情况(你要进入位置的前一个结点是空结点)

后插(在指定结点的后面插入)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int InsertNext(LNode *p,int e){
    if(p == NULL){
        return ERROR;//再议
    }
    LNode *s = (LNode*) malloc(sizeof (LNode*));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return OK;
}
  • 因为强调的是在一个结点后面插,所以函数的形参是LNode*类型的
  • 然后申请空间插入即可
前插(在指定结点之前插入)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int InsertPrior (LNode *p,int e){
    if(p == NULL){
        return ERROR;
    }
    LNode *s;
    s = (LNode *) malloc(sizeof (LNode*));
    if(s == NULL){
        return ERROR;//内存分配失败,是每个场景都可能出现的问题
    }
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data = e;
    return OK;
}

单链表只能窥探一个指定结点之后的所有结点,之前的结点是未知的,所有想要在一个结点的前面插入一个元素,就要进行一波偷天换日,具体步骤如下:

  • 新建结点s,通过指针域变换插入到p结点的后面
  • s的数据域变为p的数据域
  • p的数据域变为要插入的e
删除
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int Delete(LinkList &L,int i,int *e){
    if(i<1){
        return ERROR;
    }
    LNode *p;
    int j = 0;
    p = L;
    if(p!=NULL && j<i-1){
        p = p->next;
        j++;
    }
    if(p == NULL){	
        return ERROR;//p是头结点
    }
    if(p->next == NULL){
        return ERROR;//要删除的结点不存在
    }
    LNode *s = p->next;//为什么不用开空间:目前理解:插入是新插的结点,是新结点,所以需要申请空间,这里只是定义s就是p的后继结点,原本就有
    *e = s->data;
    p->next = s->next;
    free(s);
    return OK;
}

找到要删除结点的前一个结点p,删除它的后继s即可

  • 定义s就是p的后继结点,原本就有,不需要申请空间

边界情况

  • i < 1:输入i值不合法
  • p->next == NULL:要删除的结点不存在
删除指定结点
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int Deletespecial(LNode *p){
    if(p == NULL){
        return ERROR;
    }
    LNode *s = p->next;//4
    p->data = s->data;//p不能是最后一个结点//3变为4
    p->next = s->next;//3的后面是5
    free(s);//删除原本的4
    return OK;
}
按位查找
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int GetElem(LinkList L,int i,int *e){
    if(i<0){
        return NULL;
    }
    LNode *p;
    int j = 0;
    p = L;
    while(p!=NULL && j<i){
        p = p->next;
        j++;
    }
    *e = p->data;
    printf("第%d个元素是%d\n",i,*e);
    return OK;
}

循环找到第i个结点返回其值即可

按值查找
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int LocateElem(LinkList &L,int e){
    LNode *p;
    int j = 0;
    p = L;
    while(p!=NULL){
        p = p->next;
        j++;
        if(p->data == e){
            break;
        }
    }
    printf("值为%d的结点的位序是%d\n",e,j);
    return j;
}

从第一个结点开始循环,直到p->data == e时结束循环,返回j值

输出
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int Print(LinkList L){
    LNode *p = L->next;//p是第一个结点
    while(p!=NULL){
        printf("%d\t",p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}
主函数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <stdlib.h>
#include "L.h"

int main (){
    LinkList L;
    int e;
    HeadInsert(L);//输入1,2,3
    Delete(L,2,&e);//删除2
    Insert(L,2,4);//在第2位插入4:    3 4 1
    InsertNext(L->next,5);//在第2位后面插入5     3 5 4 1
    InsertPrior(L->next->next,6);//在第2位前面插入6    3 6 5 4 1
    Deletespecial(L->next->next->next->next);//删除第四个结点
    InsertNext(L,9);
    Print(L);
    GetElem(L,2,&e);
    LocateElem(L,5);
}
运行:初始插入1,2,3
封装

不难发现,有一些操作的具体实现可以引用另一个操作,直接举栗子🌰,插入操作就是先找到插入结点的前一个结点,再在它的后面插一个新的结点,这不就是GetElem + InsertNext,就是先按值查找后插,所以我们注意到,在后插操作有一个边界条件是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(p==NULL){
    return ERROR;
}

当时的我一直在想,为什么输入的结点还可以为NULL,但如果把按位查找和后插操作直接拿来放在插入操作中,可以写为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//使用前需要声明这两个函数
int InsertNext(LNode *p,int e);
LNode *GetElem(LinkList L,int i);

int Insert (LinkList &L,int i,int e){
    if(i<1){
        return ERROR;
    }
    LNode *p = GetElem(L,i-1);//这里需要把函数的返回值改为返回结点类型
	InsertNext(p,e);
}

//把按位查找函数的返回值改为返回结点类型
LNode * GetElem(LinkList L,int i){
    if(i<0){
        return NULL;
    }
    LNode *p;
    int j = 0;
    p = L;
    while(p!=NULL && j<i){
        p = p->next;
        j++;
    }
    return p;
}

那么看一下按位查找函数 ,如果i<0,会返回NULL值,就会出现p==NULL传给后插 操作,那么这个边界条件的作用就会显现。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
数据结构 第4讲 单链表
链表是线性表的链式存储方式,逻辑上相邻的数据在计算机内的存储位置不一定相邻,那么怎么表示逻辑上的相邻关系呢?可以给每个元素附加一个指针域,指向下一个元素的存储位置。如图所示:
rainchxy
2018/09/13
6700
数据结构 第4讲 单链表
C语言单链表实现初始化、创建、增、删、查等基本操作(详细)
LNode *L ;             //声明一个指向单链表第一个结点的指针 (强调这是一个结点用LNode*)
莫浅子
2022/11/18
1.6K0
单链表
线性表的链式表示和实现       线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以使不连续的)。因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点。      结点包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称做指针或链。n
猿人谷
2018/01/17
9760
单链表
单链表基础练习
#include<stdio.h> #include<stdlib.h> #define OK 1 #define ERROR -1 typedef struct LNode { int data; struct LNode *next; }LNode, *LinkList; LinkList createlist(LinkList L, int n); //采用尾插法创建链表 LinkList createlist_tail(LinkList L, int
Enterprise_
2019/02/21
5620
单链表(带头结点)
运行结果: 注意:这是带头结点的单链表,所以0号节点是头结点,查找0号结点是可以查找成功的。我并没有给0号节点数据域赋值,根据个人情况去做
别团等shy哥发育
2023/02/25
3660
单链表(带头结点)
两个非递增的有序链表的合并
已知两个带头结点的非递增有序的单链表A和B,设计算法将两个单链表合并成一个非递增有序的单链表C.要求单链表C仍使用原来两个链表的存储空间
别团等shy哥发育
2023/02/27
8720
【数据结构】链表[通俗易懂]
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/115596.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/10
3840
【数据结构】详细剖析线性表
经过这段时间的学习与分享,想必大家跟我一样都已经对线性表的相关内容比较熟悉了。为了更好的巩固线性表相关的知识点,下面我们一起将线性表这个章节的内容梳理一遍吧。
蒙奇D索隆
2024/01/01
2440
【数据结构】详细剖析线性表
【数据结构】C语言实现单链表的基本操作
大家好,很高兴又和大家见面啦!!! 在上一篇中,我们详细介绍了单链表的两种创建方式——头插法与尾插法,相信大家现在对这两种方式都已经掌握了。今天咱们将继续介绍单链表的基本操作——查找、插入与删除。在开始今天的内容之前,我们先通过尾插法创建一个单链表,如下所示:
蒙奇D索隆
2023/12/29
6240
【数据结构】C语言实现单链表的基本操作
java算法刷题00——数据结构基础知识回顾
数据、数据元素(如一个账号)、数据项(密码、昵称)、数据结构(具有关系的一组数据元素集合,联想汉字的结构其实就是具有布局关系的符号组合)、数据对象(具有相同性质的数据元素的集合、一家海底捞的排队信息可以看作数据结构、全国所有门店排队信息看做同一个数据对象,它们虽无直接关系,但是具有同样的联系)
半旧518
2022/10/26
2890
c++单链表的基本操作(全)
俩个基本插入方法 #include <bits/stdc++.h> using namespace std; typedef struct LNode { int date; //节点的数据域 struct LNode *next; //节点的指针域 }LNode,*LinkList; // LinkList 为指向结构体LNode的指针类型 bool Initlist_L(LinkList &L) //构造一个空的单链表L { L = new
莫浅子
2022/11/18
6200
c++单链表的基本操作(全)
数据结构 第4-2讲 双向链表
链表是线性表的链式存储方式,逻辑上相邻的数据在计算机内的存储位置不一定相邻,那么怎么表示逻辑上的相邻关系呢?
rainchxy
2018/09/13
7230
数据结构 第4-2讲 双向链表
数据结构——链表
链式存储结构 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻有关术语 结点:数据元素的存储映像。由数据域和指针域两部分组成 - 数据域:存储元素数值数据 - 指针域:存储直接后继结点的存储位置 链表:n 个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构 - 单链表 - 结点只有一个指针域的链表,称为单链表或线性链表 - 双链表 - 有两个指针域的链表,称为双链表 - 循环链表 - 首尾相接的链表称为循环链表 头指针
ruochen
2021/06/28
6860
数据结构——链表
数据结构代码题-链表
2.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
愷龍
2023/10/16
4050
数据结构代码题-链表
【数据结构】线性表代码实现:顺序存储结构 | 链式存储结构
单链表插入:头插法(往前插入)void head_insert(Node*node,int key)
CtrlX
2022/11/18
1.9K0
【数据结构】线性表代码实现:顺序存储结构 | 链式存储结构
【数据结构】线性表代码实现:顺序存储结构 | 链式存储结构
单链表插入:头插法(往前插入)void head_insert(Node*node,int key)
CtrlX
2023/03/21
1.5K0
【数据结构】线性表代码实现:顺序存储结构 | 链式存储结构
单链表反转的分析及实现
我先画一个单链表,这个单链表有4个元素。我的思路就是,每次把第二个元素提到最前面来。比如下面是第一次交换,我们先让头结点的next域指向结点a2,再让结点a1的next域指向结点a3,最后将结点a2的
猿人谷
2018/01/17
2.3K0
单链表反转的分析及实现
数据结构 单链表&顺序表
根据文章内容总结的摘要
Kindear
2018/01/03
2.7K0
数据结构 单链表&顺序表
【图解数据结构】 线性表
1.线性表的定义 若将线性表记为(a1,...,ai-1,ai,ai+1,...,an),则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素。
撸码那些事
2018/06/21
1.3K5
数据结构之线性表
所谓顺序表就是顺序存储的线性表。顺序存储是用一组地址连续的存储单元依次存放线性表中各个元素的存储结构。
C语言与CPP编程
2020/12/02
8570
数据结构之线性表
推荐阅读
相关推荐
数据结构 第4讲 单链表
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文