一 概念与结构
带头双向循环链表
注意:这⾥的“带头”跟前⾯我们说的“头结点”是两个概念,带头链表⾥的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这⾥“放哨的。
如果,带头的链表中,只有头节点,我们就称该链表为空链表。
二 双向链表的实现
创建一个新项目,并创建三个新文件,分别是:头文件 List.h ,源文件 List.c , test.c (测试文件)。
2.1 链表的初始化
把链表初始化为空。
前面我们提过,哨兵位不存储任何有效的元素
所以我们让链表的next指针和prev指针都指向他自己
因为后面代码都需要用到申请一个新的节点,所以我们单独封装一个函数 buyNode 用来申请节点。
代码:
LTNode* buyNode(LTDatatype x) {
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
node->data = x;
node->next = node->prev = node;
return node;
}
初始化代码:
//初始化
LTNode* LTInit() {
LTNode* phead = buyNode(-1);
return phead;
}
便于测试,我们写一个打印链表的函数:
代码:
//打印链表
void LTPrint(LTNode* phead) {
LTNode* pcur = phead->next;
while (pcur != phead) {
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
2.2 尾插
在双向链表中,我们申请一个新的节点,就要修改它的前驱指针和 next 指针。比如,我们要在该链表的尾部插入一个新的节点99,我们要让 newnode 的 prev 指针 指向它前面的节点,newnode 的 next 指针指向哨兵位。
对于原链表,我们要修改哨兵位的 prev 指针和 d3 节点的 next 指针。即让 d3 的 next 指针指向 newnode ,让 phead 的 prev 指针指向 newnode 。
代码:
//尾插
void LTPushBack(LTNode* phead, LTDatatype x) {
LTNode* newnode = buyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev->next = newnode;
phead->prev = newnode;
}
测试结果:
2.3 头插
在第一个有效节点之前插入节点。
PS:在哨兵位之前插入节点不叫头插,是尾插。
从下图我们可以看出来,头插时受到影响的节点是 哨兵位 节点和 d1 节点。
newnode 插入之后,newnode变成第一个有效的节点,所以对于 newnode 来说,它的 next 指针要指向它的下一个节点 d1,它的prev 指针要指向 它的前一个结点,也就是 哨兵位。对于 d1 来说,它的 prev 指针也要指向 newnode ,对于哨兵位来说,它的的 next 指针要指向 newnode 。
代码:
//头插
void LTPushFront(LTNode* phead, LTDatatype x) {
assert(phead);
LTNode* newnode = buyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
测试结果:
2.4 判断链表是否为空
前面已经提过 如果哨兵位的 next 指针指向自己说明链表为空
代码:
bool LTEmpty(LTNode* phead) {
assert(phead);
return phead->next == phead;
//如果这个表达式为真说明链表为空
}
2.5尾删
如果我们要删除(最后一个节点) d3 节点,那么 哨兵位 和 d2 节点会受到影响。
d2 节点的 next 指针原本是指向 d3 节点的,现在把 d3 节点删除,那么 d2 节点的 next 指针就要指向 哨兵位。原本 哨兵位 的 prev 指针是指向 d3 节点的,现在我们要把 d3 节点删除,那么 哨兵位 的 prev 指针,就要指向 d2 节点。
代码;
//尾删
void LTPopBack(LTNode* phead) {
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
测试结果:
2.6 头删
如果我们要删除(第一个节点) d1 节点,那么 哨兵位 的 next 指针会受到影响和 d2 节点的 prev指针会受到影响。
原本 d2 节点的 prev 指针原本是指向 d1 节点的,现在把 d1 节点删除,那么 d2 节点的 prev指针就要指向 哨兵位。 哨兵位 的 next 指针是指向 d1 节点的,现在我们要把 d1 节点删除,那么 哨兵位 的 next 指针,就要指向 d2 节点。
代码:
//头删
void LTPopFront(LTNode* phead) {
assert(!LTEmpty(phead));
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
测试结果:
2.7 查找某个数据在链表中的位置
从第一个有效节点开始寻找,如果找到了,返回皮粗如,没找到就继续遍历,直到 pcur 和 phead相等,如果 pcur = phead 说明没找到,跳出循环,返回NULL.
//查找某个数据在链表中的位置
LTNode* LTFind(LTNode* phead, LTDatatype x) {
LTNode* pcur = phead->next;
while (pcur != phead) {
if (pcur->data == x) {
return pcur;
}
pcur = pcur->next;
}
return NULL;//没找到 返回空
}
测试结果:
2.8 在pos位置之后插⼊数据在pos 位置后插入
如果在 pos 节点后插入新的节点 newnode ,d2 的 next 指针与 d3 的 prev 指针会受到影响。
如果在 pos 位置后插入新节点,那么 pos 的 next 指针应该指向新节点 newnode ,d3 的 prev 指针应该指向 newnode。让 newnode 的 prev 指针指向 pos ,newnode 的 next 节点指向 d3。
代码:
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDatatype x) {
assert(pos);
LTNode* newnode = buyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
测试结果:
2.9 删除pos位置的数据
如果我们要删除 d1 节点,那么 哨兵位 的 next 指针会受到影响和 d2 节点的 prev指针会受到影响。
原本 d2 节点的 prev 指针原本是指向 d1 节点的,现在把 d1 节点删除,那么 d2 节点的 prev指针就要指向 哨兵位。原本 哨兵位 的 next 指针是指向 d1 节点的,现在我们要把 d1 节点删除,那么 哨兵位 的 next 指针,就要指向 d2 节点。
代码:
//删除pos位置的数据
void LTErase(LTNode* pos) {
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
测试结果:
2.10 销毁链表
销毁链表,我们需要从头节点开始销毁,如果我们要删除 d1 ,我们需要提前把 d1 后面的节点保存下来,避免删除 d1 后,找不到后面的内容。
我们建立一个循环,当 pcur = phead 时,说明我们把链表中所有的有效节点都删除了,然后在单独释放哨兵位。由于我们传的参数是一级指针,而形参的改变不会影响实参,所以我们要在测试数据时对plist进行释放。
代码:
//销毁链表
void LTDesTroy(LTNode* phead) {
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
全部代码
List.h代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDatatype;
//定义双向链表结构
typedef struct ListNode {
LTDatatype data;
struct ListNode* next;//指向下一个节点的指针
struct ListNode* prev;//指向前一个节点的指针
}LTNode;
//初始化
LTNode* LTInit();
//打印链表
void LTPrint(LTNode* phead);
//判断是否为空
bool LTEmpty(LTNode* phead);
//尾插
//因为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);
//在pos位置之后插⼊数据----pos位置之前插入代码基本是一样的(双向的)
void LTInsert(LTNode* pos, LTDatatype x);
//删除pos位置的数据
void LTErase(LTNode* pos);
//销毁链表
void LTDesTroy(LTNode* phead);
List.c代码
#include"List.h"
//添加节点
LTNode* buyNode(LTDatatype x) {
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
node->data = x;
node->next = node->prev = node;
return node;
}
//初始化
LTNode* LTInit() {
LTNode* phead = buyNode(-1);
return phead;
}
//打印链表
void LTPrint(LTNode* phead) {
LTNode* pcur = phead->next;
while (pcur != phead) {
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//判断链表是否为空
bool LTEmpty(LTNode* phead) {
assert(phead);
return phead->next == phead;
//如果这个表达式为真说明链表为空
}
//尾插
void LTPushBack(LTNode* phead, LTDatatype x) {
LTNode* newnode = buyNode(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 = buyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = 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) {
LTNode* pcur = phead->next;
while (pcur != phead) {
if (pcur->data == x) {
return pcur;
}
pcur = pcur->next;
}
return NULL;//没找到 返回空
}
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDatatype x) {
assert(pos);
LTNode* newnode = buyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
//删除pos位置的数据
void LTErase(LTNode* pos) {
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//销毁链表
void LTDesTroy(LTNode* phead) {
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}
test.c代码
#include"List.h"
void test(){
LTNode* plist = LTInit();
//尾插
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPrint(plist);
//销毁链表
LTDesTroy(&plist);
plist = NULL;
LTPrint(plist);
//查找某个数据在链表中的位置
/*LTNode* find = LTFind(plist, 2);
if (find == NULL) {
printf("没找到\n");
}
else {
printf("找到了\n");
}
LTErase(find);
LTPrint(plist);*/
//在pos位置之后插⼊数据
//LTInsert(find, 88);
//头删
/*LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);*/
//尾删
/*LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);*/
//头插
/*LTPushFront(plist, 1);
LTPushFront(plist, 2);
LTPushFront(plist, 3);
LTPushFront(plist, 4);*/
}
int main() {
test();
return 0;
}
祝大家生活愉快。
标签:prev,pos,next,链表,LTNode,phead,双向,newnode,数据结构 From: https://blog.csdn.net/2303_80645930/article/details/143021379