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

相关推荐
两点王爷10 小时前
Java基础面试题——【Java语言特性】
java·开发语言
大江东去浪淘尽千古风流人物11 小时前
【VLN】VLN(Vision-and-Language Navigation视觉语言导航)算法本质,范式难点及解决方向(1)
人工智能·python·算法
Swift社区11 小时前
Gunicorn 与 Uvicorn 部署 Python 后端详解
开发语言·python·gunicorn
菩提小狗11 小时前
小迪安全2023-2024|第5天:基础入门-反弹SHELL&不回显带外&正反向连接&防火墙出入站&文件下载_笔记|web安全|渗透测试|
笔记·安全·web安全
码农阿豪11 小时前
Python Flask应用中文件处理与异常处理的实践指南
开发语言·python·flask
岁岁种桃花儿11 小时前
CentOS7 彻底卸载所有JDK/JRE + 重新安装JDK8(实操完整版,解决kafka/jps报错)
java·开发语言·kafka
csbysj202011 小时前
AngularJS 模块
开发语言
Wentao Sun11 小时前
致敬软件创业者2026
笔记·程序人生
独好紫罗兰11 小时前
对python的再认识-基于数据结构进行-a003-列表-排序
开发语言·数据结构·python
wuhen_n11 小时前
JavaScript内置数据结构
开发语言·前端·javascript·数据结构