C语言实现双向链表

声明:本文是个人学习过程的记录,如有不同意见,欢迎友好讨论

正文如下

双向链表

链表有(2*2*2)个种类,如下

带头和不带头什么意思?如下:

带头指的是有哨兵位,哨兵位即为头节点,哨兵位不存东西,哨兵位的下一个节点才开始存有效数据

不带头指的是链表没有哨兵位,头节点即为存储有效数据的第一个节点

单向双向什么意思?如下:

单向指的是链表只可以从头到尾遍历,不能从尾到头

双向是的是链表既可以从头到尾遍历,又可以从尾到头遍历

循环不循环什么意思?如下:

不循环指的是 尾节点的next指针指向NULL

循环指的是  尾节点的next指针指向第一个节点

所以我上一个博客里面写的单链表,实际上是

不带头 单向 不循环 链表

接下来咱要写的是 带头 双向 循环 链表

简称双向链表   如下图

实际上比较常见的链表就以上两种

双向链表结构看着吓人,其实比单链表简单点,代码也要少点

在单链表中,比如尾插,需要找到尾节点,这时候需要用循环

单链表中有很多需要循环来实现的操作

但是在双向链表中,这种代码是不需要的

正式开始----

首先当然是定义双线链表节点的结构

typedef int DataType;

typedef struct ListNode

{

DataType data;   //存储的数据

struct ListNode* next;   //指向下一个节点的指针

struct ListNode* prev;   //指向上一个节点的指针

}Node;

注意区分:

单链表初始的时候:空链表

双向链表初始的时候:只有个头节点(哨兵位)

申请节点:

和之前单链表的差不多

代码如下:

Node* ApplyNode(DataType x)

{

Node* newnode = (Node*)malloc(sizeof(Node));

assert(newnode);

newnode->data = x;

newnode->next = newnode->prev = newnode;    //确保申请到的每一个节点都是可以自循环的

return newnode;

}

双线链表初始化:

与之前写的单链表不同,双向链表需要确保哨兵位的存在,所以要在所有函数运行前,运行该初始化函数

注意:双向链表的哨兵位不存实质性数据,不可以被删除,修改(除非链表销毁了)

代码如下:

void ListInit(Node** pphead)//头节点定义的时候就是一级指针,要对一级指针修改,就要取一级指针的地址,即二级指针

{

*pphead = ApplyNode(-1);

}

插入:

Q: 可以传二级指针进插入类函数吗?

A:行,但是不太安全,哨兵位不可以被修改,传二级指针有导致哨兵位被修改的风险,所以传一级指针就好了

尾插

在最后一个有效数据后边插入,如果只有哨兵位的话就在哨兵位后插入

设插入的数据为newnode

指针指向做出的改变如下

——d3的next指针指向newnode,

——哨兵位的prev指针指向newnode(即“循环”,哨兵位的上一个节点是最后一个节点,即最后插入的newnode)

——newnode的prev指针指向d3

——newnode的next指针指向哨兵位

代码如下:

  //尾插

void DLPushBack(Node* phead, DataType x)

{

assert(phead);  //phead不可以为空

Node* newnode = ApplyNode(x);

Node* last = phead->prev;  //原本的最后一个节点

last->next = newnode;  

phead->prev = newnode;

newnode->next = phead;

newnode->prev = last;

}

头插

在第一个有效数据前面插入,即哨兵位后面插入

设插入的数据为newnode

指针指向做出的改变如下

——哨兵位的next指针指向newnode

——newnode的next指向哨兵位的next原本指向的数据(提前保存哨兵位的next原本指向的数据的地址)

——newnode的prev指向哨兵位

——哨兵位的next原本指向的数据的prev指向newnode

代码如下:

  //头插

void DLPushFront(Node* phead, DataType x)

{

assert(phead);

Node* newnode = ApplyNode(x);

Node* tem = phead->next;   //提前保存哨兵位的next原本指向的数据的地址

phead->next = newnode;

newnode->prev = phead;

newnode->next = tem;

tem->prev = newnode;

}

链表中间位置插入

道理大差不差,想清楚就好写得很 跟指定位置删除思路差不多其实

删除

头删

删除第一个有效数据节点(注意,不可以删哨兵位的)

代码如下:

//头删

void DLDelFront(Node* phead)

{

assert(phead);

Node* del = phead->next;

if (del == NULL||del==phead)

{

printf("%删除失败!\n");

return;

}

phead->next = phead->next->next;

phead->next->next->prev = phead;

free(del);

del = NULL;

}

尾删

删除最后一个有效数据节点(注意,不可以删哨兵位的)

代码如下:

//尾删

void DLDelBack(Node* phead)

{

assert(phead);

Node* del = phead->prev; 

if (del == NULL || del == phead)

{

printf("%删除失败!\n");

return;

}

Node* del_prev = phead->prev->prev;

phead->prev = del_prev;

del_prev->next = phead;

free(del);

del = NULL;

}

指定删  ——》比较常用

删除对应数据的有效数据节点(注意,不可以删哨兵位的)

代码如下:

//指定删 -》比较常用

void DLDelSearch(Node* phead, DataType x)

{

assert(phead);

Node* pointer = phead->next;

if (pointer== phead)

{

printf("%双向链表未存储有效数据,删除失败!\n");

return;

}

//pointer指向的数据为x时跳出来

while (pointer->data != x)  

{

if (pointer == phead)  //遍历完成了都找不到,说明不存在,要防止死循环

{

printf("要删除的数据不存在!\n");

return;

}

pointer = pointer->next;

}

Node* del_next = pointer->next;

Node* del_prev = pointer->prev;

del_prev->next = del_next;

del_next->prev = del_prev;

free(pointer);

pointer = NULL;

}

查找

遍历然后找到对应的就行,跟前面的指定删一个道理

代码如下:

//查找

void DLSearch(Node* phead, DataType x)

{

assert(phead);

Node* pointer = phead->next; //从第一个有效数据节点开始遍历查找

if (pointer == phead)

{

printf("链表中未存储数据!\n");

return;

}

while (pointer->data!=x) //等于x时跳出

{

if (pointer==phead) //说明遍历完了都没找到

{

printf("您要查找的数据不存在!\n");

return;

}

pointer = pointer->next;

}

printf("找到了!\n");

return;

}

更改

查找到想要改的数据,然后改就行了

代码如下:

//更改

void DLChange(Node* phead, DataType x)

{

assert(phead);

Node* pointer = phead->next; //从第一个有效数据节点开始遍历查找

if (pointer == phead)

{

printf("链表中未存储数据!\n");

return;

}

while (pointer->data != x) //等于x时跳出

{

if (pointer == phead) //说明遍历完了都没找到

{

printf("您要查找的数据不存在!\n");

return;

}

pointer = pointer->next;

}

printf("找到了!\n");

DataType n = 0;

printf("请输入更改后的数据:  ");

scanf("%d", &n);

pointer->data = n;

}

打印

代码如下:

//双向链表的打印

void DLPrint(Node* phead)

{

Node* pointer = phead->next;

while (pointer != phead)

{

printf("%d   ", pointer->data);

pointer = pointer->next;

}

printf("\n"); 

}

销毁

就遍历,然后一个一个节点free,注意:哨兵位也要free

代码如下:

//销毁

void DLDestroy(Node* phead)

{

Node* pointer = phead->next;

while (pointer != phead)

{

Node* tem = pointer->next;

free(pointer);

pointer = tem;

}

//出来之后,pointer指向phead即哨兵位

free(phead);

phead = NULL;

pointer = NULL;

}

值得注意的一点是:

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值