目录
链表
链表的分类
1、单向或者双向
2、带头或者不带头
3、循环或者非循环
总结:
单链表
创建链式结构
创建新节点
尾插
尾删
头插
头删
查找节点
在pos位置后插入
删除pos位置后的节点
销毁
链表
概念:
链表是一种物理结构上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表结构中的指针链接起来的!前一个元素中存放着后一个元素的地址!
结构:
结构中存放着链表下一个元素的地址!用一个指针来存储!
一般情况下一个链表结构中存放的是元素的值以及下一个位置的指针!或者多个链接元素的指针!
链表的分类
1、单向或者双向
单向链表:
链表结构中只有一个指针,该指针是存放的是下一个元素的地址的指针变量!
看图理解:
双向链表:
结构中有两个指针,分别记录它的前一个元素和后一个元素的地址!
看图理解:
2、带头或者不带头
1、带哨兵位的头节点的单向链表:
2、带哨兵位头节点的双向链表:
3、循环或者非循环
1、单向循环链表
2、双向循环链表
3、单向带头循环链表
4、带头双向循环链表
总结:
1、图形
以上图都是链表的逻辑结构,物理上也就是底层并非是这样指向的,而是每个指针里面都存储它所对应节点的地址!
2、种类
链表类型总共有八种,分别是按照以上的三大类来组合:
第一类:单向或双向
第二类:带头或不带头
第三类:循环或不循环
八种链表:
1、单向链表
2、双向链表
3、单向带头链表
4、双向带头链表
5、单向循环链表
6、双向循环链表
7、单向带头循环链表
8、双向带头循环链表
本篇博主只带大家写一种链表:单链表,因为只要会了一种来链表,其它链表都是大同小异的!
单链表
概念:
单链表中的节点,后一个节点中存的是前一个节点的地址,最后一个节点的中的地址为空!
逻辑结构:
物理结构:
总结:
1、链式结构在逻辑上是连续的,但在物理结构上不一定连续。
2、现实中的节点一般是从堆上申请出来的
3、从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续!
下面我们用代码实现单链表!
创建链式结构
思路:
我们用一个结构体来封装链表节点的值以及该类型结构的地址,值是链表节点中要保存的值,结构体的的地址是用来存储前一个结构节点的地址,若该节点是最后一个则地址里面的值是NULL
代码:
//用typedef重命名,后续可以继续使用 typedef int SLDateType; //创建链表结构 typedef struct SListNode { SLDateType val;//节点值 struct SListNode* next;//下一个节点的位置 }SLNode;
创建新节点
思路:
创建新节点,要将链表链起来,我们就得要创造新的节点进行链接,才能形成链接功能。该功能可供我们用于头插,尾插,等功能中!
代码:
//创建新节点 SLNode* CreateNode(SLDateType x) { SLNode* newnode = (SLNode*)malloc(sizeof(SLNode)); //判断是否开辟成功 if (newnode == NULL) { perror("malloc fali:"); return; } //开始赋值 newnode->val = x; newnode->next = NULL; //返回 return newnode; }
尾插
思路:
单链表进行尾插,先要找到单链表的尾节点,随后在创建一个要插入的节点,让找到的尾节点的指针指向要插入的新节点,也就是尾节点中的结构体指针变量中保存要插入的新节点的地址!
代码:
//尾插 void SListPushBank(SLNode** pphead, SLDateType x) { //创建新节点 SLNode* newnode = CreateNode(x); //判断是否是空链表,空链表直接进行尾插 if (*pphead == NULL) { *pphead = newnode; } else { //找尾 SLNode* cur = *pphead; while (cur->next != NULL) { cur = cur->next; } //进行链接 cur->next = newnode; } }
尾删
思路:
单链表进行尾删,先进行循环找尾节点,同时用一个指针来记录尾节点的前一个节点的地址。找到尾节点之后,将尾节点的前一个节点中的指针置为NULL。再将尾节点的空间进行free释放!最后将尾节点结构中的指针置为NULL
代码:
//尾删 void SListPopBank(SLNode** pphead) { //如果是NULL链表就不能再删 assert(*pphead); //判断一个的情况 if ((*pphead)->next == NULL) { free((*pphead)->next); *pphead= NULL; } else { //找尾 SLNode* cur = *pphead; SLNode* tail = NULL; while (cur->next != NULL) { tail = cur; cur = cur->next; } //释放节点 free(cur); cur = NULL; //将前一个节点置为NULL tail->next = NULL; } }
头插
思路:
单链表头插,就是创建一个新节点,让这个新节点结构中的指针指向头节点,再让新节点成为头节点即可!
代码:
//头插 void SListPushFront(SLNode** pphead, SLDateType x) { SLNode* newnode = CreateNode(x); SLNode* tail = *pphead; *pphead = newnode; newnode->next = tail; }
头删
思路:
单链表头删,用一个指针指向头后面的节点,随后将头节点空间释放,再让后面的节点成为头即可!
代码:
//头删 void SListPopFront(SLNode** pphead) { //如果是空链表就不进行删除 assert(*pphead); SLNode* cur = (*pphead)->next; SLNode* tail = *pphead; free(tail); tail = NULL; *pphead = cur; }
查找节点
思路:
循环遍历链表,找到我们要查找的节点,返回该节点的地址即可!若找不到返回NULL
代码:
//节点查找 SLNode* SListFind(SLNode* phead, SLDateType x) { SLNode* cur = phead; while (cur) { if (cur->val == x) { return cur; } cur = cur->next; } return NULL; }
在pos位置后插入
思路:
用一个指针记录pos位置的下一个节点,随后让pos节点结构体的指针指向新插入的节点。再让新插入节点结构的指针指向pos位置的下一个节点完成链接!
代码:
// 在pos位置之后插入x void SListInsertAfter(SLNode* pos, SLDateType x) { //后面插入pos为NULL 不行 assert(pos); SLNode* newnode = CreateNode(x); SLNode* cur = pos->next; pos->next = newnode; newnode->next = cur; }
删除pos位置后的节点
思路:
用一个指针记录pos位置的下一个节点。再用一个指针记录pos位置的下下个节点,也就是pos位置的下一个节点的下一个节点,随后将pos位置的下一个节点空间释放。让pos位置节点结构中的指针指向pos位置的下下个节点即可!
代码:
// 删除pos位置之后的值 void SListEraseAfter(SLNode* pos) { //pos为NULL 不可删后面的 assert(pos); SLNode* cur = pos->next; if (cur == NULL) { free(cur); cur = NULL; pos->next = NULL; } else { SLNode* tail = cur->next; free(cur); pos->next = tail; } }
销毁
思路:
循环遍历链表,用一个指针记录销毁节点的后一个节点,一个指针记录销毁节点,让两者都往后走,依次循环进行销毁(释放节点空间)!
代码:
// 销毁 void SListDestroy(SLNode* phead) { SLNode* cur = phead; SLNode* tail = cur; while (cur) { tail = cur; cur = cur->next; free(tail); } tail = NULL; phead = NULL; }