[数据结构初阶]双链表

这篇具有很好参考价值的文章主要介绍了[数据结构初阶]双链表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

双链表定义

初始化

创建节点 

尾插

​编辑

尾删

头插

头删

打印

查找

pos插入

头插复用

尾插复用

 pos删除

头删复用

尾删复用

判空

size

销毁

 完整代码


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

[数据结构初阶]双链表,链表,数据结构

双链表定义

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

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

尾插

 

尾插的实现很简单,不需要像单链表一样遍历找尾,直接通过头部的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,避免找不到后面的节点。

[数据结构初阶]双链表,链表,数据结构

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

尾插尾删:

[数据结构初阶]双链表,链表,数据结构

 头插头删:

[数据结构初阶]双链表,链表,数据结构

查找

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

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

 测试:[数据结构初阶]双链表,链表,数据结构

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节点就能找到最后一个节点并插入,请注意这一点。

[数据结构初阶]双链表,链表,数据结构

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

测试:

[数据结构初阶]双链表,链表,数据结构

判空

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

测试: 

[数据结构初阶]双链表,链表,数据结构文章来源地址https://www.toymoban.com/news/detail-520218.html

 完整代码

//List.h
#pragma once

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>//判空
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);
}

到了这里,关于[数据结构初阶]双链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构--线性表(顺序表、单链表、双链表、循环链表、静态链表)

    前言  学习所记录,如果能对你有帮助,那就泰裤辣。 目录 1.线性表概念 定义 基本操作 2.顺序表 定义 顺序表的实现--静态分配 动态分配 顺序表的特点 顺序表的插入和删除 顺序表的查找 按位查找  按值查找 3.单链表 定义 单链表的初始化 不带头节点的单链表 带头节点的单

    2024年02月11日
    浏览(42)
  • 【数据结构与算法】顺序表与链表(单链表和双链表)超详解图示与源码。

                                                       大家好,今天我们来学习数据结构中的顺序表与链表!源码在最后附上 首先我们先来认识一下 顺序表 :                                       **如上图所示:很多人会以为数组就是顺序表,顺序表就是数组,这

    2024年02月21日
    浏览(36)
  • 初阶数据结构:链表

    经过学习与练习,已经掌握了顺序表的相关知识并且能够自我实现。在这一过程中,通过对其结构的实现,发现顺序表的尾部插入删除数据效率很高,可是,在 头部与随机插入的场景下效率低下 而且 存在扩容消耗 等一些问题。而有这样一种数据结构可以很好的解决顺序表存

    2024年01月20日
    浏览(31)
  • 【玩转408数据结构】线性表——双链表、循环链表和静态链表(线性表的链式表示 下)

            在前面的学习中,我们已经了解到了链表(线性表的链式存储)的一些基本特点,并且深入的研究探讨了单链表的一些特性,我们知道,单链表在实现插入删除上,是要比顺序表方便的,但是,单链表中每个结点仅存在一个指针指向其后续结点,那么如果我们想要找

    2024年04月10日
    浏览(40)
  • 初阶数据结构(三)链表

    💓博主csdn个人主页:小小unicorn💓 ⏩专栏分类:c++ 🚚代码仓库:小小unicorn的学习足迹🚚 🌹🌹🌹关注我带你学习编程知识 前面我们讲的线性表的 顺序存储结构 。它是有缺点的,最大的缺点就是插入和删除时需要移动大量元素,这显然就需要 耗费时间 ,那能不能想办法

    2024年02月09日
    浏览(41)
  • 【数据结构初阶】链表OJ

    OJ 方案一: 题目解析: 方案二: 题目解析:把原链表遍历一遍,插入新链表 OJ 题目解析: OJ 题目解析: OJ 题目解析: OJ 题目解析: OJ 题目解析: OJ 题目解析: OJ 题目解析: 定义快慢指针,使快指针先走与慢指针同步。然后同时走看是否相交 OJ 题目解析: OJ 题目解析:

    2024年02月05日
    浏览(37)
  • 【数据结构初阶】第四节.链表详讲

    前言 一、单链表的概念 二、链表的创建 2.1链表的初始化 2.2 打印链表 2.3 获取链表的长度 2.4 判断链表是否为空 三、新增结点 3.1头插 3.2 指定下标插入 四、删除结点 4.1 头删 4.2 指定下标的删除 4.3 删除链表中的指定元素 五、单链表查找 六、附录 总代码 测试代码 总结 前

    2023年04月15日
    浏览(58)
  • 【数据结构初阶】顺序表和链表(1)

    线性表(linear list) 是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上

    2024年02月08日
    浏览(34)
  • 初阶数据结构之---顺序表和链表(C语言)

    线性表: 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构。线性表在逻辑上是线性结构,也就是说是连续的一条直线。但在物理上并不一定是连续的。线性表在物理上存储时,通常以 数组 和 链式结构 的形式存储。

    2024年02月22日
    浏览(43)
  • 【数据结构初阶】四、线性表里的链表(带头+双向+循环 链表 -- C语言实现)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表 -- C语言实现)-CSDN博客  ====

    2024年02月08日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包