数据结构---双向链表

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


前言

1.概念

单向链表:一块内存指向下一个内存。
单链表存在一些缺陷:
1.查找速度慢。
2.不能从后往前找。
3.找不到前驱。
链表的结构分为8种:
1.单向和双向
2.带头和不带头
带头的链表有一个带哨兵位的头结点,这个节点不存储有效数据。
好处:尾插更方便,不需要二级指针了,带头结点不需要改变传过来的指针,,也就意味着不需要传二级指针
3.循环和不循环
不循环:尾是指向空的
循环:尾是指针头的
一、无头单向肺循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的领接表等等。
另外这种结构在笔试面试中出现很多。
二、带头双向循环链表:结构复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表,另外这个结构虽然结构复杂,
但是使用代码实现以后会发现带来很多优势,实现反而简单了。

2.初始化

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;

next表示指向下一个节点
prev表示前驱指针,指向上一个节点


一、双向链表的应用

1.打印链表和创建新的节点

//创建新的节点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

//打印链表
void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur!= phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2.链表的初始化和销毁

//初始化
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

3.链表的首插和尾插

//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
//上面的尾插针对空链表也是成立的,可以画个图理解一下
//双向带头循环链表结构虽然复杂了,但是操作反而简单了。
//结构设计的优势
//内存占用增加,每个节点多了一个前驱指针
//STL中的List原码就是这个结构

//头插
void ListPushfront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

4.链表的尾删和头删

//尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* last = tail->prev;
	free(tail);
	last->next = phead;
	phead->prev = last;
	tail = NULL;
}

//头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
	first = NULL;
}

5.链表的查找和指定位置的插入删除

//查找
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* phead, ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* last = pos->prev;
	ListNode* newnode = BuyListNode(x);
	last->next = newnode;
	newnode->prev = last;
	newnode->next = pos;
	pos->prev = newnode;
}

//删除pos位置的数
void ListErase(ListNode* plist, ListNode* pos)
{
	assert(pos);
	ListNode* last = pos->prev;
	ListNode* following = pos->next;
	free(pos);
	last->next = following;
	following->prev = last;
	pos = NULL;
}

二、封装文件

注:以上函数封装在List.c中

1. List.h文件

#pragma once
//双向链表定义
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;


ListNode* ListInit();
void ListPrint(ListNode* plist);
void ListDestory(ListNode* plist);
void ListPushBack(ListNode* plist, LTDataType x);
void ListPushfront(ListNode* plist, LTDataType x);
void ListPopBack(ListNode* plist);
void ListPopFront(ListNode* plist);
ListNode* ListFind(ListNode* plist, LTDataType x);
//在pos位置之前插入一个值
void ListInsert(ListNode* plist, ListNode* pos, LTDataType x);
//删除pos位置的值
void ListErase(ListNode* plist, ListNode* pos);

2.测试函数

#include "List.h"

void TestList1()
{
	ListNode* plist = ListInit();

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);

	ListPrint(plist);
	ListDestory(plist);
}
void TestList2()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);

	ListPrint(plist);
	ListDestory(plist);
}
void TestList3()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);

	ListPrint(plist);
	printf("\n");
	ListPopBack(plist);
	ListPrint(plist);
	ListDestory(plist);
}

void TestList4()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);

	ListPrint(plist);
	printf("\n");
	ListPopFront(plist);
	ListPrint(plist);
	ListDestory(plist);
}

void TestList5()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);
	ListPrint(plist);
	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		//查找同时可以附带修改
		printf("找到了");
		pos->data = pos->data * 10;
	}
	else
	{
		printf("没有找到");
	}
}
void TestList6()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);
	ListPrint(plist);
	printf("\n");
	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		ListInsert(plist, pos, 100);
		ListErase(plist, pos->prev);
	}
	ListPrint(plist);
}
int main()
{
	TestList6();
	return 0;
}

用以上函数进行测试


总结

虽然双向链表结构付咱,但是理解其结构可发现各个接口比单向链表的接口写起来更加简单,更加容易上手文章来源地址https://www.toymoban.com/news/detail-783662.html

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

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

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

相关文章

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

                                          食用指南:本文在有C基础的情况下食用更佳                                       🔥 这就不得不推荐此专栏了:C语言                                     🍀 双向链表 前 置知识 :单链表      

    2024年02月13日
    浏览(19)
  • 【数据结构】C语言实现双向链表(带头结点、循环)

    结点定义: 接口定义: 我们将申请结点的代码封装成函数,方便后续使用 由于是带头结点的双向链表,因此在使用链表前,我们需要对链表进行初始化。 遍历链表,值得说的是,带头结点的双向链表的循环结束条件是 cur != phead 尾插时,需要先找到尾结点,然后将新结点插

    2024年02月03日
    浏览(19)
  • 【数据结构初阶】四、线性表里的链表(带头+双向+循环 链表 -- C语言实现)

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

    2024年02月08日
    浏览(20)
  • 『初阶数据结构 • C语言』⑨ - 基于结点的数据结构——链表(单链表&&双向循环链表)附完整源码

      本章内容 1.什么是链表 2.链表常见几种形式 3.无头单向非循环链表的实现 3.1结点结构的定义 3.2函数接口的实现 3.2.1尾插 3.2.2尾删 4. 带头双向循环链表的实现 4.1结点结构的定义 4.2函数接口的实现 5.两种链表的差异 ①尾插与尾删的时间复杂度 ②头插与头删的时间复杂度 ③

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

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

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

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

    2024年04月27日
    浏览(11)
  • 数据结构-链表结构-双向链表

    双向链表也叫双链表,与单向链表不同的是,每一个节点有三个区域组成:两个指针域,一个数据域 前一个指针域:存储前驱节点的内存地址 后一个指针域:存储后继节点的内存地址 数据域:存储节点数据 以下就是双向链表的最基本单位 节点的前指针域指向前驱,后指针

    2024年02月04日
    浏览(18)
  • 【数据结构】双向奔赴的爱恋 --- 双向链表

    关注小庄 顿顿解馋๑ᵒᯅᵒ๑ 引言:上回我们讲解了单链表(单向不循环不带头链表),我们可以发现他是存在一定缺陷的,比如尾删的时候需要遍历一遍链表,这会大大降低我们的性能,再比如对于链表中的一个结点我们是无法直接访问它的上一个结点,那有什么解决方法呢

    2024年04月08日
    浏览(47)
  • 数据结构—双向链表

    目录 1.  链表的种类 2.  最实用的两种链表类型 3.  实现双向带头循环链表                   3.1 创建头节点         3.2 实现双向循环功能—返回头指针         3.3  尾插           3.4 头插         3.5 尾删         3.6 头删 4.  实现两个重要接口函数  

    2023年04月23日
    浏览(25)
  • 数据结构双向链表

    Hello,好久不见,今天我们讲链表的双向链表,这是一个很厉害的链表,带头双向且循环,学了这个链表,你会发现顺序表的头插头删不再是一个麻烦问题,单链表的尾插尾删也变得简单起来了,那废话不多说,让我们开始我们的学习吧! 首先我们要了解它的物理和逻辑结构

    2024年02月11日
    浏览(15)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包