数据结构——带头双向循环链表

呀哈喽,我是结衣。

前言

说到链表前面我们讲了单链表,但是链表可不止一种,要分类的话。链表可以分为带头或不带头,单向或双向,循环或者不循环,也就是说链表一共应该是有8种结构的,我们上次讲的链表就是不带头单向不循环链表。是链表中结构最简单的一种。


我们在来简单的解释一下两种链表把

1.无头单向非循环链表 :结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结

构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2.带头双向循环链表 :结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都

是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

链表的实现

前边讲了那么多,最重要的还是要自己能把链表给实现了。下面就是实现的教学了。

创建不同文件

创建3个文件,分别要实现的功能为函数的声明,函数的定义,以及测试 。目的是方便后续的增删查改。

结构体的创建

prev表示上一个节点,next表示下一个节点。

函数的声明

和单链表的函数声明差不多,都是头删,头插,尾插,尾删,打印等等...

我们先函数的声明先写完,后面再对他们挨个实现。

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

写完函数声明接下来就是函数的实现了。

函数的实现

函数的实现写在list.c文件里面

我们先来讲头节点的创建。头节点是链表的头部且不会存储有效的数据。

头节点的创建

c 复制代码
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	head->data = -1;//可以随便存一个数据,在后续的操作中不会用到头节点的数据。
	head->prev = head;
	head->next = head;//prev和next都要指向本身,以实现循环。
	return head;
}

写完头节点的创建,我们还要写一个打印函数,为了让后续的操作更加直观。

打印函数的实现

c 复制代码
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	printf("phead");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("phead\n");
}

cur!=pHead,目的是为了不打印头节点。

下面我们要写一个插入函数,才能直观的观察。所以我们接下来写尾插函数。

尾插函数的实现

画图解释

c 复制代码
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	cur->prev = newnode;
	newnode->next = cur;
}

写到尾插函数,我们又要写一个新节点的创建函数

c 复制代码
ListNode* CreatNode(LTDataType x)
{
	ListNode* newnode = (ListNode* )malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

欧克,写完了开始打印测试。

看上去是成功了,那就说明没什么问题了,有问题后续再检查。

头插函数的实现

尾插写完当然就是头插了。

画图解释

c 复制代码
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* next = pHead->next;
	cur->next = newnode;
	newnode->prev = cur;
	newnode->next = next;
	next->prev = newnode;
}

接下来测试看看。

依旧没有问题。

尾删函数的实现

删除后要记得free哦

c 复制代码
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead->next != pHead);//当只有头节点时报错
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	cur->prev = tail->prev;
	tail->prev->next = cur;
	free(tail);
	tail = NULL;
}

测试

我们先删一些,再全部删完,再多删一个分别测试。


从这三张图片我们也可以看出来,代码是没有问题的。(在这里我悄悄改了一下打印函数的代码,看出来了吗?)

头删函数的实现

要记得free哦

c 复制代码
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead->next != pHead);
	ListNode* first = pHead->next;
	pHead->next = first->next;
	first->next->prev = pHead;
	free(first);
	first = NULL;
}

下面再测试一下,有没有问题。

同样也是没有问题的。

销毁函数的实现

为什么就到销毁了呢?当然是想把剩下的指定位置的增删查改当成作业咯。

c 复制代码
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
	//最后free pHead
	free(pHead);
	pHead = NULL;
}

就这样结束咯。

下面我们把代码都贴出来,(也包括查找函数和指定位置的增删查改函数)

代码

list.h

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

list.c

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
ListNode* CreatNode(LTDataType x)
{
	ListNode* newnode = (ListNode* )malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	head->data = -1;//可以随便存一个数据,在后续的操作中不会用到头节点的数据。
	head->prev = head;
	head->next = head;//prev和next都要指向本身,以实现循环。
	return head;
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	printf("phead<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("phead\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	cur->prev = newnode;
	newnode->next = cur;
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = CreatNode(x);
	ListNode* cur = pHead;
	ListNode* next = pHead->next;
	cur->next = newnode;
	newnode->prev = cur;
	newnode->next = next;
	next->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead->next != pHead);//当只有头节点时报错
	ListNode* cur = pHead;
	ListNode* tail = pHead->prev;
	cur->prev = tail->prev;
	tail->prev->next = cur;
	free(tail);
	tail = NULL;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead->next != pHead);
	ListNode* first = pHead->next;
	pHead->next = first->next;
	first->next->prev = pHead;
	free(first);
	first = NULL;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
	//最后free pHead
	free(pHead);
	pHead = NULL;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	ListNode* cur = pHead->next;
	if (x == -1)
	{
		return NULL;
	}
	while (cur != pHead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* newnode = CreateNode(x);
	ListNode* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

test.c

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include "list.h"
void test1()
{
	ListNode* head = ListCreate();//头节点的创建
	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPrint(head);
	ListPushFront(head, 5);
	ListPushFront(head, 6);
	ListPushFront(head, 7);
	ListPrint(head);
}void test2()
{
	ListNode* head = ListCreate();//头节点的创建
	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPrint(head);
	ListPopFront(head);
	ListPopFront(head);
	ListPopFront(head);
	ListPopFront(head);
	ListPopFront(head);

	ListPrint(head);
}
int main()
{
	test2();

	return 0;
}


相关推荐
劲夫学编程42 分钟前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪44 分钟前
孤岛的总面积(Dfs C#
算法·深度优先
努力变厉害的小超超2 小时前
ArkTS中的组件基础、状态管理、样式处理、class语法以及界面渲染
笔记·鸿蒙
浮生如梦_3 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
wheeldown6 小时前
【数据结构】选择排序
数据结构·算法·排序算法
aloha_7896 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot