【数据结构】—C语言实现双向链表(超详细!)

这篇具有很好参考价值的文章主要介绍了【数据结构】—C语言实现双向链表(超详细!)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【数据结构】—C语言实现双向链表(超详细!),数据结构与算法炼体 淬体中,数据结构,c++,c语言,链表,学习,经验分享,开发语言

                                     食用指南:本文在有C基础的情况下食用更佳 

                                    🔥这就不得不推荐此专栏了:C语言

                                    🍀双向链表置知识:单链表 

                                    ♈️今日夜电波:Departures ~あなたにおくるアイの歌~ —EGOIST 

                                                                    3:12 ━━━━━━️💟──────── 4:13                                                                                                                             🔄   ◀️   ⏸   ▶️    ☰

                                   💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍 


一、双向链表介绍

什么是双向链表?

        它是是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两个指针,一个指向前一个节点(pre指针),一个指向后一个节点(next指针)。

双向链表的基本结构?

        一张图让你明白:

        注:此为带哨兵的双向链表

【数据结构】—C语言实现双向链表(超详细!),数据结构与算法炼体 淬体中,数据结构,c++,c语言,链表,学习,经验分享,开发语言

         注:next表示为指向下一个节点的指针,prev表示为指向上一个节点的指针,而head则是作为标兵的存在,里面不存数据,其他data存数据,此双向链表无指向NULL的指针,读者可在下文初始化双向链表得到疑惑的答案。

与单链表相比的优势?

  1. 双向遍历:由于每个节点都存储了前向和后向指针,可以从头到尾或者从尾到头方便地遍历链表。这样的遍历方式在某些场景下非常有用,特别是需要反向操作或者双向查找的情况。

  2. 方便插入和删除:在双向链表中,插入和删除操作相对容易。通过修改前后指针,可以方便地调整节点的连接关系,无需像单链表那样需要在删除节点时找到其前驱节点。

  3. 更灵活的操作:双向链表相比单链表,对于节点的操作更加灵活。例如,在单链表中,如果要删除某个节点,需要先找到它的前驱节点,而在双向链表中,可以直接通过节点本身进行删除操作。

  4. 提高性能:在某些情况下,双向链表可以提高性能。例如,在需要频繁从链表中删除节点或者在给定节点后插入新节点的情况下,双向链表可以更高效地完成这些操作。

        总的来说,双向链表在一些特定场景下相比单链表具有更多的优势和灵活性,双向链表是单链表的优化!


二、总体思路

如何实现?

        参照🍀双向链表置知识单链表 (这是个链接,快点!),我们同样需要实现增、删、查、改。同样的我们需要先定义好节点,以此得到基本的结构->接着定义好接口(定义完后发现比单链表简单多了)->最后按照接口实现每一个功能

        节点的定义(结构体)

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

        接口的定义 

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

三、具体每个接口函数的实现

        1、初始化(重点)

ListNode* ListCreate()
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (!node)
	{
		perror("malloc fail:");
		exit(-1);
	}

	node->_next = node;
	node->_prev = node;

	return node;
}

        可以看到初始化的作用是建立了一个标兵,这个标兵没有被赋值,因为他的值是多少无关紧要,重点在于:他的next指针指向的是他自己,而prev指针指向的也是他自己。这说明了什么?这说明了双向链表的最开始就是在没有任何值的时候就只有一个标兵,也就是说双向链表的每一个指针都不是空的,都是有指向的,他们指向的地址不可能为NULL。


        2、销毁链表

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* tmp = cur->_next;
		free(cur);
		cur = tmp;
	}

	free(pHead);
}

        3、查找功能(返回pos的地址)

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		if (cur->_data == x)
			return cur;
		cur = cur->_next;
	}

	return NULL;

}

        4、插入节点的实现(基于查找功能

ListNode* BuyNode(LTDataType x)//实现节点的获取
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->_data = x;
	node->_next = NULL;
	node->_prev = NULL;

	return node;
}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = BuyNode(x);
	ListNode* dist = pos->_prev;

	dist->_next = newnode;
	newnode->_prev = dist;
	newnode->_next = pos;
	pos->_prev = newnode;
	
}

     特别注意

          注意:在实现了 插入的功能后,其实一切都简单了起来,比如头插等等,只需要几行代码就能完成操作,而下面的删除操作的实现对于头删等等都是仅需要几行代码就能实现,因此,我们如果要快速的写完一个单链表或者其他链表,最好是从插入和删除开始写!o.o


        5、删除节点(基于查找功能)

void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* posPrev = pos->_prev;
	ListNode* posNext = pos->_next;

	posPrev->_next = posNext;
	posNext->_prev = posPrev;
	free(pos);
}

        6、尾插

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListInsert(pHead, x);//此为简化,如要简化代码,此段带码后的代码可都不要

	ListNode* newnode = BuyNode(x);

	pHead->_prev->_next = newnode;
	newnode->_prev=pHead->_prev;
	newnode->_next = pHead;
	pHead->_prev = newnode;
}

        7、尾删

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//ListErase(pHead->_prev);//此为简化,如要简化代码,此段带码后的代码可都不要

	if (pHead->_prev != pHead)
	{
		ListNode* tmp = pHead->_prev;
		pHead->_prev = tmp->_prev;
		tmp->_prev->_next = pHead;
		free(tmp);
		tmp = NULL;
	}
}

        8、头插

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListInsert(pHead->_next, x);//此为简化,如要简化代码,此段带码后的代码可都不要

	ListNode* newnode = BuyNode(x);
	newnode->_next=pHead->_next;
	newnode->_prev = pHead;
	pHead->_next->_prev = newnode;
	pHead->_next = newnode;
}

        9、头删

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//ListErase(pHead->_next);//此为简化,如要简化代码,此段带码后的代码可都不要

	if (pHead->_next != pHead)
	{
		ListNode* node = pHead->_next;
		node->_next->_prev = pHead;
		pHead->_next = node->_next;
		free(node);
		node = NULL;
	}
}

        10、打印

void ListPrint(ListNode* pHead)
{
	ListNode* cur = pHead->_next;
	printf("pHead<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

四、总体代码

1、头文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 01
#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;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

2、主体函数文件

#include"list.h"

ListNode* BuyNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->_data = x;
	node->_next = NULL;
	node->_prev = NULL;

	return node;
}

ListNode* ListCreate()
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (!node)
	{
		perror("malloc fail:");
		exit(-1);
	}

	node->_next = node;
	node->_prev = node;

	return node;
}

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* tmp = cur->_next;
		free(cur);
		cur = tmp;
	}

	free(pHead);
}

void ListPrint(ListNode* pHead)
{
	ListNode* cur = pHead->_next;
	printf("pHead<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListInsert(pHead, x);
	ListNode* newnode = BuyNode(x);

	pHead->_prev->_next = newnode;
	newnode->_prev=pHead->_prev;
	newnode->_next = pHead;
	pHead->_prev = newnode;
}

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//ListErase(pHead->_prev);
	if (pHead->_prev != pHead)
	{
		ListNode* tmp = pHead->_prev;
		pHead->_prev = tmp->_prev;
		tmp->_prev->_next = pHead;
		free(tmp);
		tmp = NULL;
	}
}

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//ListInsert(pHead->_next, x);
	ListNode* newnode = BuyNode(x);
	newnode->_next=pHead->_next;
	newnode->_prev = pHead;
	pHead->_next->_prev = newnode;
	pHead->_next = newnode;
}

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//ListErase(pHead->_next);
	if (pHead->_next != pHead)
	{
		ListNode* node = pHead->_next;
		node->_next->_prev = pHead;
		pHead->_next = node->_next;
		free(node);
		node = NULL;
	}
}

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		if (cur->_data == x)
			return cur;
		cur = cur->_next;
	}

	return NULL;

}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = BuyNode(x);
	ListNode* dist = pos->_prev;

	dist->_next = newnode;
	newnode->_prev = dist;
	newnode->_next = pos;
	pos->_prev = newnode;
	
}

void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* posPrev = pos->_prev;
	ListNode* posNext = pos->_next;

	posPrev->_next = posNext;
	posNext->_prev = posPrev;
	free(pos);
}

3、测试用例

#include"list.h"


void text()
{
	ListNode* phead = ListCreate();
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPrint(phead);

	ListPopBack(phead);
	ListPrint(phead);
	
	ListPushFront(phead, 10);
	ListPushFront(phead, 20);
	ListPushFront(phead, 30);
	ListPrint(phead);

	ListPopFront(phead);
	ListPrint(phead);

	ListNode* pos = ListFind(phead, 20);
	if (pos)
	{
		ListInsert(pos, 300);
	}
	ListPrint(phead);

	ListErase(pos);
	ListPrint(phead);

	ListDestory(phead);
}

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

        测试结果:

【数据结构】—C语言实现双向链表(超详细!),数据结构与算法炼体 淬体中,数据结构,c++,c语言,链表,学习,经验分享,开发语言


                    感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                ​​​​​​​       【数据结构】—C语言实现双向链表(超详细!),数据结构与算法炼体 淬体中,数据结构,c++,c语言,链表,学习,经验分享,开发语言

                                                                         给个三连再走嘛~  文章来源地址https://www.toymoban.com/news/detail-641178.html

到了这里,关于【数据结构】—C语言实现双向链表(超详细!)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构初阶】四、线性表里的链表(带头+双向+循环 链表 -- C语言实现)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表 -- C语言实现)-CSDN博客  ====

    2024年02月08日
    浏览(45)
  • 链接未来:深入理解链表数据结构(二.c语言实现带头双向循环链表)

    上篇文章简述讲解了链表的基本概念并且实现了无头单向不循环链表:链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)-CSDN博客 那今天接着给大家带来带头双向循环链表的实现 : 头文件DoubleList.h:用来基础准备(常量定义,typedef),链表表的基本框架

    2024年01月16日
    浏览(61)
  • C语言数据结构-双向链表

    带头链表的头结点,实际是\\\"哨兵位\\\",哨兵位节点不存储任何有效元素,只是站在这里\\\"放哨的\\\". 哨兵位的意义:遍历循环链表避免死循环. 笔者在删除,插入数据时,画好图后,也好了代码,但是在调试中多次出现 代码位置出错 ,导致写的代码的含义不符合预期. 所以说思路一定要清晰

    2024年02月04日
    浏览(48)
  • 双向链表(数据结构)(C语言)

    目录 概念 带头双向循环链表的实现 前情提示 双向链表的结构体定义 双向链表的初始化 关于无头单向非循环链表无需初始化函数,顺序表、带头双向循环链表需要的思考 双向链表在pos位置之前插入x 双向链表的打印 双链表删除pos位置的结点 双向链表的尾插 关于单链表的尾

    2024年02月04日
    浏览(62)
  • 【数据结构篇】手写双向链表、单向链表(超详细)

    什么是链表 ? 链表(Linked List)是用链式存储结构实现的线性表。链表示意图: 链表的组成 : 数据域 + 引用域 (数据域和引用域合称结点或元素) 数据域存放数据元素自身的数据 引用域存放相邻结点的地址 链表的特点 : 链表中元素的联系依靠引用域 具有线性结构的特

    2024年02月11日
    浏览(41)
  • 数据结构_链表_双向循环链表的初始化、插入、删除、修改、查询打印(基于C语言实现)

    版本: 2024年4月26日 V1.0 发布于博客园 目录 目录 双向循环链表公式 初始化双向循环链表 构建双向循环链表结点 创建一个空链表(仅头结点) 创建一个新结点 插入数据 头插 中插 尾插 删除数据 头删 中删 尾删 查询打印数据 遍历打印 测试 测试结果: 完整代码 DoubleCirLList.h

    2024年04月27日
    浏览(55)
  • Go语言数据结构(一)双向链表

    Go语言中list容器定义在\\\"container/list\\\"包中,实现了一个双向链表。本文第一部分总结源码包中的方法,第二部分展示使用list包的常见示例用法以及刷题时的用法。 食用指南:先看第二部分的常用示例用法然后再用到时在第一部分找对应的方法。 更多内容以及其他Go常用数据结

    2024年01月19日
    浏览(38)
  • 数据结构——双向链表(C语言版)

    上一章: 数据结构——单向链表(C语言版)-CSDN博客 目录 什么是双向链表? 双向链表的节点结构 双向链表的基本操作 完整的双向链表示例 总结 什么是双向链表? 双向链表是一种常见的数据结构,它由一系列节点组成,每个节点包含两个指针:一个指向前一个节点,一个

    2024年03月26日
    浏览(56)
  • 数据结构-双向链表(c++)超全超详细

    单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点的前驱结点(插入,删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。 提示:以下是本篇文章正文内容,下面案

    2023年04月08日
    浏览(45)
  • 数据结构——实现双向链表

    怎么说呢?光乍一听名字好像很难的样子是吧,那如果你这样认为的话,可就要让你大跌眼镜了哦,其实双向带头循环链表从操作和理解上来说都是要易于单项不带头不循环链表(俗称单链表)的。 咱们就来见识见识吧!希望真的能让你们“大跌眼镜”哈! 双向带头循环链

    2024年02月07日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包