双链表接口实现
这次我们实现的是带头双向循环的链表
,不仅有指向前一个节点的prev指针,还有指向下一个节点的next指针,最后一个节点有指向开头的指针next,开头的节点有指向结尾的指针prev,形成循环

双链表定义:
1 2 3 4 5 6 7 8
| typedef int LTDataType;
typedef struct ListNode { struct ListNode* next; struct ListNode* prev; LTDataType data; }LTNode;
|
有人问为什么每次都要重新定义数据类型,因为每次的节点数据类型不一定是一样的,定义一下方便修改所有地方的数据类型
双链表节点创建
1 2 3 4 5 6 7 8 9 10 11 12 13
| LTNode* BuyListNode(LTDataType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); if (newnode == NULL) { perror("malloc fail"); return NULL; } newnode->data = x; newnode->prev = NULL; newnode->next = NULL; return newnode; }
|
还是和之前的链表节点创建一样,这里不过多叙述
双链表初始化
1 2 3 4 5 6 7
| LTNode* LTInit() { LTNode* phead = BuyListNode(-1); phead->next = phead; phead->prev = phead; return phead; }
|
通常我们会在主函数创建一个指针接收一个初始化指针,prev和next都指向自己形成循环
,也就是一个哨兵位,也间接规避了链表为空的情况
,直接可以PushBack节点
双链表销毁
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void LTDestroy(LTNode* phead) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { LTNode* next = cur->next; free(cur); cur = next; } free(phead); phead = NULL; }
|
head是哨兵位需要开始时被访问,所以从phead->next开始
,先保存下一个节点的指针,然后释放当前移动指针指向的节点,再把保存过的节点赋值给移动指针实现移动遍历节点
,最后再释放哨兵位head
双链表打印
1 2 3 4 5 6 7 8 9 10 11 12 13
| void LTPrint(LTNode* phead) { assert(phead); printf("<= head =>"); LTNode* cur = phead->next; while (cur != phead) { printf(" %d =>", cur->data); cur = cur->next; } printf("\n"); }
|
要根据双链表的特性来,双链表有哨兵位,所以打印要从哨兵位的下一个节点开始打印
,直到遇到的节点的next指针指向哨兵位
双链表检查是否为空
1 2 3 4 5
| int LTEmpty(LTNode* phead) { assert(phead); return phead->next == phead ? 0 : 1; }
|
为了方便在增删查改中避免链表为空的情况
,and多处需使用到
,所以特别写一个判空函数,因为是循环链表,所以指向自己表明链表只有一个哨兵位
,则返回0,反则返回1
双链表尾插
1 2 3 4 5 6 7 8 9 10 11
| void LTPushBack(LTNode* phead, LTDataType x) { assert(phead);
LTNode* newnode = BuyListNode(x); phead->prev->next = newnode; newnode->prev = phead->prev; newnode->next = phead; phead->prev = newnode; }
|
由于是带头循环,所以每个节点都有4条逻辑线连接着,通常先修改两个节点间的链接,再修改循环的链接
双链表头插
1 2 3 4 5 6 7 8 9 10
| void LTPushFront(LTNode* phead, LTDataType x) { assert(phead);
LTNode* newnode = BuyListNode(x); newnode->next = phead->next; phead->next->prev = newnode; phead->next = newnode; newnode->prev = phead; }
|
无论是什么情况下,都要注意连接的顺序是否会影响到其他步骤的正常赋值
,所以头插就需要先链接newnode的next指针,即通常先链接后面的节点
双链表尾删
1 2 3 4 5 6 7 8 9 10 11 12 13
| void LTPopBack(LTNode* phead) { assert(phead); assert(LTEmpty(phead));
LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev;
tailPrev->next = phead; phead->prev = tailPrev; free(tail); tail = NULL; }
|
首先遇到删除就要注意是不是空链表,存不存在节点来删除,然后要分别存储最后一个节点和倒数第二个节点
,方便进行链接释放的操作
双链表头删
1 2 3 4 5 6 7 8 9 10 11
| void LTPopFront(LTNode* phead) { assert(phead); assert(LTEmpty(phead));
LTNode* first = phead->next; phead->next = first->next; first->next->prev = phead; free(first); first = NULL; }
|
相同的操作,无论是删除还是销毁,都要记得存储需要释放的节点相关指针
,因为需要在删除之后将其连接的的节点连接起来
双链表查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| LTNode* LTFind(LTNode* phead, LTDataType x) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }
|
我们在查找时肯定是查找有效的数据,所以我们查找是从哨兵位后一个节点开始的,查找方法也很简单,遍历整个双链表,看看能否找到相应的数据,找得到就返回对应节点,找不到就返回空
双链表在pos位置插入x
1 2 3 4 5 6 7 8 9 10
| void LTInsert(LTNode* pos, LTDataType x) { assert(pos);
LTNode* newnode = BuyListNode(x); pos->prev->next = newnode; newnode->prev = pos->prev; pos->prev = newnode; newnode->next = pos; }
|
因为双向链表有prev,就不用在意是pos前还是pos后插入
,这里用的是pos前插入
双链表在pos位置删除x
1 2 3 4 5 6 7
| void LTErase(LTNode* phead, LTNode* pos) { assert(LTEmpty(phead)); pos->prev->next = pos->next; pos->next->prev = pos->prev; free(pos); }
|
其实和头删尾删的原理是一样的,直接删除,然后将剩余两个断开的节点链接就行了
顺序表和链表对比

顺序表和链表各有各的好处
🚩顺序表: 适用于对随机访问性能要求高
,元素数量可预估
,且不需要频繁进行插入和删除操作的场景
🚩链表: 适用于需要频繁插入和删除元素
,元素数量动态变化
,难以预估元素数量的场景
代码展示
传送门:Gitee双链表代码
List.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h>
typedef int LTDataType;
typedef struct ListNode { struct ListNode* next; struct ListNode* prev; LTDataType data; }LTNode;
LTNode* LTInit();
void LTDestroy(LTNode** pphead);
void LTPrint(LTNode* phead);
LTNode* BuyListNode(LTDataType x);
int LTEmpty(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
|
List.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| #define _CRT_SECURE_NO_WARNINGS #include "List.h"
LTNode* LTInit() { LTNode* phead = BuyListNode(-1); phead->next = phead; phead->prev = phead; return phead; }
void LTDestroy(LTNode* phead) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { LTNode* next = cur->next; free(cur); cur = next; } free(phead); phead = NULL; }
void LTPrint(LTNode* phead) { assert(phead); printf("<= head =>"); LTNode* cur = phead->next; while (cur != phead) { printf(" %d =>", cur->data); cur = cur->next; } printf("\n"); }
LTNode* BuyListNode(LTDataType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); if (newnode == NULL) { perror("malloc fail"); return NULL; } newnode->data = x; newnode->prev = NULL; newnode->next = NULL; return newnode; }
int LTEmpty(LTNode* phead) { assert(phead); return phead->next == phead ? 0 : 1; }
void LTPushBack(LTNode* phead, LTDataType x) { assert(phead);
LTNode* newnode = BuyListNode(x); LTNode* tail = phead->prev; tail->next = newnode; newnode->prev = tail; newnode->next = phead; phead->prev = newnode; }
void LTPopBack(LTNode* phead) { assert(phead); assert(LTEmpty(phead));
LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev;
tailPrev->next = phead; phead->prev = tailPrev; free(tail); tail = NULL; }
void LTPushFront(LTNode* phead, LTDataType x) { assert(phead);
LTNode* newnode = BuyListNode(x); newnode->next = phead->next; phead->next->prev = newnode; phead->next = newnode; newnode->prev = phead; }
void LTPopFront(LTNode* phead) { assert(phead); assert(LTEmpty(phead));
LTNode* first = phead->next; phead->next = first->next; first->next->prev = phead; free(first); first = NULL; }
LTNode* LTFind(LTNode* phead, LTDataType x) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }
void LTInsert(LTNode* pos, LTDataType x) { assert(pos);
LTNode* newnode = BuyListNode(x); pos->prev->next = newnode; newnode->prev = pos->prev; pos->prev = newnode; newnode->next = pos; }
void LTErase(LTNode* phead, LTNode* pos) { assert(LTEmpty(phead)); pos->prev->next = pos->next; pos->next->prev = pos->prev; free(pos); }
|
希望读者们多多三连支持
小编会继续更新
你们的鼓励就是我前进的动力!
