【数据结构初阶】4. 带头双向循环链表代码实现及总结

1. 带头双向循环链表代码实现

gitee代码提交–带头双向循环链表

01. 结点的定义与声明

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

因为是双向链表,所以结点应该包含prev指针指向前结点,next指向后结点

02. 链表初始化

LTNode* ListInit()
{
	LTNode* guard = malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}

将头结点(guard)初始化,使头结点的prev和next指针指向自身
【数据结构初阶】4. 带头双向循环链表代码实现及总结_第1张图片
初始化接口也可以设计成 void ListInit(LTNode** pphead);的形式
(因为ListInit 函数改变了链表指向,要修改链表指向要传二级指针的形式)

03. 创建结点接口

LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

04. 尾插接口

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第2张图片

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;

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

05. 头插接口

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第3张图片

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = BuyListNode(x);
	LTNode* first = phead->next;

	newnode->next = first;
	first->prev = newnode;
	newnode->prev = phead;
	phead->next = newnode;
}

这样实现接口就不需要考虑先后顺序

// 先链接newnode 和 phead->next节点之间的关系
	/*LTNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;*/

如果不创建 first 结构体指针的话,需要首先链接newnode和phead->next 结点之间的关系
不然等phead->next 变成了 newnode 就无法找到 phead->next->prev

06. 打印接口

遍历链表,打印出结点对应的data域数据
但是链表为循环链表,判断条件要控制妥当,不然可能出现死循环的情况
cur 结点从phead->next 开始遍历,若cur == phead 说明遍历结束

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

07. 测试头插尾插接口

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第4张图片
接口测试成功

08. 判断链表为空接口

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

若phead->next == phead 则链表为空
在尾删和头删的接口中要加入assert断言,判断当前链表是否为空

assert(!LTEmpty(phead));

若phead->next == phead 说明链表中只有phead(guard) 哨兵位结点,无法进行删除操作 – 报错

09. 尾删接口

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第5张图片

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* prev = tail->prev;

	prev->next = phead;
	phead->prev = prev;

	free(tail);
	tail = NULL;
}

10. 头删接口

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

	second->prev = phead;
	phead->next = second;

	free(first);
	first = NULL;
}

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第6张图片

11. 测试尾删头删

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第7张图片
尾删头删接口测试完成

12. 查找接口

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

根据所要查找的LTDataType的值遍历一遍链表找到结点,返回类型为LTNode*

13. 计算链表长度

size_t ListSize(LTNode* phead)
{
	assert(phead);
	size_t n = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++n;
		cur = cur->next;
	}
	return n;
}

遍历链表,++n即可

14. 在pos位置之前插入

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第8张图片

void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

15. 删除pos位置

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第9张图片

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* next = pos->next;
	LTNode* prev = pos->prev;

	next->prev = prev;
	prev->next = next;
	free(pos);
	pos = NULL;
}

16. 销毁接口

void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	//phead = NULL;
}

cur遍历链表,用next存储cur的next(下一结点),最后再释放掉哨兵位结点phead
在接口中写的phead = NULL不起作用
要改变结构体指针所指向的内容需要传二级指针的方式void ListDestroy(LTNode** pphead)
或者拿返回值接收的方式 (LTNode* LIstDestroy(LTNode* phead))
这里选择不改变结构体指针,让调用ListDestory的人置空NULL (主要是为了保持接口一致性)
而且销毁接口调用完,该项目也就完成了,没必要复杂化

17. 测试上述接口

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第10张图片
到这里,整个带头双向循环链表的代码就实现完成了!!!✿✿ヽ(°▽°)ノ✿
那代码部分还存在什么问题吗?

2. 优化接口

既然可以在任意位置进行插入删除,那么尾插尾删头插头删的接口就可以进行优化

2.1 尾插

void LTPushBack(LTNode* phead, LTDataType x)
{
	ListInsert(phead, x);
}

phead->prev 即是尾结点,在phead 前插入就是尾插

2.2 头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	ListInsert(phead->next, x);
}

phead->next 就是头结点,在phead->next 前插入就是头插

2.3 尾删

void LTPopBack(LTNode* phead)
{
	ListErase(phead->prev);
}

2.4 头删

void LTPopFront(LTNode* phead)
{
	ListErase(phead->next);
}

其实带头双向循环链表的实现也就是主要实现任意位置的插入删除即可,至于尾插尾删头插头删这类接口只是方便学习理解

3. 顺序表和链表总结:

【数据结构初阶】4. 带头双向循环链表代码实现及总结_第11张图片
【数据结构初阶】4. 带头双向循环链表代码实现及总结_第12张图片
要想了解cpu高速缓存命中率,首先就需要了解存储器的相关概念:
【数据结构初阶】4. 带头双向循环链表代码实现及总结_第13张图片
再拓展一点:
链表遍历时,将结点附近的数据都加载进高速缓存(而高速缓存空间小,如果空间不够会将原来的数据移出缓存区)
那么大量无效数据进入缓存会造成缓存污染
【数据结构初阶】4. 带头双向循环链表代码实现及总结_第14张图片

你可能感兴趣的:(数据结构初阶,链表,数据结构)