前言
数据结构与算法是衡量程序员内功的至关重要的指标之一,数据结构在众多领域得到广泛应用,业界甚至有“程序等于数据结构加算法”的说法。众多中间件开发者、架构师们正致力于优化中间件、项目架构以及算法,以提升运行效率并减少内存占用,在这个过程中,数据结构扮演着至关重要的角色。此外,数据结构中融入了诸多面向对象的理念,因此,深入学习并掌握数据结构对于提高逻辑思维以及处理抽象问题的能力具有重要意义。
学习数据结构与算法有何意义?对于学生而言,它是基础课程,考研时亦是必考内容。即便步入职场,在竞争激烈的大公司求职,数据结构与算法同样构成面试和笔试的关键考察点。一旦步入职场,数据结构与算法更是内功修为的重要体现,对程序员而言,若想取得理想成果,掌握数据结构与算法则是不可或缺的基本功。
数据结构

[id_695621009]
计算机存储和组织数据的方法被称为数据结构,它是由相互间存在一种或多种特定联系的数据元素所组成的集合。一般而言,恰当选取的数据结构能够显著提升程序运行的效率或数据存储的效能。
概括来说,数据结构是通过特定的执行规则和相应的算法,将一系列存储结构有效组合而成的高效存储形式。在诸如关系型数据库、非关系型数据库、搜索引擎数据存储、消息队列等众多领域,我们都能看到这些大型数据结构被出色地应用。当然,这些应用不仅需关注结构本身,还需综合考虑操作系统、网络等其他相关因素。
关于这个数据结构与算法的专栏,我们程序员需要首先掌握的是在内存中操作的各种抽象数据结构。这些数据结构类型相对较为单一,包括线性结构、树、图等。
相关术语
在数据结构与算法领域,许多人对于数据、数据对象、数据元素以及数据项之间的联系感到困惑。为此,我们可以绘制一张图表来梳理它们之间的关系,接下来,我将通过一个实例与大家共同探讨。

用户信息表users
users的pojo对象
class users
{ //略 int id; String name; String sex;
}
//list和woman是数据
List<users>list;//数据对象list
List<users>woman;//数据对象woman
list.add(new users(001,"bigsai","man"));向数据集内新增元素,一个名为users的记录包含三个数据字段,分别是(001,bigsai,man)。
list.add(new users(002,"smallsai","man"));//数据元素
list.add(new users(003,"菜虚鲲","woman"));//数据元素
woman.add(list.get(2));"菜虚鲲"这一数据项、"woman"这一类别以及它们共同组成的元素。
数据是对客观事物进行符号化表示的一种形式,涵盖了所有能够被输入至计算机并被程序处理的符号集合。在上述表格中,所展示的三条用户信息记录便属于此类数据,尽管可能存在多个表格和集合,此处仅展示了一个。这些数据通常是由用户直接输入或通过自定义方式构建而成。此外,图像和声音同样也被视为数据的一种。
数据元素作为数据的最小单元,构成了数据的基本组成部分。它由多个数据项组合而成,可以类比为一种POJO对象,亦或是数据库中的一条具体记录。以菜虚鲲的记录为例,它便是一个典型的数据元素。
用户字段或属性,包括id、name、sex等要素,它们构成了数据项。这些数据项是数据元素中不可进一步分割的最小单元。它们可以被视为一个POJO对象,或者是一张名为(people)的表中的一个属性或字段的值。
数据对象指的是由性质相同的多个数据元素所组成的集合,它构成了数据的一部分。例如,前文提到的users表、list集合以及woman集合均属于数据对象的范畴。即便是单独的一张表格或一个集合,也能够被视为数据对象的存在。
总体来看,数据涵盖的范围最为广泛,所有这些数据统称为数据,而所谓的“数据对象”实际上是指具有相似性质的一组集合,这组集合是数据的一部分,却并非构成数据的最小单元,真正构成数据最小单元的是数据元素。以一个例子来说明,表格中的cat和dog都是数据,其中表cat特指一类数据对象(因为它描述的是cat这一类对象),然而数据的基本构成并非猫和狗本身,而是它们各自的个体,例如小猫咪1号、大猫咪2号、哈士奇1号以及藏獒2号等,这些具体的每一条才是构成数据的基本单元。
还有数据类型,抽象数据类型也在下面讲一讲。
数据类型
原子类型指的是那些不能再进一步分割的数据类型。例如,int、char、double和float等均属于此类。
该数据类型能够进一步细分为多个组成部分。例如,结构体所构建的各种不同结构体类型。
抽象数据类型,即ADT,它定义了数据的存储结构和一系列操作算法。通过这种方式,我们可以在研究和应用时,仅关注其结构而不必深究其具体实现细节。例如,当我们使用List、Map、Set等数据结构时,只需了解它们的API和基本功能特性即可。具体的实现方式可能存在差异,例如,List的构建可以选择数组或链表等不同的策略。
三要素
数据元素间的相互关系构成了逻辑结构,这种结构可以进一步划分为线性与非线性两大类。线性结构通常包括顺序表和链表等,而非线性结构则涵盖了集合、树和图等类型。
数据结构在计算机内部的表达形式,通常被称作映像或物理结构,其存储方式主要可以划分为顺序存储、链式存储、索引存储以及散列(哈希)存储等几种类型。以下这张图可以为您提供一个对这些存储方式的基本认识,仅供参考,不涉及更深入的探讨:

数据的处理涉及运算的界定与执行,其中运算的界定依赖于逻辑架构,而运算的执行则依赖于存储架构。
在此容易产生误解的是逻辑结构与存储结构的区别。从字面上理解,“逻辑”二字即揭示了其本质,即逻辑关系是指数据间存在的数据关联,而非物理地址的关联。例如,线性结构与非线性结构,它们描述的是数据间联系的方式和形态,主要关注的是数据本身。我所关注的是数据结构的功能特性,例如线性表这种结构具有前后顺序性,若需要构建一个有序的集合,线性表便是一个合适的选择。
存储结构是与物理地址紧密相连的。由于相同的逻辑结构可以通过不同的存储结构来实施,这可能导致适用的场景和性能表现各异。以线性表为例,它可能采用多种不同的存储结构来实现。例如,顺序表和链表(如ArrayList和LinkedList)便是两种不同的存储结构实现。顺序表采用顺序存储(即数组)的方式,而链表则通过链式存储(即链表)的方式来实现。该研究聚焦于计算机在运行过程中物理地址之间的关联。然而,通常情况下,同一类存储结构所实现的数据结构,它们之间既存在一些相似之处,也存在一些共同的不足(例如,线性结构便于查找但插入困难,链式结构则相反)。
算法分析
前文已阐述数据结构的基本概念,接下来,本文将详细阐述算法分析的相关内容。

算法具备五个核心属性:它们分别是有限性、明确性、可实现性、数据输入以及结果输出。这些概念从字面来看即可明了,有限性意味着算法必须有一个终止点,不能陷入无休止的循环;明确性则表明每一条指令都有其特定的含义,相同的输入将产生一致的输出结果;可实现性确保算法的每一步骤都能通过多次执行来完成;数据输入可以是零个或多个(包括零个);而结果输出则必须有一个或多个(至少一个)。
优秀的算法设计往往更应重视其执行效率与资源消耗(包括时间与空间),复杂度概念通常更多地体现为一种量级水平,而非具体的数值表述。
空间复杂度
概念上,它指的是在算法执行过程中,临时使用的存储空间量,通常用S(n)表示,并定义为O(f(n))。
算法评估中,空间复杂度的比重相对较小——我们通常为了节省时间而牺牲空间,采用相应的数据结构和算法——然而,其重要性不容小觑。无论是做题还是实际的项目开发,内存使用都是一个至关重要的考量因素。在Java编程中,这一点尤为明显。Java本身内存占用就较大,若存储逻辑不当,更会额外消耗系统资源,给服务带来压力。
算法在许多情形下是以牺牲存储空间为代价来换取处理速度的提升。以我们熟知的字符串匹配函数String.contains()为例,大家普遍知道它采用的是暴力搜索法,其时间复杂度为O(n^2),且无需额外内存支持。相较之下,KMP算法在速度和效率上与暴力方法相当,但为了实现这一目标,KMP算法需要借助额外的数组(如next数组)来进行标记和存储,从而产生了空间上的开销。例如,归并排序在递归分解的过程中,会利用新的数组进行逐层计算,从而提升运算速度;尽管如此,它所引发的内存消耗增加并不显著。
当然,算法在占用空间方面应严格控制在jvm设定的上限之内,通常这个上限是2G(即2147483645字节)。特别是当使用二维数组处理多种多维数据时,若设置过大,很可能会引发heap OutOfMemoryError错误。
时间复杂度
在计算机科学领域,算法的时间复杂度被定义为一个函数,该函数从本质上描述了算法执行所需的时间。这个函数与算法输入数据的长度密切相关。通常,时间复杂度以大O符号来表示,且在表示过程中,会省略掉函数中的低阶项和首项系数。采用这种表示方法后,我们所说的时间复杂度便具有了渐近性质,它主要关注的是当输入数据量无限增大时算法的运行情况。
时间复杂度的排序:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n)
关于时间复杂度的理解,许多人的印象并不清晰。下面,我将以实例来阐述几个关于时间复杂度的概念。
O(1): 常数函数
O(logn): 对数函数
O(n): 线性函数
O(nlogn):
O(n^2)
自然,若算法的输入规模固定为n=10000,那么,不同算法的时间复杂度将导致它们执行所需的次数和耗时均有所差异。
具体n执行次数
O(1)
10000
O(log2n)
10000
14
O( n^1/2)
10000
100
O(n)
10000
10000
O(nlog2 n)
10000
140000
O(n^2)
10000
100000000
O(n^3)
10000
1000000000000
降低算法复杂度的方式有多种,其中一些依赖于数据结构的特性与优势,例如二叉排序树在查找操作上的高效性,以及线段树在动态排序中的优异表现。这些数据结构在处理特定问题时展现出极佳的性能。此外,还有的是通过算法策略来实现,以排序算法为例,冒泡排序虽然简单直接,但其时间复杂度为O(n^2),而快速排序和归并排序等算法则能将时间复杂度降至O(nlogn)。要想变得更快,那就得掌握更高级的数据结构和更精巧的算法。
时间复杂度计算
进行时间复杂度分析通常包括以下步骤:首先,识别出执行频率最高的代码段落;其次,估算该段落的执行次数规模;最后,用大O符号来表示计算结果。此外,还需遵循两条基本规则:
在执行同一程序中的多个并列语句时,应选取其中数值最大的结果。
在计算T(n)的值时,我们将其分解为两部分,即O(m)和O(n),然后将它们相加,得到O(m)+O(n)。根据数学中的最大值运算规则,这个结果等于O(m)和O(n)中的较大者,即max(O(m),O(n))。
对于T(n)的计算,我们可以将其表示为O(n)与O(nlogn)的和,即T(n)=O(n)+O(nlogn)。根据数学中的最大值原则,这等于O(n)和O(nlogn)中的较大者,即max(O(n),O(nlogn))。因此,最终的结果是T(n)=O(nlogn)。
乘法规则:循环结构,时间复杂度按乘法进行计算,eg:
T(n)=O(m)*O(n)=O(mn)
函数T(n)的时间复杂度为O(m),乘以另一个O(m)的时间复杂度,结果为O(m^2),这对应于两层嵌套的for循环。
自然,众多算法的执行效率与输入数据紧密相连,它们可以细分为最佳执行效率(即在最少执行次数下完成),最差执行效率(即在最少执行次数下完成),以及平均执行效率。在排序算法中,这些概念已有详尽的分析,然而,我们通常采用平均执行效率作为评估算法优劣的标准。
数据结构与算法学习
在梳理完数据结构与算法的基础知识之后,我在学习这一领域时,将经典的数据结构与算法学习流程详细记录如下,旨在为大家提供一份参考。
数据结构经典算法
阅读完此篇文章后,您应对数据结构与算法有了较为清晰的理解。这两者之间存在着紧密的联系,数据结构旨在支持特定算法的执行,而算法则是实现目标的核心所在。在学习数据结构与算法之前,可以先查阅书籍或博客,对它们的功能有所了解。接着,深入研究其运作机制。然后,动手实践,如编写数据结构或解决相关题目。如此层层递进,仅仅理解是不够的,大量代码实战是必不可少的。而且,这一过程永无止境,活到老,学到老,实践到老。
愿各位在2021年携手努力,我将在2021年对专栏中的数据结构与算法系列文章进行精心改进并发布,这是系列的开篇之作,敬请各位关注与期待!
原创不易,bigsai请csdn的朋友们帮两件事帮忙一下:
Copyright © 2018 All Rights Reserved 版权所有 宁国人才网 苏ICP备2024139598号-6
地址:安徽省合肥市瑶海区 EMAIL:
Powered by PHPYun.