带头双向循环链表的基本操作

一、带头双向循环链表的含义

带头双向循环链表‌是一种数据结构,它结合了双向链表和循环链表的特点,链表包含一个哨兵位(或称为头节点),这个节点不存储实际的数据,主要用于简化对链表的插入和删除操作,因为在进行这些操作时,不需要特别处理头节点。与单向链表不同,每个节点包含两个指针,一个指向下一个节点(next),另一个指向前一个节点(prev),这使得数据可以从两个方向进行遍历。与单向链表不同,每个节点包含两个指针,一个指向下一个节点(next),另一个指向前一个节点(prev),这使得数据可以从两个方向进行遍历。

二、带头双向循环链表的构成

typedef int LTDataType;//将变量类型重定义,方便更改数据类型
typedef struct ListNode {
    LTDataType val;//数据域
    struct ListNode* prev;//指向下一个节点
    struct ListNode* next;//指向下一个节点
}ListNode;//将带头双向循环链表重命名方便后面使用

三、带头双向循环链表的接口功能实现

1、带头双向循环链表的初始化

将链表进行初始化,防止出现野指针之类的情况。

ListNode* ListCreate() {
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//使用malloc创建一个头结点
	if (newnode == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = newnode;//将头结点的下一个指向本身
	newnode->prev = newnode;//将头结点的上一个指向本身
	newnode->val = 0;//初始化头结点中的数据初始化为任何值都行,头结点的数据域不存储任何数据,所以该值不会被访问
	return newnode;
}
2、带头双向循环链表的打印输出接口。

该接口用于调试观察,向链表中插入的值正确与否,实现了一个从前向后遍历访问,一个从后往前遍历访问。

void ListPrint1(ListNode* pHead) {//--------->从前往后
	assert(pHead);
	ListNode* cur = pHead->next;//将头结点的下一个赋值给中间变量cur,头结点不被访问
	while (cur != pHead) {//使用头结点来作为循环结束条件,当cur到达头结点时就意味着已经遍历一遍了
		printf(" %d ", cur->val);//打印输出
		cur = cur->next;//切换节点
	}
	printf("\n");//换行符
}

void ListPrint2(ListNode* pHead) {//---------->从后往前
	assert(pHead);
	ListNode* cur = pHead->prev;//将头结点的上一个赋值给中间变量cur,头结点不能被访问
	while (cur != pHead) {使用头结点来作为循环结束条件,当cur到达头结点时就意味着已经遍历一遍了
		printf(" %d ", cur->val);//打印输出
		cur = cur->prev;//切换节点
	}
	printf("\n");//换行符
}
3、创建带头双向循环链表节点

创建链表的节点在头插、尾插等几个接口都会使用到,所以我把他封装成一个公共函数。

ListNode* CreateNode(LTDataType x) {
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//创建链表节点
	if (newnode == NULL) {
		perror("malloc fail");//如果链表节点创建失败,对失败原因进行打印
		exit(-1);
	}
	newnode->next =NULL;//将新节点的next和prev暂时置空,
	newnode->prev = NULL;
	newnode->val = x;//赋值给新节点
	return newnode;//返回新节点

}
4、带头双向循环链表尾插

链表尾插时注意先后顺序,防止链接错误。

void ListPushBack(ListNode* pHead, LTDataType x) {
	assert(pHead);
	ListNode* newnode = CreateNode(x);//创建节点
	pHead->prev->next = newnode;//将头结点的prev的next连接到新结点
	newnode->prev = pHead->prev;//将新节点的prev连接到头结点的以前的prev
	pHead->prev = newnode;//将头结点的prev更新为新节点
	newnode->next = pHead;//将新节点的next链接到头结点
	//ListInsert(pHead,x);//这时将在任意位置插入接口实现后,可以将上面的步骤之间简化为调用该函数
	
}
5、带头双向循环链表尾删

注意这里释放tail的时机,否则可能造成释放错误

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;
	//ListErase(pHead->prev);//待实现任意位置删除元素方可使用
//
}
6、带头双向循环链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {
	assert(pHead);
	ListNode* newnode = CreateNode(x);
	pHead->next->prev = newnode;
	newnode->next = pHead->next;
	pHead->next = newnode;
	newnode->prev = pHead;
	//ListInsert(pHead->next, x);
}
7、带头双向循环链表头删
void ListPopFront(ListNode* pHead) {
	assert(pHead);
	ListNode* next = pHead->next;
	ListNode* nnext = next->next;
	free(next);
	nnext->prev = pHead;
	pHead->next = nnext;
	//ListErase(pHead->next);
}
8、带头双向循环链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
	assert(pHead);
	assert(pHead->next);
	ListNode* cur = pHead;
	while (cur != NULL) {
		if (cur->val == x)//将链表的值和需要找到的值做比较
			return cur;//成功返回节点,错误返回空
		cur = cur->next;
	}
	return NULL;
}
9、带头双向循环链表任意位置插入
void ListInsert(ListNode* pos, LTDataType x) {
	assert(pos);
	ListNode* newnode = CreateNode(x);
	ListNode* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	pos->prev = newnode;
	newnode->next = pos;

}
9、带头双向循环链表任意位置删除
void ListErase(ListNode* pos) {
	assert(pos->next);
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}

四、带头双向循环链表的优缺点

优点包括‌:
  1. 任意位置插入删除效率高‌:由于双向链表的设计,可以在任何位置进行插入或删除操作,且时间复杂度为O(1),这意味着不需要移动大量数据,操作速度快。
  2. 空间利用率高‌:可以按需申请释放空间,不会造成空间浪费,有效利用了存储空间。
  3. 可以双向寻找‌:由于存在前驱与后继的关系,可以双向寻找数据,这在某些应用场景中可能非常有用
缺点包括:
  1. 不支持随机访问‌:与顺序表相比,双向链表不支持通过下标直接访问元素,这限制了某些算法的应用,如排序和二分查找等。
  2. 需要额外的空间存储指针‌:除了存储数据外,还需要额外的空间来存储指向前驱和后继的指针,这会增加一定的内存开销。

综上所述,带头双向循环链表在需要频繁进行插入和删除操作,且对空间利用率有较高要求的场景中表现优异。然而,由于其不支持随机访问的特性,对于需要快速访问或修改大量数据的位置的应用,可能不是最佳选择‌。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值