【数据结构】C语言实现双向链表(带头结点、循环)

news2024/9/23 13:24:52

C语言实现双向链表(带头结点、循环)

  • 一、带头结点的循环双向链表
  • 二、结点与接口定义
  • 三、实现
    • 3.1 申请节点
    • 3.2 初始化
    • 3.3 打印
    • 3.4 尾插
    • 3.5 头插
    • 3.6 尾删
    • 3.7 判断链表为空断言
    • 3.8 头删
    • 3.9 查找find
    • 3.10 插入insert-在pos之前插入
    • 3.11 头插尾插复用insert
    • 3.12 删除erase-删除pos位置
    • 3.13 头删尾删复用erase
    • 3.14 销毁destroy
  • 源码
  • 总结


一、带头结点的循环双向链表

在这里插入图片描述

二、结点与接口定义

结点定义:

typedef int ListDataType;
typedef struct LinkedListNode
{
	struct LinkedListNode* next;
	struct LinkedListNode* prev;
	ListDataType data;
}LinkedListNode;

接口定义:

LinkedListNode* LinkedListInit();
void LinkedListPrint(LinkedListNode* phead);

bool LinkedListEmpty(LinkedListNode* phead);
void LinkedListPushBack(LinkedListNode* phead, ListDataType x);
void LinkedListPushFront(LinkedListNode* phead, ListDataType x);
void LinkedListPopBack(LinkedListNode* phead);
void LinkedListPopFront(LinkedListNode* phead);

LinkedListNode* LinkedListFind(LinkedListNode* phead, ListDataType x);

// 在pos之前插入
void LinkedListInsert(LinkedListNode* pos, ListDataType x);
// 删除pos位置的值
void LinkedListErase(LinkedListNode* pos);

void LinkedListDestroy(LinkedListNode* phead);

三、实现

3.1 申请节点

我们将申请结点的代码封装成函数,方便后续使用

LinkedListNode* CreateLinkedListNode(ListDataType x)
{
	LinkedListNode* newnode = (LinkedListNode*)malloc(sizeof(LinkedListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

3.2 初始化

由于是带头结点的双向链表,因此在使用链表前,我们需要对链表进行初始化。

LinkedListNode* LinkedListInit()
{
	LinkedListNode* phead = CreateLinkedListNode(230510);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

3.3 打印

遍历链表,值得说的是,带头结点的双向链表的循环结束条件是 cur != phead

void LinkedListPrint(LinkedListNode* phead)
{
	assert(phead);

	LinkedListNode* cur = phead->next;

	printf("guard<->");
	while (cur != phead)
	{
		printf("%d<->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

3.4 尾插

尾插时,需要先找到尾结点,然后将新结点插入到尾结点后面。

void LinkedListPushBack(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	// 1.找到尾结点
	LinkedListNode* tail = phead->prev;

	// 2.插入到尾结点后面
	LinkedListNode* newnode = CreateLinkedListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

3.5 头插

第一种写法,要注意现将newnode后面的结点进行链接,然后再讲newnode链接到phead后面。

void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	LinkedListNode* newnode = CreateLinkedListNode(x);
	// 与原来的第一个数据结点链接
	newnode->next = phead->next;
	phead->next->prev = newnode; // newnode->next->prev = newnode;

	// newnode变为新的第一个数据结点
	phead->next = newnode;
	newnode->prev = phead;
}

第二种写法(推荐写法),我们使用变量记录phead的next,记为first,这样newnode插入到phead和first之间,这样逻辑比较清楚。

void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	LinkedListNode* newnode = CreateLinkedListNode(x);
	// 用变量记录第一个结点
	LinkedListNode* first = phead->next;

	// 与原来的第一个数据结点链接
	newnode->next = first;
	first->prev = newnode; 

	// newnode变为新的第一个数据结点
	phead->next = newnode;
	newnode->prev = phead;
}

3.6 尾删

phead的prev是尾tail,tail的prev是tailPrev。

void LinkedListPopBack(LinkedListNode* phead)
{
	assert(phead);

	LinkedListNode* tail = phead->prev;
	LinkedListNode* tailPrev = tail->prev;

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

上面代码的问题是链表为空的情况报错,于是我们在该函数内部对空链表进行断言:

void LinkedListPopBack(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead)); // 删除时链表不能为空

	LinkedListNode* tail = phead->prev;
	LinkedListNode* tailPrev = tail->prev;

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

3.7 判断链表为空断言

判断链表为空逻辑:

bool LinkedListEmpty(LinkedListNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

使用链表为空的断言:

assert(!LinkedListEmpty(phead)); // 链表为空时error

3.8 头删

头删时需要将第一个结点删除,很容易便想到以下代码:

void LinkedListPopFront(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead)); // 删除时链表不能为空

	LinkedListNode* next = phead->next;

	phead->next = next->next;
	phead->next->prev = phead; // next->next->prev = phead;
	free(next); 
}

为了提高可读性,推荐使用以下代码,定义first和second两个变量指向第一个和第二个:

void LinkedListPopFront(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead)); // 删除时链表不能为空

	LinkedListNode* first = phead->next;
	LinkedListNode* second = first->next;

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

3.9 查找find

查找的本质就是遍历链表

LinkedListNode* LinkedListFind(LinkedListNode* phead, ListDataType x)
{
	assert(phead);
	
	LinkedListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.10 插入insert-在pos之前插入

pos的来源一般是find的结果

void LinkedListInsert(LinkedListNode* pos, ListDataType x)
{
	assert(pos);

	LinkedListNode* prev = pos->prev;
	LinkedListNode* newnode = CreateLinkedListNode(x);

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

3.11 头插尾插复用insert

有了上面的insert在任意位置插入,我们可以修改尾插代码:

void LinkedListPushBack(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	// 在phead之前插入,也就是尾插
	LinkedListInsert(phead, x);
}

同理也可以修改头插代码:

void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	LinkedListInsert(phead->next, x);
}

3.12 删除erase-删除pos位置

同insert一样,pos也应该是调用者通过find返回的结果。

void LinkedListErase(LinkedListNode* pos)
{
	assert(pos);

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

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

3.13 头删尾删复用erase

有了上面的erase在任意位置删除,我们可以修改尾删的代码:

void LinkedListPopBack(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead));
	LinkedListErase(phead->prev);
}

同理也可以修改头删的代码:

void LinkedListPopFront(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead));

	LinkedListErase(phead->next);
}

3.14 销毁destroy

记得释放头结点phead:

void LinkedListDestroy(LinkedListNode* phead)
{
	assert(phead);

	LinkedListNode* cur = phead->next;
	while (cur != phead)
	{
		LinkedListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
}

源码

gitee-LinkedList

总结

带头结点、双向、循环链表的实现都非常的简单,需要注意判空条件与遍历终止的条件。

在代码写法上,对于某个节点的前一个或后一个的问题,我们最好分别使用变量去记录,这样代码的逻辑更清晰,可读性更高。例如尾插时的tail,尾删时的tail和tailPrev,以及头删时的first与second,erase中的posPrev与posNext,这些变量的使用,提高了代码的可读性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/510740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习算法实战(scikit-learn版本)---线性回归

目录 文章目标&#xff1a; 1&#xff0c;导入库 2&#xff0c;导入数据集 3&#xff0c;缩放/归一化训练数据 4,创建并拟合回归模型 5,查看参数 6,预测 7&#xff0c;可视化 有一个开源的、商业上可用的机器学习工具包&#xff0c;叫做[scikit-learn](https://scik…

阿里、京东等大厂年薪50w的测试都是什么水平?

各位做测试的朋友&#xff0c;但凡经历过几次面试&#xff0c;那么你一定曾被问到过以下问题&#xff1a; 1、在Linux环境下&#xff0c;怎么执行web自动化测试&#xff1f; 2、Shell如何&#xff0c;Docker熟悉吗&#xff1f; 3、全链路的压测实操过吗&#xff0c;如何推进与开…

【Linux】基本权限

&#x1f601;作者&#xff1a;日出等日落 &#x1f514;专栏&#xff1a;Linux 任何值得到达的地方&#xff0c;都没有捷径。 目录 Linux权限: 权限的概念&#xff1a; Linux上面的用户分类&#xff1a; Linux权限管理 文件访问者的分类&#xff08;人&#xff09; 文件…

小心白蛇!PyPI仓库被持续投放White Snake后门组件

背景 墨菲安全实验室在持续监测开源软件仓库中的投毒行为&#xff0c;4 月 14 日起陆续发现至少 41 个包含白蛇&#xff08;White Snake&#xff09;后门的 Python 包被发布到 PyPI 仓库&#xff0c;目前相关的后门包仍在持续发布。 事件简述 白蛇 &#xff08;WhiteSnake&a…

三种方法教你让模糊照片秒变高清图

现在随着数字相机和智能手机的普及&#xff0c;我们拍摄的照片数量越来越多&#xff0c;但是有些照片可能因为环境或技术等原因导致模糊不清&#xff0c;这时候我们就需要使用一些软件或工具来让照片变得清晰&#xff0c;以满足我们的需求。 下面介绍三种常用的照片变清晰的方…

专科生学习云计算的就业前景如何?

作为专科学历学习云计算&#xff0c;就业前景肯定是有的。因为目前开设云计算这门专业主要也是专科院校&#xff0c;目前入行的学历要求也是专科为起点&#xff0c;更加侧重技术技能水平&#xff0c;技术过关了才能找到合适的工作。 云计算作为一种新兴的IT技术方向&#xff0…

AI别来搅局,chatGPT的世界不懂低代码

ChatGPT单月访问量再创新高 根据SimilarWeb统计&#xff0c;ChatGPT上月全球访问量17.6亿次&#xff0c;已超越必应、鸭鸭走DuckDuckGo等其他国际搜索引擎&#xff0c;并达到谷歌的2%&#xff0c;百度的60%。 这会&#xff0c;程序员失业的段子又得再来一遍了&#xff1a; 拖…

Qt+WebRTC学习笔记(七)ubuntu22.04下搭建coturn(STUN/TURN)

前言 因工作原因&#xff0c;很长时间没更新相关文档了&#xff0c;笔者之前测试时&#xff0c;一直使用示例自带的公网中转服务器。考虑到后期项目需要&#xff0c;笔者在线搭建一个coturn服务器测试&#xff0c;供有需要的小伙伴使用 一、安装coturn 若需要最新版本的cotu…

Linux修改文件权限

目录 1、常用操作 2、文件属性 3、chmod命令详解 1.修改文件属主,也可以修改文件属组 2.修改文件权限 4.chgrp命令详解 1、常用操作 查看权限操作命令:# ls -l 在 Linux 中我们通常使用以下两个命令来修改文件或目录的所属用户与权限&#xff1a; chown (change ownerp)…

[python] Python类型提示指北

Python3.5 版本引入了类型提示&#xff08;Type Hints&#xff09;&#xff0c;它允许开发者在代码中显式地声明变量、函数、方法等的类型信息。这种类型声明不会影响 Python 解释器的运行&#xff0c;但可以让 IDE 和静态分析工具更好地理解代码&#xff0c;同时提高代码的可读…

《Netty》从零开始学netty源码(五十六)之MessageSizeEstimator

MessageSizeEstimator 在channel的配置类中有一个属性msgSizeEstimator&#xff0c;它的功能就是用来预估消息的大小&#xff0c;它的赋值过程如下&#xff1a; 接口MessageSizeEstimator只有一个方法newHandle()&#xff0c;它返回的接口handle是MessageSizeEstimator的内部类…

SpringBoot基础篇1(搭建环境+基础配置)

一、SpingBoot入门案例 SpringBoot是用来简化Spring应用的初始搭建以及开发过程。 先快速搭建一个SpringBoot&#xff1a; 创建一个空project&#xff0c;再创建SpringBoot模块。 点击Create&#xff0c;出现以下页面配置成功 创建一个控制器测试一下&#xff1a; RestCo…

Centos8搭建SMB服务

这里以Centos8为例&#xff0c;搭建简易的SMB服务。虚拟机配置&#xff1a;内存8G、存储64G、CPU单核四线程、网络NAT模式跳过虚拟机与系统配置部分&#xff0c;不清楚虚拟机配置以及创建的请查阅其他文档此文章只用于练习用&#xff0c;商业和个人用可以见解Truenas系统 更新…

改进YOLOv5 | C3模块改动篇 | 轻量化设计 |骨干引入高效卷积运算 DSConv: Efficient Convolution Operator

论文地址:https://arxiv.org/pdf/1901.01928v1.pdf 引入了一种卷积层的变体,称为DSConv(分布偏移卷积),其可以容易地替换进标准神经网络体系结构并且实现较低的存储器使用和较高的计算速度。 DSConv将传统的卷积内核分解为两个组件:可变量化内核(VQK)和分布偏移。 通过…

C 高级 /Day 2

#include<stdio.h> int Max_arr(int (*arr)[5],int row,int hang) {int max0;//最大值int k1;//循环行for(int i0;i<hang;i){int k1;//循环列for(int j0;j<row;j){//控制行和列for(int m0;m<hang;m){for(int n0;n<row;n){//如果是第m行的其他的元素&#xf…

MySQL原理(一):逻辑存储结构

前言 从本文开始&#xff0c;我将分享一下近期学习 MySQL 的笔记&#xff0c;其中大部分来源于极客时间的《MySQL实战45讲》、小林coding、以及部分其他博客和书籍。 这次系列文章着重讲 MySQL 的原理部分&#xff0c;主要是用于面试&#xff0c;也就是我们常说的八股&#x…

华为OD机试 - 有效的括号(Java)

一、题目描述 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。…

力扣习题+——单链表

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重要…

Redis 学习笔记

一、redis中的常见数据结构 Redis共有5种常见数据结构&#xff0c;分别字符串&#xff08;STRING)、列表&#xff08;LIST&#xff09;、集合&#xff08;SET)、散列&#xff08;HASH&#xff09;、有序集合&#xff08;ZSET)。 二、redis中字符串(String)介绍 String 类型是…

[ 云计算 华为云 ] 华为云开天 aPaaS:构建高效的企业数字化平台(上)

文章目录 前言一、 什么是 aPaaS1.1 初识 aPaaS 二、华为云开天 aPaaS2.1 华为云服务类型与种类2.1.1 基础 aPaaS2.1.2 行业 aPaaS&#xff08;一&#xff09;工业 aPaaS&#xff08;二&#xff09;政务 aPaaS&#xff08;三&#xff09;电力 aPaaS&#xff08;四&#xff09;矿…