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

前言:前面我们实现了顺序表和单链表,这次来实现一个结构更复杂的链表-----带头双向循环链表。不要被它的名字吓到哦,只是结构复杂而已,它的结构更有利于代码的实现。

1 双向循环链表的介绍

有了单链表的基础,要实现这个双向循环带头链表其实并不难。下面我们先来了解一下什么是双向循环带头链表。

这就是双向循环带头链表的结构图,可以很清晰的看到,这个链表需要两个指针,一个指向后继结点,一个指向前驱节点,其次还需要一个头结点。只是这个头结点并不需要存储有效数据

2 双向循环链表的实现

2.1 双向循环带头链表的定义

c 复制代码
//存储的数据类型
typedef int LDataType;
//链表的定义
typedef struct ListNode
{
	LDataType val;
	struct ListNode* next;//指向后继节点
	struct ListNode* prev;//指向前驱节点
}LTNode;

2.2 双向循环带头链表的接口

c 复制代码
//初始化双向循环带头链表'
LTNode* ListInit();

//打印
void ListPrint(plist);

//尾插
void ListPushBack(LTNode* phead, LDataType x);

//尾删
void ListPopBack(LTNode* phead);

//头插
void ListPushFront(LTNode* phead, LDataType x);

//头删
void ListPopFront(LTNode* phead);

//查找
LTNode* ListFind(LTNode* phead, LDataType x);

//pos位置之前插入
void ListInsert(LTNode* pos, LDataType x);

//删除pos位置
void ListErase(LTNode* pos);

//销毁链表
void ListDestroy(LTNode* phead);

2.2.1 初始化链表

c 复制代码
//初始化双向循环带头链表
LTNode* ListInit()
{
	//哨兵位头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2.2.2 创建新节点

c 复制代码
//创建新节点
LTNode* BuyListNode(LDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->val = x;
	newnode->prev=newnode->next=NULL;
	return newnode;
}

2.2.3 链表尾插

c 复制代码
//尾插
void ListPushBack(LTNode* phead, LDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	/*LTNode* newnode = (LTNode*)malloc(sizeof(LDataType));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->val = x;*/
	LTNode* newnode = BuyListNode(x);
	newnode->next = phead;
	phead->prev = newnode;
	newnode->prev = tail;
	tail->next = newnode;
}
通过phead->prev就可以找到链表的尾节点,增加的节点newnode->prev
应该链接链表尾节点,链表的尾节点链接newnode,newnode->next应
该链接链表的头结点,链表的头结点的prev保存newnode的地址,使
newnode成为链表新的尾节点。

2.2.4 链表头插

c 复制代码
//头插
void ListPushFront(LTNode* phead, LDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* pheadNext = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	pheadNext->prev = newnode;
	newnode->next = pheadNext;
}
先创建一个指针指向头结点的后继结点,再让newnode->next保存该
节点的地址,该节点的prev保存newnode的地址,phead->next保存
newnode的地址,newnode->prev保存头结点的地址。

2.2.5 链表尾删

c 复制代码
//尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}
通过phead->prev找到链表尾节点,再通过尾节点的prev找到尾节点
的前驱节点,让该前驱节点的next指向phead,phead->prev指向该前
驱节点,最后释放尾节点。

2.2.6 链表头删

c 复制代码
//头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* head = phead->next;
	LTNode* next = head->next;
	next->prev = phead;
	phead->next = next;
	free(head);
}
首先通过phead->next找到链表头结点的后继结点,再通过该后继结点找到下一个节点,使该节点的prev保存头结点的地址,头结点的next保存该节点的地址,最后释放该后继结点。

2.2.7 链表的查找

c 复制代码
//查找
LTNode* ListFind(LTNode* phead, LDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

从头结点的后继结点开始遍历查找,找到了就返回该节点的地址,找不到返回NULL(当再一次遍历到头结点时,说明未找到)。

2.2.8 从pos位置之前插入

c 复制代码
//pos位置之前插入
void ListInsert(LTNode* pos, LDataType x)
{
	assert(pos != NULL);
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}
首先找到pos位置的前驱节点,再让newnode->prev指向该前驱节
点,该前驱节点的next指向newnode,pos->prev指newnode,
newnode->next指向pos。

2.2.9 删除pos位置

c 复制代码
//删除pos位置
void ListErase(LTNode* pos)
{
	assert(pos != NULL);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}
找到pos位置的前驱节点和后继结点,让该前驱节点的next指向该后
继结点,该后继结点的prev指向该前驱节点。

2.2.10 链表的打印

c 复制代码
//打印
void ListPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

从头结点的后继结点开始遍历链表,当节点不是头结点时就打印该节点的值。

2.2.11 销毁链表

c 复制代码
//销毁链表
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* tmp = cur;
		cur = cur->next;
		free(tmp);
		tmp = NULL;
	}
	cur = NULL;
	phead = NULL;
}

从头结点的后继结点开始销毁,先记录该后继结点的下一个节点,再销毁该后继结点。重复上述操作,直到再一次回到头结点的位置,此时销毁该头结点

3 完整代码的实现

List.h文件

c 复制代码
#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LDataType;

typedef struct ListNode
{
	LDataType val;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;


//初始化双向循环带头链表'
LTNode* ListInit();

//打印
void ListPrint(plist);

//尾插
void ListPushBack(LTNode* phead, LDataType x);

//尾删
void ListPopBack(LTNode* phead);

//头插
void ListPushFront(LTNode* phead, LDataType x);

//头删
void ListPopFront(LTNode* phead);

//查找
LTNode* ListFind(LTNode* phead, LDataType x);

//pos位置之前插入
void ListInsert(LTNode* pos, LDataType x);

//删除pos位置
void ListErase(LTNode* pos);

//销毁链表
void ListDestroy(LTNode* phead);

List.c文件

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"

//初始化双向循环带头链表
LTNode* ListInit()
{
	//哨兵位头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

//打印
void ListPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

//销毁链表
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* tmp = cur;
		cur = cur->next;
		free(tmp);
		tmp = NULL;
	}
	cur = NULL;
	phead = NULL;
}

//创建新节点
LTNode* BuyListNode(LDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->val = x;
	//newnode->prev = newnode->next = NULL;
	return newnode;
}

//尾插
void ListPushBack(LTNode* phead, LDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	/*LTNode* newnode = (LTNode*)malloc(sizeof(LDataType));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->val = x;*/
	LTNode* newnode = BuyListNode(x);
	newnode->next = phead;
	phead->prev = newnode;
	newnode->prev = tail;
	tail->next = newnode;
}

//尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	//free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}

//头插
void ListPushFront(LTNode* phead, LDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* pheadNext = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	pheadNext->prev = newnode;
	newnode->next = pheadNext;
}

//头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* head = phead->next;
	LTNode* next = head->next;
	next->prev = phead;
	phead->next = next;
	free(head);
}

//查找
LTNode* ListFind(LTNode* phead, LDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

//pos位置之前插入
void ListInsert(LTNode* pos, LDataType x)
{
	assert(pos != NULL);
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

//删除pos位置
void ListErase(LTNode* pos)
{
	assert(pos != NULL);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

test.c文件

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"


void ListTest1()
{
	LTNode* plist = ListInit();

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);

	ListPrint(plist);

	ListPopBack(plist);
	ListPopBack(plist);

	ListPrint(plist);

	ListPushFront(plist, 6);
	ListPushFront(plist, 7);
	ListPushFront(plist, 8);
	ListPushFront(plist, 9);
	ListPushFront(plist, 10);

	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);
	ListPopFront(plist);

	ListPrint(plist);

	LTNode* pos = ListFind(plist, 2);
	if (NULL != pos)
	{
		printf("找到了\n");
	}
	else
	{
		printf("找不到\n");
	}

	ListInsert(pos, 10);
	
	ListPrint(plist);

	pos = ListFind(plist, 1);

	ListErase(pos);

	ListPrint(plist);

	ListDestroy(plist);
	plist = NULL;

	//ListPrint(plist);

}
int main()
{
	ListTest1();
	return 0;
}

4 顺序表与链表的对比

相关推荐
爱吃烤鸡翅的酸菜鱼3 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
寻找码源1 小时前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
手握风云-1 小时前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
美式小田3 小时前
单片机学习笔记 9. 8×8LED点阵屏
笔记·单片机·嵌入式硬件·学习
熬夜学编程的小王3 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
猫爪笔记3 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html
阿史大杯茶3 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
_不会dp不改名_3 小时前
HCIA笔记3--TCP-UDP-交换机工作原理
笔记·tcp/ip·udp
Chris _data3 小时前
二叉树oj题解析
java·数据结构
-一杯为品-3 小时前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程