【脚踢数据结构】链表(2)

news2024/9/25 21:31:16
  • (꒪ꇴ꒪ ),Hello我是祐言QAQ
  • 我的博客主页:C/C++语言,Linux基础,ARM开发板,软件配置等领域博主🌍
  • 快上🚘,一起学习,让我们成为一个强大的攻城狮!
  • 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏

        

【脚踢数据结构】链表(1)_祐言QAQ的博客-CSDN博客

 接上文:

2.单项循环链表

        单向循环链表和普通单向链表的操作逻辑几乎一样,唯一就是初始化和结尾判断有区别(单向循环链表里面每一个节点的next都要有指向,最后一个节点的next指向head节点)。

(1)初始化     

//初始化(区别于普通单向链表)
singly_list init(void)
{
	singly_list head = malloc(sizeof(listnode));
	if (head != NULL)
	{
		head->next = head;
	}
	return head;
}

(2)尾插法

//尾插法插入新节点(有区别)
void insert_tail(singly_list head, singly_list new)
{
	if (head == NULL || new == NULL)
	{
		return;
	}
	//(1)找到原来链表中最后一个节点
	singly_list p = head;//定义一个临时指针变量p,用来遍历链表,找到最后节点
	while(p->next != head)
	{
		p = p->next;//一个一个往后找
	}
	//(2)将原来链表中最后一个节点的后继指针next设置为new(新节点地址)
     p->next = new;
	//(3)将新的尾部节点 new 的next指向头节点head
	new->next = head;
}

(3)其他算法和单向链表几乎一样

        只需要将原来判断结尾的 p->next == NULL 改为 p->next == head 和将p->next != NULL换成p->next != head。

3.单项循环链表小练习

        据说犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,犹太人与Josephus及他的朋友躲到一个洞中,族人决定宁愿死也不要被敌人找到,于是决定了一个自杀方式,所有人排成一个圆圈,由第1个人 开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

        然而Josephus 和他的朋友并不想死,Josephus要他的朋友先假装遵从,他将朋友与自己安排在两个特殊的位置,于是逃过了这场死亡游戏。现在假设有n个人形成一个单向循环链表,求最后剩余的两个节点。

0

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

//数据域
typedef int Database;

//链表的节点定义
typedef struct Node
{
	Database data;		//数据域
	struct Node *next;	//指针域
}node;

//初始化链表
node *init_list()
{
	node *head = malloc(sizeof(node));
	if (head!=NULL)
	{
		head->data = 1;
		head->next = head;
	}
	return head;
}

//创建节点
node *create_node(Database data)
{
	node *new = malloc(sizeof(node));
	if (new!=NULL)
	{
		new->data = data;
		new->next = NULL;
	}
	return new;
}

//尾插
void insert_tail(node *head, node *new)
{
	node *p = head;
	while(p->next!=head)
	{
		p = p->next;
	}
	p->next = new;
	new->next = head;
}

void delete_node(node *head)
{
	node *p = head;
	while(p->next->next!=p)
	{
		node *dele = p->next->next;
		p->next->next = dele->next;
		free(dele);
		p = p->next->next;
	}
	
	printf("%d %d\n", p->data, p->next->data);
}

void display(node *head)
{
	node *p = head;
	while(p->next!=head)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}

int main(int argc, char const *argv[])
{
	node *head = init_list();
	int num;
	scanf("%d", &num);
	for (int i = 2; i <= num; ++i)
	{
		insert_tail(head, create_node(i));
	}

	delete_node(head);


	return 0;
}

 运行结果:

        不难推算出,当有十个人时,第4个和第10个人会幸存。

 4.双向循环链表

        双向循环链表是一种常见的链表数据结构,它在单向链表的基础上进行了扩展,允许链表中的节点以双向方式连接,并且链表的尾节点指向头节点,形成一个闭环,从而形成循环。

双向循环链表由节点构成,每个节点包含三个部分:

        数据域:用于存储节点的数据;
        前驱指针(prev):指向链表中的前一个节点,使得节点之间可以双向连接;
        后继指针(next):指向链表中的后一个节点,同样用于双向连接。


 双向循环链表的特点包括:

        尾节点的后继指针指向头节点,头节点的前驱指针指向尾节点,形成循环;
        每个节点都有一个前驱和一个后继,除了头节点的前驱指针指向尾节点,尾节点的后继指针指向头节点,其他节点之间通过prev和next指针连接;
        可以从头节点或尾节点开始遍历整个链表,并且可以进行前驱和后继的操作。


双向循环链表相对于单向链表的优点在于:

        可以双向遍历链表,更加方便地实现向前或向后遍历节点;
        删除操作时,不需要特殊处理链表尾部的节点,因为尾节点的后继指针指向头节点,循环回到链表的开头。


使用双向循环链表的一些注意事项包括:

        确保在插入和删除节点时正确地维护prev和next指针,避免出现链表断裂或死循环等问题;
        注意在访问节点的前驱和后继指针时,先判断节点是否为头节点或尾节点,避免出现空指针引用的错误;
       使用双向循环链表时,需要额外的内存来存储prev指针,所以在内存使用上可能会比单向链表略高。

(1)双向循环链表初始化

//初始化链表
node *init_double_list()
{
	node *head = malloc(sizeof(node));
	if (head == NULL)
	{
		printf("malloc error\n");
		exit(0);
	}
	else
	{
		head->prev = head;
		head->next = head;
	}
	return head;
}

(2)新建链表

//数据
typedef int DataType;

//节点
typedef struct Node
{
	DataType data;		//数据域
	struct Node *prev;	//指针域:前驱指针
	struct Node *next;	//指针域:后继指针
}node;


//创建节点
node *create_double_node(DataType data)
{
	node *new = malloc(sizeof(node));
	if (new != NULL)
	{
		new->data = data;
		new->prev = NULL;
		new->next = NULL;
	}
	return new;
}

(3)尾插法

//尾插法,实际是向头节点前一个的节点后面插入,相当于头插
void insert_double_tail(node *head, node *new)
{
	//拿到头节点的前驱节点
	node *tail = head->prev;
	new->next = head;  // 1
	new->prev = tail;  // 2
	head->prev = new;  // 3
	tail->next = new;  // 4
}

(4)头插法

        头插法其实就是尾插法的变形,相当于tail是头(head),而把new放在tail(head)后。

//头插法
void insert_double_head(node *head, node *new)
{
	node *tmp = head->next;
	new->next = tmp;
	new->prev = head;
	tmp->prev = new;
	head->next = new;
}

(5)遍历     

        双向链表的遍历有两种常见的方式:前驱遍历和后继遍历。

        前驱遍历(正向遍历): 前驱遍历是从链表的头节点开始,沿着后继指针(next)向后遍历到链表的尾节点为止。这种遍历方式是最常见的,它按照链表中节点的顺序,从前向后逐个访问节点的数据。

        遍历过程示意图(假设节点数据是整数):

head -> 1 -> 2 -> 3 -> 4 -> tail


void display_next(node *head)
{
	node *p = head;
	while(p->next != head)
	{
		printf("%d ", p->next->data);
		p = p->next;
	}
	printf("\n");
}

        后继遍历(逆向遍历): 后继遍历是从链表的尾节点开始,沿着前驱指针(prev)向前遍历到链表的头节点为止。这种遍历方式是比较少见的,它按照链表中节点的逆序,从后向前逐个访问节点的数据。

        遍历过程示意图(假设节点数据是整数):

tail -> 4 -> 3 -> 2 -> 1 -> head

void display_prev(node *head)
{
	node *p = head;
	while(p->prev != head)
	{
		printf("%d ", p->prev->data);
		p = p->prev;
	}
	printf("\n");
}

(6)查找节点

node *find_double_node(node *head, DataType data)
{
	if (double_isempty(head))
	{
		return NULL;
	}
	node *p = head;
	while(p->next!=head)
	{
		if (p->next->data == data)
		{
			return p->next;
		}
		p = p->next;
	}
	return NULL;
}

(7)删除节点

bool delete_double_node(node *head, DataType data)
{
	if (double_isempty(head))
	{
		return false;
	}
	node *p = head;
	while(p->next!=head)
	{
		if (p->next->data == data)
		{
			node *dele = p->next;
			dele->next->prev = p;
			p->next = dele->next;
			free(dele);
			dele = NULL;
			return true;//continue;
		}
		p = p->next;
	}
	return false;
}

(8)更新节点

void update_double_node(node *head, DataType old_data, DataType new_data)
{
	if (double_isempty(head))
	{
		return ;
	}
	node *p = find_double_node(head, old_data);
	if (p!=NULL)
	{
		p->data = new_data;
		printf("更新成功\n");
	}
	else
	{
		printf("更新失败\n");
	}
}


(9)清空

void clear_double_list(node *head)
{
	if (double_isempty(head))
	{
		return ;
	}
	while(head->next != head)
	{
		node *dele = head->next;
		dele->next->prev = head;
		head->next = dele->next;
		free(dele);
		dele = NULL;
	}
}

注意:

        (1)双向循环链表的实现相对复杂,需要维护两个指针。
        (2)需要特别注意循环条件和指针的正确更新,以避免陷入死循环或产生错误的链接。

        更多C语言Linux系统ARM板实战数据结构相关文章,关注专栏:

   手撕C语言

            玩转linux

                    脚踢数据结构

                            6818(ARM)开发板实战

📢写在最后

  • 今天的分享就到这啦~
  • 觉得博主写的还不错的烦劳 一键三连喔~
  • 🎉感谢关注🎉

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

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

相关文章

Effective Java笔记(29)优先考虑泛型

一般来说 &#xff0c;将集合声 明参数化&#xff0c;以及使用 JDK 所提供的泛型方法&#xff0c;这些都不太困难 。编写自己的泛型会比较困难一些&#xff0c;但是值得花些时间去学习如何编写 。 以简单的&#xff08;玩具&#xff09;堆校实现为例 &#xff1a; // Object -…

【OpenGauss源码学习 —— 执行算子(SeqScan算子)】

执行算子&#xff08;SeqScan算子&#xff09; 执行算子概述扫描算子SeqScan算子ExecInitSeqScan函数InitScanRelation函数ExecSeqScan函数 总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵…

IoTDB1.X windows运行失败问题的处理

在windows运行 IoTDB1.x时 会出现如图所示的问题 为什么会出现这样的问题&#xff1f;java没有安装还是未调用成功&#xff0c;我是JAVA8~11~17各种更换都未能解决问题&#xff0c;最后对其bat文件进行查看&#xff0c;发现在conf\datanode-env.bat、conf\confignode-env.bat这…

拆解与重构:慕云游首页组件化设计

目录 前言1 项目准备1.1 创建项目目录1.2 搭建项目开发环境 2 项目组件化2.1 在当前环境启动原有项目2.2 顶部组件2.3 幻灯片组件2.3.1 功能实现2.3.2 加载中组件2.3.3 结构和样式2.3.4 使用Ajax获取数据 2.4 机酒自由行组件2.5 拆分余下的css文件 3 项目完善4 源码 前言 在现代…

C 语言的逻辑运算符

C 语言的逻辑运算符包括三种&#xff1a; 逻辑运算符可以将两个关系表达式连接起来. Suppose exp1 and exp2 are two simple relational expressions, such as cat > rat and debt 1000 . Then you can state the following: ■ exp1 && exp2 is true only if bo…

用库造一个list的轮子 【C++】

文章目录 list的模拟实现默认成员函数构造函数拷贝构造函数赋值运算符重载析构函数 迭代器迭代器为什么要存在&#xff1f;const_iteratorbegin和end inserterasepush_back && pop_backpush_front &&pop_frontswap 完整代码 list的模拟实现 默认成员函数 构造…

SpringBoot 底层机制分析[上]

文章目录 分析SpringBoot 底层机制【Tomcat 启动分析Spring 容器初始化Tomcat 如何关联Spring 容器】[上]搭建SpringBoot 底层机制开发环境Configuration Bean 会发生什么&#xff0c;并分析机制提出问题&#xff1a;SpringBoot 是怎么启动Tomcat &#xff0c;并可以支持访问C…

ios启动崩溃保护

网传上个月下旬小红书因为配置问题导致连续性启动崩溃&#xff0c;最终只能通过紧急发版解决。对于冷启动崩溃保护的最容易查到的资料来源于微信读书团队的分享。 何为保护&#xff1f;要保护什么&#xff1f;该怎样保护&#xff1f;带着这几个疑问&#xff0c;一一谈一下个人的…

浅谈常态化压测

目录 一、常态化压测介绍 1.什么是常态化压测 2.为什么要进行常态化压测 3.常态化压测的价值 二、常态化压测实践 1.常态化压测流程介绍 2.首次进行常态化压测实践 2.1 准备阶段 2.2 执行阶段 2.3 调优阶段 2.4 复盘阶段 三、常态化压测总结 一、常态化压测介绍 1…

AI让分子“起死回生”:拯救抗生素的新希望

生物工程师利用人工智能(AI)使分子“起死回生”[1]。 为实现这种分子“复活”,研究人员应用计算方法对来自现代人类(智人)和我们早已灭绝的远亲尼安德特人和丹尼索瓦人的蛋白质数据进行分析。这使研究人员能够鉴定出可以杀死致病细菌的分子&#xff0c;从而促进研发用于治疗人类…

微信生态升级!小绿书来了!

如你所知&#xff0c;微信不只是一个聊天工具。一切从照片开始&#xff0c;你拍了一张照片&#xff0c;你就拥有了自己的相册&#xff0c;在“朋友圈”你可以了解朋友们的生活。如你所见&#xff0c;微信&#xff0c;是一个生活方式。不知不觉间&#xff0c;微信已经走过了 11个…

Docker的入门与使用

什么是Docker&#xff1f; docker官网 简介与概述 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#x…

C字符串与C++ string 类:用法万字详解(上)

目录 引言 一、C语言字符串 1.1 创建 C 字符串 1.2 字符串长度 1.3 字符串拼接 1.4 比较字符串 1.5 复制字符串 二、C字符串string类 2.1 解释 2.2 string构造函数 2.2.1 string() 默认构造函数 2.2.2 string(const char* s) 从 C 风格字符串构造 2.2.3 string(co…

通讯协议034——全网独有的OPC HDA知识一之聚合(三)时间加权平均

本文简单介绍OPC HDA规范的基本概念&#xff0c;更多通信资源请登录网信智汇(wangxinzhihui.com)。 本节旨在详细说明HDA聚合的要求和性能。其目的是使HDA聚合标准化&#xff0c;以便HDA客户端能够可靠地预测聚合计算的结果并理解其含义。如果用户需要聚合中的自定义功能&…

使用一个python脚本抓取大量网站【2/3】

一、说明 我如何使用一个 Python 脚本抓取大量网站&#xff0c;在第 2 部分使用 Docker &#xff0c;“我如何使用一个python脚本抓取大量网站”统计数据。在本文中&#xff0c;我将与您分享&#xff1a; Github存储库&#xff0c;您可以从中克隆它;链接到 docker 容器&#xf…

软件定制开发平台:管好数据资源,降本提质!

在如今的发展时代&#xff0c;利用好优质的软件定制开发平台&#xff0c;定能给广大用户提高办公协作效率&#xff0c;创造可观的市场价值。作为服务商&#xff0c;流辰信息一直在低代码市场勤于钻研&#xff0c;不断努力&#xff0c;保持敏锐的市场眼光和洞察力&#xff0c;为…

Modelsim恢复编辑器的解决方案——只能将外部编辑器删除后,重新匹配编辑器

Modelsim恢复编辑器的解决方案——只能将外部编辑器删除后&#xff0c;重新匹配编辑器 1&#xff0c;Modelsim和Questasim是相互兼容的&#xff0c;配置的编辑器变成了sublime&#xff0c;且更换不了编辑器2&#xff0c;解决问题的方案&#xff0c;还是没得到解决3&#xff0c;…

Markdown和LaTex的学习

下载Typora Typora(免费版) 轻量级Markdown编辑器 - 哔哩哔哩 (bilibili.com) 部分编辑器需要进入设置 中开启特定的 Markdown 语法&#xff0c;例如 Typora 就需要手动开启 高亮 功能 Typora的使用&#xff1a; Typora中各种使用 - lyluoye - 博客园 (cnblogs.com) 标题 #…

数据库的存储过程、触发器、事件 实现(超精简)

一 存储过程 什么是存储过程 &#xff1a; 自己搜 和代码写的有什么区别&#xff1a; 没区别 为什么用存储过程&#xff1a; 快 例子 -- 创建 test名字的存储过程 CREATE PROCEDURE test(in idin INT) BEGIN-- 创建变量declare id int default 0;declare stopflag int defau…

爬虫015_python异常_页面结构介绍_爬虫概念介绍---python工作笔记034

来看python中的异常 可以看到不做异常处理因为没有这个文件所以报错了 来看一下异常的写法