数据结构——单向链表

news2024/9/30 17:38:47

目录

前言

一、单向链表

二、单向链表基本操作

1、链表单创建

2.节点插入

(1)尾部插入

 (2)任意位置插入

3、单向链表节点删除

4、链表打印

5、释放链表

6、链表逆序

 ......

三、链表测试

总结



前言

        链表(Linked List)是一种常见的数据结构,它属于线性表的一种链式存储结构,其逻辑上相邻的元素在物理存储位置并不相邻。它由一系列节点(Node)组成,每个节点包含数据部分和指向列表中下一个节点(或者上一个节点)的指针(链接)。链表中的节点通过指针相互连接,从而形成一个序列。链表可以分为几种不同的类型,但最常见的是单向链表和双向链表。


一、单向链表

        在单向链表中,每个节点包含两个部分:

        1、数据部分:存储节点的数据,数据类型可以是整型、浮点型、字符型或自定义的数据结构(如结构体)等。

        2、指针部分(也称为链接或“next”指针):指向链表中下一个节点的指针。链表的最后一个节点的指针部分通常设置为NULL,表示链表的结束。

         链表就如同一群人手拉着手站在一起,最开始的一个人要拉着一根杆,防止链子丢失了。每个人都带着属于自己的数据(如姓名、性别、年龄等),但他们都手拉着手,随意找到上一个人,便可以自然而然的知到下一个人,他们的手就是next指针。

         链表特性:

         1、动态数据结构:链表的节点可以动态地分配和释放,因此链表是一种动态数据结构。链表的大小可以在运行时动态地增加或减少,不需要像数组那样在创建时指定大小。

        2、非连续存储:链表中的节点可以存储在内存中的任何位置,不像数组那样要求所有元素连续存储。但每个节点都需要额外的内存来存储指针,这增加了链表的内存开销。

        3、灵活:通过指针,可以很容易地在链表中的任何位置插入或删除节点,而不需要移动其他节点,通常只需要修改节点的指针,因此效率较高,尤其是在链表中间或末尾进行这些操作时。。

        4、访问方式:单链表不支持快速随机访问,因为从链表的头节点到任意节点的访问都需要从头开始遍历。

        5、常见运用:在实际应用中,链表常用于实现栈、队列等数据结构,或者在需要频繁插入和删除操作而不太需要随机访问的场景中。

二、单向链表基本操作

1、链表单创建

        首先声明链表节点。

typedef int data_t;

typedef struct node {
	data_t data;
	struct node * next;
}listnode, * linklist;

         然后进行链表创建,一般只需要创建一个头节点即可,因为在刚创建时,没有数据要放在链表上,即没有新节点插入。

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

linklist list_create() //创建链表,即头节点
{
	linklist H;
	H = (linklist)malloc(sizeof(listnode));//给节点动态分配内存
	if (H == NULL)//判断是否申请内存成功 
	{
		printf("malloc failed\n");
		return H;
	}	
	//如果成功了,则进行赋初值
	H->data = 0; //一般存放节点个数
	H->next = NULL;//刚开始时没有节点插入链表,所以该头节点即是尾节点
	return H; //返回头节点指针
}

int main(void)
{
	linklist H;	
	H = list_create();//外部调用函数进行创建
	if (H == NULL)
		return -1;
	return 0;
}

2.节点插入

(1)尾部插入

        将链表创建完成后,就可以向链表插入数据了,其中最简单的插入方式为,尾部插入。基本步骤为:

        1、创建新的节点,用以存放将插入的数据;

        2、找到当前链表的尾部,即next指针指向NULL的节点;

        3、将新创建的节点放在尾部,原来尾部节点的next指针指向自己,自己的next指针指向NULL即可。

int list_tailinsert(linklist H, data_t data) //传入待插入的链表地址和待插入的数据
{
	linklist p;//定义临时指针变量
	linklist q;
	if (H == NULL) //检验传入的链表是否有效
	{
		printf("H is NULL\n");
		return -1;
	}
	//创建新节点并检查有效性,存放待插入数据
	if ((p = (linklist)malloc(sizeof(listnode))) == NULL)
	{
		printf("malloc failed\n");
		return -1;
	}
	p->data = data;
	p->next = NULL;

	q = H;
	while (q->next != NULL) //遍历链表找尾部
	{
		q = q->next;
	}

	q->next = p;//插入链表,尾部的next指针指向新节点
	H->data++;//插入数据后,数据个数加一 
	return 0;
}

 (2)任意位置插入

        在这里,我们用-1表示链表的头节点位置,存放数据的第一个节点用0表示,后面的位置依次即可,可以自定义。其插入基本步骤:

        1、对插入位置的有效性进行判断;

        2、查找待插入位置的前一个节点,如下查找某一位置的节点的操作;

//寻找链表上某一位置的节点的地址,返回该节点地址
linklist list_getpos(linklist H, int pos) //传入链表指针和待寻找节点的位置
{
	linklist p;
	int i;
	if (H == NULL) 
	{
		printf("H is NULL\n");
		return NULL;
	}
	if (pos == -1) return H;//如果是-1,则返回链表头节点,因为用0表示第一个节点的位置
	p = H;
	i = -1;
	while (i < pos) //遍历寻找
	{
		p = p->next;
		if (p == NULL) //没有到达指定位置,链表已经结尾了,位置错误,返回NULL
		{
			printf("pos is invalid\n");
			return NULL;
		}
		i++;
	}
	return p;
}

        3、创建新节点,存放待插入数据;

        4、重新连接节点,即插入新节点,先将上一个节点的next指针内容赋给新节点的next指针,再将新节点的地址赋给上一个节点的next指针(切勿将顺序搞反)。

//任意位置插入,传入链表地址,待插入数据,待插入位置
int list_insert(linklist H, data_t data, int pos) 
{
	linklist p;
	linklist new;
	if (H == NULL) 
	{
		printf("H is NULL\n");
		return -1;
	}
	p = list_getpos(H, pos-1);//找到待插入位置的上一个节点P
	if (p == NULL) return -1;//没有找到则返回

	if ((new = (linklist)malloc(sizeof(listnode))) == NULL)//创建待插入的新节点
	{
		printf("malloc failed\n");
		return -1;
	}
	new->data = data;//存放数据
	new->next = NULL;
//插入链表,注意先后次序,以免节点丢失
	new->next = p->next;
	p->next = new;
    H->data++;//插入数据后,数据个数加一 
	return 0;
}

3、单向链表节点删除

        删除节点也可以像插入一样,可进行尾部删除和任意位置删除的操作,尾部删除可对照插入进行,不再赘述。下面进行任意位置节点的删除操作,其基本步骤为:

        1、查找待删除节点的上一个节点;

        2、将待删除的next指针赋给上一个节点的next指针,这样便可以从链表上去掉待删除节点;

        3、然后将删除的节点释放掉内存即可。

int list_delete(linklist H, int pos) 
{
	linklist p;
	linklist deletenode;
	if (H == NULL) return -1;
	
	p = list_getpos(H, pos-1);//寻找待删除节点的上一个节点
	if (p == NULL) return -1;//没有找到则返回
	if (p->next == NULL) //如果要删除的节点不存在,则返回
	{
		printf("delete pos is invalid\n");
		return -1;
	}
	
	deletenode = p->next;//找到要删除的节点
	//将待删除的next指针赋给上一个节点的next指针
	p->next = deletenode->next;//也可以用p->next = p->next->next;
	//printf("free:%d\n", deletenode->data);
	//释放删除节点的内存
	free(deletenode);
	deletenode = NULL;

	H->data--;//删除节点后,数据个数减一 
	return 0;
}

4、链表打印

        我们需要查看链表时,就需要遍历打印出来,如下操作。

int list_show(linklist H) //链表打印显示
{
	linklist p;
	if (H == NULL)
	{
		printf("H is NULL\n");
		return -1;
	}
	p = H;
	while (p->next != NULL)//遍历打印
	{
		printf("%d ", p->next->data);
		p = p->next;
	}
	puts("");
	return 0;
}

5、释放链表

        当链表使用完成之后,需要释放其占用的内存。

int list_free(linklist H) 
{
	linklist p;
	if (H == NULL) return 0;//没有头节点(即链表),则不用释放
	
	p = H;
	//printf("free:");
	//头节点依次往后移动,然后将前面的删掉
	while (H != NULL) 
	{
		p = H;
		//printf("%d ", p->data);
		free(p);
		H = H->next;
	}
	puts("");
	return 0;
}

6、链表逆序

        链表反序主要有以下步骤:

        1、对当前链表的节点数进行判断(头节点不算),如果没有节点或者只有一个节点,则不需要逆序;

        2、将待逆序的链表的第二个及以后的部分分离,这样,待逆序链表只有头节点和第一个节点了,然后依次取出分离部分的头节点在待逆序链表的头部进行插入,便实现了逆序操作。

int list_reverse(linklist H) 
{
	linklist p;
	linklist q;
	if (H == NULL) 
	{
		printf("H is NULL\n");
		return -1;
	}
	//如果是空链表,或者是只有一个节点,则没有逆序的必要,返回
	if (H->next == NULL || H->next->next == NULL) return 0;
	
	//开始时将链表分为两段
	p = H->next->next;//p第二个节点及之后的节点
	H->next->next = NULL;//H只有第一个节点

	while (p != NULL) 
	{
		q = p;
		p = p->next;//p继续往后移
		
		q->next = H->next;//在H链表的头部进行插入
		H->next = q;
	}
	return 0;
}

 ......

三、链表测试

        通过以上链表的基本操作,已基本可以使用链表了,如下简单测试:

int main(void)
{
	linklist H;
	int value;
	H = list_create();//创建链表
	if (H == NULL)
		return -1;

	printf("input:");
	while (1) 
	{
		scanf("%d", &value);//输入要插入的值
		if (value  < 0)//输入负数退出尾部插入的操作
			break;
		list_tailinsert(H, value);//在链表尾部进行插入
		printf("input:");
	}
	list_show(H);//打印显示当前链表内容

	list_insert(H, 100, 1);//在1位置处插入数据为100的节点,位置从0开始算
    list_show(H);

    list_delete(H, 2);//删除位置2所在的节点
    list_show(H);

	printf("H=%p\n", H);//打印链表头节点地址
	H = list_free(H);//释放链表
	printf("H=%p\n", H);

	return 0;
}

总结

        链表作为一种灵活且高效的数据结构,在计算机科学的各个领域都有着广泛的应用,更多操作需要自己灵活展现。

有误之处望指正!!

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

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

相关文章

单细胞Seurat的umi矩阵-与feature、counts(用于质控)

目录 关于umi矩阵学习 用umi计算feature、counts值 ①meta数据查看 ②Count和Feature计算(生成Seurat时自动计算) 1)提取UMI矩阵 2)计算 其他指标 评估质量指标(重点) 1)UMI计数 2)基因计数 3)UMIs vs. genes detected 4)线粒体计数比率 5)综合过滤 过…

【C语言篇】文件操作(下篇)

文章目录 前言文件的顺序读写fscanf和fprintffread和fwrite 文件的随机读写fseekftellrewind 文件读取结束的判定容易被错误使用的feof 文件缓冲区 前言 本篇接上一篇文件操作&#xff08;上篇&#xff09;的内容 文件的顺序读写 在上一篇已经介绍了前面四个了&#xff0c;接…

【人工智能基础四】循环神经网络(RNN)与长短时记忆网络(LSTM)

文章目录 一. RNN1. 循环神经网络结构2. 循环神经网络计算2.1. 机器翻译2.2. 循环体 二. 长短时记忆网路&#xff08;LSTM&#xff09;1. 产生背景2. LSTM的设计思想与LSTM的链式结构2.1. LSTM的设计思想2.2. LSTM链式结构图与遗忘门 3. 长短时记忆网络结构 一. RNN RNN出现的…

五种多目标算法(MOGOA、MOMA、MODA、MOPSO、NSGA2)性能对比(MATLAB代码)

一、算法介绍 MOGOA&#xff1a;多目标蝗虫优化算法 MOMA&#xff1a;多目标蜉蝣算法 MODA&#xff1a;多目标蜻蜓算法 MOPSO&#xff1a;多目标粒子群优化算法 NSGA2&#xff1a;非支配排序遗传算法II 这些算法都是针对多目标优化问题设计的元启发式算法&#xff0c;每种…

Java | Leetcode Java题解之第321题拼接最大数

题目&#xff1a; 题解&#xff1a; class Solution {public int[] maxNumber(int[] nums1, int[] nums2, int k) {int m nums1.length, n nums2.length;int[] maxSubsequence new int[k];int start Math.max(0, k - n), end Math.min(k, m);for (int i start; i < e…

C语言 | Leetcode C语言题解之第321题拼接最大数

题目&#xff1a; 题解&#xff1a; int compare(int* subseq1, int subseq1Size, int index1, int* subseq2, int subseq2Size, int index2) {while (index1 < subseq1Size && index2 < subseq2Size) {int difference subseq1[index1] - subseq2[index2];if (…

Codeforces Round 963 (Div. 2) A~C

封面原图 画师やんよ 掉大分的一场 连夜补题 真的不会写啊真的红温了 A - Question Marks 题意 选择题中答案为ABCD的题目各有n道&#xff0c;小明的答案给你&#xff0c;其中&#xff1f;表示这道题空着没写&#xff0c;问他的最高得分 思路 空着的题目肯定没分 超出选项…

【连续数组】python刷题记录

R3-前缀和专题 绝对要用字典记录 ben神&#xff0c;前缀和字典 class Solution:def findMaxLength(self, nums: List[int]) -> int:#前缀和字典,key为差值&#xff0c;value为坐标dict{0:-1}#当前1和0的差值counter0ret0for i,num in enumerate(nums):#多1&#xff0b;1if…

服务器自动部署网络安装环境

实验环境 rhel7&#xff1a;IP地址为172.25.254.200、主机名为node1.rhel7.org 实验配置 一.kickstart自动安装脚本制作 1.安装图形化生成kickstart自动安装脚本的工具 [rootnode1 ~]# yum install system-config-kickstart 2. 启动图形制作工具 [rootnode1 ~]# system-…

[Meachines] [Easy] shocker CGI-BIN Shell Shock + Perl权限提升

信息收集 IP AddressOpening Ports10.10.10.56TCP:80,2222 $ nmap -p- 10.10.10.56 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) |_http-title: Site doesnt have a title (text/html). |_http-server-…

《集成学习实战》:解锁机器学习的智慧合力

《集成学习实战》&#xff1a;解锁机器学习的智慧合力 在机器学习的浩瀚领域中&#xff0c;集成学习以其独特的“集体智慧”理念脱颖而出&#xff0c;成为解决复杂问题、提升模型性能的强大工具。《集成学习实战》一书&#xff0c;正是这样一本引领读者深入探索集成学习奥秘的…

task1打卡:Linux

闯关任务 hello_world 任务1 Linux命令 任务2 conda创建虚拟环境并运行test.py 任务3 test.sh

Spark wordcount实验

Spark WordCount实验一 启动spark 1. 数据准备 创建建数据文件夹 进入data文件夹 创建文本文件 并查看是否创建成功 文件内容 查看文件目录 启动pyspark 3、输入代码 从本地读入文本数据 读入1中创建好的data.txt文本文件。 并计算打印结果 Spark WordCount实验二 切工作目…

链表(真题)

1.两两交换&#xff1a;给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。【2021计专】 struct ListNode* swapPairs(struct ListNode* head) {typedef struct ListNode LTNode;//设置虚拟头结点LTNode* dummyHead (LTNode*)malloc(siz…

浅谈哈希与哈希表(c++)

目录 一、哈希的基本概念&#xff08;一&#xff09;哈希函数的特性&#xff08;二&#xff09;哈希冲突 二、C 中的哈希表实现三、哈希表的性能分析四、哈希表的应用场景五、优化哈希表的策略六、例题讲解【模板】字符串哈希题目描述输入格式输出格式样例 #1样例输入 #1样例输…

Vue Amazing UI:高颜值、高性能的前端组件库

Vue Amazing UI&#xff1a;高颜值、高性能的前端组件库 在当今前端开发中&#xff0c;Vue Amazing UI 作为一款功能强大的 UI 组件库&#xff0c;为开发者提供了全面的解决方案。本文将介绍 Vue Amazing UI 的基本信息、特点以及如何快速部署和使用。 软件简介 Vue Amazing U…

解决方案:Cannot write to ‘torch-2.0.1+cu118-cp310-cp310-linux_x86_64.whl.3’ (成功).

文章目录 一、现象二、解决方案 一、现象 在服务器执行以下语句 wget https://download.pytorch.org/whl/cu118/torch-2.0.1%2Bcu118-cp310-cp310-linux_x86_64.whl报的错误如下&#xff1a; --2024-06-16 17:01:40-- https://download.pytorch.org/whl/cu118/torch-2.0.1%…

Visual Studio 配置Go开发环境

文章目录 安装Go配置环境变量Visual Studio 配置Go开发环境安装GO扩展安装/更新GO工具 编写Hello World 安装Go 本文基于Windows安装演示&#xff0c;安装链接Go安装包&#xff0c;根据需要选择对应安装包即可&#xff0c;没有需要直接根据系统选择最新安装包。 安装包长这样…

C语言程序设计-[3] 运算符和表达式

C语言的运算符也存在优先级和结合性的概念&#xff0c;在同一表达式中&#xff0c;优先级高的先结合&#xff0c;优先级相同时&#xff0c;就需要考虑结合性(分为左结合性和右结合性——对于单目、三目和赋值运算符表达式&#xff0c;从右至左运算&#xff1b;其他运算符表达式…

【Android】跨程序共享数据——内容提供器初识

跨程序共享数据——探究内容提供器 内容提供器的简介 主要用于在不同的应用程序之间实现数据共享的功能&#xff0c;它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是Android实现跨程序共享数据的标准方式。…