数据结构(初阶)笔记归纳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试题等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~

相关推荐
Xin_ye100861 天前
C# 零基础到精通教程 - 第七章:面向对象编程(入门)——类与对象
开发语言·c#
憧憬成为java架构高手的小白1 天前
docker学习笔记(基于b站多个视频学习)【未完结】
笔记·学习
Mr. zhihao1 天前
深入解析redis基本数据结构
数据结构·数据库·redis
念何架构之路1 天前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星1 天前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
审判长烧鸡1 天前
【Go工具】go-playground是什么组织?官方的?
开发语言·安全·go
失去的青春---夕阳下的奔跑1 天前
560. 和为 K 的子数组
数据结构·算法·leetcode
kkeeper~1 天前
0基础C语言积跬步之字符函数与字符串函数(上)
c语言·开发语言
黎阳之光1 天前
黎阳之光:以视频孪生重构智慧医院信息化,打造高标项目核心竞争力
大数据·人工智能·物联网·算法·数字孪生
RainCity1 天前
Java Swing 自定义组件库分享(七)
java·笔记·后端