c++数据结构算法复习基础-- 2 -- 线性表-单链表-常用操作接口-复杂度分析

1、链表

特点

每一个节点都是在堆内存上独立new出来的,
节点内存不连续·

优点

内存利用率高,不需要大块连续内存
插入和删除节点不需要移动其它节点,时间复杂度O(1)。
不需要专门进行扩容操作

缺点

内存占用量大,每一个节点多出存放地址的空间。
节点内存不连续,无法进行内存随机访问
链表搜索效率不高,只能从头节点开始逐节点遍历

理解,链表存在的意义

假设下面这个方框代表我们计算机里边的内存,这里假设他只有100兆。
在这里插入图片描述
每一个程序运行以后变成进程,进程在运行的过程中,可能需要有这个分配内存的这么一些操作,那么这块儿内存呢,就被分配出去,多大呢,假设第一次分配30兆。
在这里插入图片描述

随着进程的运行,一会儿又有一块内存被分配出去,假设这个是20兆。
随着程序的运行呢,又有一部分内存,分配出去了,这一块儿又有十兆的空间被进程分配走了。
那么现在在这100兆的内存上,这内存都被进程在运行过程中分配走了。
在这里插入图片描述

内存有分配就有释放,那么这个内存它是一块儿一块儿紧挨着分配的,但是他释放,那就不一定是挨紧挨着一个一个释放了。
因为每一个内存都有它所属的应用的一些业务,这业务执行完了,这内存没有用了,他才释放。
那么这里假设40M的内存所属的这个业务,其直线的周期比较长,所以它释放的就慢。这个红色20M的跟这个绿色10M的这两块内存,他们所属的业务执行比较快,那这块内存就会先于40M释放掉了,我们应用程序都释放掉了,还有交还给系统了。
在这里插入图片描述

此时,就形成了所谓的内存的碎片,堆内存的碎片
我们这个内存现在剩余空间,看着100兆,应该是有20兆,加上十兆,他是总共有30兆。
如果我们的进程现在运行过程中,他需要一个线性表,来存储一组数据,他需要25兆的空间,但此时我们不能分配一个25兆空间的数组,为什么呢,因为数组要求内存绝对的、连续的,此时内存有空闲,有总共是20兆加10兆,也就是30兆的空间,看起来是可以分配25兆的,但是呢,你分配的是数组内存,他需要25兆内存,完全是连续的内存。此时就存在问题。
我们堆内存在通过malloc或者new来分配的时候,是这样连续分配的。但是呢,不同的内存块儿隶属于不同的业务,那业务执行的周期长短不一样,那么也就是说内存块分配的时候挨个分配,但是释放的时候却不一定是挨个儿释放的,哪个业务执行完了,相应的内存块儿会去释放掉内存,交还给这个系统,所以我们内存上就产生了碎片化
碎片化导致我们空闲的内存,没有办法连到一块儿。当然了,内核会进行内存碎片的一些整理,过多的细节这里暂时不讨论。总之,此时,我们在内存碎片比较多的情况下,我们去开辟大内存数组的时候,往往容易开辟失败,整个的空闲空间是有,但是连续的这么大的空间,他不一定有。所以此时呢,我们就有了链表它存在的意义。
数组:每一个元素的内存都是连续的,
链表:每一个节点都是独立分配的。
在这里插入图片描述
那么如何将各个独立的节点链接起来的。链表的结构体就设计成这样:
在这里插入图片描述

每个节点分为两部分,一部分称之为数据域,用于保存数据,一部分称之为地址域,用来保存下一个节点的地址。

2、数组和链表的区别

1)数组和链表的定义

数组的定义

数组是一组具有相同数据类型的变量的集合,这些变量称之为集合的元素

每个元素都有一个编号,称之为下标,可以通过下标来区别并访问数组元素,数组元素的个数叫做数据的长度

链表的定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表的特性是在中间任意位置插入和删除元素都非常快,不需要移动其它元素

对于单向链表而言,链表中的每一个元素都要保存一个指向下一个元素的指针

对于双向链表而言,链表中的每个元素既要保存指向下一个元素的指针,又要保存指向上一个元素的指针

对于双向循环链表而言,链表中的最后一个元素保存一个指向第一个元素的指针

2)数组和链表的区别

数组链表
(1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素(1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存
数组从栈上分配内存,使用方便,但是自由度小链表从堆上分配内存,自由度大,但是要注意内存泄漏
数组在内存中顺序存储,可通过下标访问,访问效率高链表访问效率低,如果想要访问某个元素,需要从头遍历
数组的大小是固定的,所以存在访问越界的风险只要可以申请得到链表空间,链表就无越界风险

3)数组和链表的使用场景

数组使用场景链表使用场景
数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储
数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构

原文链接:https://blog.csdn.net/weixin_42438797/article/details/115339605

3、单向链表常用操作接口

寻找尾结点思路:

先定义一个指针,让它指向头结点,
然后判断当前这个指针指向的结点的next域为不为空(不为空就说明他不是尾节点,为空了就说明他是尾结点,因为链表里边所有节点的地址域都不为空,除了尾节点。)
再遍历链表的每一个节点,让指针P从当前节点,跳到下一个节点,直到尾节点。

节点简单设计

struct Node
{
	Node(int data=0):data_(data),next_(nullptr){}
	int data_;
	Node* next_;
};

1)构造函数

Clink()
	{
		//给head_初始化指向头节点
		head_ = new Node();
	}

2)析构函数

思路图:

在这里插入图片描述

代码实现

	~Clink()
	{
		//节点的释放

		//错误示例
		//delete []head_;//链表不连续
		//head_ = nullptr;

		Node* p = head_;
		while(p != nullptr)
		{
			head_ = head_->next_;
			delete p;
			p = head_;
		}
		head_ = nullptr;
	}

3)尾插

思路图:

在这里插入图片描述

代码实现

	//链表的尾插法 O(n)
	void InsertTail(int val)
	{
		//先找到当前链表的末尾节点
		Node* p = head_;
		while(p->next_ != nullptr)
		{
			p = p->next_;
		}
		//生成新节点
		Node* node = new Node(val);
		//把新节点挂在尾节点的后面
		p->next_ = node;
	}

4)头插

思路图:

在这里插入图片描述

代码实现

	//链表的头插法 O(1)
	void InsertHead(int val)
	{
		Node* node = new Node(val);
		node->next_ = head_->next_;
		head_->next_ = node;

	}

5)链表的删除

删除第一个符合条件的节点:

思路图:
在这里插入图片描述
代码实现:

//链表节点的删除  O(n) + O(1)
	void Remove(int val)//这里删除值val的第一个节点
	{
		Node *p = head_;

		while(p->next_ != nullptr)
		{
			if(p->next_->data_ == val)
			{
				Node *q = p->next_;
				p->next_ = p->next_->next_;
				delete(q);
				return;//删除第一个
			}
			else
			{
				p=p->next_;
			}
			
		}
		/*
		Node *q = head_;
		Node *p = head_->next_;
		while(p != nullptr)
		{
			if(p->data_ == val)
			{
				q->next_ = p->next_;
				delete p;
				return;//删除第一个
			}
			else
			{
				q = p;
				p = p->next_;
			}
			
		}*/
	}

删除所有符合条件的节点:

思路图:
在这里插入图片描述
代码实现:

void RemoveALL(int val)//这里删除值val的所有节点
	{
		Node *p = head_;

		while(p->next_ != nullptr)
		{
			if(p->next_->data_ == val)
			{
				Node *q = p->next_;
				p->next_ = p->next_->next_;
				delete(q);
				//return;//删除第一个
			}
			else
			{
				p=p->next_;
			}
			
		}
		/*
		Node *q = head_;
		Node *p = head_->next_;
		while(p != nullptr)
		{
			if(p->data_ == val)
			{
				q->next_ = p->next_;
				delete p;
				//对指针p进行重置
				p = q->next_;
			}
			else
			{
				q = p;
				p = p->next_;
			}
			
		}*/
	}

6)单链表的搜索接口简单实现

链表搜索 list O(n)
区别于
数组的下标访问/随机访问 O(1) 搜索 O(n)

思路图

在这里插入图片描述

代码实现:

	//链表搜索 list O(n) 区别于数组的下标访问/随机访问 O(1)  搜索 O(n)
	bool Find(int val)//这里返回bool
	{
		Node* p = head_->next_;
		while(p != nullptr)
		{
			if(p->data_ == val)
			{
				return true;
			}
			else
			{
				p = p->next_;
			}
		}
		return false;
	}

7)测试,以及完整代码

单链表接口简单实现

#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <string.h>
using namespace std;

//节点类型
struct Node
{
	Node(int data=0):data_(data),next_(nullptr){}
	int data_;
	Node* next_;
};

//单链表代码实现
struct Clink
{
public:
	Clink()
	{
		//给head_初始化指向头节点
		head_ = new Node();
	}
	~Clink()
	{
		//节点的释放

		//错误示例
		//delete []head_;//链表不连续
		//head_ = nullptr;

		Node* p = head_;
		while(p != nullptr)
		{
			head_ = head_->next_;
			delete p;
			p = head_;
		}
		head_ = nullptr;
	}

public:
	//链表的尾插法 O(n)
	void InsertTail(int val)
	{
		//先找到当前链表的末尾节点
		Node* p = head_;
		while(p->next_ != nullptr)
		{
			p = p->next_;
		}
		//生成新节点
		Node* node = new Node(val);
		//把新节点挂在尾节点的后面
		p->next_ = node;
	}
	//链表的头插法 O(1)
	void InsertHead(int val)
	{
		Node* node = new Node(val);
		node->next_ = head_->next_;
		head_->next_ = node;

	}
	//链表节点的删除
	void Remove(int val)//这里删除值val的第一个节点
	{
		Node *p = head_;

		while(p->next_ != nullptr)
		{
			if(p->next_->data_ == val)
			{
				Node *q = p->next_;
				p->next_ = p->next_->next_;
				delete(q);
				return;//删除第一个
			}
			else
			{
				p=p->next_;
			}
			
		}
		/*
		Node *q = head_;
		Node *p = head_->next_;
		while(p != nullptr)
		{
			if(p->data_ == val)
			{
				q->next_ = p->next_;
				delete p;
				return;//删除第一个
			}
			else
			{
				q = p;
				p = p->next_;
			}
			
		}*/
	}

	void RemoveALL(int val)//这里删除值val的所有节点
	{
		Node *p = head_;

		while(p->next_ != nullptr)
		{
			if(p->next_->data_ == val)
			{
				Node *q = p->next_;
				p->next_ = p->next_->next_;
				delete(q);
				//return;//删除第一个
			}
			else
			{
				p=p->next_;
			}
			
		}
		/*
		Node *q = head_;
		Node *p = head_->next_;
		while(p != nullptr)
		{
			if(p->data_ == val)
			{
				q->next_ = p->next_;
				delete p;
				//对指针p进行重置
				p = q->next_;
			}
			else
			{
				q = p;
				p = p->next_;
			}
			
		}*/
	}
	//搜索 list O(n)
	bool Find(int val)//这里返回bool
	{
		Node* p = head_->next_;
		while(p != nullptr)
		{
			if(p->data_ == val)
			{
				return true;
			}
			else
			{
				p = p->next_;
			}
		}
		return false;
	}
	
	//链表打印
	void show()
	{
		Node *p = head_->next_;
		//while(p->next != nullptr) //error
		while(p != nullptr)
		{
			cout<< p->data_ << " ";
			p=p->next_;
		}
		cout << endl;
	}

private:
	Node* head_;//指向链表的头节点
};

测试用例

int main()
{
	Clink link;
	srand(time(0));
	for(int i = 0; i<10; i++)
	{
		int val = rand() % 100;
		link.InsertTail(val);
		cout << val << " ";
	}
	cout << endl;

	//测试头插
	link.show();
	cout << endl;

	//测试尾插
	link .InsertTail(10);
	link .InsertTail(20);
	link .InsertTail(20);
	link .InsertTail(10);
	link .InsertTail(20);
	link.show();
	cout << endl;

	//测试删除第一个节点
	link.Remove(10);
	link.show();
	cout << endl;

	//测试删除所有节点
	link.RemoveALL(20);
	link.show();
	cout << endl;

	//测试搜索
	if(link.Find(10))
	{
		cout <<"find"<<endl;
	}
	else
	{
		cout <<"false"<<endl;
	}

}

运行结果:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode238题:除自身以外数组的乘积(python3)

代码思路&#xff1a; 当前位置的结果就是它左部分的乘积再乘以它右部分的乘积&#xff0c;因此需要进行两次遍历&#xff0c;第一次遍历求左部分乘积&#xff0c;第二次遍历求右部分的乘积&#xff0c;再将最后的计算结果一起求出来。 class Solution:def productExceptSelf(…

【力扣 - 杨辉三角】

题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]] 提示: 1 < numRows < 30 方法一&#xff1a;数学 思路…

【免费】两阶段鲁棒优化matlab实现——CCG和benders

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序采用matlab复现经典论文《Solving two-stage robust optimization problems using a column-and-constraint generation method》算例&#xff0c;实现了C&CG和benders算法两部分内容&#xff0c;通过…

93. 递归实现组合型枚举 刷题笔记

与我前面发的递归实现那一题有点相似 可以看看 94. 递归实现排列型枚举 刷题笔记-CSDN博客 思路 用u记录 选到哪一个位置 一旦选完 就输出 该题 要求升序 我们在选数时加入一个条件 大于上一个选择的数即可 依旧是从小到大搜到符合条件的每一个数 代码 #include<…

安防视频监控EasyCVR平台使用GB28181协议接入时,如何正确配置端口?

国标GB28181协议EasyCVR安防视频监控平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流…

多线程万字详解

进程和线程是计算机程序执行的两个重要概念。 1.进程&#xff1a; 进程是操作系统分配资源的基本单位&#xff0c;每个进程都有自己独立的地址空间&#xff0c;每启动一个进程&#xff0c;系统就会为它分配内存。进程间通信比较复杂&#xff0c;需要用到IPC&#xff08;InterP…

Day07:基础入门-抓包技术全局协议封包监听网卡模式APP小程序PC应用

目录 非HTTP/HTTPS协议抓包工具 WireShark 科来网络分析系统 WPE封包 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件上传下载/端口服务/Sh…

Vue3使用JSX/TSX

文章目录 1. 什么是 JSX & TSX?JSX&#xff08;JavaScript XML&#xff09;TSX&#xff08;TypeScript XML&#xff09; 2.Vue3 中使用 TSX基本渲染 & 响应式 & 事件 3.JSX 和 template 哪个好呢&#xff1f;总结 1. 什么是 JSX & TSX? 提示&#xff1a;JSX…

springboot231基于SpringBoot+Vue的乡政府管理系统

乡政府管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装乡政府管理系统软件来发挥其高效…

LeetCode:2867. 统计树中的合法路径数目(筛质数+ DFS Java)

目录 2867. 统计树中的合法路径数目 题目描述&#xff1a; 实现代码与思路&#xff1a; 筛质数 DFS 原理思路&#xff1a; 2867. 统计树中的合法路径数目 题目描述&#xff1a; 给你一棵 n 个节点的无向树&#xff0c;节点编号为 1 到 n 。给你一个整数 n 和一个长度为 …

市场复盘总结 20240229

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 60% 最常用…

LeetCode 刷题 [C++] 第102题.二叉树的层序遍历

题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 题目分析 题目中要求层序遍历二叉树&#xff0c;即二叉树的广度优先搜索(BFS)。BFS一般使用队列的先入先出特性实现&#…

弹窗内容由后端返回,如何让点击按钮的事件交由前端控制?

一、场景 背景&#xff1a;因为系统里经常有新活动或者公告需要通知所有用户&#xff0c;希望前端维护的这个弹窗里的内容可以由后端接口返回。这样就不需要每次上新活动的时候&#xff0c;前端项目都发版了。因此&#xff0c;前端维护了这个弹窗和它的关闭事件&#xff0c;至…

SDWAN异地组网难在哪?怎么解决?

SD-WAN作为一种先进的网络技术&#xff0c;为企业提供了更加灵活和高效的网络连接方案。然而&#xff0c;在异地组网的过程中&#xff0c;SD-WAN也面临一些挑战。本文将探讨SD-WAN异地组网所面临的难题&#xff0c;并提供相应的解决方案。 挑战一&#xff1a;网络延迟和不稳定性…

fork创建子进程及僵尸进程的产生及规避

本篇文章的学习与总结来源于 https://www.bilibili.com/cheese/play/ep182659?csourcecommon_hp_history_null&t3&spm_id_from333.1007.top_right_bar_window_history.content.click 通常使用fork()函数产生新的子进程&#xff0c;需要包含两个头文件<sys/types.h…

在Windows中安装PyTorch

文章目录 1. 创建虚拟环境2. 检查显卡版本和CUDA3. 下载链接4. 下载5. 等待6. 检测 1. 创建虚拟环境 具体查看我之前写的 《在Windows中利用Python的venv和virtualenv创建虚拟环境》 2. 检查显卡版本和CUDA 这种情况是需要电脑上有单独的英伟达的显卡、或者英伟达的显卡和集显…

Rocky Linux 运维工具 mv

一、mv的简介 ​​mv​是Linux系统中的命令&#xff0c;用于移动文件或重命名文件。它可以在同一文件系统内将文件从一个目录移动到另一个目录&#xff0c;也可以修改文件的名称。 二、mv的参数说明 1、 三、mv的实战示例 1、重命名 ###查看目录/root/下的文件列表 [rootloc…

Aws Ec2服务器设置密码登录

通过密钥&#xff0c;ssh登录到服务器 切换到root sudo -i开始设置root的新密码 passwd root输入并确认新密码即可 5.修改ssh配置文件 vim /etc/ssh/sshd_config6.重启sshd配置 systemctl restart sshd

Linux:Makefile的相关知识

背景&#xff1a; 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的 规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复…

周鸿祎首堂免费课与千万网友分享“AGI趋势”

“我讲课不割韭菜&#xff0c;宗旨是免费、分享、科普、交流。AI时代技术发展迅速&#xff0c;AI知识普及尤为重要。”2月29日&#xff0c;360公司创始人周鸿祎免费课正式开启&#xff0c;全网多平台直播了AI系列第一讲“预见AGI”&#xff0c;千万网友观看。免费课上&#xff…