揭开线性表的神秘面纱——双向链表

双向链表分类

根据链表的分类组合方式,双向链表包含:不带头非循环双向链表;带头非循环双向链表;不带头循环双向链表;带头循环双向链表。

图例:

不带头双向非循环链表

带头双向非循环链表 

不带头双向循环链表

带头双向循环链表

1. 带头双向循环链表概述

在单链表那篇文章中我们使用c语言实现了无头单向非循环链表,本文将实现带头双向循环链表,在诸多链表结构中,上述两种链表在实际生活与面试题中最为常见。

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

2. 剖析带头双向循环链表 

无头单向非循环链表结构简单,但代码实现复杂,而带头双向循环链表结构复杂,但代码实现简单,这是为什么呢?

让我们一同寻求真相。

如图所示

无头单向非循环链表:

无头单向非循环链表 结构极为简单,但每个节点只存储了下一节点的地址,假设phead指针指向首元节点,那么phead就只能按照d1->d2->d3这样的路径前进,当到达尾节点,就要重新从首元节点开始。同时因为没有头节点,因此进行插入删除等操作时都需要判断链表是否为NULL。

带头双向循环链表: 

带头双向循环链表结构虽然复杂,每个节点存储了下一节点与上一节点的地址,并且较之无头单向非循环链表多了一个头节点,但代码实现极为简单。首先,无论从哪一个节点,都能迅速进行操作,不必担心路径问题,比如从头结点只需要进行 head=head->prev,此时head就指向了d3尾节点。同时头节点的存在让对链表操作时更是少了很多麻烦,无论何时,链表都存在一个节点,因此不需要考虑链表为NULL的情况,但要注意,不能对头节点进行增删查改等操作。

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

需要实现的接口函数

DoubleList.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;


	ListNode* ListCreate(LTDataType x);// 创建返回链表的头结点.
	ListNode* InitList();//初始化链表
	ListNode* ListDestory(ListNode* pHead);// 双向链表销毁
	// 双向链表打印
	void ListPrint(ListNode* pHead);
	// 双向链表尾插
	void ListPushBack(ListNode* pHead, LTDataType x);
	// 双向链表尾删
	void ListPopBack(ListNode* pHead);
	// 双向链表头插
	void ListPushFront(ListNode* pHead, LTDataType x);
	// 双向链表头删
	void ListPopFront(ListNode* pHead);
	// 双向链表查找
	ListNode* ListFind(ListNode* pHead, LTDataType x);
	// 双向链表在pos的前面进行插入
	void ListInsert(ListNode* pos, LTDataType x);
	// 双向链表删除pos位置的节点
	//ListNode* ListErase(ListNode* pHead,ListNode* pos);
	void ListErase(ListNode* pHead, ListNode* pos);

DoubleList.c 

#include"DoubleList.h"

ListNode* ListCreate(LTDataType x)
{
	assert(x);
	struct ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

ListNode* InitList()
{
	struct ListNode* pHead = ListCreate(-1);
	pHead->next = pHead;
	pHead->prev = pHead;
	return pHead;
}
// 双向链表销毁
ListNode* ListDestory(ListNode* pHead)
{
	assert(pHead);
	struct ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		struct ListNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
	free(pHead);
	pHead = NULL;
	return pHead;
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	struct ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead, x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead, pHead->prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead, pHead->next);
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	assert(x);
	struct ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	struct ListNode* Prev = pos->prev;
	struct ListNode* newnode = ListCreate(x);
	Prev->next = newnode;
	pos->prev = newnode;
	newnode->next = pos;
	newnode->prev = Prev;
}
 //双向链表删除pos位置的节点
void ListErase(ListNode* pHead, ListNode* pos)
{
	assert(pos);
	assert(pos != pHead);
	struct ListNode* Prev = pos->prev;
	struct ListNode* Next = pos->next;
	Prev->next = Next;
	Next->prev = Prev;
	free(pos);
}

以上是各接口函数的具体实现。

代码注意事项

<> 由于双向链表的在指定位置插入(删除),与头插尾插(头删尾删)实现功能重复且代码的书写没有区别,因此头插尾插(头删尾删)调用了在指定位置插入(删除)的函数。

<> 如果链表中只有一个头节点,那么他的prev与next都应该指向自己。

<>有关两个链表的实现,老铁们应该发现了函数传参的不同,这个涉及传参概念。

由于单链表并没有头节点,因此需要创建第一个节点,然后让头指针指向它,因此phead指针需要改变,而形式传参并不会改变函数外实际参数的值,因此我们需要地址传递,传入phead指针的地址,因此传入二级指针。

但本文的双链表有头节点,头指针始终指向头节点,并不需要改变phead的指向,也就是说不用改变函数外实际参数的值,因此值传递即可,传入参数本身。

感谢观看,期待大佬们的指正!

  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值