之前我们写过了单链表的博文了,我们发现这是不是找头找尾有点麻烦啊。这里让我们来引入是双向带头的循环的链表。
双向循环链表
至此,正文开始:
首先让我们来区分什么几种类型:
类型
单向链表,双向链表,带头/不带头,循环/不循环
1.单向链表
2.双向链表:
3.带头/不带头
4.循环/非循环:
带头循环/不带头不循环
区别:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了
好了,有了上面图的认识,下面我们主要讲的是带头循环的双向链表。
创建结构体:
因为我们要的是双向循环链表,由上面的图片知道,我们需要知道头节点的前一个,尾节点的下一个这样的一些指针,所以我们才创建了一个结构体的prev指针和next指针,
剩下的在之前的文章已经讲过原因了,这里就不再多讲。
typedef int SLDatatype;
typedef struct Listnode
{
struct Listnode* next;
struct Listnode* prev;
SLDatatype data;
}Listnode;
创建新节点
由于后面向插入的代码中都要用到2,所以我们在这里先创建一个函数,来使后面不那么杂糅
Listnode* BuyListnode(SLDatatype x)
{
//扩容
Listnode* node=( Listnode*) malloc(sizeof( Listnode));
//判断
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
这里为什么是这个呢?
你看创建新节点,而它也是要双向的,
因为它只有一个,它的下一个和前一个是不是就是它本身,即空
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
解释:上面当中的exit(-1)
在C语言中, exit(-1) 是用于终止程序执行并返回一个状态码给操作系统的语句。
exit 函数定义在 stdlib.h 头文件中,它接受一个整数参数作为程序的退出状态码。通常约定, 0 表示程序正常结束,而非零值(这里是 -1 )一般表示程序出现了错误或异常情况而终止。例如:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("程序准备异常终止\n");
exit(-1);
// 这行代码不会执行,因为exit会直接结束程序
printf("这行不会输出");
return 0;
}
上述代码运行时会输出 程序准备异常终止 后就终止了,后面的 printf 语句不会被执行,同时返回 -1 这个状态码给操作系统告知其是异常结束。
其他的就跟顺序表,单链表中的大差不差。
初始化
初始化,即给链表创建一个头节点。
Listnode* ListInit()
{
Listnode* phead = BuyListnode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
解答:
上面的BuyListnode(-1)为啥是赋值-1呢?
答:因为我们的头节点不存储有效数据。
打印部分
void ListPrint(Listnode* phead)
{
assert(phead);
因为是带头的,而打印时不需要打印头的,所以是next
Listnode* cur = phead->next;
printf("<=head=>");
while (cur !=phead)
{
printf("%d=>", cur->data);
cur = cur->next;
}
printf("\n");
}
释放部分
//释放
void ListDestory(Listnode* phead)
{
assert(phead);
Listnode* cur = phead;
while (cur)
{
Listnode* temp = cur->next;
free(cur);
cur = temp;
}
}
判断是否空
bool Empty(Listnode* phead)
{
assert(phead);
if (phead->next==phead)
{
return true;
}
else
{
return false;
}
}
尾插部分:
//尾插
void ListPushBack(Listnode* phead,SLDatatype x)
{
assert(phead);
Listnode* newnode = BuyListnode(x);
Listnode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
分析:
这里就非常能体现这个的优势了
比如下面:
已经知道了phead了不是吗。
然后,因为它是循环的,
phead的前一个(prev)就是tail了,这样就直接能找到尾了。这是不是就非常简便了是不是!
接着,我们就跟着下图插入就非常简便了。
tail的next指向新节点
新节点前一个是指向tail
新节点的next指向头节点。
头节点的前一个是指向新节点。
就完成了!!!!
头插部分:
//头插
void ListPushFront(Listnode* phead, SLDatatype x)
{
assert(phead);
Listnode* newnode = BuyListnode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
解答:
因为我们弄了带头的哨兵。
所以直接在phead的下一个插入即是尾插了。
其他的跟尾插的思路差不多。
尾删部分
//尾删
void ListPopBack(Listnode* phead)
{
assert(phead);
Listnode* tail = phead->prev;
Listnode* tailprev = tail->prev;
phead->prev = tailprev;
tailprev->next = phead;
free(tail);
tail = NULL;
}
分析:
先找到尾,这在上面已经说过方法了。
再定义一个prev,即tail的前一个
然后将原本头指向尾改成指向prev
原本尾tail指向头phead的改成prev指向头
最后,再释放d3,就完成了。
中间随意位置插入
void ListInsert(Listnode* pos, SLDatatype x)
{
assert(pos);
Listnode* prev = pos->prev;
Listnode* newnode = BuyListnode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
分析:
先定义一个pos位置
找到pos位置后,再定义pos前一个指针,即pos->prerv
再插入就行了(按照上面的插入过程一样)
下面的图虽然有点吧准确(数太少了),但是基本原理是一样的
中间随意位置删除
void ListErase(Listnode* pos)
{
assert(pos);
Listnode* prev = pos->prev;
Listnode* Next = pos->next;
prev->next = Next;
Next->prev = prev;
free(pos);
//等用的人弄空
//pos = NULL;
}
分析:
也是先找到pos位置,
定义pos的前一个数指针。和pos的后一个数指针
再将,pos的前一个数指针,指向,pos的后一个数指针
最后,把pos指针释放掉。
查找部分
Listnode* Find(Listnode* phead, SLDatatype x)
{
assert(phead);
Listnode* cur = phead->next;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
跟单链表差不多,也挺容易理解的。这里就不过多去讲了。
总代码
List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"
Listnode* BuyListnode(SLDatatype x)
{
//扩容
Listnode* node=( Listnode*) malloc(sizeof( Listnode));
//判断
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
Listnode* ListInit()
{
Listnode* phead = BuyListnode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void ListPrint(Listnode* phead)
{
assert(phead);
Listnode* cur = phead->next;
printf("<=head=>");
while (cur !=phead)
{
printf("%d=>", cur->data);
cur = cur->next;
}
printf("\n");
}
//释放
void ListDestory(Listnode* phead)
{
assert(phead);
Listnode* cur = phead;
while (cur)
{
Listnode* temp = cur->next;
free(cur);
cur = temp;
}
}
//判断空
bool Empty(Listnode* phead)
{
assert(phead);
if (phead->next==phead)
{
return true;
}
else
{
return false;
}
}
//尾插
void ListPushBack(Listnode* phead,SLDatatype x)
{
assert(phead);
Listnode* newnode = BuyListnode(x);
Listnode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//头插
void ListPushFront(Listnode* phead, SLDatatype x)
{
assert(phead);
Listnode* newnode = BuyListnode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
//尾删
void ListPopBack(Listnode* phead)
{
assert(phead);
Listnode* tail = phead->prev;
Listnode* tailprev = tail->prev;
phead->prev = tailprev;
tailprev->next = phead;
free(tail);
tail = NULL;
}
void ListInsert(Listnode* pos, SLDatatype x)
{
assert(pos);
Listnode* prev = pos->prev;
Listnode* newnode = BuyListnode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
void ListErase(Listnode* pos)
{
assert(pos);
Listnode* prev = pos->prev;
Listnode* Next = pos->next;
prev->next = Next;
Next->prev = prev;
free(pos);
//等用的人弄空
//pos = NULL;
}
Listnode* Find(Listnode* phead, SLDatatype x)
{
assert(phead);
Listnode* cur = phead->next;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
头文件LIst.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLDatatype;
typedef struct Listnode
{
struct Listnode* next;
struct Listnode* prev;
SLDatatype data;
}Listnode;
//创建新节点
Listnode* BuyListnode(SLDatatype x);
//销毁
void ListDestory(Listnode* phead);
//初始化
Listnode* ListInit();
//打印
void ListPrint(Listnode* phead);
//判断空
bool Empty(Listnode* phead);
//尾插
void ListPushBack(Listnode* phead,SLDatatype x);
//头插
void ListPushFront(Listnode* phead, SLDatatype x);
//尾删
void ListPopBack(Listnode* phead);
//插中间随意位置
void ListInsert(Listnode* pos, SLDatatype x);
//删中间随意位置
void ListErase(Listnode* pos);
//寻找
Listnode* Find(Listnode* phead, SLDatatype x);
测试代码test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "list.h"
Test1()
{
Listnode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
/*ListPushFront(plist, 5);
ListPushFront(plist, 6);*/
//ListPopBack(plist);
Listnode*pos= Find(plist, 3);
// ListInsert(pos,3);
ListErase(pos);
ListPrint(plist);
}
int main()
{
Test1();
return 0;
}
最后,鸡汤部分:
你准备独自远行,忍受孤独和孤独,忍受身心的压迫,让汗水化为泪水,但你的脚步从未停止。干得好,即使不能获得桂冠,只要坚持下去,一定会得到最后的掌声。
加油,赶路人!
标签:prev,Listnode,cur,--,pos,next,链表,phead,数据结构 From: https://blog.csdn.net/go_bai/article/details/144649187