链表和顺序表的优劣分析及其时间、空间复杂度分析

news/发布时间2024/5/24 6:12:23

链表和顺序表的优劣分析及其时间、空间复杂度分析

  • 一、链表和顺序表的优劣分析
  • 二、算法复杂度
    • <font face = "楷体" size = 5 color = blue>//上面算法的执行次数大致为:F(N) = N^2+2*N+10;   N =10,F(10) = 100+20+10 = 130次   N =100,F(100) = 10000+200+10 = 10210次   N =1000,F(10) = 100000+2000+10 = 1002010次 实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。 </font> <font face = "楷体" size = 5 color = blue>//大O渐进表示法

一、链表和顺序表的优劣分析

1.顺序表的优劣分析
//优势:
//*下标可以随机访问:顺序表的底层是数据,可以下标随机访问!
//CPU高速缓存命中率高(这个还是很重要!)
可以看这篇文章,理解CPU缓存知识!
什么是缓存?
  缓存是一种在计算机系统中用于存储数据的硬件或软件组件,它的主要目的是提高数据访问的速度和效率。缓存通常位于CPU内部或主存储器和CPU之间,它能够快速地存储和检索经常访问的数据和指令。当CPU需要访问数据或指令时,它会首先检查缓存中是否已经存在相应的数据或指令,如果存在,则可以直接从缓存中读取,这样就避免了从主存储器中读取数据或指令的延迟。
  缓存的设计允许它牺牲数据的实时性,以空间换时间,从而减少数据库的IO操作,减轻服务器压力,减少网络延迟,加快页面打开速度。缓存可以分为不同的层次,如CPU缓存、操作系统文件缓存和浏览器缓存等。
  CPU缓存分为L1 Cache(一级缓存)和L2 Cache(二级缓存),它们位于CPU内部,用于存储CPU即将使用的指令和数据。操作系统文件缓存用于存储操作系统读取的文件数据,而浏览器缓存则用于存储用户访问的静态或动态页面内容,以便在用户再次访问时能够快速加载。
  总的来说,缓存是一种重要的性能优化技术,它通过将数据存储在更接近处理器或数据源的位置,提高了数据处理的效率和响应速度。

1
//大概意思是在CPU附近会有三级缓存(L1,L2,L3)
//L1缓存分为两种(指令缓存和数据缓存),L2和L3缓存不分指令和数据
//L1和L2缓存在每一个CPU核中,L3则是所有CPU核心共享的内存。
//L1、L2、L3的越离CPU近就越小,速度也越快,越离CPU远,速度也越慢。
//缓存是速度最快的存储数据的地方
//后面存储数据的地方就是存储器和磁盘
//下面看看各个存储硬件的存储速度
L1 的存取速度:4 个CPU时钟周期
L2 的存取速度:11个CPU时钟周期
L3 的存取速度:39个CPU时钟周期
RAM内存的存取速度:107 个CPU时钟周期

//缓存命中问题!
//我们需要要解一个术语 Cache Line。缓存基本上来说就是把后面的数据加载到离自己近的地方,对于CPU来说,它是不会一个字节一个字节的加载的,因为这非常没有效率,一般来说都是要一块一块的加载的,对于这样的一块一块的数据单位,术语叫“Cache Line”,一般来说,一个主流的CPU的Cache Line 是 64 Bytes(也有的CPU用32Bytes和128Bytes),64Bytes也就是16个32位的整型,这就是CPU从内存中捞数据上来的最小数据单位。
//也就是说CPU在取数据计算的时候至少是取一个缓存行(64Bytes)的大小,这样,顺序表的物理地址连续,加载到缓存中,CPU取到的都是顺序表中存的有效数据,而链表的物理空间不连续,大概率只能取到一个有效数据,其它供CPU计算的数据没用!所谓的缓存命中不高!
//缺点
//前面部分的插入,删除,需要挪动数据,效率低下
//扩容(效率损失,可能还存在一定的空间浪费)

---------------------------------------
2.链表(最优结构的比较)的优劣分析
//优势:
//任意位置的插入,删除效率都很高
//按需申请释放,不存在浪费
//劣势:
//不支持下标随机访问
//CPU高速缓存命中率低可以理解为 cache line 取数据问题

二、算法复杂度

//算法在编写成可执行程序后,运行时需要消耗时间资源和(内存)空间资源,衡量一个算法的好坏,要存运行时间的多少和空间消耗大小来衡量一个算法的好坏!!!
//时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

时间复杂度
时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个
分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度

// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{for (int j = 0; j < N ; ++ j){++count;}
}for (int k = 0; k < 2 * N ; ++ k)
{++count;
}int M = 10;
while (M--)
{++count;
}

//上面算法的执行次数大致为:F(N) = N^2+2*N+10;
  N =10,F(10) = 100+20+10 = 130次
  N =100,F(100) = 10000+200+10 = 10210次
  N =1000,F(10) = 100000+2000+10 = 1002010次
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。

//大O渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。 推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。 使用大O的渐进表示法以后,Func1的时间复杂度为:
N= 10  F(N) = 100
N = 100  F(N) = 10000
N = 1000  F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界) 例如:在一个长度为N数组中搜索一个数据x 最好情况:1次找到 最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

//考虑最坏情况来写时间复杂度
//上面时间复杂度为:O(N^2)

// 计算Func2的时间复杂度?
void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N ; ++ k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}

//时间复杂度:O(N)

// 计算Func3的时间复杂度?
void Func3(int N, int M)
{int count = 0;for (int k = 0; k < M; ++ k){++count;}for (int k = 0; k < N ; ++ k){++count;}printf("%d\n", count);
}

//时间复杂度:O(M+N)

// 计算Func4的时间复杂度?
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++ k){++count;}printf("%d\n", count);
}

//O(1) --常数次用O(1)表示

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

//冒泡的时间复杂度怎么算?
//这个还是想一下,假设有N个元素,最坏排序多少趟能排好?
// N-1
// N-2
// N-3
// …
// 3
// 2
// 1
//总共执行了多少次?
// (N-1) * (1+N-1)/2 = (N-1) * N/2 = 1/2*(N^2-N);
// 则冒泡排序的时间复杂度:O(N^2)

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid-1;elsereturn mid;}return -1;
}

//二分查找的时间复杂度为多少?
//怎么理解二分查找算法的时间复杂度?
//假设有一个长度为N的数组查找一个数,最坏情况是什么?
//最坏情况是:查找的数只剩一个,或没有,二分查找的实质是每次缩小一半区间来查找!
//查找N个数,则每查找一次,少一半的数
则:N/2/2/2…/2 = 1;区间缩小了多少次?也就是除了多少个2,就是查找了几次!
//设除了x个2
N/(2^x) = 1;
N = 2^x;
x = logN
//所以二分查找算法最坏情况下查找log以2为底N的对数!
//时间复杂度为:O(logN);
1

-----------------------------------------
计算下面递归阶乘的时间复杂度?

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if(0 == N)return 1;return Fac(N-1)*N;
}

//所谓的递归多次开辟函数栈桢的过程,那么看一次开辟的栈桢执行程序是多少次,总共N次的递归的时间复杂度!
1

long long F(size_t N)
{if (0 == N)return 1;for (int i = 0; i < N; i++){//...}return F(N - 1) * N;
}

//计算上面算法的时间复杂度!
//上面函数栈桢开辟了N+1次,每一次是O(N),所以总共是O(N^2)


//总结一下递归算法计算时间复杂度,每次递归子函数消耗累加起来!

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{if(N < 3){return 1;}return Fib(N-1) + Fib(N-2);
}

1
1
1
// 2^0
// 2^1
// 2^2
// 2^3
// 2^(N-1)
//: F(n) =2^0 + 2^1 + 2^2 +…+ 2^(N-1)
//:2*F(n) =2^1 + 2^2 + 2^3 +…+ 2^N
//:F(n) = 2^N-1
//:所以斐波那契的时间复杂度:O(2^N)

什么是空间复杂度!
  空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
  空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数
空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

//空间复杂度,算的是因为算法的需要,额外开的空间!!!

// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]);exchange = 1;}}if (exchange == 0){break;}
}

//在上面的算法中额外开辟的空间变量有:end,i,exchange,说一点在这儿i虽然开辟了n次,但是这儿申请,释放的空间是同一块空间,所以没有额外开辟空间!!!
//所以此算法的空间复杂度是O(N);

-----------------------------------------

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if(N == 0)return 1;return Fac(N-1)*N;}

在这里插入图片描述

//空间复杂度就是计算算法额外开辟的空间!
//此递归,总共递归了(N+1)次,每次是O(1),总共是O(N);

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{if(N < 3){return 1;}return Fib(N-1) + Fib(N-2);

//斐波那契递归的空间复杂度怎么算?
//这个比较复杂一点,要理解一点函数栈桢的开辟才能做好!!!

1
//综上分析:虽然这个递归每一层很复杂,但是利用的空间只有一个子函数的空间,因此斐波那契的空间复杂度是O(N)


完结!!!

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

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

C++基础知识(六:继承)

首先我们应该知道C的三大特性就是封装、继承和多态。 此篇文章将详细的讲解继承的作用和使用方法。 继承 一个类&#xff0c;继承另一个已有的类&#xff0c;创建的过程 父类(基类)派生出子类(派生类)的过程 继承提高了代码的复用性 【1】继承的格式 class 类名:父类名 {}; 【…

深入浅出:探究过完备字典矩阵

在数学和信号处理的世界里&#xff0c;我们总是在寻找表达数据的最佳方式。在这篇博文中&#xff0c;我们将探讨一种特殊的矩阵——过完备字典矩阵&#xff0c;这是线性代数和信号处理中一个非常有趣且实用的概念。 什么是过完备字典矩阵&#xff1f; 首先&#xff0c;我们先…

消息中间件篇之RabbitMQ-信息堆积

一、信息堆积 当生产者发送消息的速度超过了消费者处理消息的速度&#xff0c;就会导致队列中的消息堆积&#xff0c;直到队列存储消息达到上限。之后发送的消息就会成为死信&#xff0c;可能会被丢弃&#xff0c;这就是消息堆积问题。 解决消息堆积有三种种思路&#xff1a; 1…

精品基于SpringBoot的体育馆场地预约赛事管理系统的设计与实现-选座

《[含文档PPT源码等]精品基于SpringBoot的体育馆管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#…

StarRocks——滴滴OLAP的技术实践与发展方向

原文大佬的这篇StarRocks实践文章整体写的很深入&#xff0c;介绍了StarRocks数仓架构设计、物化视图加速实时看板、全局字典精确去重等内容&#xff0c;这里直接摘抄下来用作学习和知识沉淀。 目录 一、背景介绍 1.1 滴滴OLAP的发展历程 1.2 OLAP引擎存在的痛点 1.2.1 运维…

[ 2024春节 Flink打卡 ] -- 优化(draft)

2024&#xff0c;游子未归乡。工作需要&#xff0c;flink coding。觉知此事要躬行&#xff0c;未休&#xff0c;特记 资源配置调优内存设置 TaskManager内存模型 https://nightlies.apache.org/flink/flink-docs-release-1.18/docs/deployment/config/ TaskManager 内存模型…

『SD』零基础快速搭建Stable Diffusion(Windows版)

theme: smartblue 点赞 关注 收藏 学会了 本文简介 本文介绍如何在 Windows 安装 Stable Diffusion WebUI&#xff0c;不需要懂代码&#xff0c;只要跟着本文一步步操作就能在你电脑用AI绘画了。 只需3步&#xff1a; 安装 Python &#xff0c;版本需要大于 3.10安装 Stable…

在autodl搭建stable-diffusion-webui+sadTalker

本文介绍在autodl.com搭建gpu服务器&#xff0c;实现stable-diffusion-webuisadTalker功能&#xff0c;图片音频 可生成视频。 autodl租GPU 自己本地部署SD环境会遇到各种问题&#xff0c;网络问题&#xff08;比如huggingface是无法访问&#xff09;&#xff0c;所以最好的方…

idm直链怎么用 IDM直链下载风险 Internet Download Manager下载 idm官网登录 idm直链提取

作为一款备受好评的下载加速器&#xff0c;idm几乎可以胜任所有的下载场景。无论是直链资源、磁力链接还是种子文件&#xff0c;idm均能把它们高速下载到本地保存。并且&#xff0c;理论上idm可以将下载速度提高3至5倍。有关idm直链怎么用&#xff0c;IDM直链下载风险的相关问题…

网络编程、UDP、TCP

计算机网络 就是将地理位置不同的具有独立功能的多台计算及外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统、网络管理软件以及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统 目的 传播交流信息、数据交换、通信 如何做…

【Ubuntu】解决Ubuntu 22.04开机显示器颜色(高对比度/反色)异常的问题

使用Ubuntu 22.04时强制关机了一下&#xff08;make -j16把电脑搞崩了&#xff09;&#xff0c;开机后系统显示的颜色异常&#xff0c;类似高对比度或反色&#xff0c;如下图。看着很难受&#xff0c;字体也没办法辨认。还好之前遇到过类似的问题&#xff0c;应该是一个配置文件…

【C语言】linux内核ipoib模块 - ipoib_ib_post_receive

一、中文注释 用于以太网接口&#xff08;InfiniBand&#xff09;上的IP over IB&#xff08;IPoIB&#xff09;设备的Linux内核函数&#xff0c;负责将接收缓冲区&#xff08;一个包&#xff09;提交到网络设备的队列中等待数据到达。下面是中文注释版本的函数代码&#xff1…

Linux之权限管理

目录 一. chmod 二. ACL 2.1 什么是ACL权限 2.2 操作步骤 2.2.1 添加测试目录、用户、组&#xff0c;并将用户添加到组 2.2.2 修改目录的所有者和所属组 2.2.3 设定权限 2.2.4 为临时用户分配权限 r-x 2.2.5 验证acl权限 2.2.6 控制组的acl权限 一. chmod Linux chmod…

计算机网络面经-从浏览器地址栏输入 url 到显示主页的过程?

大概的过程比较简单&#xff0c;但是有很多点可以细挖&#xff1a;DNS解析、TCP三次握手、HTTP报文格式、TCP四次挥手等等。 DNS 解析&#xff1a;将域名解析成对应的 IP 地址。TCP连接&#xff1a;与服务器通过三次握手&#xff0c;建立 TCP 连接向服务器发送 HTTP 请求服务器…

pikachu靶场-File Inclusion

介绍&#xff1a; File Inclusion(文件包含漏洞)概述 文件包含&#xff0c;是一个功能。在各种开发语言中都提供了内置的文件包含函数&#xff0c;其可以使开发人员在一个代码文件中直接包含&#xff08;引入&#xff09;另外一个代码文件。 比如 在PHP中&#xff0c;提供了&…

【无刷电机学习】各种电机优势比较

目录 0 参考出处 1 有刷与无刷比较 2 交流与直流比较 3 内转子与外转子比较 4 Delta型与Y型定子绕向比较 5 低压BLDC的一些优点 0 参考出处 【仅作自学记录&#xff0c;不出于任何商业目的。如有侵权&#xff0c;请联系删除&#xff0c;谢谢&#xff01;】 维基百科…

STP基本计算过程——选举网段的指定端口

点赞关注&#xff0c;持续更新STP专题 选举网段的指定端口 STP为每个网段选出一个指定端口&#xff08;Designated Port&#xff09;&#xff0c;指定端口为每个网段转发发往根交换机方向的数据&#xff0c;并且转发由根交换机方向发往该网段的数据。指定端口所在的交换机称为…

qtday2作业

思维导图 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;…

SparkSQL学习01

目录 1.SparkSQL特点1.1易整合1.2统一的数据访问1.3兼容Hive1.4标准的数据连接 2 SparkSQL编程模型DataFrameDataSet2.1 SQL2.2 DataFrame是什么2.3 DataSet是什么2.4 RDD&#xff0c;DataSet&#xff0c;DataFrame 3 SparkSQL核心编程3.1 编程入口3.2 SparkSQL基本编程3.2.1编…

有方机器人 STM32智能小车 项目学习笔记1

今天开始学习有方机器人--智能小车项目&#xff0c;正点原子部分的学习先放一放&#xff0c;还是小车更有吸引力哈哈。 新建工程及工程模板搭建 新建工程须知 目前常用的 STM32 的开发方式主要有基于寄存器编程、基于标准库函数编程、基于 HAL 库编程这三种。 寄存器版本--…
推荐文章