数据结构为何重要(《数据结构与算法图解》by 杰伊•温格罗)

news2024/11/26 19:47:10

本文内容借鉴一本我非常喜欢的书——《数据结构与算法图解》。学习之余,我决定把这本书精彩的部分摘录出来与大家分享。


基础数据结构:数组

数组是计算机科学中最基本的数据结构之一。如果你用过数组,那么应该知道它就是一个含有

数据的列表。它有多种用途,适用于各种场景,下面就举个简单的例子。

在一个超市的应用软件中,其源代码可能会包含以下片段:

char* array[] = { "apples", "bananas", "cucumbers", "dates", "elderberries" };

这就是一个数组,它刚好包含 5 个字符串,每个代表我会从超市买的食物。

此外,我们会用一些名为索引的数字来标识每项数据在数组中的位置。

在大多数的编程语言中,索引是从 0 算起的,因此在这个例子中, "apples" 的索为

0,"elderberries" 的索引为 4,如下所示。

若想了解某个数据结构(例如数组、集合、链表、栈和队列)的性能,得分析程序怎样操作这一

数据结构。一般数据结构都有以下 4种操作。

①读取:查看数据结构中某一位置上的数据。

② 查找:从数据结构中找出某个数据值的所在。

③插入:给数据结构增加一个数据值。

④删除:从数据结构中移走一个数据值。

本章我们将会研究这些操作在数组上的运行速度。

请切记贯穿本书的一个重要理论: 操作的速度并不按照时间计算,而是按照步数计算。

为什么呢?

简单理解为:同样的一个操作,在我这用了好久的二手笔记本上可能需要跑10秒,但在一台超级量

子计算机上可能用“一瞬间”的时间。

所以,评估一个操作的快慢不能依赖于运行时间,而是它操作的步数。

此外,操作的速度,又称为时间复杂度。

接下来我们就一起看看上述四种操作在数组上要花费多少步。


1.读取

读取,即查看数组中某个索引所指的数据值。

这只要一步就够了,因为计算机本身就有跳到任一索引位置的能力。

例如:

printf("%s\n", array[0]);//打印数组索引0处的值,其值应为apples
printf("%s\n", array[2]);//打印数组索引2处的值,其值应为cucumbers

计算机为什么能一步到位呢?原因如下。

计算机的内存可以被看成一堆格子,格子中的数字代表每个格子的地址编号(就像宿舍楼里,每个

宿舍都有各自的宿舍号),且这些地址是连续的。

当程序声明一个数组时,它会先划分出一些连续的空格子以备使用。换句话说,如果你想创建一个

包含 5个元素的数组,计算机就会找出 5个排成一行的空格子,将其当成数组。

购物清单数组的索引和内存地址,如下图所示。

计算机之所以在读取数组中某个索引所指的值时,能直接跳到那个位置上,是因为它具备以下条件。

(1) 计算机可以一步就跳到任意一个内存地址上。(就好比,要是你知道大街 123号在哪儿,那么就可以直奔过去。

(2) 数组本身会记有第一个格子的内存地址,因此,计算机知道这个数组的开头在哪里。

(3) 数组的索引从 0算起。

回到刚才的例子,当我们叫计算机读取索引 2 的值时,它会做以下演算。

(1) 该数组的索引从 0算起,其开头的内存地址为 1010。

(2) 索引 2 在索引 0 后的第 2 个格子上。

(3) 于是索引 2 的内存地址为 1012,因为 1010 + 2 = 1012。

当计算机一步跳到 1013时,我们就能获取到 "cucumbers" 这个值了。 

所以,数组的读取是一种非常高效的操作,因为它只要一步就好。一步自然也是最快的速度。

这种一步读取任意索引的能力,也是数组好用的原因之一。


2.查找

如前所述,对于数组来说,查找就是检查它是否包含某个值,如果包含,还得给出其索引。那么,

我们就试试在数组中查找 "dates" 要用多少步。

对于我们人来说,可以一眼就看到这个购物清单上的 "dates" ,并数出它的索引为 3。

但是,计算机并没有眼睛,它只能一步一步地检查整个数组。想要查找数组中是否存在某个值,计

算机会先从索引 0开始,检查其值,如果不匹配,则继续下一个索引,以此类推,直至找到为止。

我们用以下图来演示计算机如何从购物清单中查找 "dates" 。

首先,计算机检查索引 0。

因为索引 0的值是 "apples" ,并非我们所要的 "dates" ,所以计算机跳到下一个索引上。

索引 1也不是 "dates" ,于是计算机再跳到索引 2。

但索引 2 的值仍不匹配,计算机只好再跳到下一格。

到此为止,"dates"已经被找到,我们返回它的值以及它的索引 3 。

在这个例子中,因为我们检查了 4个格子才找到想要的值,所以这次操作总计是 4步。

这种逐个格子去检查的做法,就是最基本的查找方法——线性查找

到此为止,我们再思考一下,在数组上进行线性查找最多要多少步呢? 

如果我们要找的值刚好在数组的最后一个格子里(如本例的 elderberries ),那么计算机从头到尾

检查每个格子,会在最后才找到。

同样,如果我们要找的值并不存在于数组中,那么计算机也还是得查遍每个格子,才能确定这个值

不在数组中。

于是,一个 5格的数组,其线性查找的步数最大值是 5,而对于一个 500格的数组,则是 500。

以此类推,一个 N格的数组,其线性查找的最多步数是 N(N可以是任何自然数)

可见,无论是多长的数组,查找都比读取要慢,因为读取永远都只需要一步,而查找却可能需要多

步。

char* array[] = { "apples", "bananas", "cucumbers", "dates", "elderberries" };
//查找dates
int sz = sizeof(array) / sizeof(array[0]);
for (int i = 0; i < sz; i++)
{
	if (strcmp("dates", array[i])==0)
	{
		printf("找到了,其索引为:%d\n", i);
		break;
	}
}

3.插入

往数组里插入一个新元素的速度,取决于你想把它插入到哪个位置上。

假设我们想要在购物清单的末尾插入 "figs" 。那么只需一步(回忆计算机访问内存的特点)。

char* array[6] = { "apples", "bananas", "cucumbers", "dates", "elderberries" };
//在末尾插入"figs"
array[5] = "figs";

但在数组开头或中间插入,就另当别论了。这种情况下,我们需要移动其他元素以腾出空间,

于是得花费额外的步数。

例如往索引 2处插入 "figs" ,如下所示。

第 1步: "elderberries" 右移。 

第 2步: "date" 右移。 

第 3步: "cucembers" 右移。

第 4步:至此,可以在索引 2处插入 "figs" 了。 

如上所示,整个过程有 4步,开始 3步都是在移动数据,剩下 1步才是真正的插入数据。

最低效(花费最多步数)的插入是插入在数组开头。因为这时候需要把数组所有的元素都往右移。

于是,一个含有 N个元素的数组,其插入数据的最坏情况会花费 N + 1步。即插入在数组开头,导

致 N次移动,加上一次插入。


4.删除

数组的删除就是消掉其某个索引上的数据。

我们找回最开始的那个数组,删除索引 2上的值,即 "cucumbers" 。

第 1步:删除 "cucumbers" 。

虽然删除 "cucumbers" 好像一步就搞定了,但这带来了新的问题:

数组中间空出了一个格子。因为数组中间是不应该有空格的,所以,我们得把 "dates" 和 "elderberries" 往左移。 

第 2步:将 "dates" 左移。

第 3步:将 "elderberries" 左移。 

结果,整个删除操作花了 3步。其中第 1步是真正的删除,剩下的 2步是移数据去填空格。

所以,删除本身只需要 1步,但接下来需要额外的步骤将数据左移以填补删除所带来的空隙。跟插

入一样,删除的最坏情况就是删掉数组的第一个元素。因为数组不允许空元素,当索引0空出,那

么剩下的所有元素都要往左移去填空。 

可以推出,对于含有 N个元素的数组,删除操作最多需要 N步

既然学会了如何分析数据结构的时间复杂度,那就可以开始探索各种数据结构的性能差异了。了解

这些非常重要,因为数据结构的性能差异会直接造成程序的性能差异。


总结

理解数据结构的性能,关键在于分析操作所需的步数。采取哪种数据结构将决定你的程序是

能够承受住压力,还是崩溃。

不同的数据结构有不同的时间复杂度,类似地,不同的算法(即使是用在同一种数据结构

上)也有不同的时间复杂度。既然我们已经学会了时间复杂度的分析方法,那么现在就可以用它来

对比各种算法,找出能够发挥代码极限性能的那个。

这正是下一章所要讲的。

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

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

相关文章

免费U盘文件恢复,你不知道的10款u盘恢复软件

U盘是我们在工作和生活中经常使用的移动存储设备。在操作U盘时&#xff0c;里面重要的文件可能会因为疏忽而被删除。通过电脑回收站、备份等方法都不能恢复U盘里面的数据&#xff0c;我们该怎么办&#xff1f;其实U盘删除的文件在删除后不会被完全删除&#xff0c;通过u盘恢复软…

以太网 STP、RSTP、MSTP基础配置、STP生成树安全保障操作命令介绍

2.13.0 以太网 STP、RSTP、MSTP配置、生成树安全保障操作 主要参考&#xff1a;华为S2750, S5700, S6700 V200R005(C00&C01&C02&C03) 产品文档 《命令手册》 MSTP快速生成树STP配置RSTP配置MSTP配置生成树的安全保障操作&#xff08;1&#xff09;根桥保护&#xf…

jdk1.8下载与安装教程(win11)

一、JDK下载 1.首先在Oracle官网上下载jdk1.8 打开官网&#xff1a;https://www.oracle.com/ 2.选择Developer Services的Java 3.选择Oracle JDK 4.选择Java8 Window点击&#xff1a; jdk-8u351-windows-x64.exe下载 5.接受Oracle Java SE的Oracle技术网络许可协议 …

Folate-PEG-DBCO,DBCO-PEG- FA,叶酸聚乙二醇环辛炔

●中文名&#xff1a;叶酸聚乙二醇环辛炔&#xff0c;叶酸聚乙二醇二苯基环辛炔&#xff0c;DBCO-PEG-叶酸 ●英文名&#xff1a;FA-PEG-DBCO &#xff0c; Folate-PEG-DBCO&#xff0c;DBCO-PEG- FA&#xff0c;DBCO-PEG-Folate&#xff0c;DBCO-PEG- Folic acid ●外观以及…

游戏合作伙伴专题:BreederDAO 与 SuperGaming 建立 SuperCharged 合作伙伴关系

BreederDAO 很高兴地宣布与 SuperGaming 建立合作伙伴关系&#xff0c;SuperGaming 是一家充满激情的游戏工作室&#xff0c;希望通过 Tower Conquest&#xff1a;Metaverse Edition 进军 Web 3 行业&#xff0c;这是一款基于 Polygon 区块链的免费多人塔防游戏。 征服新领域 S…

产品设计学习过程中的技术和方法

在产品设计的过程中&#xff0c;当你心中有创意设计时&#xff0c;你需要写下这个创意设计&#xff0c;并生成一个例子标记&#xff0c;以便总结你以前的想法。此时&#xff0c;你需要设计性能。在设计性能的过程中&#xff0c;我们需要使用各种设计工具&#xff0c;这些设计工…

自定义Springboot Starter

1.创建一个父项目&#xff1a; demo 1.1 项目结构&#xff1a; 1.2 pom文件内容&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/X…

大四web前端网页制作课作业——HTML+CSS+JavaScript仿小米手机商城网站(37页)

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

Windows + Pycharm + Docker 配置GPU跑深度学习【不常见GPU问题】

Windows Pycharm Docker 配置GPU跑深度学习 核心配置方法回顾&#xff0c;与 GPU无法使用的解决方案。 1. 更换镜像源 用于加速资源下载&#xff01;修改配置文件: daemon.json 添加国内镜像源&#xff1a; "registry-mirrors": ["https://registry.docke…

yolov5修改骨干网络--原网络说明

yolov5l网络示意图&#xff1a; 以yolov5s为例&#xff08;模型都是在yolov5l上修改了depth_multiple和width_multiple&#xff0c;上面图形是画的yolov5l的&#xff0c;下面的yaml是yolov5s的目的是为了更好的计算网络信息&#xff09; nc: 80 # number of classes depth_mu…

JuiceFS CSI Driver 常见问题排查指南

Kubernetes 作为资源调度和应用编排的开源系统&#xff0c;正在成为云计算和现代 IT 基础架构的通用平台。JuiceFS CSI Driver 实现了容器编排系统的存储接口&#xff0c;使得用户可以在 Kubernetes 中以原生的方式使用 JuiceFS。 由于 Kubernetes 自身的复杂性&#xff0c;用…

全栈Jmeter接口测试(十一):BeanShell脚本通过BeanShell进行加解密

BeanShell脚本 BeanShell简介&#xff1a; BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些 语法和方法&#xff1b; BeanShell是一种松散类型的脚本语言&#xff1b; BeanShell是用Java写成的&#xff0c;一个小型的、免费的、可以下载、嵌入式的 Ja…

四、Docker 镜像发布阿里云、私有库(详解、实操)第一篇

1、概述 在上一篇中我们讲解到从Docker央仓库pull下来的镜像(centos7),是没有vim、ifconfig等命令的,我们可以以centos7为基础安装vim、ifconfig等需要的命令,然后export为一个tar,然后再import为一个新的增强版本的centos7镜像。那问题来了,这种export、import方式有没…

java计算机毕业设计基于安卓Android的教务的校内人员疫情排查系统设计与实现APP

项目介绍 校内人员疫情排查系统APP管理是校内人员疫情排查系统管理中对学生必不可少的一个部分。在人们校内人员疫情排查系统管理的整个过程中,校内人员疫情排查系统APP管理担负着最重要的角色。为满足如今日益复杂的管理需求,各类校内人员疫情排查系统APP管理程序也在不断改进…

java----类的加载与其初始化

java内存分析&#xff1a; 类加载的过程&#xff1a; 类的加载与ClassLoader的理解&#xff1a; 类的初始化&#xff1a; package Collections; public class text1 { public static void main(String[]args){A anew A();System.out.println(A.m); } } class A{static {System…

语言模型(马尔可夫模型,n元语法)

参考&#xff1a;8.3. 语言模型和数据集 — 动手学深度学习 2.0.0 documentation 假设长度为T的文本序列中的词元依次为x1,x2,…,xT。 于是&#xff0c;xt&#xff08;1≤t≤T&#xff09; 可以被认为是文本序列在时间步t处的观测或标签。 在给定这样的文本序列时&#xff0c;语…

Ubuntu中使用gcc/g++编译C/C++

对于习惯了使用windows进行开发的朋友们&#xff0c;如果想要编译C语言&#xff0c;只要要在windows中安装一个可以编译的IDE即可。或者直接自己加载编译的工具链&#xff0c;使用Cmd命令行也可以完成C语言的编译。 但是如果想要在linux环境中编译C语言&#xff0c;安装编译工具…

【华为上机真题 2022】消消乐游戏

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

MapReduce案例-TopN(倒序排序)

文章目录MapReduce案例-TopN(倒序排序)一、案例分析1、TopN分析法介绍2、案例需求及分析二、MapReduce 倒序排序代码实现1、准备数据文件(1) 在虚拟机上创建文本文件(2) 上传文件到HDFS指定路径2、map阶段实现(1) 创建前N成绩映射器类3、Reduce阶段实现4、Driver程序主类实现5、…

深入理解React中的虚拟DOM(源码+分析)

文章目录引文一、前端技术的发展历史1.1 前后端不分离1.1.2 静态网站1.1.3 动态网站1.2 前后端分离1.2.1 jQuery时代1.2.2 angularjs时代1.2.3 react与vue1.3 总结二、什么是虚拟dom2.1 概念2.2 react中的虚拟dom三、react虚拟DOM的优势是什么3.1 局部更新DOM的机制3.2 浏览器兼…