线性表之双链表(C语言)

简介: 线性表之双链表(C语言)

本篇博客只讲:带头双向循环链表


💨回顾单链表

之前在博客中讲过单链表,但单链表的缺陷很明显。它无法找到前驱(即前一个节点)。

单链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,
如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。


💨双链表

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

有了单链表的讲述,我们就直接进行双链表的结构体定义和一些接口实现。


💨双链表结构体的定义

typedefintLTDataType;
typedefstructListNode{
structListNode*next;
structListNode*prve;
LTDataTypedata;
}ListNode;
  1. 把int重命名为LTDataType (为了增强代码可维护性)
  2. 结构体的成员变量里,我定义了两个结构体指针,next和prve,双链表有两个指针域一个数据域,next指针指向下一个节点,prve指针指向前一个节点,data就是当前节点的数据域,存的数据类型就是LTDataType(int)。
  3. 把结构体变量重命名为ListNode,不然每次使用都要带上struct。

💨接口函数的实现

🎈开辟节点函数

ListNode*BuyListNode(LTDataTypex)
{
ListNode*newnode= (ListNode*)malloc(sizeof(ListNode));
newnode->data=x;
newnode->next=NULL;
returnnewnode;
}

我们无论是初始化链表,还是头插尾插时,一定会使用到新开辟一个节点,所以单门拿出来写成一个函数。

带返回值的目的是为了初始化链表的时候,避免使用二级指针,因为带头节点的链表我们就是可以避免使用二级指针的现象。


🎈初始化链表

ListNode*ListInit() //初始化{
ListNode*phead=BuyListNode(0);
phead->next=phead;
phead->prve=phead;
returnphead;
}

同样,为了避免使用二级指针,加入了返回值,当初始化完成时,直接把头节点指针返回给主函数,这样,一个带有头节点的双向循环链表就初始化完成了。

主函数接收如下

ListNode*plist=ListInit();
  • 初始化的时候,那个BuyListNode()括号里的任填,只要是L只是为了头插尾插而准备,一般不会用到头节点的数据域。只要数据类型是LTDataType(int)就行。
  • 初始化的时候,头节点的指针域肯定都指向自己。

🎈打印链表

voidListPrint(ListNode*phead) //打印{
ListNode*cur=phead->next;
while (cur!=phead)
    {
printf("%d ",cur->data);
cur=cur->next;
    }
printf("\n");
}

定义一个结构体指针cur,从头节点的下一个节点开始打印,直到cur再次指向头节点结束。


🎈查找

ListNode*ListFind(ListNode*phead,LTDataTypex) //查找{
assert(phead);
ListNode*cur=phead->next;
while (cur!=phead)
    {
if(cur->data==x)
returncur;
cur=cur->next;
    }
returnNULL;
}
  • 查找时把链表和你要查找的内容传过来。
  • assert判断你传的链表是否为空
  • 建一个结构体指针cur从头节点phead的下一个节点开始找,一直再次到头节点为止。
  • 如果中途找到返回指针,没找到等循环结束返回空指针


主函数接收如下

ListNode*pos=ListFind(plist,3);

因为我写的时候,初始化链表时,主函数用ListNode* plist接收,插入了一个3。


🎈在某位置前插入数据

//pos位置之前插入xvoidListInsert(ListNode*pos , LTDataTypex)
{
assert(pos);
ListNode*prve=pos->prve;
ListNode*newnode=BuyListNode(x);
prve->next=newnode;
newnode->prve=prve;
newnode->next=pos;
pos->prve=newnode;
}
  1. 先判断链表是否为空
  2. 先创建一个结构体指针prve指向当前位置的前一个节点(一定要先查找再用此函数,不然程序怎么知道你在哪个位置前插入)
  3. 创建一个newnode指针作为你要插入的新节点
  4. prve的next指针域指向newnode
  5. newnode的prve指针域指向prve(注意prve指针域是结构体的成员变量,指向的prve是我定义的结构体指针,别弄混了)
  6. newnode的next指针域指向pos
  7. pos的prve指针域指向newnode

重点注意

🌹这种先创建一个结构体指针指向pos位置前一个节点的方法,好处是为了避免你写指针时需要注意顺序问题,有了指针,你就不用担心顺序写的对不对。


🎈删除某位置的值

//删除pos位置的值voidListErase(ListNode*pos)
{
assert(pos);
ListNode*prve=pos->prve;
ListNode*next=pos->next;
prve->next=next;
next->prve=prve;
free(pos);
  1. 定义一个结构体指针prve指向当前位置的前一个节点
  2. 定义一个结构体指针next指向当前位置的后一个节点
  3. 前一个节点的next域指向后一个节点
  4. 后一个节点的prve域指向前一个节点
  5. 再释放掉此节点

🎈尾插

voidListPushBack(ListNode*phead , LTDataTypex) //尾插{
assert(phead);
ListNode*tail=phead->prve;
ListNode*newnode=BuyListNode(x);
tail->next=newnode;
newnode->prve=tail;
newnode->next=phead;
phead->prve=newnode;
// ListInsert(phead,x);}
  1. assert判断链表是否为空
  2. 新开一个tail指针指向头节点的前一个(原因开上面的重点注意)
  3. 建一个newnode就代表你要插入的节点
  4. 接下来就是更换本来的尾节点和next域指向和头节点prve域的指向就行
  5. 然后newnode新节点的指向也要定义


上面我们写了在某个位置前插入的函数,可以直接调用他 ListInsert(phead,x)


🎈头插

voidListPushFront(ListNode*phead , LTDataTypex) //头插{
assert(phead);
ListNode*first=phead->next;
ListNode*newnode=BuyListNode(x);
phead->next=newnode;
newnode->prve=phead;
newnode->next=first;
first->prve=newnode;
//    ListInsert(phead->next,x);/*另一种写法需要注意顺序ListNode * newnode = BuyListNode(x);newnode->next = phead->next;phead->next->prve = newnode;phead->next = newnode;newnode->prve = phead;*/}
  • 同样也可以直接调用上面的那个函数,具体实现和尾插差不多,只要更改指针域的指向即可
  • 下面我又写了另一种写法,这种写法就是不创建指针,但要注意顺序。

🎈尾删

voidListPopBack(ListNode*phead) //尾删{
assert(phead);
assert(phead->next!=phead);
ListNode*tail=phead->prve;
ListNode*prev=tail->prve;
prev->next=phead;
phead->prve=prev;
free(tail);
tail=NULL;
//ListErase(phead->prev);}

尾删,头删也可以调用之前我们写的删除某个位置的函数。


🎈头删

voidListPopFront(ListNode*phead) //头删{
assert(phead);
assert(phead->next!=phead);
ListNode*first=phead->next;
ListNode*second=phead->next->next;
phead->next=second;
second->prve=phead;
free(first);
first=NULL;
//ListErase(phead->next);}
目录
相关文章
|
21天前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
199 6
|
25天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
48 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
90 4
|
2月前
|
存储 缓存 C语言
C语言:链表和数组有什么区别
C语言中,链表和数组是两种常用的数据结构。数组是一种线性结构,元素在内存中连续存储,通过下标访问,适合随机访问且大小固定的情况。链表由一系列不连续的节点组成,每个节点存储数据和指向下一个节点的指针,适用于频繁插入和删除操作的场景,链表的大小可以动态变化。
|
2月前
|
C语言
无头链表再封装方式实现 (C语言描述)
如何在C语言中实现无头链表的再封装,包括创建节点和链表、插入和删除操作、查找和打印链表以及销毁链表的函数。
29 0
|
2月前
|
C语言
C语言链式结构之有头单链表再封装写法
本文介绍了如何使用C语言对有头单链表进行封装,包括节点的创建、链表的初始化、数据的插入和删除,以及链表的打印等功能。
21 1
|
2月前
|
C语言
C语言结构体链式结构之有头单链表
文章提供了一个C语言实现的有头单链表的完整代码,包括创建链表、插入、删除和打印等基本操作。
36 1
|
2月前
|
测试技术 C语言
单链表之无头链表(C语言版)
本文详细介绍了使用C语言实现无头单链表的方法,包括节点和链表结构的定义、链表的创建与销毁、节点的插入与删除,以及链表的打印等功能。文章通过具体的代码示例,展示了如何在无头链表中进行头插法、尾插法、自定义位置插入和删除,以及如何清空和销毁链表。
47 0
单链表之无头链表(C语言版)
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
66 0
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
36 0