数据结构 04(线性:双向链表)

目录

链表的分类

双向链表

概念与结构

实现双向链表

创建头文件

创建哨兵位与申请空间

销毁

打印

尾插

头插

尾删

头删

查找

在任意位置后插入

删除任意位置

双向链表的整体实现

List.h

List.c


链表的分类

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

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表双向带头循环链表

1.无头单向非循环链表:结构简单 ,一般不会单独用来存数据 。实际中更多是作为其他数据结构的子结构 ,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2.带头双向循环链表:结构最复杂 ,一般用在单独存储数据 。实际中使用的链表数据结构,都是带头双向循环链表 。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单 了,功能和单链表差不多但实现更简单更方便

本篇就讲的第二种链表。

双向链表

概念与结构

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

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

实现双向链表

创建头文件

创建一个头文件,在里面创建和声明所需要的头文件、函数和单链表结构。如下:

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

typedef int LTDataType;   //定义数据类型方便改代码

//以下是双向链表的基本结构
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//以下是对所写函数的声明,所有接下来都会写到
LTNode* BuyNode(LTDataType x);
LTNode* LTInit();
void LTDesTroy(LTNode* phead);
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);

创建哨兵位与申请空间

我们知道双向链表是需要一个不存储数据的哨兵位的,创建一个哨兵位也需要申请空间,下面给出空间申请的代码:

cpp 复制代码
LTNode* BuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node; //刚开始哨兵位的前结点和后结点都是自己。
	return node;
}

这里创建哨兵位有个问题,使用一级指针还是二级指针,因为哨兵位不存储数据,我们也不会改变哨兵位的地址,所以使用一级指针。

cpp 复制代码
LTNode* LTInit()
{
	LTNode* phead = BuyNode(-1);
	return phead;
}

销毁

cpp 复制代码
void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;     //哨兵位不存储数据,所以从next结点开始
	while (pcur->next != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

打印

链表的打印都大差不差,需要注意一下循环结束的结点。

cpp 复制代码
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

尾插

这里还需要回顾一下上面那张图

这张图很重要,这样才能理解各种插入操作。

cpp 复制代码
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyNode(x);
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

头插

cpp 复制代码
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyNode(x);
	newnode->prev = phead;
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
}

尾删

这里我使用了一个bool类型,如下:

cpp 复制代码
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return (phead->next == phead);
}

返回bool类型的函数是一种返回布尔值(truefalse)的函数。这里判断了哨兵位结点是否为空以及下一结点是否为自己。

尾删操作:

cpp 复制代码
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

头删

cpp 复制代码
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

查找

cpp 复制代码
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

在任意位置后插入

cpp 复制代码
void LTInsert(LTNode* pos, LTDataType x) //在pos后插入
{
	assert(pos);
	LTNode* newnode = BuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

删除任意位置

cpp 复制代码
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

双向链表的整体实现

List.h

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

typedef int LTDataType;

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

LTNode* BuyNode(LTDataType x);
LTNode* LTInit();
void LTDesTroy(LTNode* phead);
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);

List.c

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

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

LTNode* LTInit()
{
	LTNode* phead = BuyNode(-1);
	return phead;
}

void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur->next != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

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

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

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

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return (phead->next == phead);
}

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

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

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

void LTInsert(LTNode* pos, LTDataType x) //在pos后插入
{
	assert(pos);
	LTNode* newnode = BuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

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

顺序表与链表的分析

相关推荐
爱学大树锯几秒前
353 · 最大字母」
算法
YGGP1 分钟前
【Golang】LeetCode 416. 分割等和子集
算法·leetcode
wjykp6 分钟前
part4 反向传播算法(BP算法)
人工智能·算法·机器学习
AndrewHZ6 分钟前
【图像处理基石】图像处理领域还有哪些核心挑战与难题?
图像处理·人工智能·算法·计算机视觉·噪声·图像增强·画质增强
啊阿狸不会拉杆7 分钟前
《数字图像处理》实验8-图像识别与分类
图像处理·人工智能·算法·分类·数据挖掘·数字图像处理
月明长歌10 分钟前
【码道初阶】Leetcode138:随机链表的复制:用 HashMap 做深拷贝的标准解法
java·数据结构·算法·leetcode·链表·哈希算法
iconball19 分钟前
个人用云计算学习笔记 --23(Shell 编程-2)
linux·运维·笔记·学习·云计算
yyy(十一月限定版)19 分钟前
C语言——堆
c语言·开发语言·算法
盐焗西兰花20 分钟前
鸿蒙学习实战之路-Core Vision Kit人脸检测实现指南
android·学习·harmonyos
喜欢吃燃面20 分钟前
算法竞赛中的数据结构:图
开发语言·数据结构·c++·学习·算法