双向链表--C语言实现数据结构

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

本期带大家一起用C语言实现双向链表🌈🌈🌈

双向链表--C语言实现数据结构

一、链表的概念🌎

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的;简单来说,线性表的链式存储结构生成的表,称作“链表”。

二、链表中数据元素的构成🌎 🌍

每个元素本身由两部分组成:

1、本身的信息,称为“数据域”

2、指向直接后继的指针,称为“指针域”

三、链表的结构🌎 🌍 🌏

1、带哨兵位或者不带哨兵位 🦴

双向链表--C语言实现数据结构

2、循环或者不循环 🦴 🦴

双向链表--C语言实现数据结构

3、单向或者双向 🦴 🦴 🦴
双向链表--C语言实现数据结构

四、 双向带哨兵位循环链表的实现🌎 🌍 🌏 🌎

双向链表--C语言实现数据结构

这里先建立三个文件:
1️⃣ :SeqList.h文件,用于函数声明
2️⃣ :SeqList.c文件,用于函数的定义
3️⃣ :Test.c文件,用于测试函数
建立三个文件的目的: 将顺序表作为一个项目来进行书写,方便我们的学习与观察。

一、定义双向链表的结构体✅

双向链表的结构为
存储的有效数据data
指向后一个数据地址的指针next
指向前一个数据指针的数据prev


typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;

	struct ListNode* next;

	struct ListNode* prev;
}LTNode;

这里我们使用typedef对我们所存储的数据,以及双向链表结构体重命名,方便我们后续修改 🍊 🍋 🍒

二、接口的实现✅✅

1.双向链表的创建以及初始化
LTNode* CreatNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = CreatNode(-1);

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

双向链表--C语言实现数据结构

创建哨兵位节点

2、尾插

对链表进行尾插操作,添加数据到链表的尾部🌱 🌿 ☘️

void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);

	LTNode* newnode = CreatNode(x);

	LTNode* tail = phead->prev;

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

}

首先我们需要创建一个新的节点,然后将我们的双向链表和我们创建的新节点进行链接起来🌱 🌿
双向链表--C语言实现数据结构

3、头插

对链表进行头插操作,添加数据到链表的头部
但注意的是:不是添加到哨兵位的前面🚀 🛸
而是添加到哨兵位下一个节点的前面

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* node = CreatNode(x);
	node->next = phead->next;
	phead->next->prev = node;
	phead->next = node;
	node->prev = phead;
}

双向链表--C语言实现数据结构

4、检查链表当中是否只有哨兵位
bool LTEmpty(LTNode* phead)
{
	return phead == phead->next;
}
5、尾删

对于 双向链表 的尾删,只要找到尾结点的前一个节点改变它和哨兵位的连接关系即可🚀 🛸
如果要找到尾结点的前一个节点,那么我只需要通过
哨兵位 的 prev 找到 尾,在通过 尾 的 prev 就可以找到 尾结点的前一个节点。然后调整这个节点和哨兵位的链接关系,然后 释放尾结点 就可以了

但是要注意,当链表只有哨兵位的时候不能进行删除操作!!!✈️

如图所示

双向链表--C语言实现数据结构

void LTPopBack(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	
	LTNode* tail = phead->prev;

	LTNode* tailPrev = tail->prev;
	free(tail);

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

上面说过,如果当链表只有哨兵位的时候不能进行删除操作!!!
所以我们通过了一个函数来检查当前链表是否只有一个哨兵位✈️ ✈️

bool LTEmpty(LTNode* phead)
{
	return phead == phead->next;
}
6、头删

对于头删来说,我需要删除 哨兵位的 next 节点 ,我们
需要连接哨兵位和哨兵位next的next,然后释放哨兵位的next🌟

但是同样需要注意的是:链表当中只有哨兵位的时候我们不能对其删除!!!

如图所示:🌟
双向链表--C语言实现数据结构

void LTPopFront(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}

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

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

7、查找链表当中元素

当我们需要查找链表当中是否存在某个元素的时候,我们需要链表,看是否存在该个元素🌟🌟

但是带头双向循环链表当中不存在NULL

所以停止遍历的时候我们的终止条件为!=·phead

如果找到,返回该节点的地址;如果找不到返回 NULL

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
8、在pos位置之前插入数据

在 pos 位置之前插入,那么 通过 pos 的 prev 找到 pos 位置的上一个节点 posPrev ,然后改变 posPrev 和 新节点 newnode 之间的链接和 newnode 和 pos 之间的链接

需要同LTFind一起使用

void LTInsert(LTNode* pos, LTDataType x)
{

	LTNode* node = CreatNode(x);

	LTNode* posPrev = pos->prev;
	posPrev->next = node;
	node->next = pos;
	node->prev = posPrev;
	pos->prev = node;
}

代码实现逻辑图如下所示
双向链表--C语言实现数据结构
那么很好,有了这个接口之后,可以把它 复用 于 尾插 和 头插。

对于 尾插 来说, pos 位置就是 phead ,因为 phead 的前面就是

链表的尾,在 phead 位置前插入,就是尾插:

void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);
	LTInsert(phead, x);
}

对于 头插 来说, pos 位置就是 phead->next

为第一个节点的前面,在 phead->next 位置前插入,就是头插:


void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTInsert(phead->next, x);
}
9、删除pos位置的数据

在 pos 位置删除, 只要找到 pos 的前一个节点 posPrev

然后找到 pos 的后一个节点 posNext ,然后将这两个节点的 prev 和 next 建立正确的链接关系。然后释放 pos 节点,pos 节点置空。🌟🌟🌟

void LTErase(LTNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

同样的,这个接口也能复用于 尾删 和 头删 。

对于 尾删 来说,, pos 位置就是 phead->prev

为链表的尾,删除 phead->prev 位置,就是尾删:

为了避免空指针异常,我们需要进行判断

void LTPopBack(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->prev);
}

对于 头删 来说, pos 位置就是 phead->next

为链表的头,删除 phead->next 位置,就是头删:

为了避免空指针异常,我们需要进行判断

void LTPopFront(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->next);
}

10、打印双向链表

打印整个链表,就只需要遍历链表,控制好循环的停止条件:
循环终止条件设置为!=phead

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("哨兵位->");
	while (cur != phead)
	{
		printf("%d ", cur->data);

		cur = cur->next;
	}
	printf("\n");
}
11、销毁双向链表

我需要把 哨兵位 ,链表的节点 全部删除,那么我就要使用循环来删除。

循环的结束条件为 != phead。在销毁的过程中,每次记住我当前节点的下一个节点,以便迭代🍻🍻

但是由于我们的 形参是一级指针

所以不能够将哨兵位正常销毁,我们需要在主函数当中手动将哨兵位置为NULL

void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		LTNode* next = cur->next;

		free(cur);
		cur = next;
	}
	free(cur);
}
12、双向链表的测试

双向链表--C语言实现数据结构

双向链表--C语言实现数据结构

五、源代码💡💡💡

1、LIst.h
#pragma once


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<assert.h>

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;

	struct ListNode* next;

	struct ListNode* prev;
}LTNode;


//初始化
LTNode* LTInit();

//打印链表
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead,LTDataType x);

//头插
void LTPushFront(LTNode* phead, LTDataType x);

//尾删
void LTPopBack(LTNode* phead);

//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead,LTDataType x);
//pos之前插入
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置的值
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);

2、List.c
#define _CRT_SECURE_NO_WARNINGS


#include"List.h"


LTNode* CreatNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = CreatNode(-1);

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

bool LTEmpty(LTNode* phead)
{
	return phead == phead->next;
}
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("哨兵位->");
	while (cur != phead)
	{
		printf("%d ", cur->data);

		cur = cur->next;
	}
	printf("\n");
}



void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);

	//LTNode* newnode = CreatNode(x);

	//LTNode* tail = phead->prev;

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

	LTInsert(phead, x);
}


void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//LTNode* node = CreatNode(x);
	//node->next = phead->next;
	//phead->next->prev = node;
	//phead->next = node;
	//node->prev = phead;


	LTInsert(phead->next, x);
}


void LTPopBack(LTNode* phead)
{
	assert(phead);
	/*if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	
	LTNode* tail = phead->prev;

	LTNode* tailPrev = tail->prev;
	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;*/

	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->prev);
}


void LTPopFront(LTNode* phead)
{
	assert(phead);
	/*if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}

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

	phead->next = next;
	phead->next->prev = phead;*/

	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->next);
}


LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

void LTInsert(LTNode* pos, LTDataType x)
{

	LTNode* node = CreatNode(x);

	LTNode* posPrev = pos->prev;
	posPrev->next = node;
	node->next = pos;
	node->prev = posPrev;
	pos->prev = node;
}

void LTErase(LTNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		LTNode* next = cur->next;

		free(cur);
		cur = next;
	}
	free(cur);
}
3、test.c
#define _CRT_SECURE_NO_WARNINGS

#include"List.h"

void test1()
{
	printf("尾插:");
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");
}


void test2()
{
	printf("头插:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTPrint(plist);
	LTDestroy(plist);
	plist = NULL;
	printf("\n");
}

void test3()
{
	printf("尾删:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	 LTPopBack(plist);
	 LTPopBack(plist);
	 LTPopBack(plist);
	 LTPopBack(plist);

	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");
}

void test4()
{
	printf("头删:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);

	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	

	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");

}

void test5()
{
	printf("pos之前插入");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTNode* pos = LTFind(plist,5);
	
	if (pos != NULL)
	{
		LTInsert(pos, 9);

	}

	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");

}

void test6()
{
	printf("删除pos的值:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTNode* pos = LTFind(plist, 3);
	if (pos != NULL)
	{
		LTErase(pos);
	}
	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");

}
int main()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	return 0;
}

六、感谢与交流✅

🌹🌹🌹如果大家通过本篇博客收获了,对顺序表有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️💯💯💯

双向链表--C语言实现数据结构文章来源地址https://www.toymoban.com/news/detail-445102.html

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

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

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

相关文章

  • C语言数据结构-双向链表

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

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

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

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

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

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

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

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

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

    2024年03月26日
    浏览(47)
  • 数据结构——实现双向链表

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

    2024年02月07日
    浏览(48)
  • 数据结构:详解【链表】的实现(单向链表+双向链表)

    1.顺序表的问题和思考 问题: 中间/头部的插入删除,时间复杂度为O(N)。 增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据

    2024年03月26日
    浏览(55)
  • 【数据结构】双向链表的实现

    我要扼住命运的咽喉,他却不能使我完全屈服。                      --贝多芬 目录 一.带头循环的双向链表的特点 二.不带头不循环单向链表和带头循环的双向链表的对比 三.初始化链表,创建哨兵结点 四.双向链表的各种功能的实现 1.双向链表的尾插 2.双向链表的打印 

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

    注意: 双向链表又称带头双向循环链表 这⾥的“带头”跟前⾯我们说的“头节点”是两个概念,实际前⾯的在单链表阶段称呼不严 谨,但是为了同学们更好的理解就直接称为单链表的头节点。 带头链表⾥的头节点,实际为“ 哨兵位 ”,哨兵位节点不存储任何有效元素,只

    2024年02月06日
    浏览(40)
  • 【(数据结构)— 双向链表的实现】

    注意: 这里的 “带头” 跟前面我们说的 “头节点” 是两个概念,实际前面的在单链表阶段称呼不严 谨,但是为了同学们更好的理解就直接称为单链表的头节点。 带头链表里的头节点,实际为 “哨兵位” ,哨兵位节点不存储任何有效元素,只是站在这里“放哨 的” “哨

    2024年02月06日
    浏览(38)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包