一听就很吓人的双向带头循环链表的完美实现
开篇前的碎碎念
这部分不重要,着急看正文的读者朋友可忽略:
好久没写博客了,空档期很长,长达20天之久,有种不应该有的高三周末玩一下午手机没有学习的罪恶感,也许未必可能大概率是以下原因导致的。
1.特种兵式旅游
整个五月份笔者走过了20个城市,对郑州,青岛,长沙,日照尤为深刻,这些城市在我的18岁留下不可磨灭的影响,趁年轻,多出去看看,逃离这平凡无趣的生活。
2.花花世界迷人眼
自从五一回来,不知道为什么,认识了一些热情的女孩子,交了很多朋友,主要的精力花对人际关系的维持,虽说和朋友们聊天,分享快乐是一件无可厚非,没有回报但还是义无反顾会去做的事情,但是我还是需要控制,改进方案如下:
减少盯着手机的时间,拿出统一的时间来看消息,否则一来消息就不能自己地回复,碎片化的娱乐会把我的时间撕成碎片。
3.艾尔登法环是天
真的很好玩,尤其是多次被一个boss打败,然后总结经验总结boss特点以及应对方法然后打过boss,如果你也和我一样喜欢魂游,那么这件事真的————泰裤辣!!!
分享喜悦
上个月参加博文比赛,笔者运气不错,侥幸拿了两块奖牌,这一切的一切离不开读者朋友的支持,致谢!
正文开篇
一、双向带头循环链表是什么?
1.双向
后一个节点可以找到前一个结点,这是单链表都梦寐以求的功能,结构如下
typedef int DateType ;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
DateType data;
}LTNode;
2.带头
指的是该链表含虚拟头结点,即哨兵位,目的是方便处理头指针为空的情况
3.循环
顾名思义,就是尾节点可以找到头节点,头节点还能指向尾节点
4.功能
还能有什么功能呢,无非就是增删查改
以下是代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DateType ;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
DateType data;
}LTNode;
//初始化链表
LTNode* Initlist();
//打印双向带头循环链表
void LTPrintList(LTNode* phead);
//销毁节点
void LTDestory(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, DateType x);
//头插数据
void LTPushfront(LTNode* plist, DateType x);
//头删
void LTpopfront(LTNode* phead);
//尾删
void LTPopBack(LTNode* phead);
//查找,返回该数据的位置
LTNode* LTFind(LTNode* phead, DateType x);
//精准插入数据,在pos之前插入数据
void LTInsert(LTNode* pos, DateType x);
//删除pos位置的数据
void LTErase(LTNode* pos);
二、基础操作实现
1.初始化
如果只有一个节点,那么它肯定是自己指向自己
代码实现
LTNode* Initlist()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
2.创建节点
使用malloc函数给节点分配空间,按需分配,返回这段空间的首地址newnode
代码如下:
LTNode* BuyLTNode(DateType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
3.尾部插入数据(LTPushBack)
初始化链表后,方可导入数据
依次尾插数据,效果为:
现尾插数据8
操作为:
- 定义尾指针,通过头指针phead找到尾节点tail
- 创建新节点newnode
- 修改指针域
新节点与tail节点直接的链接
新节点与phead之间的连接
代码实现:
void LTPushBack(LTNode* phead, DateType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = BuyLTNode(x);
assert(tail);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
4.尾部删除数据(LTPopBack)
即找到倒数第二的节点ptail
然后将tail中数据和指针域置空
void LTPopBack(LTNode* plist)
{
assert(plist);
LTNode* tail = plist->prev;
LTNode* realtail = tail->prev;
realtail->next = NULL;
free(tail);
}
5.头部插入数据(LTPushfront)
在哨兵位和第二个节点之间插入节点
首先需要定义cur=phead->next
保存phead指向的节点的位置
其次修改哨兵位和新节点的指针指向
最后修改新节点与cur的指针指向
代码实现
void LTPushfront(LTNode* plist, DateType x)
{
assert(plist);
LTNode* cur = plist->next;
LTNode* newnode = BuyLTNode(x);
plist->next = newnode;
newnode->prev = plist;
newnode->next = cur;
cur->prev = newnode;
}
6.头部删除数据(LTpopfront)
修改phead和phead->next->next之间的指针关系
将头节点忽视,对头节点冷暴力,自然而然,头节点被内存回收
类似于人和人之间的关系
死亡并不可怕,最可怕的是被遗忘
(PS:作者你在emo什么???)
代码实现:
void LTpopfront(LTNode* phead)
{
LTNode* cur = phead->next;
LTNode* curnext = cur->next;
phead ->next=curnext;
curnext->prev = phead;
}
7.打印链表数据(LTPrintList)
定义结构体指针cur依次访问各节点的数据
用一个while循环控制
当cur=phead循环停止
单步调试跟踪发现循环目的达到
代码实现
void LTPrintList(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
assert(cur);
printf("【sentinel bit】><");
while (cur!=phead)
{
if (cur == NULL)
{
return NULL;
}
else {
printf("【%d】><", cur->data);
cur = cur->next;
}
}
printf("\n");
}
8.销毁链表(LTDestory)
最关键的细节就是一定一定一定要保存要销毁节点的下一个节点的位置,否则不保存找不到下一个节点,无法进行下一步销毁
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur!=phead)
{
if (cur == NULL)
{
break;
}
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
9.定位数据位置(LTFind)
输入链表中的某个数据,返回该数据的地址pos
实现思路很简单,对链表数据遍历,满足要求就返回当前节点地址
LTNode* LTFind(LTNode* phead, DateType x)
{
assert(phead);
assert(phead->next);
LTNode* cur = phead->next;
while (cur != phead)
{
if(cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
三.进阶操作
1.定位插入(LTInsert)
需要和LTFind函数搭配使用
先定位,再在pos之前插入数据
代码实现
void LTInsert(LTNode* pos, DateType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* tmp = pos->prev;
tmp->next = newnode;
newnode->prev = tmp;
newnode->next = pos;
pos->prev = newnode;
}
2.定位删除(LTErase)
需要和LTFind函数搭配使用
先定位,再删除pos位置数据
原链表数据
定位删除
代码实现
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posprev = pos->prev;
LTNode* posnext = pos->next;
posprev->next = posnext;
if (posnext == NULL)
{
return NULL;
}
posnext->prev = posprev;
free(pos);
}
总结
以上就是该链表的所以功能实现
完整代码见笔者gitee网址
查看完整代码点我
欢迎评论区一起交流,我们一起进步
他眺望着她的火车,她的旅程。他可以望见她的火车,但眺望不到她的旅程。
她发现自己的弱点像雨后春笋,任何一场雨下在任何一个角落,笋尖便会猝不及防地钻出地面,若要长成一棵竹子也好,可惜,弱点的春笋,最终都是被人割去食用的。
——《黄雀记》*苏童