文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是链表
在影视作品中,我们可能都见过地下工作者的经典话语:
“上级的姓名、住址,我知道,下级的姓名、住址,我也知道,但是这些都是我们党的秘密,不能告诉你们!”
地下党借助这种单线联络的方式,灵活隐秘地传递着各种重要信息。
在计算机科学领域里,有一种数据结构也恰恰具备这样的特征,这种数据结构就是链表。
链表是什么样的?为什么说它像地下党呢?
让我们来看一下单向链表的结构。
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
链表的第一个节点被称为头节点,最后一个节点被称为尾节点,尾节点的next指针指向空。
与数组按照下标来随机寻找元素不同,对于链表的其中一个节点A,我们只能根据节点A的next指针来找到该节点的下一个节点B,
再根据节点B的next指针找到下一个节点C…
这正如地下党的联络方式,一级一级,单线传递。
要想让每个节点都能回溯到它的前置节点,我们可以使用双向链表。
什么是双向链表?
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,
还拥有指向前置节点的prev指针。
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
二、链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构
虽然有这么多的链表的结构,但是我们实际中最常用的还是这两种结构:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
三、链表的实现
3.1 无头单向非循环链表的基本操作
1.插入节点
分为三种情况:
- 尾部插入
- 头部插入
- pos位置之后插入
尾部插入,是最简单的情况,把最后一个节点的next指针指向新插入的节点即可。
头部插入,可以分为两个步骤。
第一步,把新节点的next指针指向原先的头节点。
第二步,把新节点变为链表的头节点。
pos位置之后插入,同样分为两个步骤。
第一步,新节点的next指针,指向pos位置之后的节点。
第二步,pos位置节点的next指针,指向新节点。
代码
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode *newnode=BuySListNode(x);//动态申请一个结点
if(*pplist==NULL) //当链表没有节点时
{
*pplist=newnode;
}
else //有任意节点
{
SListNode *cur=*pplist;
while(cur->next) //遍历找尾节点,尾的特征是next指针指向空
{
cur=cur->next;
}
cur->next=newnode;//尾插
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode *newnode=BuySListNode(x);
newnode->next=*pplist;
*pplist=newnode;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode *newnode=BuySListNode(x);
newnode->next=pos->next;
pos->next=newnode;
}
2.删除节点
链表的删除操作同样分为三种情况:
- 尾部删除
- 头部删除
- pos位置之后删除
尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即可。
头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
pos位置之后删除,同样很简单,把pos位置的next指针,指向要删除节点的下一个节点即可。
代码
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);//考虑链表没有任何节点的情况 不能进行删除
if((*pplist)->next==NULL)//考虑只有一个节点的情况
{
free(*pplist);
*pplist=NULL;
}
else
{
SListNode *cur=*pplist;
while(cur->next->next)//找尾的上一个节点
{
cur=cur->next;
}
free(cur->next);//释放内存
cur->next=NULL;//成为新的尾
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);//考虑链表没有任何节点的情况 不能进行删除
SListNode *del=*pplist;//保存记录要删的头节点
*pplist=del->next;//存放新的头节点
free(del);//释放旧的头节点
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);//考虑pos之后为空指针NULL
SListNode *del=pos->next;//保存要删除的节点
pos->next=del->next;//pos的next指针指向 要删除节点的下一节点
free(del); //释放内存(删除节点)
}
3.查找节点、修改节点
在查找元素时,链表只能从头节点开始向后一个一个节点逐一查找。
例如给出一个链表,需要查找data 为3的节点。
第一步,将查找的指针定位到头节点。
第二步,判断其data 是否为我们要查找的,如果是则返回当前节点指针,否则根据当前节点的next指针,定位到下一个节点。
第三步,直到找到目标数据,返回当前节点指针,或直到链表走到NULL也没查找到 ,返回NULL。
修改节点(查找和修改要配合使用)
根据返回的指针,可以修改节点的data,直接把旧数据替换成新数据。
代码
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode *cur=plist;
while(cur) //遍历链表
{
if(cur->data==x)
{
return cur;
}
cur=cur->next;
}
return NULL;
}
int main()
{
SListNode *pos=SListFind(plist,3);//调用查找函数,pos接收返回值
if(pos!=NULL)//当返回值不为空
{
pos->data=0;//对节点进行修改
}
}
🍎无头+单向+非循环链表增删查改实现
完整代码(含测试)
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x)
{
SListNode *newnode=(SListNode *)malloc(sizeof(SListNode));
if(newnode==NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data=x;
newnode->next=NULL;
return newnode;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode *cur=plist;
while(cur)
{
printf("%d-->",cur->data);
cur=cur->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode *newnode=BuySListNode(x);
if(*pplist==NULL)
{
*pplist=newnode;
}
else
{
SListNode *cur=*pplist;
while(cur->next)
{
cur=cur->next;
}
cur->next=newnode;
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode *newnode=BuySListNode(x);
newnode->next=*pplist;
*pplist=newnode;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
if((*pplist)->next==NULL)
{
free(*pplist);
*pplist=NULL;
}
else
{
SListNode *cur=*pplist;
while(cur->next->next)
{
cur=cur->next;
}
free(cur->next);
cur->next=NULL;
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode *del=*pplist;
*pplist=del->next;
free(del);
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode *cur=plist;
while(cur)
{
if(cur->data==x)
{
return cur;
}
cur=cur->next;
}
return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode *newnode=BuySListNode(x);
newnode->next=pos->next;
pos->next=newnode;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);
SListNode *del=pos->next;
pos->next=del->next;
free(del);
}
// 单链表的销毁
void SListDestroy(SListNode** pplist)
{
assert(pplist);
SListNode *del=*pplist;
while(del)
{
SListNode *next=del->next;
free(del);
del=next;
}
*pplist=NULL;
}
int main()
{
SListNode *plist=NULL;
SListPushFront(&plist,1);
SListPushFront(&plist,2);
SListPushFront(&plist,3);
SListPushFront(&plist,4);
SListPrint(plist);
SListPopBack(&plist);
SListPopFront(&plist);
SListPopBack(&plist);
SListPopFront(&plist);
SListPrint(plist);
SListPushFront(&plist,1);
SListPushBack(&plist,2);
SListPushBack(&plist,3);
SListPushBack(&plist,4);
SListPushBack(&plist,5);
SListPrint(plist);
SListNode *pos=SListFind(plist,3);
if(pos!=NULL)
{
pos->data=0;
}
SListPrint(plist);
pos=SListFind(plist,0);
if(pos!=NULL)
{
SListInsertAfter(pos,30);
}
SListPrint(plist);
pos=SListFind(plist,2);
if(pos!=NULL)
{
SListEraseAfter(pos);
}
SListPrint(plist);
SListDestroy(&plist);
return 0;
}
3.2 带头双向循环链表的基本操作
双向带头循环链表结构
1.插入节点
分为三种情况:
- 尾部插入
- 头部插入
- pos位置之前插入
代码
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
assert(plist);
//当链表没有节点或有任意节点时 此代码都适用
ListNode *tail=plist->prev;
ListNode *newnode=BuyListNode(x);
tail->next=newnode;
newnode->prev=tail;
newnode->next=plist;
plist->prev=newnode;
}
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
assert(plist);
ListNode *newhead=BuyListNode(x);
//当链表没有节点或有任意节点时 此代码都适用
//这里没有像上面图所示一样记录头节点
newhead->next=plist->next;
plist->next->prev=newhead;
newhead->prev=plist;
plist->next=newhead;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode *posprev=pos->prev;
ListNode *newnode=BuyListNode(x);
newnode->next=pos;
pos->prev=newnode;
newnode->prev=posprev;
posprev->next=newnode;
}
2.删除节点
分为三种情况:
- 尾部删除
- 头部删除
- pos位置删除
代码
// 双向链表尾删
void ListPopBack(ListNode* plist)
{
assert(plist);
assert(plist->prev!=plist);//考虑链表没有节点
ListNode *tail=plist->prev;
ListNode *tailprev=tail->prev;
free(tail);
tailprev->next=plist;
plist->prev=tailprev;
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{
assert(plist);
assert(plist->next!=plist);//考虑链表没有节点
ListNode *first=plist->next;
ListNode *second=first->next;
plist->next=second;
second->prev=plist;
free(first);
}
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode *posprev=pos->prev;
ListNode *posnext=pos->next;
posprev->next=posnext;
posnext->prev=posprev;
free(pos);
}
此链表查找节点和不带头单向非循环链表 基本一致,除了循环条件不一样,这里就不介绍了。
🍏带头+双向+循环链表增删查改实现
完整代码(含测试)
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 动态申请一个结点
ListNode* BuyListNode(SLTDateType x)
{
ListNode *newnode=(ListNode *)malloc(sizeof(ListNode));
if(newnode==NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data=x;
newnode->next=NULL;
newnode->prev=NULL;
return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
ListNode *head=BuyListNode(-1);
head->next=head;
head->prev=head;
return head;
}
// 双向链表销毁
void ListDestory(ListNode* plist)
{
assert(plist);
ListNode *del=plist->next;
while(del!=plist)
{
ListNode *next=del->next;
free(del);
del=next;
}
free(plist);
}
// 双向链表打印
void ListPrint(ListNode* plist)
{
assert(plist);
ListNode *cur=plist->next;
printf("哨兵位<===>");
while(cur!=plist)
{
printf("%d<===>",cur->data);
cur=cur->next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
assert(plist);
ListNode *tail=plist->prev;
ListNode *newnode=BuyListNode(x);
tail->next=newnode;
newnode->prev=tail;
newnode->next=plist;
plist->prev=newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* plist)
{
assert(plist);
assert(plist->prev!=plist);
ListNode *tail=plist->prev;
ListNode *tailprev=tail->prev;
free(tail);
tailprev->next=plist;
plist->prev=tailprev;
}
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
assert(plist);
ListNode *newhead=BuyListNode(x);
newhead->next=plist->next;
plist->next->prev=newhead;
newhead->prev=plist;
plist->next=newhead;
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{
assert(plist);
assert(plist->next!=plist);
ListNode *first=plist->next;
ListNode *second=first->next;
plist->next=second;
second->prev=plist;
free(first);
}
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
assert(plist);
ListNode *cur=plist->next;
while(cur!=plist)
{
if(cur->data==x)
{
return cur;
}
cur=cur->next;
}
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode *posprev=pos->prev;
ListNode *newnode=BuyListNode(x);
newnode->next=pos;
pos->prev=newnode;
newnode->prev=posprev;
posprev->next=newnode;
}
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode *posprev=pos->prev;
ListNode *posnext=pos->next;
posprev->next=posnext;
posnext->prev=posprev;
free(pos);
}
int main()
{
ListNode * head=ListCreate();
ListPushFront(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
ListPushBack(head, 4);
ListPushFront(head, 0);
ListPushBack(head, 5);
ListPrint(head);
ListNode* pos = ListFind(head, 3);
if (pos)
{
ListInsert(pos, 30);
}
ListPrint(head);
pos = ListFind(head, 30);
if (pos)
{
ListErase(pos);
}
ListPrint(head);
ListPopBack(head);
ListPopFront(head);
ListPrint(head);
ListPopBack(head);
ListPopFront(head);
ListPrint(head);
ListPopBack(head);
ListPopFront(head);
ListPrint(head);
ListDestory(head);
head=NULL;
return 0;
}
完。