【DS】C语言实现带头双向循环链表(详细图解)


上篇博客已经对链表的种类进行了简略介绍。详细讲解了如何实现 单链表,感兴趣的可以去看看。现在进行 带头双向循环链表(习惯称为 双向链表)的实现

双向链表的的实现

  • 与单链表一样,双向链表也是对增删查改这些功能进行实现
    双向链表目录

双向链表节点结构定义

  • 带头双向循环链表:带头,双向,循环就是这个链表的特点,他与单链表就是两个极端,一个十分简陋,一个应有尽有。但双向链表的实现可要比单链表简单许多,这都多亏了他的结构特点
  • 个人建议写双向链表时一定要画图,这样会清晰很多
    双向链表
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;//尾
	LTDataType data;
	struct ListNode* next;//头
}LTNode;
  • 该结构包括了一个前驱指针指向前一个节点
  • 数据域
  • 指向下一个节点的next指针

打印数据

  • 为了方便观察,还是先实现一个打印函数。
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur!= phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
}

双向链表

  • 由于结构不同,不能再像单链表这样来遍历链表了。观察上图可以看到head是链表的头,完成一次循环后会回到head,所以pcur!= phead,便是控制循环的条件

初始化

  • 带头即malloc出一个节点,这个节点作为头节点(也称为哨兵位),数据域不存储有效数据(这里默认设置为-1但,是无效数据)。
  • 这时只有一个头节点,把他们都指向头节点,再返回即完成初始化。
    头节点操作
LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		return 1;
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

插入数据

  • 这里我们也进行头插和尾插的操作。
  • 频繁插入数据就需要频繁申请节点,所以依旧封装一个ListBuyNode函数来申请节点并把数据放入节点,最后返回。
LTNode* ListBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc");
		return 1;
	}
	node->data = x;
	node->next = node->prev = NULL;
	return node;
}
  • 还需要注意到是prev指针和next指针指向的是哪里,弄清楚这个就很好理解插入操作了
    指针介绍

头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->next = phead->next;
	node->prev = phead;

	phead->next->prev = node;
	phead->next = node;
}
  • 使用assert断言来确保传过来的是有效的地址
  • 插入数据时我们一般先对新节点进行连接,再去处理前一个和后一个节点
  • 实在不理解时就去画图!!!画图后会发现清晰明了
    头插操作

尾插

void LTPushback(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->prev = phead->prev;
	node->next = phead;

	phead->prev->next = node;
	phead->prev = node;
}
  • 看图操作清晰明了,操作顺序依旧先对新节点进行操作,再处理其他节点
    尾插

删除数据

头删

void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;
	
	del->next->prev = phead;
	phead->next = del->next;
	
	free(del);
	del = NULL;
}
  • 删除数据必须确保有数据,所以用assert断言一下phead->next != phead
  • 头删比较简单,只需处理要删除节点del的下一个节点的prev指针和头节点的next指针即可
    头删

尾删

void LTPopBack(LTNode* phead)
{
	assert(phead&&phead->next!=phead);
	LTNode* del = phead->prev;

	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;

}
  • 尾删需要处理头节点的prev指针和删除节点前一个节点的next指针
    尾删

指定位置插入数据

  • 要找到指定的位置,通过LTFind函数实现。
  • 遍历链表,找到指定的数据则返回其地址,否则返回空
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
  • 这里我们只实现了指定位置后插入数据,你也可以自己实现指定位置之前。
void LTInser(LTNode* phead, LTNode* pos, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	
	node->prev = pos;
	node->next = pos->next;

	pos->next->prev = node;
	pos->next = node;
}
  • 依旧是先对node节点操作,再对pos节点的next指针和pos节点的下一个节点的prev操作
    指定位置之后

指定位置删除数据

void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead && phead->next != phead);
	LTNode* del = pos;

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

	free(del);
	del = NULL;
}
  • 删除指定位置数据也简单,只对del节点的上一个节点的next指针和del节点的下一个节点的prev指针操作即可
    删除指定位置数据

销毁链表

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);//需要手动置空哨兵位
	phead = NULL;
}
  • 由于链表节点是在堆区开辟的,为防止内存泄漏,需要及时释放
  • 用next指针保存下一个节点,再释放当前节点,不断遍历直到释放完
  • 最后手动置空一下头节点

总结

带头双向循环链表因为结构的不同,比单链表更为容易实现,而且在尾插操作是可以直接找到尾节点,不需要遍历链表来找尾,在时间复杂度上更胜一筹。而单链表在OJ题上用得很多,也十分重要。
自此数据结构中最常用的单向不带头不循环(单链表)和带头双向循环链表(双向链表)的实现就完成了,会了这两种最常用的链表,剩余六种也是湿湿碎啦。希望这两篇博客对大家有所帮助。

完整代码

  • 分为三个文件,也可以自己合成一个文件。
    List.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;//尾
	LTDataType data;
	struct ListNode* next;//头
}LTNode;

LTNode* LTInit();//初始化链表
void LTPrint(LTNode* phead);//打印链表
void LTPushback(LTNode* phead, LTDataType x);//尾插
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopBack(LTNode* phead);//尾删
void LTPopFront(LTNode* phead);//头删
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
void LTInser(LTNode* phead, LTNode* pos, LTDataType x);//指定位置之后插入
void LTErase(LTNode* phead, LTNode* pos);//删除指定位置
void LTDestory(LTNode* phead);//销毁链表

List.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include"List.h"

LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		return 1;
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur!= phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
}

LTNode* ListBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc");
		return 1;
	}
	node->data = x;
	node->next = node->prev = NULL;
	return node;
}

void LTPushback(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->prev = phead->prev;
	node->next = phead;

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

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->next = phead->next;
	node->prev = phead;

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

void LTPopBack(LTNode* phead)
{
	assert(phead&&phead->next!=phead);
	LTNode* del = phead->prev;

	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;

}

void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;

	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

void LTInser(LTNode* phead, LTNode* pos, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->prev = pos;
	node->next = pos->next;

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

void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead && phead->next != phead);
	LTNode* del = pos;

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

	free(del);
	del = NULL;
}

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);//需要手动置空哨兵位
	phead = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include"List.h"

void LTtest()
{
	//LTNode*phead= LTInit();

	//LTPushback(phead, 0);
	//LTPushback(phead, 1);
	//LTPushback(phead, 2);
	//LTPushFront(phead, 0);
	//LTPopBack(phead);
	//LTPopFront(phead);
	//LTNode* find = LTFind(phead, 0);
	//LTInser(phead, find, 5);

	//LTPrint(phead);
	//
	//LTDestory(phead);
	
}


int main()
{
	LTtest();

	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值