数据结构笔记第3篇:双向链表

1、双向链表的结构

**注意:**这里的 "带头" 跟前面我们说的 "头结点" 是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头结点。

带头链表里的头结点,实际为 "哨兵位" ,哨兵位节点不存储任何有效元素,只是站在这里 "放哨的"。

"哨兵位" 存在的意义:

遍历循环链表避免死循环。

**注意:**双向链表中,哨兵位的下一个节点是链表的第一个节点(头结点)。哨兵位的前一个节点是链表的最后一个节点(尾结点)。所以双向链表的头插是在哨兵位的后面插入数据。尾插则是在哨兵位之前插入数据。

哨兵位是作为头结点和尾结点的中点,是头结点的起点也是尾节点的终点。这样解释更容易理解。

2、 双向链表的实现

List.h 链表函数链表节点类型的声明:

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLDataType;
typedef struct ListNode
{
	SLDataType data;//存储数据
	struct ListNode* prve;//存储前一个节点的地址
	struct ListNode* next;//存储下一个节点的地址
}SList;
SList* SLInit();

void SLPushBack(SList* phead, SLDataType x);//尾插
void SLPrint(SList* phead);//显示链表数据
void SLPustFront(SList* phead, SLDataType x);//头插
void SLPopBack(SList* phead);//尾删
void SLPopFront(SList* phead);//头删
void SLInsert(SList* pos, SLDataType x);//指定位置插入
SList* SLfind(SList* phead, SLDataType x);//查找节点
void SLEarse(SList* pos);//指定位置删除
void SLDestory(SList** pphead);//链表销毁

List.c链表函数的实现:

cpp 复制代码
#include "List.h"
//链表初始化
SList* SLInit()
{
	SList* phead = (SList*)malloc(sizeof(SList));
	if (phead == NULL)
	{
		perror("malloc error");
		return NULL;
	}
	phead->data = -1;
	//因为是循环链表,所以初始化要遵循循环格式
	phead->next = phead;
	phead->prve = phead;
	return phead;
}
//创建链表节点
SList* ListBuyNode(SLDataType x)
{
	SList* retNode = (SList*)malloc(sizeof(SList));
	if (retNode == NULL)
	{
		perror("malloc error");
		return NULL;
	}
	retNode->data = x;
	retNode->prve = retNode;
	retNode->next = retNode;
	return retNode;
}
//链表尾插
void SLPushBack(SList* phead, SLDataType x)
{
	assert(phead);
	SList* Node = ListBuyNode(x);
	Node->prve = phead->prve;
	Node->next = phead;
	phead->prve->next = Node;
	phead->prve = Node;
}
//链表数据显示
void SLPrint(SList* phead)
{
	assert(phead);
	SList* pcur = phead->next;
	while (pcur != phead)//哨兵位作为结束标识
	{
		printf("%d", pcur->data);
		if (pcur->next != phead)
			printf("->");
		pcur = pcur->next;
	}
	printf("\n");
}
//链表头插
void SLPustFront(SList* phead, SLDataType x)
{
	assert(phead);
	SList* Node = ListBuyNode(x);
	Node->next = phead->next;
	Node->prve = phead;
	phead->next->prve = Node;
	phead->next = Node;
}
//链表尾删
void SLPopBack(SList* phead)
{
	assert(phead);
	assert(phead->next != phead);
	SList* del = phead->prve;
	del->prve->next = phead;
	phead->prve = del->prve;
	free(del);
	del = NULL;
}
//链表头删
void SLPopFront(SList* phead)
{
	assert(phead);
	assert(phead->next != phead);
	SList* del = phead->next;
	del->next->prve = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}
//指定位置插入
void SLInsert(SList* pos, SLDataType x)
{
	assert(pos);
	SList* Node = ListBuyNode(x);
	Node->next = pos->next;
	Node->prve = pos;
	pos->next = Node;
	Node->next->prve = Node;
}
//查找节点
SList* SLfind(SList* phead, SLDataType x)
{
	assert(phead);
	SList* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//指定位置删除
void SLEarse(SList* pos)
{
	assert(pos);
	pos->prve->next = pos->next;
	pos->next->prve = pos->prve;
	free(pos);
	pos = NULL;
}
//链表销毁
void SLDestory(SList** pphead)
{
	assert(pphead && *pphead);
	SList* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		SList* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	*pphead = NULL;
}

test.c函数的调用:

cpp 复制代码
#include "List.h"

void SLtest()
{
	SList* plist = NULL;
	plist = SLInit();
	//尾插
	SLPushBack(plist, 1);
	SLPushBack(plist, 2);
	SLPushBack(plist, 3);
	SLPushBack(plist, 4);
	SLPrint(plist);//打印:1->2->3->4
	//头插
	SLPustFront(plist, 5);
	SLPustFront(plist, 6);
	SLPustFront(plist, 7);
	SLPrint(plist);//打印:7->6->5->1->2->3->4
	//尾删
	SLPopBack(plist);
	SLPrint(plist);//打印:7->6->5->1->2->3
	//头删
	SLPopFront(plist);
	SLPrint(plist);//打印:6->5->1->2->3
	//指定位置插入
	SList* find = SLfind(plist, 5);
	SLInsert(find, 11);
	SLPrint(plist);//打印:6->5->11->1->2->3
	//指定位置删除
	find = SLfind(plist, 1);
	SLEarse(find);
	SLPrint(plist);//打印:6->5->11->2->3
	//链表销毁
	SLDestory(&plist);
}
int main()
{
	SLtest();
	return 0;
}

3、顺序表和双向链表的优缺点分析

|------------------|----------------------|---------------------|
| 不同点 | 顺序表 | 链表 |
| 存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
| 随机访问 | 支持O(1) | 不支持O(N) |
| 任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需要修改指针指向 |
| 插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
| 应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |

数据结构第3篇笔记到这里也就结束了,我们下一篇笔记再见-

相关推荐
肥猪猪爸5 分钟前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
linux_carlos5 分钟前
环形缓冲区
数据结构
readmancynn17 分钟前
二分基本实现
数据结构·算法
yuwinter20 分钟前
鸿蒙HarmonyOS学习笔记(2)
笔记·学习·harmonyos
Bucai_不才21 分钟前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
盼海27 分钟前
排序算法(四)--快速排序
数据结构·算法·排序算法
一直学习永不止步42 分钟前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表
游走于计算机中摆烂的1 小时前
启动前后端分离项目笔记
java·vue.js·笔记
珹洺2 小时前
C语言数据结构——详细讲解 双链表
c语言·开发语言·网络·数据结构·c++·算法·leetcode
_whitepure2 小时前
常用数据结构详解
java·链表····队列·稀疏数组