1.1数据结构的定义
我们可以将数据结构看成是在数据处理过程中一种分析、组织数据的方法与逻辑,它考虑到了数据见的特性与相互关系。简单来说,数据结构的定义就是一种程序设计优化的方法论,它不仅仅讨论到储存的数据,同时也考虑到彼此之间的关系与运算,使之达到加快程序执行速度与减少内存占用空间等作用。计算机的两大特点:速度快、容量大。
数据结构无疑就是数据进入计算机内处理的一套完整逻辑,就像程序设计师必须选择一种数据结构来进行数据的新增、修改、删除、存储等操作。如果在选择数据结构时做了错误的决定,那么程序执行起来的速度可能变得非常低效,如果选错了数据类型,那后果更是不堪设想。
因此,当我们要求计算机为我们解决问题时,必须以计算机所能接受的模式来确认问题,而安排适当的算法去处理数据,就是数据结构要讨论的重点。总之,数据结构就是数据与算法的研究与讨论。
1.1.1数据与信息
谈到数据结构,首先必须了解何谓数据,何谓信息。从字面上来看,所谓数据,指的就是一种未经过处理的原始文字、数字、符号或图形等,它所表达出来的只是一种没有评估价值的基本元素或项目。例如姓名或我们常看到的课表、通讯簿等都可泛称是一种“数据”。
当数据经过处理,例如以特定的方式系统地整理、归纳甚至进行分析后,就成为“信息”。而这样处理的过程就称为“数据处理”。另外,数据和信息的角色并不是绝对一成不变,同一份文件可能在某种情况下为数据,而在另一种情况下则为信息。
1.1.2数据的特性
数据根据计算机总存储和使用的对象,可分为两大类:一类为数值数据,如0,1,2,3,…,9所组成,可用运算符来进行运算的数据;另一类为字符数据,如A,B,C,…,+,*等非数值数据。
不过,如果按照数据在计算机程序设计语言中的存在层次来分,则可以分为以下三种类型:
1)基本数据类型
不能以其他类型来定义的数据类型,或称为标量数据类型,几乎所有的程序设计语言都会为标量数据类型提供一组基本数据类型,例如Java语言中的基本数据类型,就包括了整数(int),浮点(float),字符(char)等。
2)结构数据类型
结构数据类型也称为虚拟数据类型,是一种比基本数据类型更高一级的数据类型,例如字符串(string),数组(array),指针(pointer),列表(list),文件(file)等。
3)抽象数据类型
可以将一种数据类型看成是一种值的集合,以及在这些值上所进行的运算及其所代表的属性所成的集合。“抽象数据类型”(ADT)比结构数据类型更高级,是指一个数学模型以及定义在此数学模型上的一组数学运算或操作。也就是说,ADT在计算机中是表示一种“信息隐藏”的程序设计思想以及信息之间的某一种特定的关系模式。例如堆栈就是一种典型的数据抽象类型,它具有后进先出的数据操作方式。
1.2算法
没有最好的程序设计语言,只有是否适合的程序设计语言。程序设计语言本来就只是工具,从来都不是算法的重点,我们知道一个程序能否快速高效地完成预定地任务,算法才是其中关键的因素。所以我们可以这么认为:“数据结构加上算法等于可执行的程序”。
1.2.1算法的定义
在韦氏辞典中算法定义为:“在有限步骤内解决数学问题的程序”。如果运行在计算机领域中,我们也可以把算法定义成:“为了解决某项工作或某个问题,所需要有限数量的机械性或重复性指令与计算步骤“。
算法必须符合的五个条件:输入、输出、明确性、有限性、有效性。
1.3算法效能分析
对一个程序或算法效能的评估,经常是从时间与空间两种因素来进行考虑。时间方面是指程序的运行时间,称为”时间复杂度“。空间方面则是此程序在计算机内存所占的空间大小,称为”空间复杂度“。
1.空间复杂度是一种以概量方式来衡量所需要的内存空间。而这些所需要的内存空间,通常可以分为”固定空间内存“(包括基本程序代码、变量、常数等)与”变动空间内存“(随程序或进行时而改变大小的使用空间,例如引用类型变量)。由于计算机硬件发展的日新月异及设计所使用的计算机不同所以纯粹从程序或算法的效率角度来看,应该以算法的运行时间为主要评估与分析的依据。
2.时间复杂度是算法的执行步骤计数来衡量运行时间。例如同样是两行指令:a=a+1与a=a+0.3/0.7*10005.由于涉及变量存储类型与表达式的复杂度,因此真正绝对精确的运行时间一定不相同。不过如此大费周章地去考虑程序的运行时间往往寸步难行,而且毫无意义。这时可以利用一种”概量“的概念来衡量运行时间,我们称为”时间复杂度“,其详细定义如下:
1)在一个完全理想状态下的计算机中,我们定义T(n)来表示程序执行所要花费的时间,其中n代表数据输入量。当然程序的运行时间或最大运行时间是时间复杂度的衡量标准,一般以Big-oh表示。
2)由于分析算法的时间复杂度必须考虑它的成长比率往往是一种函数,而时间复杂度本身也是一种”渐进表示“。
1.3.1Big-oh
O(f(n))可视为某算法在计算机中所需运行时间不会超过某一常数倍数的f(n),也就是说当某算法的运行时间T(n)的时间复杂度为O(f(n))(读成big-oh of f(n)或order is f(n))。
意思是存在两个常数c与n0,当n>=n0,则T(n)<=cf(n),f(n)又称为运行时间的成长率。
事实上,时间复杂度只是执行次数的一个概略的量度层级,并非真实的执行次数。而Big-oh则是一种用来表示最坏运行时间的表现方式,它也是最常用于在描述时间复杂度的渐进式表示法。
n>=16时,时间复杂度的优劣比较为:
O(1)<O(log2n)<O(n)<O(nlog2n)<O(n^2) < O(n^3)< O(2^n)
1.3.2Ω(Omega)
即运行时间量度的最好情况。
1.3.3θ(theta)
θ是一种比Big-O与Ω更精确的时间复杂度渐进表示法。
1.4 常见算法
1.4.1分治法
如快速排序法、递归算法、大整数乘法。核心思想是将一个难以直接解决的大问题依照相同的概念,分割成两个或更多的子问题,以便各个击破,即“分而治之”。
1.4.2递归法
递归是一种很特殊的算法,分治法和递归法很像一对孪生兄弟,都是将一个复杂的算法问题进行分解,让规模越来越小,最终使子问题容易求解。
函数或子程序不单纯只是能够被其他函数调用或引用的程序单元,在某些程序设计语言中还提供了自己调用自己的功能,这两种调用的功能就是所谓的递归。即假如一个函数或子程序是由自身所定义或调用的,就称为递归。它至少定义两个条件,包括一个可以反复执行的递归过程,与一个跳出执行过程的出口。
在系统中具体实现递归时,则要用到堆栈的数据结构,所谓堆栈就是一组相同数据类型的集合,所有的操作均在这个结构的顶端进行,具有后进先出的特性。
1.4.3贪心法
贪心法又称贪婪算法,方法是从某一起点开始,在每一个解决问题步骤中使用贪心原则,即采用在当前状态下最有利或最优化的选择,不断地改进该解答,持续在每一步骤中选择最佳的方法,并且逐步逼近给定的目标,当达到某一步骤不能再继续前进时,算法就停止,就是尽可能快地求得更好的解。
贪心法地思想虽然是把求解地问题分成若干个子问题,不过不能保证求得地最后解是最佳的,贪心法容易过早做决定,只能求满足某些约束条件下可行解地范围,当然,对于有些问题却可以得到最佳解。贪心法经常用于求解图地最小生成树(MST)、最短路径与哈夫曼编码等。
1.4.4动态规划法
动态规划法类似于分治法,用于研究多阶段决策过程的优化过程与求得一个问题的最佳解。动态规划法主要的做法是:如果一个问题答案与子问题答案相关的话,就能将大问题拆解成各个小问题,其中与分治法最大不同的地方是可以让每一个子问题的答案被存储起来,以供下次求解时直接取用。这样的作法不但能减少再次计算的时间,并可将这些解组合成大问题的解答,故而使用动态规划可以解决重复计算的问题。
1.4.5迭代法
迭代法是指无法使用公式一次求解,而需要使用迭代,例如用循环去重复执行程序代码的某些部分来得到答案。
循环必须加入控制变量的起始值以及递增或递减表达式,编写循环过程必须检查离开循环体的条件是否存在或是否正确,如果条件不存在,则会让循环体一直执行而无法停止,导致“无限循环”。循环结构通常需要具备三个条件:(1)变量初始值;(2)循环条件判别式;(3)调整变量递减值。
1.4.5枚举法
枚举法又称穷举法,枚举法是一种常见的数学方法,是我们在日常中用到最多的一种算法,它的核心思想就是:列举所有的可能。根据问题要求,逐一列举问题的解答,或者为了便于解决问题,把问题分为不重复、不遗漏的两种情况,逐一列举各种情况,并加以解决,最终达到解决问题的目的。枚举法这种分析问题、解决问题的方法,得到的结果总是正确的,枚举法的缺点就是速度太慢。
回溯法也算是枚举法的一种,对于某些问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,同时避免枚举不正确的数值。一旦发现不正确的数值,就不再递归到下一层,而是回溯到上一层,以节省时间,是一种走不通就退回再走的方式。它的特点主要是在搜索过程中查找问题的解,当发现不满足求解条件时,就回溯(即返回),尝试别的路径,避免无效搜索。例如老鼠走迷宫就是一种回溯法的应用。