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

(一)带头双向链表定义

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单。

  • 带头:即具有头节点,它不用存储数据。对链表进行插入删除操作时也不会影响改节点。
  • 双向:即链表的节点增加了一个节点指针,它存储上一个结点的地址。
  • 循环:链表的头结点存储了尾结点的地址,链表的尾结点存储了头节点的地址。

如图:有一个头结点,它和尾部连接,尾部也和它连接,而其余的也是双向连接。
在这里插入图片描述

(二)带头双向链表实现

(1)创建结构体

typedef int DataType;
typedef struct ListNode
{
	struct ListNode *next;
	struct ListNode *pre;
	DataType data;
}LTNode;

此结构中比单链表结构增加一个结构体指针pre,用于存放上一个节点的地址。
next是存放一个节点的地址。
data是存放数据。

(2)具体函数实现及解析

(*)申请结点

LTNode* BuyListNode(DataType x)//申请结点
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror( "malloc fail");
		exit(-1);
	}
	node->next = NULL;
	node->pre = NULL;
	node->data = x;
	return node;
}

动态申请结点,函数返回的是一个指针类型,用malloc开辟一个LTNode大小的空间,并用node指向这个空间,再判断是否为空,如为空就perror,显示错误信息。反之则把要存的数据x存到newnode指向的空间里面,把指针置为空。

(*)初始化创建头结点

LTNode* LTInit()//初始化创建头结点
{
	LTNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->pre = phead;
	return phead;
}

单链表开始是没有节点的,可以定义一个指向空指针的结点指针,但是此链表不同,需要在初始化函数中创建个头结点,它不用存储有效数据。因为链表是循环的,在最开始需要让头结点的next和pre指向头结点自己。
因为其他函数也不需要用二级指针(因为头结点指针是不会变的,变的是next和pre,改变的是结构体,只需要用结构体针即可,也就是一级指针)为了保持一致此函数也不用二级指针,把返回类型设置为结构体指针类型。

(*)打印链表

void LTPrint(LTNode* phead)//打印链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

打印链表,先断言phead,它不能为空,再把头结点下个地址存到cur中,用while循环去遍历,终止条件是等于头指针停止,因为他是循环的,并更新cur。

(*)在pos位置之前插入数据

void LTInsert(LTNode* pos, DataType x)//在pos位置之前插入数据
{
	assert(pos);
	LTNode* node = BuyListNode(x);
	LTNode* bef = pos->pre;
	bef->next = node;
	node->pre = bef;
	node->next = pos;
	pos->pre = node;
}

断言pos,不能为空,插入数据先申请一结点放到定义的node指针变量中,为了不用考虑插入顺序,先把pos前面的存到bef中,然后就可以随意链接:
bef指向新节点,新节点前驱指针指向bef,新节点指向pos,pos前驱指针指向新节点。

(*)删除任意位置数据

void LTErase(LTNode* pos)//删除pos位置数据
{
	assert(pos);
	pos->pre->next = pos->next;
	pos->next->pre = pos->pre;
	free(pos);
}

删除把pos位置之前的结点直接指向pos的下一个结点,把pos下一个结点的前驱指针指向pos之前的结点。

(*)尾插

void LTPushBack(LTNode* phead, DataType x)//尾插
{
    /*assert(phead);//复杂方法
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;*/
	assert(phead);//简便方法
	LTInsert(phead, x);
}

简便方法:尾插是在尾部插入,用简便方法调用LTInsert函数,传入头指针和x。

复杂方法是:申请结点newnode,把头指针前的上一个结点存到尾指针变量中,再双向链接newnode,最后还得把头和尾(刚申请的结点)循环起来。

(*)尾删

void LTPopBack(LTNode* phead)//尾删
{
	//assert(phead);//复杂方法
	//assert(phead->next != phead);  // 空
	//LTNode* tail = phead->prev;
	//LTNode* tailPrev = tail->prev;
	//tailPrev->next = phead;
	//phead->prev = tailPrev;
	//free(tail);
	assert(phead);//简便方法
	assert(phead->next != phead);  // 空

	LTErase(phead->pre);
}

简便方法:因为是尾删,删的是尾部,直接调用LTErase函数传入头指针的上一个结点,也就是尾部,因为是双向循环不用遍历直接直到尾部。

复杂方法:先把头结点上一个结点地址存起来,再把尾部的上一个结点地址存起来,再把第二次存的直接链接头部,头部链接第二次存的结点,再把第一次的结点释放掉。

(*)头插

void LTPushFront(LTNode* phead, DataType x)//头插
{
	//assert(phead);//复杂方法
	//LTNode* newnode = BuyListNode(x);
	//LTNode* back = phead->next;
	//phead->next = newnode;
	//newnode->prev = phead;
	//newnode->next = back;
	//back->prev = newnode;
	assert(phead);//简便方法
	LTInsert(phead->next, x);
}

简便方法:因为是头插直接调用LTInsert函数传 头结点下一个结点指针和x。

复杂方法:申请结点存到newnode,再把头结点下一个结点地址存到指针back里,头部和新节点和back,三节点双向链接。

(*)头删

void LTPopFront(LTNode* phead)//头删
{
	//assert(phead);
	//assert(phead->next != phead); // 空
	/*LTNode* back = phead->next;
	LTNode* second = back->next;
	free(back);
	phead->next = second;
	second->prev = phead;*/
	assert(phead);
	assert(phead->next != phead);  // 空
	LTErase(phead->next);

}

简便方法:因为头删,直接调LTErase函数传入头结点下一个指针。

复杂方法:先把头结点下一个结点地址存到back指针里,再把back一个结点地址存到second指针里,先释放中间的back,最后头结点和second双向链接。

(*)查找

LTNode* LTFind(LTNode* phead, DataType x)//查找
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;

}

查找把头结点下一个结点存到cur,然后用while循环遍历,终止条件是cur等于头结点指针,如果cur等于x,直接返回cur指针,再更新cur,最后遍历完返回NULL,表示没有该数据。

(*)释放链表

void LTDestroy(LTNode* phead)//释放链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

释放链表从头开始释放,把头结点下一个结点存到cur中,再用用while循环,终止条件是cur不等于头指针,在里面把cur下一个指针存到next中,释放掉cur,再把next更新为cur。
最后头结点也是申请的,也得释放。

(*)判断是否为空

bool LTEmpty(LTNode* phead)//判断是否为空
{
	assert(phead);

	return phead->next == phead;
}

用布尔类型来判断是否为空,直接return 如果头结点下一个结点等于头结点,说明为空,返回true,否则返回false。

(*)求链表长度

size_t LTSize(LTNode* phead)//求链表长度
{
	assert(phead);

	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

求链表长度,先把头结点下一个结点存到cur中,再用while循环遍历终止条件是cur等于头结点,用size++记录长度,并更新cur,最后返回size,32位机器下是无符号整型size_t。
( 此类型可以提高代码的可移植性,有效性,可读性。此链表长度可能是字符型或者数组整型,他可以提供一种可移植方法来声明与系统中可寻址的内存区域一致的长度,而且被设计的足够大,能表示内存中任意对象的大小)

(三)带头双向链表实现代码

(1)list.c

#include"list.h"
LTNode* BuyListNode(DataType x)//申请结点
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror( "malloc fail");
		exit(-1);
	}
	node->next = NULL;
	node->pre = NULL;
	node->data = x;
	return node;
}

LTNode* LTInit()//初始化创建头结点
{
	LTNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->pre = phead;
	return phead;
}

void LTPrint(LTNode* phead)//打印链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void LTInsert(LTNode* pos, DataType x)//在pos位置之前插入数据
{
	assert(pos);
	LTNode* node = BuyListNode(x);
	LTNode* bef = pos->pre;
	bef->next = node;
	node->pre = bef;
	node->next = pos;
	pos->pre = node;
}

void LTErase(LTNode* pos)//删除pos位置数据
{
	assert(pos);
	pos->pre->next = pos->next;
	pos->next->pre = pos->pre;
	free(pos);
}

void LTPushBack(LTNode* phead, DataType x)//尾插
{
	/*assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;*/
	assert(phead);
	LTInsert(phead, x);
}

void LTPopBack(LTNode* phead)//尾删
{
	//assert(phead);
	//assert(phead->next != phead);  // 空
	//LTNode* tail = phead->prev;
	//LTNode* tailPrev = tail->prev;
	//tailPrev->next = phead;
	//phead->prev = tailPrev;
	//free(tail);
	assert(phead);
	assert(phead->next != phead);  // 空

	LTErase(phead->pre);
}

void LTPushFront(LTNode* phead, DataType x)//头插
{
	//assert(phead);
	//LTNode* newnode = BuyListNode(x);
	//LTNode* back = phead->next;
	//phead->next = newnode;
	//newnode->prev = phead;
	//newnode->next = back;
	//back->prev = newnode;
	assert(phead);
	LTInsert(phead->next, x);
}

void LTPopFront(LTNode* phead)//头删
{
	//assert(phead);
	//assert(phead->next != phead); // 空
	/*LTNode* back = phead->next;
	LTNode* second = back->next;
	free(back);
	phead->next = second;
	second->prev = phead;*/
	assert(phead);
	assert(phead->next != phead);  // 空
	LTErase(phead->next);

}

LTNode* LTFind(LTNode* phead, DataType x)//查找
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;

}

void LTDestroy(LTNode* phead)//释放链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

bool LTEmpty(LTNode* phead)//判断是否为空
{
	assert(phead);

	return phead->next == phead;
}

size_t LTSize(LTNode* phead)//求链表长度
{
	assert(phead);

	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

(2)list.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int DataType;
typedef struct ListNode
{
	struct ListNode *next;
	struct ListNode *pre;
	DataType data;
}LTNode;

LTNode* BuyListNode(DataType x);//申请结点

LTNode* LTInit();//初始化

void LTPrint(LTNode* phead);//打印链表

void LTInsert(LTNode* pos, DataType x);//在任意位置插入数据

void LTErase(LTNode* pos);//删除任意位置数据

void LTPushBack(LTNode* phead, DataType x);//尾插

void LTPopBack(LTNode* phead);//尾删

void LTPushFront(LTNode* phead, DataType x);//头插

void LTPopFront(LTNode* phead);//头删

LTNode* LTFind(LTNode* phead, DataType x);//查找

void LTDestroy(LTNode* phead);//释放链表

bool LTEmpty(LTNode* phead);//判断是否为空

size_t LTSize(LTNode* phead);//求链表长度

(3)test.c

#include"list.h"
void test1()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 1);//尾插
	LTPushBack(phead, 2);
	LTPushBack(phead, 3);
	LTPushBack(phead, 4);
	LTPushBack(phead, 5);
	LTPrint(phead);

	LTPopBack(phead);//尾删
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPrint(phead);
}
void test2()
{
	LTNode* phead = LTInit();
	LTPushFront(phead, 1);
	LTPushFront(phead, 2);//头插
	LTPushFront(phead, 3);
	LTPushFront(phead, 4);
	LTPushFront(phead, 5);
	LTPrint(phead);

	LTPopFront(phead);
	LTPrint(phead);
	LTPopFront(phead);
	LTPrint(phead);//头删
	LTPopFront(phead);
	LTPrint(phead);
	LTPopFront(phead);
	LTPrint(phead);
	LTPopFront(phead);
	LTPrint(phead);
}
void test3()
{
	LTNode* phead = LTInit();
	LTPushFront(phead, 1);
	LTPushFront(phead, 2);//头插
	LTPushFront(phead, 3);
	LTPushFront(phead, 4);
	LTPushFront(phead, 5);
	LTPrint(phead);

	LTNode* pos = LTFind(phead, 3);//查找
	if (pos)
	{
		pos->data *= 2;
	}
	LTPrint(phead);

	LTDestroy(phead);
	phead = NULL;
}

int main()
{
	test1();
	test2();
	test3();
	return 0;
}

(四)带头双向链表测试结果

在这里插入图片描述

  • 54
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 42
    评论
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7昂7.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值