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

本文详细介绍了双向带头循环链表的概念、模型及常见操作,如初始化、尾插、头插、头删、尾删、任意位置插入和删除等。通过示例代码展示了如何高效地实现这些操作,特别强调了一级指针在实现过程中的优势。此外,还提供了完整的C语言实现代码供参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.什么是双向带头循环链表?模型。

2.1 创建结点函数

2.2初始化

3.尾插实现

3.1图示

 3.2尾插

4.打印函数实现

4.1打印函数

5.思考为啥是一级指针

6.头插实现

6.2实现代码

6.3特殊情况

7.头删

7.1图解

7.2代码

7.3多删

8.尾删 

8.1上图

8.2代码 

 9.在任意(pos)位置的插入删除。

9.1 插入

 9.2尾插·头插的复用

9.3思考

9.4pos位置的删除

9.6思考 

10销毁

11.SList.h代码

 12.SList.c代码


1.什么是双向带头循环链表?模型。

上次写的链表是单向的,就是只去不会,但是我想让他有来有回所以叫单向,带头是创建一个哨兵位头结点,但不影响实际写的链表。循环我就不解释了。

上模型:

2.初始化链表

2.1 创建结点函数

ListNode* BuyListNode(SLTDateType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));  //开辟一个结点
	if (node == NULL)
	{
		perror("malloc:fault");
		exit(-1);
	}
	node->date = x;      //把值给这个结点
	node->next = NULL;   //	全都赋值
	node->prev = NULL;    //全都赋值
	return  node;
}

2.2初始化

//初始化
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(-1);  //创建哨兵位,不一定取-1,想取啥取啥
	phead->next = phead;
	phead->prev = phead;
	//既然是循环,那它的头指向自己,尾指向自己
	return phead;
}

刚开始就一个头结点,所以要首尾都指向自身。

3.尾插实现

3.1图示

 3.2尾插

//尾插
void ListPushBack(ListNode* phead, SLTDateType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* tail = phead->prev;
	tail->next = newnode;  //把newnode给tail
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

最灵性的的是tail=phead->prev,所以首尾相连了。

要实现尾插,无非四步d3结点和newnode相连,phead和newnode相连。

4.打印函数实现

4.1打印函数

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

注意不打印头结点所以cur是phead->next; 判断条件是!=phead,因为这是一个循环,最后一个跟phead连着呢,所以不等于。

4.2尾插实现

5.思考为啥是一级指针

单链表是二级指针实现尾插的,为啥这个是一级指针?为啥能用一级指针?

当我们写单链表时,要实现尾插首先我们要找到尾,咱们一般都是定义一个一级指针并改变这个指针来找尾,但是循环链表就不需要了。因为它的尾和phead紧密相连(设cur是尾),cur->next就是头,所以不需要一级指针的改变,所以一级指针就可以应付了。

6.头插实现

6.1图解

是插在d1前面不是head前面。

6.2实现代码

//头插
void ListPushFront(ListNode* phead, SLTDateType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* next = phead->next;   //来记录d1的地址放止一会找不到。
	
	//还是四步链接
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next=next;
	next->prev=newnode;
}

 注意:其实不加这个next也可以,为啥还要加,就看上面这个代码,如果先连接phead和newnode那d1的地址咱就找不到了,所以就只能先连接后边的,但是加上next就不用看顺序来

了。

6.3特殊情况

就只有一个头结点进行头插,那next就是phead了。

再看咱这代码加图,照样可以。把d1换成phead,一样。

7.头删

7.1图解

注意啊兄弟们,不是删除第一个,第一个是哨兵节点,d1才是实际上的第一个。

7.2代码

//头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);

}

 cur->next是d2,把d2的地址给phead,所以是cur->next->prev。

7.3多删

如果五个数据删六次呢?

所以代码:

//头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);

}

8.尾删 

8.1上图

双链表对尾删最友好的地方是不用遍历找尾,phead->prev就是尾。

8.2代码 

//尾删
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;
}

 

 9.在任意(pos)位置的插入删除。

9.1 插入

//pos位置插
void ListInsert(ListNode* pos, SLTDateType x)
{   
	assert(pos);
	ListNode* prev = pos->prev;   //pos前面的那个结点
	ListNode* newnode = BuyListNode(x);

	//四步走
	prev->next=newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

 9.2尾插·头插的复用

9.3思考

为啥尾插是参数phead?

 d3是尾,要在d3那插入可不是尾插,在phead前插入不就是尾插吗?因为phead->prev必须指向链表的尾部。

9.4pos位置的删除

 9.5复用

9.6思考 

为啥尾插是phead,而尾删是phead->prev?

首先我们要理解进行尾插时,就是在pos前插入,那要找到pos位置,并在它前边插入,pos就是phead。

而进行尾删时也要找到pos,并删除pos,这个是删除pos位。所以是phead->prev。

10销毁

//销毁
void ListDestory(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

11.SList.h代码

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

typedef int SLTDateType;

typedef struct ListNode
{
	struct ListNode* prev;     //头
	struct ListNode* next;     //尾
	SLTDateType  date;
}ListNode;

 //初始化
ListNode* ListInit();

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

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

//尾插
void ListPushBack(ListNode* phead, SLTDateType x);

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

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

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

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

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

//销毁
void ListDestory(ListNode* phead);

 12.SList.c代码

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

ListNode* BuyListNode(SLTDateType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));  //开辟一个结点
	if (node == NULL)
	{
		perror("malloc:fault");
		exit(-1);
	}
	node->date = x;      //把值给这个结点
	node->next = NULL;   //	全都赋值
	node->prev = NULL;    //全都赋值
	return  node;
}
//初始化
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(-1);  //创建哨兵位,不一定取-1,想取啥取啥
	phead->next = phead;
	phead->prev = phead;
	//既然是循环,那它的头指向自己,尾指向自己
	return phead;
}

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


//尾插
void ListPushBack(ListNode* phead, SLTDateType x)
{
	assert(phead);
	ListInsert(phead, x);
	//ListNode* newnode = BuyListNode(x);
	//ListNode* tail = phead->prev;
	//tail->next = newnode;  //把newnode给tail
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;
}


//头插
void ListPushFront(ListNode* phead, SLTDateType x)
{
	assert(phead);
	ListInsert(phead->next, x);
	//ListNode* newnode = BuyListNode(x);
	//ListNode* next = phead->next;   //来记录d1的地址放止一会找不到。
	//
	////还是四步链接
	//phead->next = newnode;
	//newnode->prev = phead;
	//newnode->next=next;
	//next->prev=newnode;
}

//头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListErase(phead->next);

	/*ListNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);*/

}

//尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead); //前车之鉴
	ListErase(phead->prev);

	/*ListNode* tail = phead->prev;
	ListNode* tailPrev = tail->prev;
	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;*/
}

//pos位置插
void ListInsert(ListNode* pos, SLTDateType x)
{   
	assert(pos);
	ListNode* prev = pos->prev;   //pos前面的那个结点
	ListNode* newnode = BuyListNode(x);

	//四步走
	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);
}

//销毁
void ListDestory(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值