当我与单链表分手后,在酒吧邂逅了双向循环链表.....

链表的种类有8种,但我们最常用的为无头单向非循环链表带头双向循环链表

带头双向循环链表 

当带头双向循环链表只有哨兵位头的时候,双向链表的指向如下图。

head->pre和head->next都是指向自己,这个是有巨大优势的,代码实现会很方便。 

1.无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
来很多优势,实现反而简单了,后面我们代码实现了就知道了。

在上个博客实现顺序表和单链表的时候我提过这两点,让我们来进入双向循环链表的实现吧!

带头双向循环链表的结点开辟

LTNode* buynode(SedListtype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->pre = NULL;
	newnode->next = NULL;

	return newnode;


}

这个开辟结点应该没有什么好说的,带头双向循环链表多了个指针pre。 

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

LTNode* LTInit()
{
	LTNode* phead= buynode(-1);
	phead->pre = phead;
	phead->next = phead;
	return phead;

}

哨兵位的结点值是可以随便给的,毕竟只负责站岗,当双向链表为空,phead->pre和phead->next都指向自己,这才是双向循环链表的核心!这里为什么要返回头指针,因为为了与其他增删查改方法实现统一使用一级指针要不然初始化双向链表需要使用二级指针(因为结构体指针是一级指针,为了改变结构体的内容就要传值并用二级指针接受一级指针(结构体指针)才能改变结构体内容),但为了避免问题复杂化,二级指针变为一级指针最简单方法就是自己返回值。

带头双向循环链表的尾插

void LTPushBack(LTNode* phead, SedListtype x)
{
	assert(phead);
	
	LTNode* newnode = buynode(x);
	LTNode* tail = phead->pre;
	tail->next = newnode;
	newnode->pre = tail;
	newnode->next = phead;
	phead->pre = newnode;
}

不用像单链表一样找尾tail,phead->pre就是tail,而且单链表需要判断链表为空不为空的两种情况且需要2级指针,不像带头双向循环链表带有哨兵位的头指针使用一级指针就可以,如果链表为空,这段代码也同样适用,即是头也是尾,双向链表改变的是结构体,用结构体指针就够了,不需要二级指针。

带头双向循环链表为空时

带头双向循环链表不为空时 

带头双向循环链表的头插

void LTPushFront(LTNode* phead, SedListtype x)
{
	assert(phead);
	LTNode* newnode = buynode(x);
	LTNode* frist = phead->next;
	phead->next = newnode;
	newnode->pre = phead;
	newnode->next = frist;
	frist->pre = newnode;


}

错误写法
Void LTPushFront(LTNode* phead, LTDataType x)
assert(phead);
LTNode* newnode = BuyLTNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = phead->next;
phead->next->prev = newnode;
最常见错误之一,会找不到phead->next的原来的地址值

调整一下顺序即可
void LTPushFront(LTNode* phead, LTDataType x)
assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;

先保存phead->next头结点的值,避免找不到或者改变phead的值,然后再按照常规连接即可。

当链表为空,这段代码也一样适用,因为双向链表的核心就是头指针的pre和next都是指向自己,这是带头双向循环链表的优势。

 

布尔值判断

bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

这是为了判断在进行头删和尾删的时候,不能把哨兵位(头指针)给删除掉 ;布尔值就是布尔值是“真” True 或“假” False 中的一个,如果删除到哨兵位(头指针)就返回头指针本身。

带头双向循环链表的尾删

void LTPopBack(LTNode* phead)
{
	assert(phead);
    assert(!LTEmpty(phead));
	LTNode* tail = phead->pre;
	LTNode* pre = tail->pre;
	free(tail);
	pre->next = phead;
	phead->pre = pre;
}

assert(!LTEmpty(phead));是为了防止删除哨兵位(头结点)。

先找尾tail,然后tail->pre就是上一个结点pre的值,再释放掉尾tail,最后常规连接即可。 

带头双向循环链表的头删

void LTPopFront(LTNode* phead)
{
	assert(phead);
    assert(!LTEmpty(phead));
	LTNode* frist = phead->next;
	LTNode* second = frist->next;

	phead->next = second;
	second->pre = phead;
	free(frist);

}

assert(!LTEmpty(phead));是为了防止删除哨兵位(头结点)。 

头删的代码肯定不止一种写法,但我们这种写法是最通俗易懂且简便的,可以让别人一眼就能看出我们的意图所在,先用frist存phead->next的值,再用second存frist->next的值,然后常规连接释放即可。

带头双向循环链表的查找

LTNode* Find(LTNode* phead, SedListtype x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

当cur不等于phead(哨兵位)时就遍历完了链表,这个应该没啥好说的,各位兄弟姐妹也会自己写。但这个查找遍历与顺序表和单链表都一样,查找链表内对应值的内容是可以修改的。 

 带头双向循环链表的插入(pos之前插入)

void LTInsert(LTNode* pos, SedListtype x)
{
	assert(pos);
	LTNode* newnode = buynode(x);
	LTNode* prepos = pos->pre;
	prepos->next = newnode;
	newnode->pre = prepos;
	newnode->next = pos;
	pos->pre = newnode;


}

先用pos->pre找到pos上一个结点的值,再用一个临时变量存储,再常规连接即可。 

  带头双向循环链表的删除(pos位置删除)

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* pospre = pos->pre;
	LTNode* posnext = pos->next;
	pospre->next = posnext;
	posnext->pre = pospre;
	free(pos);

}

 利用pos找出pos前面与后面结点的值,用两个临时变量存储,常规连接两个变量即可。

 带头双向循环链表的打印

void LTprint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("guard<==>");哨兵位
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");


}

  带头双向循环链表的销毁

void LTDestory(LTNode* phead)
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

用next存储结点值,再一个个释放即可。 

使用完动态开辟的结点值后要销毁,要不然会内存泄漏,phead可以在test.c文件中再置空,因为释放掉以后,不会再有人使用,但为了安全最好在test.c置空一下即可。

带头双向循环链表的完整代码以及运行结果

SedList.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int SedListtype;
typedef struct SedList
{
	struct SedList* pre;
	struct SedList* next;
	SedListtype data;

}LTNode;

bool LTEmpty(LTNode* phead);//布尔值防止删除哨兵位
LTNode* LTInit();//初始化;
void LTprint(LTNode* phead);//打印
void LTDestory(LTNode* phead);//销毁

void LTPushBack(LTNode* phead, SedListtype x);//尾插
void LTPushFront(LTNode* phead, SedListtype x);//头插

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

LTNode* Find(LTNode* phead,SedListtype x);//查找

void LTInsert(LTNode* pos, SedListtype x);//pos之前插入
void LTErase(LTNode* pos);//pos位置删除
SedList.c
#include"sedlist.h"
LTNode* buynode(SedListtype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->pre = NULL;
	newnode->next = NULL;

	return newnode;


}
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

LTNode* LTInit()
{
	LTNode* phead= buynode(-1);
	phead->pre = phead;
	phead->next = phead;
	return phead;

}
void LTprint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("guard<==>");//哨兵位
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");


}
void LTDestory(LTNode* phead)
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

void LTPushBack(LTNode* phead, SedListtype x)
{
	assert(phead);
	
	LTNode* newnode = buynode(x);
	LTNode* tail = phead->pre;
	tail->next = newnode;
	newnode->pre = tail;
	newnode->next = phead;
	phead->pre = newnode;

}

void LTPushFront(LTNode* phead, SedListtype x)
{
	assert(phead);
	LTNode* newnode = buynode(x);
	LTNode* frist = phead->next;
	phead->next = newnode;
	newnode->pre = phead;
	newnode->next = frist;
	frist->pre = newnode;


}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* tail = phead->pre;
	LTNode* pre = tail->pre;
	free(tail);
	pre->next = phead;
	phead->pre = pre;
}
void LTPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* frist = phead->next;
	LTNode* second = frist->next;

	phead->next = second;
	second->pre = phead;
	free(frist);



}
LTNode* Find(LTNode* phead, SedListtype x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void LTInsert(LTNode* pos, SedListtype x)
{
	assert(pos);
	LTNode* newnode = buynode(x);
	LTNode* prepos = pos->pre;
	prepos->next = newnode;
	newnode->pre = prepos;
	newnode->next = pos;
	pos->pre = newnode;


}
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* pospre = pos->pre;
	LTNode* posnext = pos->next;
	pospre->next = posnext;
	posnext->pre = pospre;
	free(pos);

}
test.c
#include"sedlist.h"
void  test1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);

	LTprint(plist);
	LTDestory(plist);
	plist = NULL;

}
void  test2()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);

	LTprint(plist);
	LTDestory(plist);
	plist = NULL;

}
void  test3()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPopBack(plist);
	LTPopBack(plist);
	LTPushBack(plist, 4);
	LTprint(plist);
	LTDestory(plist);
	plist = NULL;

}
void test4()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPopFront(plist);
	LTPopFront(plist);
	LTPushFront(plist, 5);
	LTprint(plist);
	LTDestory(plist);
	plist = NULL;

	


}
void test5()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 5);
	LTNode* pos = Find(plist, 2);
	pos->data = 12;
	LTprint(plist);
	LTDestory(plist);
	plist = NULL;




}
void test6()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 5);
	LTNode* pos = Find(plist, 2);
	if (pos)
	{
		LTInsert(pos, 24);
	}
	LTprint(plist);
	LTDestory(plist);
	plist = NULL;




}
void test7()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 5);
	LTNode* pos = Find(plist, 3);
	if (pos)
	{
		LTErase(pos);
	}
	LTprint(plist);
	LTDestory(plist);
	plist = NULL;

}
int main()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	test7();

	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值