在我们实现单链表的时候,我们总是需要找到链表的尾部,这样的实现是非常的麻烦的,所以将链表设置为循环型链表十分的有必要
单链表:https://blog.csdn.net/EchoToMe/article/details/128344734?spm=1001.2014.3001.5501
和单链表一样,写一个循环双链表需要三个文件,两个.c文件一个.h文件
我们先来书写DList.h文件,将循环双链表需要的接口函数先进行书写
DList.h文件的书写
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef struct Listnode
{
Listnode* next;
Listnode* prev;
int data;
}Listnode;
Listnode* ListInit();//初始化
void ListPushBack(Listnode* plist,int x);//链表尾插
void ListPopBack(Listnode* plist);//链表尾删
void ListPushFront(Listnode* plist,int x);//链表头插
void ListPopFront(Listnode* plist);//链表头删
void PrintList(Listnode* plist);//打印链表内容
void ListDestroy(Listnode* plist);//链表销毁
Listnode* ListFind(Listnode* plist,int x);//查找列表元素
void InsertFront(Listnode* plist, Listnode* pos, int x);//在给定位置之前插入元素
void PopThis(Listnode* plist, Listnode* pos);//删除指定位置的元素
Listnode* CreatNewNode(int x);//创建一个新结点
这里对链表中的元素的数据类型可以不是int类型,可以是其他的类型,可以在我们书写的时候进行定义,同时也可以利用typedef处理一个数据类型,本文用int类型举例
书写完DList.h文件后,下面要对这些接口函数进行一个定义,此时我们需要书写DList.c文件
DList.c文件的书写
CreatNewNode函数的书写
Listnode* CreatNewNode(int x)
{
Listnode* newnode = (Listnode*)malloc(sizeof(Listnode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
在这个单独的一个接口函数中,我们只需要把newnode的next和prev全部设为空指针,在我们使用的时候再单独对这两个指针进行操作即可
ListInit函数的书写
Listnode* ListInit()
{
Listnode* phead = CreatNewNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
要完成一个双循环链表的关键在于链表的第一个元素和最后一个元素相连
ListPushBack函数的书写
void ListPushBack(Listnode* ps, int x)
{
Listnode* newnode = CreatNewNode(x);
Listnode* last = ps->prev;
last->next = newnode;
newnode->prev = last;
newnode->next = ps;
ps->prev = newnode;
}
结合下面图片就很好理解这个ListPushBack代码的书写
ListPopBack函数的书写
void ListPopBack(Listnode* ps)
{
Listnode* last = ps->prev;
Listnode* prlast = last->prev;
ps->prev = prlast;
prlast->next = ps;
free(last);//对之前的空间进行释放
}
同样的可以结合图片进行理解
ListPushFront函数的书写
void ListPushFront(Listnode* ps, int x)
{
Listnode* newnode = CreatNewNode(x);
Listnode* next = ps->next;
ps->next = newnode;
newnode->prev = ps;
newnode->next = next;
next->prev = newnode;
}
同时利用图片的形式来进行描述:
但是需要注意的是,此时必须先将原本ps的下一个结点进行标记,不然直接进行修改的话会导致找不到ps的下一个结点
ListPopFront函数的书写
void ListPopFront(Listnode* ps)
{
Listnode* next = ps->next;
Listnode* nnext = next->next;
ps->next = nnext;
nnext->prev = ps;
free(next);
next = NULL;
}
这里和上面同理,需要先记录一下除了哨兵结点以外的后两个结点,在利用画图的形式对这个代码进行理解:
ListPrint函数的书写
书写ListPrint函数实际上是对这个双循环链表进行一次遍历
void ListPrint(Listnode* ps)
{
Listnode* cur = ps->next;
while (cur != ps)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
DestroyList函数的书写
其实这个销毁函数和打印函数的本质是相同的,其实都是对这个链表的一个遍历
void ListDestroy(Listnode* ps)
{
Listnode* cur = ps->next;
while (cur != ps)
{
Listnode* next = cur->next;
free(cur);
cur = next;
}
free(ps);
ps = NULL;
}
注意这个地方需要在循环内标记next结点,因为如果我们不标记这个结点的话,利用free处理后我们就找不到下一个结点的地址
ListFind函数的书写
也是利用一个遍历的思想查找链表中元素所在的结点位置
Listnode* ListFind(Listnode* ps,int x)
{
Listnode* cur = ps->next;
while (cur != ps)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
InsertFront函数的书写
void InsertFront(Listnode* plist, Listnode* pos, int x)
{
Listnode* newnode = CreatNewNode(x);
Listnode* begin = pos->prev;
begin->next = newnode;
newnode->prev = begin;
newnode->next = pos;
pos->prev = newnode;
}
结合画图来看就会容易很多:
PopThis函数的书写
void PopThis(Listnode* plist, Listnode* pos)
{
Listnode* begin = pos->prev;
Listnode* next = pos->next;
begin->next = next;
next->prev = begin;
free(pos);
}
结合图片来看: