数据结构——双向带头循环链表的C语言代码实现

系列文章目录

数据结构——顺序表的C语言代码实现
数据结构——八种链表的C语言代码实现
数据结构——栈的C语言代码实现



前言

众所周知,链表主要有八种形式:
在这里插入图片描述

在上篇文章中我们实现了形式上最为简便的单项不带头非循环链表,了解了链表的基本特点和实现方法,故在此基础上来实现更为使用的双向带头循环链表。


提示:以下是本篇文章正文内容,下面案例可供参考

一、基础知识

1.双向链表的概念

双向链表较与单链表最大的区别便是:多了一个指向前一个节点的指针
在实现双向带头循环链表前,我们需要先弄明白何为头指针头结点首元节点
通俗的理解中:

·头指针便是链表中第一个节点的存储地址;
·头结点是一个不存储数据的节点,它的作用是使得链表的头指针非空,便于插入和删除等操作的实现;
·首元节点是链表中用于存储线性表第一个数据元素的节点。
在这里插入图片描述

2.头结点的优势

头结点优势在于使头指针永远非空
这样就使得:

· 尾插中不用讨论头指针为空的情况,也不用进行传址调用;
·尾删中不会出现改变头指针的情况,只需要传值调用
·等等…

二、代码实现

1.List.h

(1)引用函数库

代码如下(示例):
尤其注意引用stdlib.h函数库,如果忘记引用该函数库,而直接使用malloc等函数时,编译器不会报错或警告,但程序总是崩溃或不执行,这会使得我们不断怀疑程序编写的正确性,浪费大量时间查错。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>

(2)定义链表

代码如下(示例):

//便于统一更改所存储数据的类型
typedef int ListDataType;
//定义双向链表的节点
typedef struct ListNode
{
	ListDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LN;

(3)接口函数声明

代码如下(示例):
注意凡是带有头结点的链表,一般都有初始化头结点这一接口函数,实现头指针的永久非空!

//初始化头结点
void ListInit(LN** phead);

//尾插法
void ListPushBack(LN* phead,ListDataType x);

//头插法
void ListPopBack(LN* phead);

//尾删法
void ListPushFront(LN* phead, ListDataType x);

//头删法
void ListPopFront(LN* phead);

//查找链表中某数值所属节点的位置
LN* ListFind(LN* phead, ListDataType x);

//在指定节点前插入
void ListInsertFront(LN* phead, ListDataType x,ListDataType insert);

//在指定节点后插入
void ListInsertBack(LN* phead, ListDataType x, ListDataType insert);

//更改指定节点
void ListNodeChange(LN* phead, ListDataType x, ListDataType num);

//删除指定节点
void ListDeleteNode(LN* phead, ListDataType x);

//删除链表
void ListFree(LN* phead);

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

2.List.c

实现各个接口函数,注意引用List.h头文件。

#include"List.h"

(1)初始化头结点

代码如下(示例):

//初始化头结点
void ListInit(LN** phead)
{
	//带头节点的链表的最大特点便在于初始化头结点
	//使得头指针在插入前便指向头结点
	//省去了插入,删除过程中判断是否改变头指针的工作
	*phead = (LN*)malloc(sizeof(LN));
	(* phead)->next = *phead;
	(*phead)->prev = *phead;
}

(2)尾插法

代码如下(示例):

//尾插法
void ListPushBack(LN* phead,ListDataType x)
{
	//无需传入二级指针,因为头指针始终指向头结点
	//插入过程中不会改变头指针,故只需传值调用
	if (phead == NULL)
	{
		printf("未对头结点进行初始化!\n");
		return;
	}
	LN* tail = phead->prev;
	LN* tem = (LN*)malloc(sizeof(LN));
	tem->data = x;
	tail->next = tem;
	tem->prev = tail;
	tem->next = phead;
	phead->prev = tem;
}

(3)尾删法

代码如下(示例):

//尾删法
void ListPopBack(LN* phead)
{
	if (phead->next == NULL)
	{
		printf("链表为空\n");
		return;
	}
	LN* tail = phead->prev;
	tail->prev->next = phead;
	phead->prev = tail->prev;
	free(tail);
}

(4)头插法

代码如下(示例):

//头插法
void ListPushFront(LN* phead, ListDataType x)
{
	if (phead == NULL)
	{
		printf("未对头结点进行初始化!\n");
		return;
	}
	LN* tem = (LN*)malloc(sizeof(LN));
	tem->data = x;
	phead->next->prev = tem;
	tem->next = phead->next;
	phead->next = tem;
	tem->prev = phead;
}

(5)头删法

代码如下(示例):

//头删法
void ListPopFront(LN* phead)
{
	if (phead->next == NULL)
	{
		printf("链表为空\n");
		return;
	}
	LN* tem = phead->next->next;
	free(phead->next);
	phead->next = tem;
	tem->prev = phead;
}

(6)查找链表中某数值所属节点的位置

代码如下(示例):

//查找链表中某数值所属节点的位置
LN* ListFind(LN* phead, ListDataType x)
{
	if (phead->next == NULL)
	{
		printf("链表为空\n");
		return;
	}
	LN* tem = phead->next;
	while (tem != phead)
	{
		if (tem->data == x)
		{
			return tem;
		}
		tem = tem->next;
	}
	return NULL;
}

(7)在指定节点前插入

代码如下(示例):

//在指定节点前插入
void ListInsertFront(LN* phead, ListDataType x,ListDataType insert)
{
	LN* tem = ListFind(phead, x);
	if (tem == NULL)
	{
		printf("链表中没有%d\n",x);
		return;
	}
	else
	{
		LN* pos = tem->prev;
		pos->data = insert;
	}
}

(8)在指定节点后插入

代码如下(示例):

//在指定节点后插入
void ListInsertBack(LN* phead, ListDataType x, ListDataType insert)
{
	LN* tem = ListFind(phead, x);
	if (tem == NULL)
	{
		printf("链表中没有%d\n", x);
		return;
	}
	else
	{
		LN* pos = tem->next;
		pos->data = insert;
	}
}

(9)删除指定节点

代码如下(示例):

//删除指定节点
void ListDeleteNode(LN* phead, ListDataType x)
{
	LN* tem = ListFind(phead, x);
	if (tem == NULL)
	{
		printf("链表中没有%d\n", x);
		return;
	}
	else
	{
		LN* prevnode = tem->prev;
		LN* nextnode = tem->next;
		prevnode->next = nextnode;
		nextnode->prev = prevnode;
		free(tem);
	}
}

(10)改变指定节点

代码如下(示例):

//改变指定节点
void ListNodeChange(LN* phead, ListDataType x, ListDataType num)
{
	LN* tem = ListFind(phead, x);
	if (tem == NULL)
	{
		printf("链表中没有%d\n", x);
		return;
	}
	else
	{
		tem->data = num;
	}
}

(11)删除链表

代码如下(示例):

//删除链表
void ListFree(LN* phead)
{
	LN* tem = phead->next;
	while (tem != phead)
	{
		LN* nextnode = tem->next;
		free(tem);
		tem = nextnode;
	}
	free(phead);
}

(12)打印链表

代码如下(示例):

//打印链表
void ListPrint(LN* phead)
{
	LN* tem = phead->next;
	while (tem!=phead)
	{
		printf("%2d ", tem->data);
		tem = tem->next;
	}
	printf("\n");
}

3.test.c

代码如下(示例):

#include"List.h"
int main()
{
	LN* plist = NULL;
	ListInit(&plist);
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPushFront(plist, 6);
	ListPushFront(plist, 7);
	ListPushFront(plist, 8);
	ListPushFront(plist, 9);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListInsertFront(plist, 6, 11);
	ListPrint(plist);
	ListInsertBack(plist, 11, 12);
	ListPrint(plist);
	ListFree(plist);

	ListDeleteNode(plist, 1);
	ListDeleteNode(plist, 8);
	ListDeleteNode(plist, 4);
	ListPrint(plist);
	ListFree(plist);

	return 0;
}

总结

本文在上篇单链表的基础上延伸实现了双向带头循环链表,相信各位也在实现该链表的过程中,发现了头结点和循环的妙处。虽然看似最复杂,但是代码实现却是最为逻辑的,简便的!

如果读完有所脾益,就麻烦大家动手点个赞和关注,感谢!

坚持手敲数据结构,我们下篇见!
在这里插入图片描述

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值