数据结构-带头双向循环链表的基本实现(C语言,简单易懂,含全部代码)

链表的概念和结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

结构:实际中链表的结构非常多样,以下情况组合起来就有8种链表结构。

(1)单向、双向
(2)带头、不带头
(3)循环、非循环

本篇主要详解带头双向循环链表,结构如图:
在这里插入图片描述

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

链表的实现

首先在头文件中定义结构体和提供接口:

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

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
	
}ListNode;

void ListPrint(ListNode* phead);//打印链表

ListNode* BuyListNode(LTDataType x);//开辟新节点

ListNode* ListInit();//初始化哨兵位

void ListPushBack(ListNode* phead, LTDataType x);//尾插,只要不改变头指针的内容,就不用传二级指针

void ListPushFront(ListNode* phead, LTDataType x);//头插

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

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

ListNode* ListFind(ListNode* phead, LTDataType x);//查找

void ListInsert(ListNode* pos, LTDataType x);//pos位置前插

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

其次在源文件中实现接口功能:
(1)链表打印

void ListPrint(ListNode* phead)
{
	ListNode* cur = phead;
	while (cur != phead)
	{
		printf("%d", cur->data);
		cur = cur->next;//找到下一个节点
	}
	printf("\n");
}

(2)开辟新节点

ListNode* BuyListNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

(3)初始化哨兵位

ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

(4)尾插
带头双向循环链表的头插尾插非常方便,只需要找到头(phaed->next)和尾(phead->prev)并链接

void ListPushBack(ListNode* phead, LTDataType x)
{
	//无论是0个(只有哨兵位)还是多个节点都可以,与节点个数无关
	assert(phead);
	ListNode* tail = phead->prev;//找尾
	ListNode* newnode = BuyListNode(x);

	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

在这里插入图片描述
(5)头插

void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* first = phead->next;
	ListNode* newnode = BuyListNode(x);
	//phead newnode first 
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

在这里插入图片描述
(6)尾删

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* tailPrev = tail->prev;
	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;
}

(7)头删

void ListPopFront(ListNode* phead)
{
	assert(phead);//没有节点
	assert(phead->next != phead);//只有phead

	ListNode* first = phead->next;
	ListNode* second = first->next;
	
	free(first);

	phead->next = second;
	second->prev = phead;
}

(8)查找x

ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

(9)pos位置前插

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

(10)pos位置删除

void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);
}

主函数的设计大家可以自由发挥,做个简单的测试功能调用函数或是创建菜单栏实现交互都可以。我水平有限,请朋友们谅解!写的不好的地方还请大佬们指出。最后,如果这篇文章对你有帮助,就点个赞或者收藏评论一下吧,谢谢大家支持😁。

下期预告——栈和队列详解!