考研笔记·第二章·线性表·链表

线性表·线性表的链式表示

一、单链表相关概念

1.定义

  • 单链表是线性表的链式存储

  • 通过一组任意的存储单元来存储线性表中的数据元素

  • 对每个链表结点,除存放元素自身的信息外,还需要存放一个指向其后继的指针

  • 节点类型和指针定义

    • typedef struct LNode{				//定义单链表节点类型
      	ELemType data;					//数据域
      	struct LNode *next;				//指针域
      }LNode,*LinkList;
      

2.特点

  • 通过“链”建立起数据元素之间的逻辑关系

  • 指针的设置是任意的,可以很方便的表示各种逻辑结构

  • 插入和删除操作不需要移动元素,只需要修改指针,但也会失去顺序表可随机存取的优点

3.优缺点

优点:

  • 解决顺序表需要大量连续空间的缺点
  • 对于插入或删除数据越频繁的操作,单链表的效率就越明显

缺点:

  • 需要开辟指针域,需要的空间更大。
  • 查找某一序号的值时,需要从表头开始遍历。

4.头结点&头指针

4.1定义
  • **头结点:**在链表表头之前插入一个指向链表表头的结点,该结点称为头结点,头结点数据域为空,指针域直线表头节点。
  • **头指针:**一般把链表中的第一个结点称为头指针,其存储链表的第一个数据元素
4.2 头结点与头指针的区别
  • 不管带不带头结点,头指针都始终指向链表的第一个节点
  • 头结点是带头结点的链表中的第一个节点,节点内通常不存储信息
4.3 引入头结点的优点
  • 单链表设置头结点的目的是方便运算的实现
  • 好处一:有头节点后,插入和删除数据元素的算法就统一了,不再需要判断是否在第一个元素之前插入或删除第一个元素
  • 好处二:不论链表是否为空,其头指针是指向头节点的非空指针,链表的头指针不变,因此空表和非空表的处理也就统一了

二、单链表上的操作

1、采用头插法建立单链表

1.1定义

​ 从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。后面再有新生成的节点,都插在头结点与链表表头之间。

1.2图解头插法

在这里插入图片描述

1.3代码实现
LinkList List_HeadInsert(LinkList &L)
{ //逆向建立单链表
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode)); //创建头结点
    L->next = NULL;                      //初始为空链表
    scanf("%d", &x);                     //输入节点的值
    while (x != 9999)
    {                                      //输入9999表示结束
        s = (LNode *)malloc(sizeof(LNode)); //创建新节点
        s->data = x;
        s->next = L->next;
        L->next = s;                 //将新节点插入表中,L为头指针
        scanf("%d", &x);
    }
    return L;
}
每个节点的插入的时间复杂度为O(1),建表的时间复杂度为O(n)
1.4头插法优缺点

优点:算法实现简单

缺点:生成的链表中结点的次序和输入数据的顺序不一致(可以利用这个特点将链表节点倒置)

2、采用尾插法建立单链表

2.1定义

​ 从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点,不然每次插入都需要从表头遍历到表尾。

2.2图解尾插法

在这里插入图片描述

2.3代码实现
LinkList List_TailInsert(LinkList &L)
{ //正向建立单链表
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    LNode *s, *r = L; // r为表尾指针
    scanf("%d", &x);  //输入节点的值
    while (x != 9999)
    {                                      //输入9999表示结束
        s = (LNode *)malloc(sizeof(LNode)); //创建新节点
        s->data = x;
        r->next = s;
        r = s; // r指向新的表尾节点
        scanf("%d", &x);
    }
    r->next = NULL; //尾节点指针置空
    return L;
}

每个节点的插入的时间复杂度为O(1),建表的时间复杂度为O(n)

3、按序号查找节点值

3.1算法思路

在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。

3.2代码实现
LNode *GetElem(LinkList L, int i)
{
    int j = 1;          //计数,初始为1
    LNode *p = L->next; //第1个节点指针赋给p
    if (i == 0)
        return L; //若i=0,则返回头结点
    if (i < 1)
        return NULL; //若i无效,则返回NULL
    while (p && j < i)
    { //从第i个节点开始找,查找第i个节点
        p = p->next;
        j++;
    }
    return p; //返回第i个节点的指针,若i大于表长,则返回NULL
}
时间复杂度为O(n)

4、按值查找表节点

4.1算法思路

从单链表第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。

4.2代码实现
LNode *LocateElem(LinkList L, ElemType e)
{
    LNode *p = L->next;
    while (p != NULL && p->data != e) 
	        //从第i个节点开始查找data域为e的节点
        p = p->next;
    return p;

5、插入节点操作

5.1算法思路

插入操作是将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i−1个结点,再在其后插入新结点。

5.2示意图

在这里插入图片描述

5.3代码实现
p = GetElem(L, i - 1); //查找插入位置的前驱结点
s->next = p->next;
p->next = s;

6、删除节点操作

6.1算法思路

删除操作是将单链表的第i个结点删除。先检查删除位置的合法性,然后查找表中第i−1个结点,即被删结点的前驱结点,再将其删除。

6.2示意图

在这里插入图片描述

6.3代码实现
p = GetElem(L, i - 1); //查找删除位置的前驱结点
q = p->next;           //令q指向被删除节点
p->next = q->next;     //将*q节点从链中断开
free(q)                //释放节点的存储空间

7、求表长操作(不含头结点)

设置一个计数器,每访问一个节点,计数器加1,直到访问到空节点为止

三、双链表

1.概念

双链表结点中有两个指针prior和next,分别指向其前驱节点和后继节点

1.为什么引入双链表

  • 单链表只能从头结点依次顺序地向后遍历
  • 单链表不能快速的前驱结点进行操作

2.双链表结点类型和指针定义

typedef struct DNode{				//定义双链表节点类型
	ELemType data;					//数据域
	struct DNode *prior,*next;		//前驱和后继指针
}DNode,*DLinkList;

3.双链表的插入操作

3.1示意图

在这里插入图片描述

3.2代码实现
s->next = p->next; //将节点*s插入到*p之后
p->next->prior = s;
s->prior = p;
p->next = s

4.双链表的删除操作

4.1示意图

在这里插入图片描述

4.2代码实现
p->next = q->next;  //删除节点*q
q->next->prior = p;
free(q)

**注意:**插入删除操作时一定要注意操作顺序,不要丢失指向后继结点的指针。

5.单/双链表常考结论

  • 带头结点的单链表,判断表为空的条件: head->next==NULL
  • 不带头结点的单链表,判断表为空的条件:head==NULL

四、循环链表

1.循环单链表

1.1定义

将单链表表尾节点的指针域设置为头结点地址,使单链表形成一个环形,即为循环单链表。

1.2示意图

在这里插入图片描述

1.3特点
  • 最后一个节点指向头结点
  • 可以从任意一个节点开始遍历整个链表
  • 仅设置尾指针

2.循环双链表

2.1定义

将双链表表尾节点的后继指针指向头结点,头结点前驱指针指向表尾节点,使双链表形成一个环形,即为循环双链表。

2.2示意图

在这里插入图片描述

2.3特点
  • 最后一个节点的next指针指向头结点
  • 头结点的prior指针指向最后一个节点

3.常考结论

  • 判断带头结点循环单链表为空的条件: head->next==head
  • 注意在计算线性表长度的时候, 头结点不计算在内
  • 带头结点的双循环链表L为空的条件是: L->prior==L && L->next==L(即头结点的prior和next都指向自己)

五、静态链表

1.定义

  • 借助数组来描述线性表的链式存储结构,因此静态链表需要提前申请一片连续的空间。
  • 节点也有数据域data和指针域next,这里的指针是节点的相对位置(数组下标),又称游标。

2.结构描述

#define Maxsize 50
typedef struct
{                  //定义单链表节点类型
    ElemType data; //数据域
    int next;      //下一个元素的数组下标
} SLinkList[Maxsize];

3.示意图

在这里插入图片描述

注意:静态链表中,指针域为-1时,即表示此节点为表尾节点。

4.常考结论

  • 静态链表需要分配较大空间,容量固定不可变
  • 插入和删除不需要移动元素,只需要修改指针
  • 静态链表不能随机存取,只能从头结点开始依次往后查找

欢迎加入我们的计算机考研交流群,群号: 629154108。
欢迎关注我们的微信公众号:Lander计算机。
欢迎关注我们的知乎账号:Lander计算机考研。
欢迎关注我们的小红书账号:Lander计算机考研。

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值