数据结构——双向链表的实现

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

一、双向链表的结构

数据结构——双向链表的实现,数据结构,数据结构,链表,c语言

注意:双向链表又称带头双向循环链表
这⾥的“带头”跟前⾯我们说的“头节点”是两个概念,实际前⾯的在单链表阶段称呼不严
谨,但是为了同学们更好的理解就直接称为单链表的头节点。
带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨 的”
哨兵位”存在的意义: 遍历循环链表避免死循环。
双向链表每个节点储存一个有效数据+前驱指针+后继指针

二、. 双向链表的实现

2.1 创建&初始化

2.2.1  List.h

#pragma once
typedef struct ListNode
{
	int val;
	struct ListNode* next;
	struct  ListNode* prev;
	

}LTNode; 


//初始化
LTNode* LTInit();

2.2.2  List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
LTNode* LTInit()//哨兵位初始化
{
	LTNode* head = (LTNode*)malloc(sizeof(LTNode));
	head->val = -1;
	head->next = head->prev =head;
	return head;
}

2.2.3 text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	

	return 0;
}

代码运行测试:

数据结构——双向链表的实现,数据结构,数据结构,链表,c语言

2.2尾插&头插

分析:

数据结构——双向链表的实现,数据结构,数据结构,链表,c语言

尾插
1.往d3节点的后面插入数据叫做尾插  

 2.往哨兵位head之前插入数据也叫尾插 

头插

在哨兵位和头节点之间插入

2.2.1  List.h

//尾插
//1.往d3节点的后面插入数据叫做尾插    2.往哨兵位head之前插入数据也叫尾插
void LTPushBack(LTNode* head, int x);

//打印
void LTPrint(LTNode* head);
//头插
void LTPushFront(LTNode* head, int x);

2.2.2  List.c

//创建新节点
LTNode* Listbuynode(int x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	node->val = x;
	node->next = node->prev = NULL;
	return node;
}
void LTPushBack(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head;
	node->prev = head->prev;

	//对原来的尾节点和哨兵位进行操作
	head->prev->next = node;
	head->prev = node;
}
void LTPrint(LTNode* head)
{
	assert(head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}

void LTPushFront(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head->next;
	node->prev = head;

	//对哨兵位和头节点进行操作
	head->next->prev = node;
	head->next = node;
}

2.2.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);
    LTPushBack(head, 3);
    LTPushFront(head,4);//4->1->2->3
	LTPrint(head);
	

	return 0;
}

2.3  头删&尾删

2.3.1  List.h

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

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

2.3.2  List.c

void LTPopBack(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);
	//将尾节点进行保存
	LTNode* del = head->prev;
	//连接次尾节点和哨兵位
	del->prev->next = head;
	head->prev = del->prev;
	free(del);
	del = NULL;

}
void LTPopFront(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);

	//将头节点进行保存
	LTNode* del = head->next;
	//连接哨兵位和次头节点
	head->next = del->next;
	del->next->prev = head;
	free(del);
		del = NULL;
}

2.3.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
	LTPushFront(head, 4);
	LTPrint(head);//4->1->2->3
	LTPopFront(head);
	
	LTPrint(head);//1->2->3
	LTPopBack(head);
	LTPrint(head);1->2
	

	return 0;
}

2.4  查找数据&在pos节点后插入数据&删除pos节点

2.4.1  List.h


//在pos位置之后插入数据
void LTInsert(LTNode* pos, int x);
//删除pos节点
void LTErase(LTNode* pos);

//查找数据
LTNode* LTFind(LTNode* head, int x);

2.4.2  List.c

void LTInsert(LTNode* pos, int x)
{
	assert(pos);
	LTNode* node = Listbuynode(x);
	//先处理新节点
	node->prev = pos;
	node->next = pos->next;
	//在处理前后节点
	pos->next = node;
	node->next->prev = node;
}

LTNode* LTFind(LTNode* head, int x)
{
	assert(head);
	assert(head->next!=head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

2.4.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
    LTPushBack(head, 4);
	
	
	//LTPopBack(head);
	//LTPrint(head);
	//LTPopBack(head);
	LTNode* find = LTFind(head, 4);
	LTInsert(find, 11);
    LTPrint(head);//1->2->3->4->11
	LTErase(find);//1->2->3->11


	LTPrint(head);
	

	return 0;
}

2.5  销毁

若销毁接口用二级指针接受,传哨兵位指针的地址,那么可以改变哨兵位(指针指向),使哨兵位指向NULL;

若销毁接口用一级指针接受,传一级指针(哨兵位指针),传过去的形参(是指针存储的地址),不能够改变指针的指向,在对形参操作,可以释放哨兵位指向的地址空间(形参的值为空间地址),但是不能改变实参指针的指向(实参依然指向原来被释放的地址空间),需要手动将实参置为NULL.

简而言之,若需要改变一级指针指向,需要传二级指针。

前面都是用一级指针传参,为了保持接口的一致性,我们用一级指针传参文章来源地址https://www.toymoban.com/news/detail-737654.html

2.5.1  List.h

//销毁
void LTDestroy(LTNode* phead);

2.5.2  List.c

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = pcur->next;
	while (pcur != phead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(phead);
	phead = NULL;
}

2.5.3  text.c 

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
	LTPushFront(head, 4);
	/*LTPrint(head);
	LTPopFront(head);*/
	
	LTPrint(head);
	//LTPopBack(head);
	//LTPrint(head);
	//LTPopBack(head);
	LTNode* find = LTFind(head, 4);
	/*LTInsert(find, 11);*/
	LTErase(find);


	LTPrint(head);
	LTDestroy(head);
	head = NULL;

	return 0;
}

2.6  完整代码

2.6.1  List.h

#pragma once
typedef struct ListNode
{
	int val;
	struct ListNode* next;
	struct  ListNode* prev;
	

}LTNode; 


//初始化
LTNode* LTInit();
//销毁
void LTDestroy(LTNode* phead);
//尾插
//1.往d3节点的后面插入数据叫做尾插    2.往哨兵位head之前插入数据也叫尾插
void LTPushBack(LTNode* head, int x);

//打印
void LTPrint(LTNode* head);
//头插
void LTPushFront(LTNode* head, int x);

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

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

//在pos位置之后插入数据
void LTInsert(LTNode* pos, int x);
//删除pos节点
void LTErase(LTNode* pos);

//查找数据
LTNode* LTFind(LTNode* head, int x);

2.6.2  List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
LTNode* LTInit()//哨兵位初始化
{
	LTNode* head = (LTNode*)malloc(sizeof(LTNode));
	head->val = -1;
	head->next = head->prev =head;
	return head;
}
//创建新节点
LTNode* Listbuynode(int x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	node->val = x;
	node->next = node->prev = NULL;
	return node;
}
void LTPushBack(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head;
	node->prev = head->prev;

	//对原来的尾节点和哨兵位进行操作
	head->prev->next = node;
	head->prev = node;
}
void LTPrint(LTNode* head)
{
	assert(head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}

void LTPushFront(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head->next;
	node->prev = head;

	//对哨兵位和头节点进行操作
	head->next->prev = node;
	head->next = node;
}
void LTPopBack(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);
	//将尾节点进行保存
	LTNode* del = head->prev;
	//连接次尾节点和哨兵位
	del->prev->next = head;
	head->prev = del->prev;
	free(del);
	del = NULL;

}
void LTPopFront(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);

	//将头节点进行保存
	LTNode* del = head->next;
	//连接哨兵位和次头节点
	head->next = del->next;
	del->next->prev = head;
	free(del);
		del = NULL;
}

void LTInsert(LTNode* pos, int x)
{
	assert(pos);
	LTNode* node = Listbuynode(x);
	//先处理新节点
	node->prev = pos;
	node->next = pos->next;
	//在处理前后节点
	pos->next = node;
	node->next->prev = node;
}

LTNode* LTFind(LTNode* head, int x)
{
	assert(head);
	assert(head->next!=head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = pcur->next;
	while (pcur != phead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(phead);
	phead = NULL;
}

2.6.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
	LTPushFront(head, 4);
	/*LTPrint(head);
	LTPopFront(head);*/
	
	LTPrint(head);
	//LTPopBack(head);
	//LTPrint(head);
	//LTPopBack(head);
	LTNode* find = LTFind(head, 4);
	/*LTInsert(find, 11);*/
	LTErase(find);


	LTPrint(head);
	LTDestroy(head);
	head = NULL;

	return 0;
}

三、顺序表和双向链表的优缺点分析

不同点
顺序表
链表(单链表)
存储空间上
物理上一定连续
逻辑上连续,但物理上不⼀定连续
随机访问
⽀持O(1)
不支持,O(n)
任意位置插⼊或者删除元素
可能需要搬移元素,效率低O(N)
只需修改指针指向
插入
动态顺序表,空间不够时需要扩容
没有容量的概念
应⽤场景
元素⾼效存储+频繁访问
任意位置插⼊和删除频繁

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

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

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

相关文章

  • 【数据结构】—带头双向循环链表的实现(完美链表)

    链表结构一共有八种形式,在前面的文章里已经讲完了不带头单向非循环链表的实现,但是我们发现该链表实现尾插与尾删时比较麻烦,要先从头节点进行遍历,找到尾节点,时间复杂度为O(N),而本次所讲的带头双向循环单链表,则可以直接找到尾节点。 虽然该链表看起来

    2024年01月25日
    浏览(63)
  • 【数据结构】双向链表的增删查改(C 代码实现)

    引入双向链表:关于单链表的问题与讨论 单链表存在的毛病: 因为单链表 只能单向 遍历链表, 对于 前插 这个操作,单链表必 须得找到所需前插节点位置的前一个 ,那么这时就得 从头指针重新遍历一次 链表,会造成时间复杂度大大增加。 没有头节点(哨兵位)无法删除

    2024年02月08日
    浏览(54)
  • 数据结构:链表基础OJ练习+带头双向循环链表的实现

    目录 一.leetcode剑指 Offer II 027. 回文链表 1.问题描述 2.问题分析与求解 (1) 快慢指针法定位链表的中间节点 (2) 将链表后半部分进行反转 附:递归法反转链表 (3) 双指针法判断链表是否回文 二.带头双向循环链表的实现 1.头文件 2.节点内存申请接口和链表初始化接口 3.链表的打

    2024年02月02日
    浏览(51)
  • 【数据结构】链表的分类和双向链表

    本篇是基于上篇单链表所作,推荐与上篇配合阅读,效果更加 http://t.csdnimg.cn/UhXEj 链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构: 我们一般叫这个头为哨兵位 我们上回讲的单链表就是不带头单项不循环链表。 今天我们要讲带头双向循环的链表。 不过

    2024年01月25日
    浏览(38)
  • 双向链表--C语言实现数据结构

    本期带大家一起用C语言实现双向链表🌈🌈🌈 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的;简单来说,线性表的链式存储结构生成的表,称作“链表”。 每个元素本身由两部分组成: 1、本身的信息,称

    2024年02月04日
    浏览(58)
  • 【数据结构 -- C语言】 双向带头循环链表的实现

    目录 1、双向带头循环链表的介绍 2、双向带头循环链表的接口 3、接口实现 3.1 开辟结点 3.2 创建返回链表的头结点 3.3 判断链表是否为空 3.4 打印 3.5 双向链表查找 3.6 双向链表在pos的前面进行插入 3.6.1 头插 3.6.2 尾插 3.6.3 更新头插、尾插写法 3.7 双向链表删除pos位置的节点

    2024年02月09日
    浏览(64)
  • 探索数据结构:双向链表的灵活优势

    ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:数据结构与算法 贝蒂的主页:Betty’s blog 前面我们学习了单链表,它解决了顺序表中插入删除需要挪动大量数据的缺点。但同时也有仍需改进的地方,比如说:我们有时候需要寻找某个节点

    2024年03月16日
    浏览(59)
  • 数据结构---双向链表的基本操作

    头插法 遍历链表 尾插法 头删法 尾删法 按位置插入数据 按位置删除数据 dooublelinklist.c doublelinklist.h doublemain.c

    2024年02月22日
    浏览(53)
  • 【数据结构】—C语言实现双向链表(超详细!)

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

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

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

    2024年02月03日
    浏览(71)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包