[数据结构初阶]双链表

目录

双链表定义

初始化

创建节点 

尾插

​编辑

尾删

头插

头删

打印

查找

pos插入

头插复用

尾插复用

 pos删除

头删复用

尾删复用

判空

size

销毁

 完整代码


前面我们学习了单链表,但按照带头不带头(哨兵)和循环不循环我们可以有四种单链表,双链表也是如此,我们最常用到的是无头单向非循环链表带头双向循环链表,今天我们给大家讲解这种双链表的相关知识,让大家对链表有一个更深层次的理解。

[数据结构初阶]双链表_第1张图片

双链表定义

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

初始化

        思考一下,双链表的初始化该怎么定义?

        我们不妨对比一下单链表和顺序表的初始化,顺序表是一块连续储存结构,他需要通过结构体使size=0完成初始化(capacity可以分配初始空间),而单链表是一个一个节点通过指针连结起来的,在创建新节点后,我们就直接将它的指针置空,可以说基本不需要写一个初始化函数。

        而带头循环双链表不同,它的初始化要先创建出哨兵节点为了构成循环,它的两个指针必须指向自己

创建节点 

 首先我们写一个创建节点函数:

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

因为改变了指针,我们用返回值接收。 

接着让两个结构体两个成员指针指向自身(哨兵节点),完成初始化。

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);//数据无意义
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

因为初始化只需一次,我们这里也用一级指针返回接收,当然,传递指针地址改变指针也行。

尾插

[数据结构初阶]双链表_第2张图片
 

尾插的实现很简单,不需要像单链表一样遍历找尾,直接通过头部的prev节点就能进行尾的插入。而且后面的增删改操作都不需要传二级指针(头节点不变)。

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

刚才我们将头节点自己指向自己的目的现在大家应该能体会到了。 

        不要去看代码,要自己去想这个图,这样你会觉得连结的过程十分丝滑。这里为什么没有将新节点指针置空呢,因为双向节点的每一个指针都指向前/后的对象,所以不存在指向空指针问题。

尾删

尾删类似于尾插,同样妙的一点是,删除操作也仅仅是改变指针指向并释放空间,并不复杂。

 只有一个节点:


 

当删除到只剩一个节点时,tail的前一个节点正好就是头节点,此时删除后就又恢复到了初始状态。

void LTPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);//防止删掉自己
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
}

头插

头插需要注意要保存下一个节点的地址或者先将newnode连结下一个节点再用phead连结newnode,避免找不到后面的节点。

[数据结构初阶]双链表_第3张图片

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

	phead->next = newnode;
	newnode->prev = phead;
//顺序无关
	/*LTNode* first = phead->next;
	phead->next = newnode;
	newnode->next = first;
	newnode->prev = phead;
	first->prev = newnode;*/
}

头删

注意判断是否可能删除自身的情况。 

void LTPopFront(LTNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);//空
	LTNode* first = phead->next;
	LTNode* second = first->next;
	free(first);

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

打印

我们先来实现一下打印函数并测试一下我们之前的代码。打印可以倒着打印或者正向打印,这里我们实现正向打印。注意部是不能打印的。

void LTPrint(LTNode* phead)//打印
{
	assert(phead);
	LTNode* cur = phead->next;//正向
	while (cur != phead)//结束条件
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
		printf("\n");
}

尾插尾删:

[数据结构初阶]双链表_第4张图片

 头插头删:

[数据结构初阶]双链表_第5张图片

查找

查找是从头节点的下一个开始。找到数据后可以通过返回的指针对数据进行修改。

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 nullptr;//找不到返回空
}

 测试:[数据结构初阶]双链表_第6张图片

pos插入

与单链表不同的是,这里不需要传二级头指针就能实现插入,且不用循环找它的前一个节点(Prev指针)。这也体现了这种链表的优势,

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	// prev newnode pos
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

头插复用


void LTPushFront(LTNode* phead, LTDataType x)//头插
{
	assert(phead);
    LTInsert(phead->next, x);
}

尾插复用

尾插复用一般人认为是在最后一个节点复用,实际上我们传递的是头节点。因为我们的插入位置是pos的前一个节点,如果传递最后一个节点就插入到它前面去了,而传递phead,通过它的prev节点就能找到最后一个节点并插入,请注意这一点。

[数据结构初阶]双链表_第7张图片

void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
    LTInsert(phead,x);
}

 pos删除

删除只需找到pos位置的前后节点然后就可以删除啦。在复用尾删头删的时候也是直接传要删除的节点位置就行了。

void LTErase(LTNode* pos)//pos删除
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	free(pos);

	prev->next = next;
	next->prev = prev;
}

头删复用

void LTPopFront(LTNode* phead)//头删
{	
    assert(phead);
	assert(phead->next != phead);//空
    LTErase(phead->next);
}

尾删复用

void LTPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);
    LTErase(phead->prev);
}

测试:

[数据结构初阶]双链表_第8张图片

判空

bool LTEmpty(LTNode* phead)//判空
{
	return phead->next == phead;
}

size

这个接口一般不常用,遍历一遍就能得到长度。大家想想可不可以用哨兵位记录它的长度呢?

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

销毁

和单链表销毁的流程一样,注意最后头节点也要释放掉

和单链表一样,带头双向链表也要传递二级指针才能使指针正确置空(顺序表是直接将成员指针置空)。为了保持一级指针一致性,我们也可以在上一层栈帧最后进行置空操作。

void LTDestroy(LTNode* phead)
{
	assert(phead);

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

		cur = next;
	}

	free(phead);
}

测试: 

[数据结构初阶]双链表_第9张图片

 完整代码

//List.h
#pragma once

#include 
#include 
#include 
#include //判空
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDataType data;
}LTNode;
LTNode* BuyListNode(LTDataType x);//创建节点

LTNode* LTInit();//初始化

void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPrint(LTNode* phead);//打印
void LTPopFront(LTNode* phead);//头删

LTNode* LTFind(LTNode* phead, LTDataType x);//查找
void LTInsert(LTNode* pos, LTDataType x);//pos插入
void LTErase(LTNode* pos);//pos删除
bool LTEmpty(LTNode* phead);//判空
size_t LTSize(LTNode* phead);
void LTDestroy(LTNode* phead);//销毁
//List.cpp
#include"List.h"

LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));//开空间
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	LTNode* next = nullptr;
	LTNode* prev = nullptr;
	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;
	//LTInsert(phead, x);
}
void LTPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);//防止删掉自己
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	LTErase(phead->prev);
}
void LTPushFront(LTNode* phead, LTDataType x)//头插
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;
	//顺序无关
	/*LTNode* first = phead->next;
	phead->next = newnode;
	newnode->next = first;
	newnode->prev = phead;
	first->prev = newnode;*/
	//LTInsert(phead->next, x);
}

void LTPopFront(LTNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);//空
	LTNode* first = phead->next;
	LTNode* second = first->next;
	free(first);

	second->prev = phead;
	phead->next = second;
	//LTErase(phead->next);
}
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 nullptr;//找不到返回空
}
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	// prev newnode pos
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
void LTErase(LTNode* pos)//pos删除
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	free(pos);

	prev->next = next;
	next->prev = prev;
}
void LTPrint(LTNode* phead)//打印
{
	assert(phead);
	LTNode* cur = phead->next;//正向
	while (cur != phead)//结束条件
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
bool LTEmpty(LTNode* phead)//判空
{
	return phead->next == phead;
}
size_t LTSize(LTNode* phead)
{
	assert(phead);
	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}
void LTDestroy(LTNode* phead)
{
	assert(phead);

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

		cur = next;
	}

	free(phead);
}

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