数据结构(初阶)笔记归纳6:双向链表的实现

双向链表的实现

目录

双向链表的实现

一、双向链表的概念及结构

1.1.双向链表的概念

1.2.双向链表的结构

1.2.1.结构图

1.2.2.节点的组成

二、双向链表的实现

2.1.双向链表文件结构

2.2.头文件编写(List.h)

2.2.1.头文件包含

2.2.2.节点数据类型重定义

2.2.3.双向链表节点定义

2.2.4.双向链表的初始化

2.2.5.双向链表的尾插

2.2.6.双向链表的头插

2.2.7.双向链表的尾删

2.2.8.双向链表的头删

2.2.9.双向链表的查找

2.2.10.pos位置后插入数据

2.2.11.删除pos节点

2.2.12.双向链表的销毁

2.3.源文件编写(List.c)

2.3.1.头文件包含

2.3.2.节点申请函数

2.3.3.双向链表的初始化

2.3.4.双向链表的打印

2.3.5.双向链表的尾插

2.3.6.双向链表的头插

2.3.7.双向链表的尾删

2.3.8.双向链表的头删

2.3.9.双向链表的查找

2.3.10.pos位置后插入数据

2.3.11.删除pos节点

2.3.12.双向链表的销毁

2.4.测试文件编写(test.c)

2.4.1.头文件包含

2.4.2.测试文件01

三、总结


一、双向链表的概念及结构

1.1.双向链表的概念

带头双向循环链表

1.2.双向链表的结构

1.2.1.结构图
1.2.2.节点的组成

**数据域:**用于存储该节点的数据

**指针域:**用于存储上一个节点的地址(前驱指针)和下一个节点的地址(后置指针)

cpp 复制代码
struct ListNode
{
    int data;//节点数据
    struct ListNode* next;//指向下一个节点的指针变量
    struct ListNode* prev;//指向前一个节点的指针变量
};

二、双向链表的实现

2.1.双向链表文件结构

  • 头文件(List.h):双向链表的结构创建,双向链表的方法声明
  • 源文件(List.c):双向链表的方法实现
  • 测试文件(test.c):测试数据结构的方法

2.2.头文件编写(List.h)

2.2.1.头文件包含
cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
2.2.2.节点数据类型重定义
cpp 复制代码
typedef int LTDataType;
2.2.3.双向链表节点定义
cpp 复制代码
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;
2.2.4.双向链表的初始化

法1:

cpp 复制代码
void LTInit(LTNode** pphead);

法2:

cpp 复制代码
LTNode* LTInit();
2.2.5.双向链表的尾插
cpp 复制代码
void LTPushBack(LTNode* phead,LTDataType x);

**注:**哨兵位不会改变,所以传一级指针即可

2.2.6.双向链表的头插
cpp 复制代码
void LTPushFront(LTNode* phead,LTDataType x);
2.2.7.双向链表的尾删
cpp 复制代码
void LTPopBack(LTNode* phead);
2.2.8.双向链表的头删
cpp 复制代码
void LTPopFront(LTNode* phead);
2.2.9.双向链表的查找
cpp 复制代码
LTNode* LTFind(LTNode* phead, LTDataType x);
2.2.10.pos位置后插入数据
cpp 复制代码
void LTInsert(LTNode* pos, LTDataType x);
2.2.11.删除pos节点
cpp 复制代码
void LTErase(LTNode* pos);
2.2.12.双向链表的销毁
cpp 复制代码
void LTDesTroy(LTNode* phead);

2.3.源文件编写(List.c)

2.3.1.头文件包含
cpp 复制代码
#include "SList.h"
2.3.2.节点申请函数
cpp 复制代码
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}

	node->data = x;
	node->prev = node->next = node;

	return node;
}

**注:**由于双向链表是循环链表,所以新节点的前驱指针和后置指针都先指向自己

2.3.3.双向链表的初始化

法1:

cpp 复制代码
void LTInit(LTNode** pphead)
{
	/*创建哨兵位*/
	*pphead = LTBuyNode(-1);
}

法2:

cpp 复制代码
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}
2.3.4.双向链表的打印
cpp 复制代码
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

**解析:**从头节点的下一个节点开始打印,当节点再次走到头节点时停止打印

2.3.5.双向链表的尾插
cpp 复制代码
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

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

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

解析:

  • 断言判断传参是否为NULL
  • 创建临时变量newnode接收新节点地址
  • 新节点的前驱指针指向上一个节点
  • 新节点的后置指针指向哨兵位
  • 上一个节点的后置指针指向新节点
  • 哨兵位的前驱指针指向新节点
2.3.6.双向链表的头插
cpp 复制代码
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

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

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

解析:

  • 断言判断传参是否为NULL
  • 创建临时变量newnode接收新节点地址
  • 新节点的后置指针指向下一个节点
  • 新节点的前驱指针指向哨兵位
  • 下一个节点的前驱节点指向新节点
  • 哨兵位的后置指针指向新节点
2.3.7.双向链表的尾删
cpp 复制代码
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);

	LTNode* del = phead->prev;
	
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}

解析:

  • 断言判断传参是否为NULL,双向链表是否只有哨兵位
  • 创建临时变量del,存放要删除的尾节点地址
  • 尾节点前一个节点的后置指针指向哨兵位
  • 哨兵位的前驱指针指向尾节点的前一个节点
  • 释放尾节点,将del置为NULL
2.3.8.双向链表的头删
cpp 复制代码
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);

	LTNode* del = phead->next;

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

	free(del);
	del = NULL;
}

解析:

  • 断言判断传参是否为NULL,双向链表是否只有哨兵位
  • 创建临时变量del,存放要删除的哨兵位下一个节点地址
  • 哨兵位下下个节点的前驱指针指向哨兵位
  • 哨兵位的后置指针指向哨兵位下下个节点
  • 释放哨兵位下一个节点,将del置为NULL
2.3.9.双向链表的查找
cpp 复制代码
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
2.3.10.pos位置后插入数据
cpp 复制代码
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);

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

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

解析:

  • 断言判断传参是否为NULL
  • 创建临时变量newnode接收新节点地址
  • 新节点的后置指针指向pos位下一个节点
  • 新节点的前驱指针指向pos位的节点
  • pos位下一个节点的前驱指针指向新节点
  • pos位节点的后置指针指向新节点

2.3.11.删除pos节点
cpp 复制代码
void LTErase(LTNode* pos)
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

解析:

  • 断言判断传参是否为NULL
  • pos位下一个节点的前驱指针指向pos上一个节点
  • pos位上一个节点的后置指针指向pos下一个节点
  • 释放pos位节点,将pos置为NULL
2.3.12.双向链表的销毁
cpp 复制代码
void LTDesTroy(LTNode* phead)
{
	assert(phead);

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

解析:

  • 断言判断传参是否为NULL
  • 创建临时变量pcur存放哨兵位下一个节点
  • 循环释放每一个节点
  • 创建临时变量next存放pcur的下一个节点
  • 释放完pcur节点后pcur走到下一个节点
  • 最后释放哨兵位,将phead置为NULL

2.4.测试文件编写(test.c)

2.4.1.头文件包含
cpp 复制代码
#include "SList.h"
2.4.2.测试文件01
cpp 复制代码
void ListTest01()
{
    /*哨兵位初始化 法1*/
	LTNode* plist = NULL;
	LTInit(&plist);

     /*哨兵位初始化 法2*/
    LTNode* plist = LTInit();

    /*双向链表的尾插*/
    LTPushBack(plist,1);
    LTPushBack(plist,2);
    LTPushBack(plist,3);

    /*双向链表的头插*/
    LTPushFront(plist,1);
    LTPushFront(plist,2);
    LTPushFront(plist,3);

    /*双向链表的尾删*/
    LTPopBack(plist);

    /*双向链表的头删*/
    LTPopFront(plist);

    /*双向链表的查找*/
    LTNode* find = LTFind(plist,3);
    if(find == NULL)
    {
        printf("找不到!\n");
    }
    else
    {
        printf("找到了")
    }

    /*pos位后插入数据*/
    LTInsert(find,66);

    /*删除pos节点*/
    LTErase(find);

    /*双向链表的打印*/
    LTPrint(plist);

    /*双向链表的销毁*/
    LTDesTroy(plist);
    
    /*手动置空*/
    plist = NULL;
}

int main()
{
	ListTest01();
	return 0;
}

三、总结

本篇博客是对于数据结构中双向链表实现的整理归纳,后续还会更新链表OJ试题等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~

相关推荐
学计算机的计算基3 分钟前
回溯算法下篇:四道经典题讲透约束剪枝、原地标记、预计算与状态压缩
java·笔记·算法
2501_909509108 分钟前
DAY 28
开发语言·python
user-猴子14 分钟前
从零构建 2048 游戏,解析“Python-Use”范式的完整闭环
开发语言·python·游戏
一只小菜鸡..14 分钟前
Stanford CS144 学习笔记 (九):大师访谈与互联网的未来
笔记·学习
霸道流氓气质30 分钟前
SpringBoot中实现告警判定算法-位报警与模拟量阈值-技术详解
spring boot·算法·jquery
qq_4017004132 分钟前
Qt容器性能优化:QVector、QHash、QMap到底应该怎么选?
开发语言·qt·性能优化
十月的皮皮32 分钟前
STM32从零到量产开发:四路继电器工业控制模块开发实战笔记(小白视角)
c语言·笔记·stm32·stm32cubemx·hal库
行者全栈架构师32 分钟前
混元 Hy3 Agent 实战:季度报告 3 小时变 40 分钟
算法·架构·代码规范
星栈独行36 分钟前
Node 接口该写同步还是异步?
服务器·开发语言·后端·程序人生·node.js
疯狂打码的少年37 分钟前
【软件工程】软件项目管理(人员/产品/过程/项目)
笔记·软件工程