【数据结构】链表之双向链表

学习了单链表之后呢,我们继续学习今天的带头双向循环链表,对比无头单向非循环链表,它的结构最复杂,可能有人看了它的结构图会觉得比单链表还难以实现,其实不然,双向链表由于自身结构优势使得它实现起来十分简单,不信,我们往下看。

目录

 初始化函数

尾插函数

尾删函数

头插函数

 头删函数

 某一位置前插入函数

删除某一位置函数

查找函数

销毁函数

全部代码

List.h

List.c

Test.c


【数据结构】链表之双向链表_第1张图片

 首先我们观察上图,对比单链表,今天的主角有什么特点?

首先有一个哨兵位的头节点,其次每个结点有两个指针prev和next,prev指向前一个结点,next指向下一个结点,head的prev指向链表的尾,链表的尾指向head。思考如果只有一个结点指针如何指向呢,要想体现循环,那么就需要让prev和next都指向自己,如下图:

【数据结构】链表之双向链表_第2张图片

那么,我们依旧是按照单链表的实现来写对应的双向链表

首先我们先将结构确定好

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

 初始化函数

考虑到后边尾插头插需要申请新的结点,所以我们把申请结点封装成BuyListNode函数

 初始化这里有个需要注意的地方是如果你的初始化函数返回值是void,那么要想初始化有效,参数必须是二级指针,原因还是形参是实参的临时拷贝,但这样初始化函数代码与其他函数代码不够统一,因为其他函数基本都是在结构体做文章,需要改变的是结构体中指针的指向关系,所以参数用的都是一级指针,因此这里我们也尽量使用一级指针,那么就需要带有返回值返回一个phead

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

LTNode*  LTInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

尾插函数

到这,我们双向链表的优势就有所体现了,由于头结点的prev指向尾,所以尾插时就不需要遍历找尾,直接根据head就可以,根据它的结构图不难发现只需要改变四次指针指向关系就完成了尾插

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

为了方便我们观察,我们再写一个打印函数,测试一下

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

【数据结构】链表之双向链表_第3张图片

尾删函数

如果链表为空则不需要删,那么我们增加一个判断函数

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	tail = NULL;
}

 测试一下:

【数据结构】链表之双向链表_第4张图片

头插函数

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

 【数据结构】链表之双向链表_第5张图片

 头删函数

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);
	cur = NULL;
}

【数据结构】链表之双向链表_第6张图片

 某一位置前插入函数

void LTInsert(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;
}

当然有了这个函数我们的头插尾插都可以复用该函数

删除某一位置函数

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* p = pos->prev;
	LTNode* n = pos->next;
	p->next = n;
	n->prev = p;
	free(pos);
}

这里某一位置插入和删除函数需要和查找函数配套使用

查找函数

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;
}

测试一下写的删除函数和查找函数: 

【数据结构】链表之双向链表_第7张图片

 同理,头删尾删也可以复用这一块儿函数

销毁函数

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

全部代码

List.h

#include
#include
#include
#include

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

LTNode* LTInit();//初始化
void LTDestroy(LTNode* phead);//销毁
bool LTEmpty(LTNode* phead);//非空判断
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPrint(LTNode* phead);//打印
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
void LTInsert(LTNode* pos, LTDataType x);//某一位置前插入
void LTErase(LTNode* pos);//删除某一位置
LTNode* LTFind(LTNode* phead, LTDataType x);//查找某一位置

List.c

#include"List.h"

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

LTNode*  LTInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

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

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	tail = NULL;
}

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

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;
	free(cur);
	cur = NULL;
}

void LTInsert(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;
}

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* p = pos->prev;
	LTNode* n = pos->next;
	p->next = n;
	n->prev = p;
	free(pos);
}

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;
}

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

Test.c

#include"List.h"

void TestList1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPrint(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	LTPrint(plist);
}

void TestList2()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);
	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTErase(pos);
		pos = NULL;
	}
	LTPrint(plist);
}


int main()
{
	TestList2();
	return 0;
}

到这呢双向链表就结束了,对比单链表我们发现双向链表的代码还是十分简单的,而且逻辑十分清晰,看到这的老铁三连一下吧,码字不易,给博主点支持!

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