C语言实现数据结构——循环双链表

在我们实现单链表的时候,我们总是需要找到链表的尾部,这样的实现是非常的麻烦的,所以将链表设置为循环型链表十分的有必要
单链表: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);
}

结合图片来看:
在这里插入图片描述

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值