首页 > 其他分享 >数据结构中的双向链表

数据结构中的双向链表

时间:2024-08-17 17:27:36浏览次数:13  
标签:结点 prev next 链表 LTNode phead 双向 newnode 数据结构

1.链表的分类

链表的结构非常多样,以下情况组合起来就是8种(2x2x2)链表结构:

 

 在带头链表中,除了头结点,其他结点均存储有效的数据。

头结点是占位子的,也叫做“哨兵位”。head结点就是头结点。

 循环的链表尾结点不为NULL, 不循环的链表尾结点为NULL

单链表:不带头单向不循环链表

双向链表:带头双向循环链表

双向链表结构相较于单链表来说要复杂一些,但是接口的实现上要比单链表简单很多

双向链表的结点结构:数据+指向下一个结点的指针+指向前一个结点的指针

struct ListNode
{
  int date;
  struct ListNode *next;
  struct ListNode *prev;

}

 2.双向链表的实现

2.1头结点的创建

//创建头节点
LTNode* LTBuyNode(LTDateType x)
{
	LTNode* newnode = (LTNode*)malooc(sizeof(LTNode);
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->date = x;
	//因为是双向循环,要循环起来,所以头结点要自己指向自己。
	newnode->next = newnode->prev = newnode;
}


//初始化
void LTInit(LTNode** pphead)
{
	//创建一个头结点
	*pphead = LTBuyNode(-1);
}

双向链表为空的情况就是只有一个头结点

2.2插入

传的是一级指针还是二级指针要看pphead指向的结点会不会发生改变,也就是头结点会不会发生改变。

如果发生改变,那么pphead的改变要影响实参,传二级

如果不发生改变,pphead不会影响实参,传一级。

2.2.1尾插

尾插影响的是尾插前一个结点和头结点,改变他们的指向就好了。

先修改插入的结点的指向,比较方便

//插入
//传的是一级指针还是二级指针要看pphead指向的结点会不会发生改变,也就是头结点会不会发生改变。
//如果发生改变,那么pphead的改变要影响实参,传二级
//如果不发生改变,pphead不会影响实参,传一级。
//尾插
void LTPushBack(LTNode* pphead, LTDateType x)
{
    assert(pphead);
	LTNode* newnode = LTBuyNode(x);
	//pphead pphead->prev newnode
	newnode->next = pphead;
	newnode->prev = pphead->prev;

	pphead->prev->next = newnode;
	pphead->prev = newnode;
}

2.2.2头插

头插是在哨兵位与第一个有效结点之间插入数据,不是在哨兵位前插入数据,在哨兵位前插入数据是尾插。

受到影响的有哨兵位,第一个有效节点,还是先改插入newnode的指向。

 

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

	LTNode* newnode = LTBuyNode(x);

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

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

}

2.3双向链表的打印

双向链表的死循环的,为了让他不死循环,结束条件可以是不等于哨兵位。

//打印
void LTprint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{
		printf("%d->", pcur->date);
		pcur = pcur->next;
	}
	printf("\n");
}

2.4判断链表是否为空

//判断链表是否为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	//返回true表示链表为空,返回false表示链表不为空
	//非0表示true,0表示false。
	//如果phead->next == phead,返回1
	//如果phead->next != phead,返回0
	return phead->next == phead;
}

2.5删除

不要把哨兵位删除,不会影响哨兵位,一级指针。

还要判断一下链表不为NULL,不然没东西删。

2.5.1尾删

思路:影响到的有尾结点的前一个结点以及哨兵位的结点,改变他们的指向,然后释放尾结点

void LTPopBack(LTNode* phead)
{
	assert(phead);
	//判断链表是否为NULL
	assert(!LTEmpty(phead));

	//phead prev(del->prev) del(phead->prev)
	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

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

	free(del);
	del = NULL;

}

2.5.2头删

头删删的是哨兵位后面的结点,

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	//phead del(phead->next) del->next
	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	 
	free(del);
	del = NULL;
}

2.6查找

//查找
LTNode* LTFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->date == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

2.7在指定位置(pos)之后插入节点

先改变newnode的指向,再改相邻的指向

 

//在指定位置(pos)之后插入节点
void LTInsert(LTNode* pos, LTDateType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);

	//pos pos->prev pos->next
	newnode->prev = pos;
	newnode->next = pos->next;

	pos->next->prev = newnode;
	pos->next = newnode;

}

2.8删除指定位置的结点

//删除指定位置的结点
void LTErase(LTNode* pos)
{
	assert(pos);

	//pos pos->prev pos->next
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
	pos = NULL;

}

2.9销毁

二级指针,因为会影响到哨兵位。

从第一个有效的结点开始销毁。

//销毁
void LTDesTroy(LTNode** pphead)
{
	assert(pphead && *pphead);
	LTNode* pcur = (*pphead)->next;
	while (pcur!=*pphead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	free(*pphead);
	*pphead = NULL;
	pcur =	NULL;

}

2.10初始化和销毁也可以是一级指针

这样做的目的是为了保证接口的一致性

1.销毁的一级指针

要在.h文件中手动置为NULL

//销毁
void LTDesTroy2(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	free(phead);
	pcur = phead = NULL;
}

2.初始化的一级指针

//初始化
LTNode* LTInit2()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

 第1种方法要提前弄一个plist变量,传他的地址才能调用

第二种可以直接调用。

标签:结点,prev,next,链表,LTNode,phead,双向,newnode,数据结构
From: https://blog.csdn.net/2401_84421190/article/details/141092571

相关文章

  • 代码随想录day3 | LeetCode203. 移除链表元素、LeetCode707. 设计链表、LeetCode206.
    代码随想录day3|LeetCode203.移除链表元素、LeetCode707.设计链表、LeetCode206.反转链表为了防止早上写博客上传图片失败,今天试试下午写,发现图片上传正常链表基础文章链接:链表基础C/C++的定义链表节点方式,如下所示://单链表structListNode{intval;/......
  • 数据结构与算法(五)栈
    一.定义:栈是限定仅在表尾进行插入或删除操作的线性表,因此,对栈来说,表尾端具有其特殊含义,称为栈顶(top),表头端称为栈底(bottom)。不含元素的空表称为空栈。栈是重要的线性结构之一,从数据结构的角度来说,栈也是线性表在理解栈的含义要注意:首先,它是一个线性表,也就是说栈元素具有线性......
  • 数据结构与算法(六)二叉树
    二叉树是一种数据结构,广泛用于计算机科学和编程中。它具有以下几个重要特征:1.基本定义:①二叉树:每个节点最多有两个子节点,分别称为左子节点和右子节点。②节点:二叉树的基本单位,包含数据以及指向其子节点的指针。③根节点:二叉树的第一个节点,没有父节点。④叶子节点:没有子节点的......
  • 【数据结构】数据结构 知识总复习
    文章目录1.复杂度计算1.1时间复杂度1.2空间复杂度1.3如何计算2.常见线性结构2.1顺序表和链表2.1.1顺序表(ArrayList)2.1.2链表(LinkedList)2.2栈和队列2.2.1什么是栈?什么是队列?关系是什么?2.2.2如何实现?数组链表原生实现?适配器?3.常见非线性结构3.......
  • 合并K个升序链表-力扣
    /***Definitionforsingly-linkedlist.*structListNode{*intval;*ListNode*next;*ListNode():val(0),next(nullptr){}*ListNode(intx):val(x),next(nullptr){}*ListNode(intx,ListNode*next):val(x),next(ne......
  • 数据结构与算法--二叉排序树
    文章目录回顾提要1.二叉排序树的定义与特性2.二叉排序树的构建与插入3.二叉排序树的查找4.二叉排序树的删除5.二叉排序树的优点小结回顾查找算法:顺序查找、折半查找、索引查找。存储结构:顺序表、链表。优点:算法简单,查找效率高(折半查找)。缺点:查找效率低(顺序查找),需要......
  • 链表多选题(1)链表
    描述在一个链表中,已知p是指向结点c的指针变量,q是指向结点d的指针变量,s是指向结点h的指针变量,若在p和q之间插入结点s,则执行() 输入描述无输出描述如果选择A选项,输出按照下面方法输出:示例#include<iostream>usingnamespacestd;intmain(){cout<<"AB";......
  • 【408DS算法题】016基础-倒序输出单链表的结点值
    Index题目分析实现总结题目给定单链表的头结点,倒序输出单链表的结点值。分析实现要倒序输出链表结点值,首先可以想到的是先将链表的结点值存储到数组中,然后利用数组随机访问的特性进行倒序输出。如果考虑其它思路的话,还可以使用栈来代替数组——将链表元素依次......
  • 【数据结构与算法】分治法
    分治法目录一.分治法的思想二.分治法的步骤三.举个例子四.具体实现五.完整代码一.分治法的思想将一个大问题,拆解成为若干个小问题,而且大问题与小问题的解决方法一样.说到这里我们可以联想到递归,没错就是用递归的思想.分:递归解决较小的问题治:子问题的解构建原......
  • 【数据结构与算法】A*算法——自动寻路
    这里写目录标题一.为什么用A*算法二.A*算法的实现原理三.A*算法的实现1.初始化地图2.格子初始化3.两个列表4.起点到终点的路径5.起点到终点的最佳路径★6.资源的释放四.完整代码1.Astar.h2.Astar.cpp3.main.cpp4.运行结果一.为什么用A*算法上节课我们已经讲了最短......