数据结构:双向链表的实现(C实现)

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

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》


前言

本篇博客,将要实现的是带头双向循环链表,该结构实现尾插,尾删只需要O(1)的时间复杂度。
其结构如下所示:

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言


一、实现思路

1.节点的结构(ListNode)

既然要实现的链表是双向循环的,那么对于指针域,我们就需要指向节点上一个的指针指向节点下一个的指针。至于双向循环,我们只需要尾节点的next指向头结点,头结点的prev指向尾节点,即可实现双向循环。
如下:
数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

typedef int LTDataType;//方便以后修改数据类型

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

2.新节点的创建(BuyListNode)

动态开辟一块空间,使该节点的prev,next都指向自己(方便头结构的创建),再为data赋值,返回该空间地址。

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

//创建新节点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}

3.头结点的创建(ListCreate)

复用BuyListNode函数,使头结点的data为无效数据(这里即为-1)。

//创建返回链表的头结点
ListNode* ListCreate()
{
	return BuyListNode(-1);
}

4.双向链表的销毁(ListDestroy)

要销毁链表,我们就要遍历链表,那么如何遍历链表?

  • 以遍历到头节点为结束条件
  • 从头结点的下一个节点开始遍历

如上,我们就可以循环遍历链表。
销毁节点,我们需要两指针变量,一个记录要free的节点(cur),一个记录要free的节点的下一个节点(curNext)。每次free(cur),使cur = curNext。
数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

//双向链表的销毁
void ListDestroy(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* curNext = cur->next;
		
		free(cur);
		cur = curNext;
	}
	free(pHead);
}

5.双向链表的打印(ListPrint)

我们只有遍历打印链表即可。

  • 以遍历到头节点为结束条件
  • 从头结点的下一个节点开始遍历
//双向链表的打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	printf("head<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

6.双向链表的尾插(ListPushBack)

我们只需要让尾节点(tail)的next指向newnode,newnode的prev指向尾节点(tail),newnode的next指向头结点,头结点的prev指向newnode.
我们知道该链表是双向循环的,那么头结点的上一个节点就是尾节点。(与单链表相比该链表实现尾插并不需要遍历找尾,时间复杂度是O(1) )。
数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* tail = pHead->prev;

	tail->next = newnode;
	newnode->prev = tail;
	pHead->prev = newnode;
	newnode->next = pHead;
}

7.双向链表的尾删(ListPopBack)

我们只需要两个指针tail (指向尾节点),tailPrev (指向尾节点的上一个节点)。
free掉tail,使tailPrev的next指向头结点,头结点的prev指向tailPrev。

  • 注意:head->next == head 时,链表无有效数据,不能尾删数据。

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

//双向链表的尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//pHead->next == pHead时,链表没有元素
	assert(pHead->next != pHead);

	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	pHead->prev = tailPrev;
	tailPrev->next = pHead;
	free(tail);
}

8.双向链表的头插(ListPushFront)

我们只需要一个指针 first (头结点的下一个节点),使first的prev指向newnode,newnode的next指向first,head的next指向newnode,newnode的prev指向head。

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言
head节点是哨兵位,不存储有效数据。

//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;

	newnode->next = first;
	first->prev = newnode;
	newnode->prev = pHead;
	pHead->next = newnode;
}

9.双向链表的头删(ListPopFront)

我们需要两个指针frist (指向头结点的下一个节点),second (指向头结点的下一个的下一个节点)。
free掉first,使second的prev指向头结点,头结点的next指向second。

  • 注意:如果head->next == head,表示链表为空,不能头删。

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

//双向链表的头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);

	ListNode* first = pHead->next;
	ListNode* second = first->next;

	second->prev = pHead;
	pHead->next = second;
	free(first);
}

10.双向链表的查找(ListFind)

我们需要遍历链表,比较链表元素是否是要查找对象。如果找到了,返回该节点的地址。如果找不到,返回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;
}

11.双向链表在pos前插入(ListInsert)

我们需要一个指针 posPrev (指向pos前一个节点)。
pos的prev指向newnode,newnode的next指向pos;posPrev的next指向newnode,newnode的prev指向posPrev。

  • 如果pos指向头结点(哨兵位),那么ListInsert相当与完成尾插。
  • 如果pos指向头结点(哨兵位)的下一个节点,那么ListInsert相当于头插。

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言

//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = BuyListNode(x);
	ListNode* posPrev = pos->prev;

	newnode->prev = posPrev;
	posPrev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}

12.双向链表删除pos位置处的节点(ListErase)

我们需要两个指针posPrev(记录pos的上一个节点的地址),posNext(记录pos的下一个节点的地址)。free掉pos,使posNext的prev指向posPrev,posPrev的next指向posNext。

  • 如果pos指向头结点的下一个,那么ListErase相当于头删。
  • 如果pos指向头结点的上一个(也就是尾节点),那么ListErase相当于尾删。

数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言


//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

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

二、代码实现

对于头插,尾插函数,我复用了ListInsert函数。
对于头删,尾删函数,我复用了ListErase函数。

//Lish.h 文件

#pragma once

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


typedef int LTDataType;

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


//创建返回链表的头结点
ListNode* ListCreate();

//创建新节点
ListNode* BuyListNode(LTDataType x);

//双向链表的销毁
void ListDestroy(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);


//Lish.c  文件

#include "List.h"


//创建返回链表的头结点
ListNode* ListCreate()
{
	return BuyListNode(-1);
}


//创建新节点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}


//双向链表的打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	printf("head<=>");
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	/*ListNode* newnode = BuyListNode(x);
	ListNode* tail = pHead->prev;

	tail->next = newnode;
	newnode->prev = tail;
	pHead->prev = newnode;
	newnode->next = pHead;*/
	
	ListInsert(pHead, x);
}


//双向链表的尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//pHead->next == pHead时,链表没有元素
	assert(pHead->next != pHead);

	/*ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	pHead->prev = tailPrev;
	tailPrev->next = pHead;
	free(tail);*/

	ListErase(pHead->prev);
}


//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	/*ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;

	newnode->next = first;
	first->prev = newnode;
	newnode->prev = pHead;
	pHead->next = newnode;*/

	ListInsert(pHead->next, x);
}


//双向链表的头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);

	/*ListNode* first = pHead->next;
	ListNode* second = first->next;

	second->prev = pHead;
	pHead->next = second;
	free(first);*/

	ListErase(pHead->next);
}



//双向链表的查找
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;
}



//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = BuyListNode(x);
	ListNode* posPrev = pos->prev;

	newnode->prev = posPrev;
	posPrev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}



//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

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


//双向链表的销毁
void ListDestroy(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* curNext = cur->next;
		
		free(cur);
		cur = curNext;
	}
	free(pHead);
}

总结

以上就是双向链表的实现!!!
数据结构:双向链表的实现(C实现),数据结构,数据结构,链表,c语言文章来源地址https://www.toymoban.com/news/detail-630742.html

到了这里,关于数据结构:双向链表的实现(C实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索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日
    浏览(39)
  • 双向链表--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日
    浏览(72)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包