双向带头循环链表(图解)

单链表地址

头节点(哨兵位)

什么是头节点呢?头节点也叫哨兵节点,他在链表中进行不了任何操作,只是用来放哨用的,在单链表中我们当我们尾插的时候我们呢需要判断此时的节点是否是空,如果不是空我们才可以尾插,是空的话我们直接赋值,而有了头节点我们就不需要判断是否为空了,直接在头节点后插入即可。这就是头节点的好处,尾插,尾删等也有好处(下文再说)

双向循环结构

单链表中我们只有一个指针指向下一个节点,而双向链表则需要两个指针,一个指向前一个节点,一个指向后一个节点,由于我们需要循环起来,所以最后一个节点我们要指向头节点(这里是指第一个节点) 为了避免搞混我下文把第一个节点称为头节点,哨兵节点就为哨兵节点

哨兵节点如下:
在这里插入图片描述
注意这里我们是要创建一个哨兵节点,所以我们需要返回一个节点,而我们接下来的操作都是在哨兵节点基础上进行的,所以每一次进行基本增删操作的时候可以传一级指针,因为我们接受的哨兵节点肯定是一个指针类型的。

结构定义如下:
在这里插入图片描述

代码实现:

//创建哨兵节点
List* ListHeadCreat()
{
	List* newhead = (List*)malloc(sizeof(List));
	if (newhead == NULL)
	{
		printf("malloc fail!\n");
		exit(-1);
	}
	newhead->val = -1;
	newhead->next = newhead->prev = newhead;
	return newhead;
}

头插

首先我们要创建一个新的节点,先让这个新的节点指向自己
头插这里分两种情况:
1. 只有一个哨兵节点
(1)
newNode (新节点)的next指向phead,再让newNodeprev指向phead
(2)
phead->next指向newNode,再让phead->prev指向newNode
2. 不止一个节点
(1)
newNode的next指向phead->prev(此时由于链表的最后一个元素就是哨兵节点上一个节点),再让newNode->prevphead
(2)
phead->prev指向newNode,phead->prev->next(最后一个节点的下一个节点)指向newNode
在这里插入图片描述

代码实现:

//头插
void ListPushFront(List* phead, LDataType x)
{
	assert(phead);
	List* newNode = BuyNode(x);	
	newNode->prev = phead;
	newNode->next = phead->next;

	phead->next = newNode;
	//如果只有一个节点,要把此时的哨兵节点的上一个节点指向新插入的节点
	if (phead->prev == phead)
		phead->prev = newNode;
}

在这里插入图片描述

尾插

尾插无论是只有哨兵节点还是多个节点,代码操作都是一样的。
(1)
newNode->next指向phead
newNode->prev
指向最后一个节点
(2)
phead->prev->next(最后一个节点)指向newNode
phead->prev(哨兵节点指向尾)指向newNode

在这里插入图片描述
代码实现:

//尾插
void ListPushBack(List* phead, LDataType x)
{
	assert(phead);
	List* newNode = BuyNode(x);
	newNode->next = phead;
	newNode->prev = phead->prev;
	
	phead->prev->next = newNode;
	phead->prev = newNode;
}

在这里插入图片描述

头删

头删我们得先保证链表中还有元素,所以我们需要判断以下phead->next是否还是Phead,如果是就说明此时只有哨兵节点

头删也是两种情况都是一样的代码
这里把第一个节点写为del,我们只需要让phead->next指向del,del->prev指向phead,这样就相当于把del从这个链表中断开了,接着就放心的free掉就可以了。

在这里插入图片描述
代码实现:

//头删
void ListPopFront(List* phead)
{
	assert(phead && phead->next != phead);
	List* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

在这里插入图片描述

尾删

尾删跟头删差不多,只不过我们要找到最后一个节点(phead->prev),这就是双链表的好处

尾删的两种情况也都是一致的:

End = phead->prev(最后一个节点)
End->prev->next = phead
phead->next = End->prev
free(End)

在这里插入图片描述
代码实现:

//尾删
void ListPopBack(List* phead)
{
	assert(phead && phead->prev != phead);
	List* End = phead->prev;
	End->prev->next = phead;
	phead->prev = End->prev;
	free(End);
	End = NULL;
}

在这里插入图片描述

在指定位置之前插入数据

如下图:
无论在什么位置之前插入元素,都是一样的代码,首先我们还得写一个查找函数,这里只需要遍历找值就可以了,所以不单独写出来了,跟这里写一块了。
在这里插入图片描述
代码实现:

//查找指定元素
List* ListFind(List* phead, LDataType x)
{
	assert(phead && phead->next != phead);
	List* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

//在指定位置之前插入元素
void ListInsertFront(List* pos, LDataType x)
{
	assert(pos);
	List* newNode = BuyNode(x);
	newNode->prev = pos->prev;
	newNode->next = pos;

	pos->prev->next = newNode;
	pos->prev = newNode;
}

在这里插入图片描述

删除指定位置之前的数据

这里需要注意,由于我们删除的是指定位置之前的数据,所以我们要保证这个pos->prev这个位置不能是哨兵节点,同时pos->prev->prev不能为空,必须有至少两个节点才能删除指定位置之前的数据

在这里插入图片描述
代码实现:

//删除指定位置之前的元素
void ListEarseFront(List* pos, List* phead)
{
	assert(pos && pos->prev && pos->prev != phead && pos->prev->prev);
	List* del = pos->prev;
	pos->prev->prev->next = pos;
	pos->prev = pos->prev->prev;
	free(del);
	del = NULL;
}

在这里插入图片描述

销毁链表

销毁链表分为两种方式:

(1)
由于我们是传入的phead是一级指针,而我们函数的参数也是用的一级指针接受的,因为函数的形参只是实参的一份临时拷贝,由于每次malloc的内存都是在区内开辟的所以我们可以做到释放每一个节点,但是phead这个指针却并不能置为空,如果不置空的话,他指向了被释放掉了的空间,还能找到那个空间,所以此时phead就是野指针,我们也只能在调用**ListDesTory()**这个销毁函数的下面手动把phead置为空
代码实现:

void ListDesTory(List* phead)
{
	assert(phead);
	List* cur = phead->next;
	while (cur != phead)
	{
		List* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

手动置空
在这里插入图片描述

(2)
当我们想要在函数内部修改变量的时候,我们因该传入它的地址,就像如果我们想要在函数里面修改 int a = 1这个a的值,那我们应该传入的是**(&a),同时我们呢要用int*** 的指针来接收,同样的这里我们呢传入**(&phead),那我们就要用(List**)**来接受,这样在函数内部就可以把phead置为空了

代码实现:

void ListDesTory(List** pphead)
{
	assert(pphead && *pphead);
	List* cur = (*pphead)->next;
	while (cur != (*pphead))
	{
		List* next = cur->next;
		free(cur);
		cur = next;
	}
	free((*pphead));
	*pphead = NULL;	
}

全部代码

List.h

#pragma once

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

typedef int LDataType;
typedef struct List
{
	struct List* prev;
	struct List* next;
	LDataType val;
}List;


//创建哨兵节点
List* ListHeadCreat();
//打印节点
void ListPrint(List* phead);
//头插
void ListPushFront(List* phead,LDataType x);
//尾插
void ListPushBack(List* phead, LDataType x);

//头删
void ListPopFront(List* phead);
//尾删
void ListPopBack(List* phead);

//查找指定元素
List* ListFind(List* phead, LDataType x);
//在指定位置之前插入元素
void ListInsertFront(List* pos,LDataType x);
//删除指定位置之前的元素
void ListEarseFront(List* pos, List* phead);
//销毁链表
void ListDesTory(List* phead);
//销毁链表
//void ListDesTory(List** pphead);



List.c

#define _CRT_SECURE_NO_WARNINGS

#include "Lish.h"


//创建哨兵节点
List* ListHeadCreat()
{
	List* newhead = (List*)malloc(sizeof(List));
	if (newhead == NULL)
	{
		printf("malloc fail!\n");
		exit(-1);
	}
	newhead->val = -1;
	newhead->next = newhead->prev = newhead;
	return newhead;
}

//打印节点
void ListPrint(List* phead)
{
	assert(phead && phead->next);
	List* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
}

List* BuyNode(LDataType x)
{
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL)
	{
		printf("malloc fail!\n");
		exit(-1);
	}
	newNode->val = x;
	newNode->next = newNode->prev = newNode;
	return newNode;
}
//头插
void ListPushFront(List* phead, LDataType x)
{
	assert(phead);
	List* newNode = BuyNode(x);	
	newNode->prev = phead;
	newNode->next = phead->next;

	phead->next = newNode;
	//如果只有一个节点,要把此时的哨兵节点的上一个节点指向新插入的节点
	if (phead->prev == phead)
		phead->prev = newNode;
}
//尾插
void ListPushBack(List* phead, LDataType x)
{
	assert(phead);
	List* newNode = BuyNode(x);
	newNode->next = phead;
	newNode->prev = phead->prev;
	
	phead->prev->next = newNode;
	phead->prev = newNode;
}

//头删
void ListPopFront(List* phead)
{
	assert(phead && phead->next != phead);
	List* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}
//尾删
void ListPopBack(List* phead)
{
	assert(phead && phead->prev != phead);
	List* End = phead->prev;
	End->prev->next = phead;
	phead->prev = End->prev;
	free(End);
	End = NULL;
}

//查找指定元素
List* ListFind(List* phead, LDataType x)
{
	assert(phead && phead->next != phead);
	List* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

//在指定位置之前插入元素
void ListInsertFront(List* pos, LDataType x)
{
	assert(pos);
	List* newNode = BuyNode(x);
	newNode->prev = pos->prev;
	newNode->next = pos;

	pos->prev->next = newNode;
	pos->prev = newNode;
}

//删除指定位置之前的元素
void ListEarseFront(List* pos, List* phead)
{
	assert(pos && pos->prev && pos->prev != phead && pos->prev->prev);
	List* del = pos->prev;
	pos->prev->prev->next = pos;
	pos->prev = pos->prev->prev;
	free(del);
	del = NULL;
}
//销毁链表
void ListDesTory(List* phead)
{
	assert(phead);
	List* cur = phead->next;
	while (cur != phead)
	{
		List* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}
销毁链表
//void ListDesTory(List** pphead)
//{
//	assert(pphead && *pphead);
//	List* cur = (*pphead)->next;
//	while (cur != (*pphead))
//	{
//		List* next = cur->next;
//		free(cur);
//		cur = next;
//	}
//	free((*pphead));
//	*pphead = NULL;	
//}

结语

OK了,链表在单链表和双向循环带头链表写完了就告一段落了,感觉有帮助的话可以点点赞,如果有哪写错了欢迎指出来~
在这里插入图片描述

  • 29
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值