基于C语言的双向循环带头链表实现

Posted 胖仙人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于C语言的双向循环带头链表实现相关的知识,希望对你有一定的参考价值。

->Gitee源代码点击这里<-
结构说明:
①一个结点中有三部分
a.prev指针:存放前一个结点的指针
b.data:存放有效值
c.next指针:存放下一个结点的指针
②链表有一个头结点,其中不存放有效值,但存放了头结点的指针和尾结点的指针
③之所以成为循环,是因为尾结点的下一个结点不再为NULL,而是指向头结点

④当链表尾空时,头结点的prev部分和next部分都存放头结点自己的地址

根据结构描述创建结点的结构体

typedef int DLDataType;

typedef struct DoubleLinkedListNode

	typedef struct DoubleLinkedListNode* prev;
	DLDataType data;
	typedef struct DoubleLinkedListNode* next;
DLNode;

插入数据就需要创建结点,所以第一步要设计创建结点的接口

void BuyDLNode(DLDataType data);
void BuyDLNode(DLDataType data)

	DLNode* node = (DLNode*)malloc(sizeof(DLNode));
	if (node == NULL)
	
		printf("Buy new node failed\\n");
		exit(-1);
	
	node->prev = NULL;
	node->next = NULL;
	node->data = data;
	return node;

链表未插入数据之前,头结点就已经存在,即头结点的存在就象征了双链表的存在,所以我们要对头结点进行初始化,并在main函数中实际创建一个头结点

DLNode* PheadInit();
DLNode* PheadInit()

	DLNode* phead = BuyDLNode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;

int main()

	DLNode* phead = PheadInit();

数据处理:
一、头插数据
插入时和单链表的原理类似,区别在于双链表头插时需要处理的指针关系更多

void DoubleLinkedPushFront(DLNode* phead, DLDataType data);
void DoubleLinkedPushFront(DLNode* phead,DLDataType data)

	assert(phead);
	DLNode* newnode = BuyDLNode(data);
	DLNode* firstnode = phead->next;  //找到原来的第一个结点

	//处理newnode和firstnode之间的关系
	newnode->next = firstnode;
	firstnode->prev = newnode;

	//处理phead和newnode之间的关系
	phead->next = newnode;
	newnode->prev = phead;

在设计插入数据的接口时,我们通常还需要考虑极端情况,如:头插入第一个结点时,上述代码是否适用
示意图:

当链表中无结点时,此时找第一个结点 first = phead->next即为phead自己本身,通过示意图可以看到,上述代码对于极端情况也同样适用

二、尾插数据
由于双向和循环,双向循环链表在尾插时则不再需要通过遍历来找寻尾结点,phead->prev即为尾结点的地址
尾插示意图:

void DoubleLinkedPushBack(DLNode* phead, DLDataType data);
void DoubleLinkedPushBack(DLNode* phead, DLDataType data)

	DLNode* newnode = BuyDLNode(data);
	DLNode* tail = phead->prev;
	//处理tail和newnode之间的关系
	tail->next = newnode;
	newnode->prev = tail;
	
	//处理phead和newnode之间的关系
	phead->prev = newnode;
	newnode->next = phead;

同样,也要对极端情况进行考虑,当链表中没有结点时,tail即phead自己本身

上述代码也同样适用

三、打印接口
打印接口可以帮助我们更直观的看到链表内的数据,方便接口测试

void DoubleLinkedPrint(DLNode* phead);
void DoubleLinkedPrint(DLNode* phead)

	DLNode* cur = phead->next;
	printf("phead->");
	while (cur != phead)
	
		printf("%d->", cur->data);
		cur = cur->next;
	
	printf("phead\\n");

四、头删数据

void DoubldeLinkedPopFront(DLNode* phead);
void DoubldeLinkedPopFront(DLNode* phead)

    assert(phead);
	assert(phead->next != phead);
	DLNode* first = phead->next;

	phead->next = first->next;
	first->next->prev = phead;
	
	free(first);
	first = NULL;


五、尾删数据

void DoubleLinkedPopBack(DLNode* phead);
void DoubleLinkedPopBack(DLNode* phead)

    assert(phead);
	assert(phead->prev != phead);
	DLNode* tail = phead->prev;
	DLNode* newtail = tail->prev;
	
	phead->prev = newtail;
	newtail->next = phead;
	free(tail);
	tail = NULL;

六、在任意位置 **_pos _**前插入数据
由于双向链表结点的双指向性,我们可以设计单链表不方便实现的接口。而由于链表不能随机访问,所以这里仍然要配合查找接口的使用来获取我们想要的 pos
查找接口:
找到返回pos,否则返回NULL

DLNode* DoubleLinkedFind(DLNode* phead,DLDataType data);
DLNode* DoubleLinkedFind(DLNode* phead, DLDataType data)

	DLNode* cur = phead->next;
	while (cur != phead)
	
		if (cur->data == data)
			return cur;
		else
			cur = cur->next;
	
	return NULL;

插入接口:

void DoubleLinkedInsert(DLNode* pos, DLDataType data);
void DoubleLinkedInsert(DLNode* pos, DLDataType data)

	DLNode* prev = pos->prev;
	DLNode* newnode = BuyDLNode(data);
	prev->next = newnode;
	newnode->prev = prev;

	newnode->next = pos;
	pos->prev = newnode;

有了此接口,我们还可以通过对该接口的复用来实现对头插和尾插接口的简化

头插
void DoubleLinkedPushFront(DLNode* phead,DLDataType data)

	DoubleLinkedInsert(phead->next,data);

//尾插
//说明:phead的前一个结点就是尾结点
void DoubleLinkedPushBack(DLNode* phead,DLDataType data)

	DoubleLinkedInsert(phead,data);

七、删除 pos 位置的结点

void DoubleLinkedDelete(DLNode* pos);
void DoubleLinkedDelete(DLNode* pos)

	DLNode* prev = pos->prev;
	DLNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);

同样,我们也可以借助该接口实现对头删和尾删的简化

void DoubldeLinkedPopFront(DLNode* phead)

    assert(phead->next!=phead);  //调用和删除接口结点内必须有结点
	DoubleLinkedDelete(phead->next);


void DoubldeLinkedPopBack(DLNode* phead)

    assert(phead->prev!=phead);  //调用和删除接口结点内必须有结点
	DoubleLinkedDelete(phead->prev);

八、链表的销毁
由于结点是动态开辟,所以双链表使用结束时,需要释放空间
这里需要注意,phead需要调用者手动指控;想要改变phead的值,实际上时需要传二级指针作为参数,但是为了保证接口参数的一致性,所以选择传一级指针

void DoubleLinkedListDestroy(DLNode* phead);
void DoubleLinkedListDestroy(DLNode* phead)

	DLNode* cur = phead->next;
	
	while (cur != phead)
	
		DLNode* next = cur->next;
		free(cur);
		cur = next;
	
	free(phead);

以上是关于基于C语言的双向循环带头链表实现的主要内容,如果未能解决你的问题,请参考以下文章

基于C语言的双向循环带头链表实现

数据结构之带头结点的循环双向链表详细图片+文字讲解

数据结构:如何用C语言快速实现带头双向循环链表

数据结构C语言版 —— 链表增删改查实现(单链表+循环双向链表)

数据结构c语言篇 《二》带头双向循环链表实现以及链表相关面试题(下)

数据结构c语言篇 《二》带头双向循环链表实现以及链表相关面试题(下)