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

相关推荐
坚持就完事了6 小时前
Java中的集合
java·开发语言
魔芋红茶6 小时前
Python 项目版本控制
开发语言·python
云小逸6 小时前
【nmap源码解析】Nmap OS识别核心模块深度解析:osscan2.cc源码剖析(1)
开发语言·网络·学习·nmap
冰暮流星6 小时前
javascript之二重循环练习
开发语言·javascript·数据库
风指引着方向6 小时前
自定义算子开发入门:基于 CANN op-plugin 的扩展实践
开发语言
dazzle6 小时前
机器学习算法原理与实践-入门(三):使用数学方法实现KNN
人工智能·算法·机器学习
那个村的李富贵6 小时前
智能炼金术:CANN加速的新材料AI设计系统
人工智能·算法·aigc·cann
Fairy要carry6 小时前
面试-GRPO强化学习
开发语言·人工智能
风指引着方向6 小时前
图编译优化全链路:CANN graph-engine 仓库技术拆解
c语言
Liekkas Kono7 小时前
RapidOCR Python 贡献指南
开发语言·python·rapidocr