数据结构:带哨兵位的双向循环链表


前言

前面已经实现了单链表,先来回顾一下链表,链表是物理结构上不一定连续,逻辑结构上连续的一种数据结构。今天要实现的是带哨兵位的双向循环链表。简单介绍一下,我们先定义它的结构。既然它要实现双向循环。那每个节点就需要跟它的前后节点有联系。然后就是这个哨兵位的节点,当链表为空时。哨兵位的节点也是存在的。那接下来就一起看看它的接口函数的实现吧。


一、带哨兵位的双向循环链表的接口函数

1.0 定义双向带哨兵位的循环链表的节点

//定义链表的数据类型
typedef int TLDatatype;

//定义节点
typedef struct TListNode
{
	struct TListNode* prev;
	struct TListNode* next;
	TLDatatype data;
}TLNode;

1.1 初始化和销毁

1.1.1 初始化

//初始化
TLNode* TLInit()
{
	//当链表为空时,哨兵位节点也依然存在,所以一开始就要有哨兵位节点。
	TLNode* phead = (TLNode*)malloc(sizeof(TLNode));
	if (phead == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//要实现循环,所以哨兵位节点的指针要指向自己
	phead->next = phead;
	phead->prev = phead;
	phead->data = 0;
	return phead;
}

1.1.2 销毁

//让cur从哨兵位节点的后一个节点开始遍历,当cur走到哨兵位节点的位置,就能销毁除了哨兵位节点的其他节点
void TLDestory(TLNode* phead)
{
	//phead cur next
	TLNode* cur = phead->next;
	TLNode* next = cur->next;
	while (cur != phead)
	{
		free(cur);
		cur = next;
		next = next->next;
	}
	//销毁哨兵位节点
	phead->prev=NULL;
	phead->next=NULL;
	free(phead);
}

1.2 尾插和尾删

1.2.1 尾插

//创建新节点
```c
TLNode* BuyNewNode(TLDatatype x)
{
	TLNode* newnode = (TLNode*)malloc(sizeof(TLNode));
	if (newnode == NULL)
	{
		perror("BuyNewNode malloc fail");
		return NULL;
	}
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return  newnode;
}
//尾插
void TLPushBack(TLNode* phead, TLDatatype x)
{
	assert(phead);
	//创建新节点
	TLNode* newnode = BuyNewNode(x);
	//链接节点,头节点的prev要指向最后一个节点,最后一个节点的next要指向phead。
	// phead tail newnode
	TLNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
	
}

1.2.2 尾删

//判断链表是否空
bool TLCheckEmpty(TLNode* phead)
{
	//如果链表的前一个节点等于哨兵位节点,说明为空
	//如果它们相等,则返回true,说明链表为空。
	//如果它们不相等,则返回false,说明链表不为空。
	return phead->prev == phead;
}



void TLPopBack(TLNode* phead)
{
	//phead是指向哨兵位节点的。它为NULL,说明哨兵位节点不存在,所以正常情况phead不可能为NULL。
	assert(phead);
	//链表为空,返回true,assert里表达式为假就会报错
	assert(!TLCheckEmpty(phead));
	
	//phead tailprev tail
	TLNode* tail = phead->prev;
	TLNode* tailprev = tail->prev;
	tailprev->next = phead;
	phead->prev = tailprev;
	free(tail);
	tail = NULL;	
}

1.3 头插和头删

1.3.1 头插

void TLPushFront(TLNode* phead, TLDatatype x)
{
	assert(phead);
	TLNode* first = phead->next;
	TLNode* newnode = BuyNewNode(x);
	
	// phead  newnode first
	newnode->next = first;
	first->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

1.3.2 头删

void TLPopFront(TLNode* phead)
{
	assert(phead);
	assert(!TLCheckEmpty(phead));
	// phead frist fnext
	TLNode* frist = phead->next;
	TLNode* fnext = frist->next;
	phead->next = fnext;
	fnext->prev = phead;
	free(frist);
	frist = NULL;
}

1.4 在pos前插入和删除

1.4.1 在pos前插入

void TLInsert(TLNode* pos, TLDatatype x)
{
	assert(pos);
	//posprev newnode pos
	TLNode* newnode = BuyNewNode(x);
	TLNode* posprev = pos->prev;
	newnode->next = pos;
	newnode->prev = posprev;
	posprev->next = newnode;
	pos->prev = newnode;
}

1.4.2 在pos前删除

void TLErase(TLNode* pos)
{
	assert(pos);
	//prev cur pos
	TLNode* cur = pos->prev;
	TLNode* prev = cur->prev;
	prev->next = pos;
	pos->prev = prev;
	free(cur);
	cur = NULL;
}

1.4.3 复用TLInsert和TLErase实现尾插/删和头插/删

实现了在pos前插入和删除的接口函数。那尾插不就是在phead前插入数据,头插就是在phead->next前插入数据。
那么尾插和头插就能这样写了。尾删和头删也能复用。

//尾插
void TLPushBack(TLNode* phead, TLDatatype x)
{
	assert(phead);
	TLInsert(phead,x);
}
//尾删

void TLPopBack(TLNode* phead)
{
	assert(phead);
	//链表为空,返回true,
	assert(!TLCheckEmpty(phead));
	TLErase(phead);
}

//头插
void TLPushFront(TLNode* phead, TLDatatype x)
{
	assert(phead);
	TLInsert(phead->next, x);
}

//头删
void TLPopFront(TLNode* phead)
{
	assert(phead);
	assert(!TLCheckEmpty(phead));
	TLErase(phead->next->next);
}

1.5 查找和打印

1.5.1 查找

//查找
TLNode* TLFind(TLNode* phead, TLDatatype x)
{
	TLNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}

1.5.2 打印

//打印
void TLPrint(TLNode* phead)
{
	assert(phead);
	TLNode* cur = phead->next;
	printf("<=");
	while (cur != phead)
	{
		printf(" %d <=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

二、链表和顺序表的对比

  1. 顺序表能在它的空间里随机访问,而链表不能。
  2. 顺序表在物理结构上连续,链表不一定连续。
  3. 顺序表在插入和删除要挪动数据。链表不需要,它只用改指针。
  4. 动态顺序表的容量需要扩容。链表不需要。

三、单链表和带哨兵位的双向循环链表的对比

  1. 单链表的结构相对简单。但它不常用单独存储数据。它的尾插和尾删的时间复杂度都为O(n)。它的头插和头删时间复杂度为O(1)。
  2. 带哨兵位的双向循环链表的结构是链表中最复杂的。它一般可以用来单独存储数据。它的尾插、尾删、头插、头删的时间复杂度都是O(1)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值