1-图文打造LeeCode算法宝典-数据结构和算法

177 阅读11分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

数据结构和算法

算法

算法就是计算或者解决问题的步骤。

算法(Algorithm)是指在有限的时间范围内,为解决某一问题而采取的方法和步骤的准确完整的描述,它是一个有穷的规则序列,这些规则决定了解决某一特定问题的一系列运算。

一个算法应该具备以下特征。

  1. 有穷性一个算法应包含有限个操作步骤,即一个算法在执行若干个操作步骤之后应该能够结束,并且每一步都要在合理时间内完成。
  2. 确定性算法中的每一个步骤必须有确切的含义,无二义性,在任何情况下,对于相同的输入只能得出相同的输出。
  3. 可行性算法中的每一个步骤都应该能够通过已经实现的基本运算的有限次执行得以实现。
  4. 输入输入指的是在算法执行时,从外界取得必要的数据。一个算法可以有一个或一个以上的输入,也可以没有输入。
  5. 输出数据结构输出指的是算法对输入数据处理后的结果。一个算法可以有一个或一个以上的输出,没有输出的算法是无意义的。

算法效率的度量

  1. 正确性(Correctness)算法的执行结果应当满足预先规定的功能和性能的要求,这是评价一个算法的最重要,也是最基本的标准。算法的正确性还包括对于输入、输出处理的明确而无歧义的描述。

  2. 可读性(Readability)算法主要是为了人阅读和交流,其次才是机器的执行。所以,一个算法应当思路清晰,层次分明,简单明了,易读易懂。即使算法已转变成机器可执行的程序,也需要考虑人能较好地阅读理解。同时,一个可读性强的算法也有助于对算法中隐藏错误的排除和算法的移植。

  3. 健壮性(Robustness)一个算法应该具有很强的容错能力,当输入不合法的数据时,算法应当能做适当的处理,使得不至于引起严重的后果。健壮性要求表明算法要全面细致地考虑所有可能出现的边界情况和异常情况,并对这些边界情况和异常情况做出妥善的处理,尽可能使算法没有意外的情况发生。

  4. 运行时间(Running Time)运行时间是指算法在计算机上运行所花费的时间,它等于算法中每条语句执行时间的总和。对于同一个问题如果有多个算法可供选择,应尽可能选择执行时间短的算法。一般来说,执行时间越短,性能越好。

  5. 占用空间(Storage Space)占用空间是指算法在计算机上存储所占用的存储空间,算法占用的存储空间是指算法执行过程中所需要的最大存储空间,对于一个问题如果有多个算法可供选择,应尽可能选择存储量需求低的算法。

算法效率分析

时间复杂度(Time Complexity)

n作为表示问题规模的量

为了便于比较同一问题的不同算法,通常把算法中基本操作重复执行的次数(频度)作为算法的时间复杂度:T(n)=f(n)

其中f(n)是规模为n的算法,重复执行基本操作的次数

比较不同算法的优劣主要应该以其“增长的趋势”为准则,我们往往研究所谓的“渐进时间复杂度”,即当n逐渐增大时T(n)的极限情况。一般把这种算法的渐进复杂度简称为时间复杂度:T(n)=O(f(n))

其中大写字母O为Order(数量级)的第一个字母,f(n)为函数形式,如T(n)=O(n2)。一般用数量级的形式表示T(n),当T(n)为多项式时,可只取其最高次幂,且其系数也可省略。T(n)=8n3+15n2+3n+1时,可以表示为T(n)=O(n3)

举例:

x=x+1;

解:语句x=x + 1;执行的频度是1,该程序段的执行时间是一个与问题n无关的常数,因此时间复杂度T(n)=O(1)

for(i=1;i<=n;i++)
	x=x+1;

解:其中第一条语句的循环变量i要增加到n,故它执行n次。第二条语句作为循环体语句也要执行n次。所以,该程序段所有语句执行的次数为:T(n)=2n。故其时间复杂度为:T(n)=O(n)。实际上,在分析时间复杂度时,只需要关注随着问题规模n增大,语句执行次数变化最快的语句即可分析出,如本例中的x=x+1就是这样的语句。

for(i=1;i<=n;i++)
	for(j=1;j<=n;j++)
		x=x+1;

解:这是二重循环的程序,外层for循环的循环次数是n,内层for循环的循环次数为n,所以,该程序段中语句x=x+1是随着问题规模n增大,语句执行次数变化最快的语句,其频度为n^2^,故其时间复杂度为T(n)=O(n^2^)

i=1;
while(i<=n) i=5*i;

解:该程序段中语句i=5*i是随着问题规模n增大,语句执行次数变化最快的语句。设执行次数为x,可以列出下列公式。

i=5^x-1^时,是最后一次循环,根据条件,可以列出下述公式:5^x-1^≤n<5^x^,从而得到:x-1≤log5n<x⇒x≈log5n,则程序段的时间复杂度为T(n)=O(log5n)。

一些常见的时间复杂度的等级包括:

O(1):常数阶,基本操作执行次数为常数

O(logn):对数阶

O(n):线性阶

O(nlogn):线性对数阶

O(n2):平方阶

O(nk):K方阶

O(xn):指数阶

一般地,对于足够大的n,常用的时间复杂性存在如下顺序:

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2^)<O(n^3^)<<O(2^n^)<O(3^n^)<...<O(n!)

空间复杂度(Space Complexity)

算法整个运行过程所占用的空间称算法的空间复杂度。

算法整个运行过程所占用的空间称算法的空间复杂度。空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。算法在计算机存储器内占用的存储空间主要分为三部分:算法源代码本身占用的存储空间、算法输入输出数据所占用的存储空间、算法运行过程中临时占用的存储空间

空间复杂度记为:S(n)=O( f(n) ),其中n是问题的规模

S(n)=O(f(n))

在对一个算法进行时间复杂度和空间复杂度的分析时,往往二者不能兼顾,考虑好的时间复杂度,就得牺牲空间复杂度的性能,反之亦然。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能、算法的使用频率、算法处理的数据量的大小、算法描述语言的特性、算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。

数据结构

作用

数据是外部世界信息的计算机化,是计算机加工处理的对象。

数据结构决定了数据的顺序和位置关系

运用计算机处理数据时,必须解决四个方面的问题:

一是如何在计算机中方便、高效地表示和组织数据;

二是如何在计算机存储器(内存和外存)中存储数据;

三是如何对存储在计算机中的数据进行操作,可以有哪些操作,如何实现这些操作以及如何对同一问题的不同操作方法进行评价;

四是必须理解每种数据结构的性能特征,以便选择一个适合于某个特定问题的数据结构

意义

当我们用计算机解决一个问题时,必须告诉计算机如何去做。这需要先分析问题,确定一个适合的数据模型;然后,需要设计一个求解这个数据模型的算法,最后编写程序,经过反复调试直至得到正确结果

基本概念和术语

数据即信息的载体,是对客观事物的符号表示,凡能输入到计算机中并被计算机程序处理的符号都可称之为数据。

数据元素是数据的基本单位,它在计算机处理和程序设计中通常作为独立个体。

数据对象是具有相同特征的数据元素的集合,是数据的一个子集。

数据结构简称DS(Data Structure),是数据及数据元素的组织形式

数据结构通常有四类基本形式:集合结构,线性结构,树型结构,图形结构或网状结构

  • 集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。各个数据元素是“平等”的,它们的共同属性是“同属于一个集合”
  • 线性结构:线性结构中的数据元素之间是一对一的关系
  • 树型结构:树形结构中的数据元素之间存在一种一对多的层次关系
  • 图状结构:图状结构中的数据元素是多对多的关系

数据的物理结构

数据结构的目的是为了在计算机中实现对它的操作,因此还需要研究在计算机中如何表示和存储数据结构,即数据的物理结构(Physical Structure)。数据的物理结构又称存储结构,它的实现依赖于具体的计算机语言。数据存储结构有顺序和链式两种不同的方式,顺序存储的特点是数据元素在存储器的相对位置来体现数据元素相互间的逻辑关系

单向链表

链表是数据结构之一,其中的数据呈线性排列。在链表中,数据的添加和删除都较为方便,就是访问比较耗费时间。

它包括两个域,其中存储数据元素信息的域称为数据域,存储直接后继存放位置的域称为指针域。

数据域为data,指针域为next。

结构

在这里插入图片描述

存储

在链表中,数据一般都是分散存储于内存中的,无须存储在连续空间内

顺序访问

因为数据都是分散存储的,所以如果想要访问数据,只能从第1个数据开始,顺着指针的指向一一往下访问(这便是顺序访问)。比如,想要找到Red这一数据,就得从Blue开始访问。

添加数据

如果想要添加数据,只需要改变添加位置前后的指针指向就可以,非常简单。比如,在Blue和Yellow之间添加Green。

在这里插入图片描述

将Blue的指针指向的位置变成Green,然后再把Green的指针指向Yellow,数据的添加就大功告成了。

在这里插入图片描述

删除数据

只需要把Green指针指向的位置从Yellow变成Red,删除就完成了。虽然Yellow本身还存储在内存中,但是不管从哪里都无法访问这个数据,所以也就没有特意去删除它的必要了。今后需要用到Yellow所在的存储空间时,只要用新数据覆盖掉就可以了。

循环链表

单向链表在尾部没有指针,但我们也可以在链表尾部使用指针,并且让它指向链表头部的数据,将链表变成环形。这便是“循环链表”,也叫“环形链表”。循环链表没有头和尾的概念。想要保存数量固定的最新数据时通常会使用这种链表。

双向链表

在单向链表的基础上,结点新增一个指向前面结点的指针域prior,若已知某个结点,要找其前驱结点,只需从表头指针出发。由这种结点组成的链表称为双向链表。

结构图

p.next.prior == p.prior.next = =p

操作类同单向链表

特点

双向链表,不仅可以从前往后,还可以从后往前遍历数据,十分方便。

双向链表存在两个缺点:一是指针数的增加会导致存储空间需求增加;二是添加和删除数据时需要改变更多指针的指向

其他数据结构见下篇文章。