【数据结构】双链表--从原理到(用C语言)实现全解析
文章目录
- 1.双链表的概念
- 1.1概念
- 1.2单链表与双链表的区别
- 1.3双链表为空的情况
- 2.双链表的功能
- 3.双链表功能的实现
- 3.1双链表的结构
- 3.2初始化
- 3.3销毁
- 3.4链表的打印
- 3.5判断链表是否为空
- 3.6尾插
- 3.7头插
- 3.8尾删
- 3.9头删
- 3.10查找指定数据
- 3.11在指定位置之后(前)插入
- 3.12删除指定位置的节点
- 4.完整代码
- List.h
- List.c
- main.c
- 运行结果
1.双链表的概念
1.1概念
双链表,即带头双向循环链表(链表有头节点,方相是双向的,且是循环的):它是一种逻辑结构为线性,物理结构不一定为线性的存储结构(与单链表基本一致)
1.2单链表与双链表的区别
- 双链表相比单链表,新增了一个指针变量prev,用来指向前一个节点
- 单链表中尾节点的next指针为空,而双链表中尾节点的next指针指向头节点,头节点的prev指针指向尾节点
- 单链表是单向不循环的,而双链表是双向循环的
- 单链表和双链表的头节点本质上是不同的,单链表中的“头节点” 由于存储了val值,所以 不是真正的头节点,仅用来表示第一个节点;而 双链表中的头节点 不存储有效数据val,只存储prev指针和next指针,所以这里的头节点 才是真正意义上的头节点,我们也可以把它称作“哨兵位”(表示只用来放哨,占一个位置,而不存储任何有效数据的节点)
综上,单链表是不带头单向不循环链表,双链表是带头双向循环链表
1.3双链表为空的情况
val中不存储有效数据,头指针head的next和prev指针都指向自己
2.双链表的功能
功能与单链表相似,同样可以概括为增删查改四个功能,但由于双链表增加了prev指针,所以可以大幅减小时间复杂度,提高代码效率
3.双链表功能的实现
3.1双链表的结构
typedef int LTDataType;//存储的数据类型
typedef struct ListNode{LTDataType val;//存储的数据struct ListNode* next;//指向后一个节点struct ListNode* prev;//指向前一个节点
}LTNode;
3.2初始化
创建一个空链表,添加一个头节点,它的next和prev指针都指向自己,其中不存储有效数据
这里我们需要先实现一个专门用来添加节点的函数:
//添加节点
LTNode* LTBuyNode(int x)
{LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));if(newNode == NULL){perror("Malloc Failed!\n");exit(1);}newNode->val = x;newNode->next = newNode->prev = newNode;return newNode;
}
初始化函数:
LTNode* LTInit(void)
{//添加头节点 传入无效数据-1LTNode* phead = LTBuyNode(-1);return phead;
}
3.3销毁
遍历并删除头节点后的每一个节点
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead)//pcur==phead时即为只有一个头节点的情况{LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);//phead = NULL;//函数是传值调用(传指针本身 没有传它的地址) 因此这一步没有意义
}
注:使用此函数后,要手动把头指针赋值为NULL,避免野指针的出现
3.4链表的打印
遍历头节点之后的所有节点,一个一个打印,直到当前节点的next指向头节点
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;if(pcur != phead) printf("%d", pcur->val);pcur = pcur->next;while(pcur != phead){printf(" -> %d", pcur->val);pcur = pcur->next;}printf("\n");
}
3.5判断链表是否为空
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
3.6尾插
双链表是循环的,通过head->prev找到尾节点,把新节点放到尾节点后即可
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->prev = phead->prev;newNode->next = phead;phead->prev->next = newNode;phead->prev = newNode;
}
注意:要先更新新节点的prev和next指针,如果先更新其他节点,那么再赋值给新节点,会出现错误
3.7头插
把新节点添加到头节点和第一个节点之间即可
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->next = phead->next;newNode->prev = phead;phead->next = newNode;newNode->next->prev = newNode;
}
3.8尾删
通过head->prev节点找到尾节点,删除即可
void LTPopBack(LTNode* phead)
{//判断链表是否为空assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
3.9头删
删除头节点和第一个节点之间的节点即可
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
3.10查找指定数据
遍历整个链表,直到节点数据值与指定数据值相等,返回该节点地址
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead){if(pcur->val == x) return pcur;pcur = pcur->next;}return NULL;
}
3.11在指定位置之后(前)插入
在pos之后插入:
void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = LTBuyNode(x);newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}
在pos之前插入:
void LTInsertBefore(LTNode* pos, LTDataType x)
{assert(pos);//即为在pos前一个节点之后插入LTInsertAfter(pos->prev, x);
}
3.12删除指定位置的节点
void LTDeletePos(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);//pos = NULL;
}
注意:pos是传值调用,在函数内不能改变它的值,所以使用该函数时,记得在函数外把pos置为NULL
4.完整代码
List.h
// List.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//双链表的结构
typedef int LTDataType;
typedef struct ListNode{LTDataType val;struct ListNode* next;struct ListNode* prev;
}LTNode;//添加节点
LTNode* LTBuyNode(int x);
//初始化
LTNode* LTInit(void);
//销毁
void LTDestroy(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//判空
bool LTEmpty(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);//在指定节点之后插入
void LTInsertAfter(LTNode* pos, LTDataType x);
//在指定节点之前插入
void LTInsertBefore(LTNode* pos, LTDataType x);//删除指定节点
void LTDeletePos(LTNode* pos);
List.c
// List.c
#include "List.h"LTNode* LTBuyNode(int x)
{LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));if(newNode == NULL){perror("Malloc Failed!\n");exit(1);}newNode->val = x;newNode->next = newNode->prev = newNode;return newNode;
}
LTNode* LTInit(void)
{//添加头节点 传入无效数据-1LTNode* phead = LTBuyNode(-1);return phead;
}
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead)//pcur==phead时即为只有一个头节点的情况{LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);//phead = NULL;//函数是传值调用(传指针本身 没有传它的地址) 因此这一步没有意义
}
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;if(pcur != phead) printf("%d", pcur->val);pcur = pcur->next;while(pcur != phead){printf(" -> %d", pcur->val);pcur = pcur->next;}printf("\n");
}
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->prev = phead->prev;newNode->next = phead;phead->prev->next = newNode;phead->prev = newNode;
}
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newNode = LTBuyNode(x);newNode->next = phead->next;newNode->prev = phead;phead->next = newNode;newNode->next->prev = newNode;
}void LTPopBack(LTNode* phead)
{//判断链表是否为空assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while(pcur != phead){if(pcur->val == x) return pcur;pcur = pcur->next;}return NULL;
}void LTInsertAfter(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newNode = LTBuyNode(x);newNode->next = pos->next;newNode->prev = pos;pos->next->prev = newNode;pos->next = newNode;
}
void LTInsertBefore(LTNode* pos, LTDataType x)
{assert(pos);//即为在pos前一个节点之后插入LTInsertAfter(pos->prev, x);
}void LTDeletePos(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);//pos = NULL;
}
main.c
#include "List.h"void test(void)
{LTNode* plist = LTInit();LTPrint(plist);LTPushBack(plist, 1);LTPrint(plist);LTPushBack(plist, 2);LTPrint(plist);LTPushFront(plist, 3);LTPrint(plist);LTPushFront(plist, 4);LTPrint(plist);printf("%p\n", LTFind(plist, 1));printf("%p\n", LTFind(plist, 5));LTInsertAfter(LTFind(plist, 4), 400);LTPrint(plist);LTInsertBefore(LTFind(plist, 2), 200);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);LTNode* pos = LTFind(plist, 200);LTDeletePos(pos);pos = NULL;LTPrint(plist);LTDestroy(plist);plist = NULL;
}
int main(void)
{test();return 0;
}