最近有个考试,是关于算法和数据结构的。很久没有看了,赶快补一下。
这个地方有个连接,比较简洁,关于遍历二叉树,还有动态演示,可以看看。
http://course.cug.edu.cn/21cn/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%BD%AF%E4%BB%B6%E6%8A%80%E6%9C%AF%E5%9F%BA%E7%A1%80/course.htm
目 录
第一章 算法
第一节 算法
第二节 算法的要素
第三节 算法的度量
第二章 常用算法
第一节 枚举法
第二节 迭代法
第三节 递归法
第三章 数据结构
第一节 逻辑结构
第二节 存储结构
第四章 线性表
第一节 顺序表
第二节 链表
第三节 栈
第五章 树及二叉树
第一节 树及遍历
第二节 二叉树
第三节 二叉树的遍历
第六章 查找
第一节 顺序查找
第二节 二分法查找
第七章 排序
第一节 选择排序
第二节 冒泡排序
第三节 插入排序
算法的要素
算法由操作与控制结构两要素组成。
l.操作
计算机尽管有许多种类,但它们都必须具备最基本的功能操作,这些功能操作包括以下几个方面:
逻辑运算:与、或、非
算术运算:加、减、乘、除
数据比较:大于、小于、等于、不等于
数据传送:输入、输出、赋值
2.算法的控制结构
一个算法的功能不仅取决于所选用的操作,还取决定于各操作之间的执行顺序,即控制结构。算法的控制结构给出了算法的框架,决定了各操作的执行次序。这些结构包括:
顺序结构:各操作是依次执行的
选择结构:由条件是否成立来决定选择执行
循环结构:有些操作要重复执行,直到满足某个条件时结束,这种控制结构也称为重复或迭代结构
3.算法的特征
有穷性 一个算法必须总是(对任何合法的输入值)在执行有穷步之后结束,且每一步都可在有穷时间内完成。
确定性 算法中每一条指令必须有确切的含义,读者理解时不会产生二义性。并且,在任何条件下,算法只有唯一的一条执行路径,即对于相同的输入只能得出相同的输出。
可行性 一个算法是可行的,即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的。
输人 一个算法有零个或多个的输入,这些输入取自于某个特定的对象的集合。
输出 一个算法有一个或多个的输出。这些输出是同输入有着某些特定关系的集合。
算法的度量
1.时间复杂性
算法执行时间需依据该算法编制的程序在计算机上运行时所消耗的时间来度量。而度量一个程序的执行时间通常有两种方法:
(1)事后统计的方法
因为很多计算机内部都有计时功能,有的甚至可精确到毫秒级,不同算法的程序可通过一组或若干组相同的统计数据以分辨优劣。但这种方法有两个缺陷:一是必须先运行依据算法编制的程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用另一种事前分析估算的方法。
(2)事前分析估算的方法
一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
依据的算法选用何种策略;
问题的规模,例如求100以内还是1000以内的素数;
书写程序的语言,对于同一个算法,实观语言的级别越高,执行效率就越低;
编译程序所产生的机器代码的质量;
机器执行指令的速度。
显然,同一个算法用不同的语言实现,或者用不同的编译程序进行编译,或者在不同的计算机上运行时,效率均不相同。这表明使用绝对的时间单位衡量算法的效率是不合适的。撇开这些与计算机硬件、软件有关的因素,可以认为一个特定算法“运行工作量”的大小,只依赖于问题的规模(通常用整数量n表示),或者说,它是问题规模的函数。一个算法是由控制结构(顺序、分支和循环三种)和原操作(指固有数据类型的操作)构成的,则算法时间取决于两者的综合效果。为了便于比较同一问题的不同算法,通常的做法是,从算法中选取一种对于所研究的问题(或算法类型)来说是基本操作的原操作,以该基本操作重复执行的次数作为算法的时间量度。
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作 T(n)=O(f(n))
它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度(Asymptotic Time Complexity),简称时间复杂度。
2.算法的存储空间需求
类似于算法的时间复杂度,以空间复杂度(Space complexity)作为算法所需存储空间的量度,记作
S(n)=O(f(n))
其中n为问题的规模(或大小)。一个上机执行的程序除了需要存储空间来寄存本身所用指令、常数、变量和输入数据外,也需要一些对数据进行操作的工作单和存储一些为实现计算所需信息的辅助空间。若输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输入和程序之外的额外空间,否则应同时考虑输入本身所需空间(和输入数据的表示形式有关)。
常用算法
为了获得一个有效的算法,必须了解一些解题的基本思想和方法。有许多问题,只要对数据对象进行仔细分析,处理的方法就有了。有些则不然。但是作为寻找思路的基本思想方法对任何算法设计都是有用的。下面我们对一些常用的基础算法作一介绍。
枚举法
枚举法亦称穷举法,它的基本思想是,首先依据题目的部分条件确定答案的大致范围,然后在此范围内对所有可能的情况逐一验证,直到全部情况验证完毕。若某个情况验证符合题目的条件,则为本题的一个答案;若全部情况验证完后均不符合题目的条件,则问题无解。枚举的思想作为一个算法能解决许多问题。
迭代法
所谓迭代(iterate)是指重复执行一组指令(或一定步骤),在每次执行这组指令时,都从变量的原值推出它的一个新值。
在科学计算领域,人们时常会遇到求解方程f(x)=0或微分方程的数值解等计算问题。可是人们却很难或无法用像一元二次方程的求根公式那样的解析法(又称直接求解法)去求解。例如,一般的一元五次或更高次方程、几乎所有的超越方程,其解都无法用解析方法表达出来。为此,人们只能用数值方法(也称数值计算方法)求出问题的近似解,若近似解的误差可以估计和控制,且迭代的次数也可以接受,它就是一种数值近似求解的好方法。它既可以用来求解代数方程,又可以用来求解微分方程,使一个复杂问题的求解过程转化为相对简单的迭代算式的重复执行过程。
递归法
递归是构造计算机算法的一种基本方法。如果一个过程直接或间接地调用它自身,则称该过程是递归的。递归过程必须有一个递归终止条件,即存在一个“递归出口”。无条件的递归是毫无意义的。
递归与递推是既有区别又有联系的两个概念。递推是从己知的初始条件出发,逐次递推出最后所求的值。而递归则是从函数本身出发,逐次上溯调用其本身求解过程,直到递归的出口,然后再从里向外倒推回来,得到最终的值。一般说来,一个递推算法总可以转换为一个递归算法。递归算法往往比非递归算法要付出更多的执行时间。尽管如此,由于递归算法编程序非常容易,各程序设计语言一般都有递归语言机制。此外,用递归过程来描述算法不仅非常自然,而且证明算法的正确性也比相应的非递归形式容易很多。因此,递归是算法设计的基本技术。
在数学及程序设计方法学中为递归下的定义是:若一个对象部分地包含它自己,或它自己给自己定义,则称这个对象是递归的;而且若一个过程直接或间接地调用自己,则称这个过程是递归的过程。
在以下三种情况下,常常要用到递归的方法。
1.定义是递归的
数学上常用的阶乘运算、幂函数、斐波那契数列等,它们的定义和计算都是递归的。
2.数据结构是递归的
某些数据结构就是递归的。例如,链表就是一种递归的数据结构。链表结点ListNode的定义由数据域data和指针域*link组成;而指针link则由ListNode定义。
3.问题的解法是递归的
有些问题只能用递归方法来解决,一个典型的例子就是汉诺塔(Tower of Hanoi)问题。
数据结构
简单说来,数据结构是研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作的一门学科。
逻辑结构
1.概念
数据结构是指数据之间的关系。数据结构包括三个方面:逻辑结构、存储结构和数据的运算。
数据的逻辑结构是对数据之间关系的描述,所以有时就把数据的逻辑结构简称为数据结构。数据的逻辑结构形式上用一个二元组
B=(K,R)
来表示,其中K是结点即数据元素的有穷集合,K是由有限个结点所构成的集合;R是K上的关系的有穷集合(即R是由有限个关系所构成的集合),而每个关系都是从K到K的关系。设r是一个K到K的关系,r∈R,若k,k'∈K,且
从逻辑上可把数据结构分为线性结构和非线性结构,在线性结构中有且只有一个终端结点和一个开始结点,并且所有结点都最多只有一个前驱结点和一个后续结点。非线性结构中可能有多个中断结点和多个开始结点,并且每个结点可能有多个前驱结点和多个后续结点。
2.术语
数据(data) 是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。
数据元素(data element) 是数据的基本单位,在计算机程序中通常作为一个整体进行处理。
数据对象(data object) 是性质相同的数据元素的集合,是数据的一个子集。
存储结构
1.概念
数据的存储结构是逻辑结构在计算机存储器中实现的,计算机存储器是由有限个存储单元组成的,每个存储单元有唯一的地址,各存储单元的地址是连续编码的。一片相邻的存储单元的整体叫做存储区域,记作M。把B存储在计算机中,首先必须建立一个从K的结点到M单元的映象S:K→M。通常有四种基本的存储映象方法,即顺序、链接、索引、散列。
数据的存储结构包括数据元素的表示和关系的表示。在计算机中表示信息的最小单位是二进制的一位,叫做位(bit)。在计算机中,由若干位组合起来形成的一个位串来表示一个数据元素(用八位二进制表示),称为元素(element)或结点(node)。当数据元素由若干数据项组成时,位串中对应与各个数据项的子位串称为数据域(data field)。这样,元素或结点可以看成是数据元素在计算机中的映象。
数据元素之间的关系在计算机中有两种不同的表示方法:顺序映象和非顺序映象,并由此得到两种不同的存储结构:顺序存储结构和链式存储结构。
2.术语
顺序映象 是元素在存储器中的相对位置来表示数据元素之间的逻辑关系。
非顺序映象 是借助指针(pointer)表示数据元素之间的逻辑关系。
数据类型 是描述程序操作对象的特性,是一个值的集合和定义在这个值集合上的一组操作的总称。
线性表
线性表是最简单、最常用也是最基本的一种数据结构。一个线性表是n个数据元素的有限序列。线性表的逻辑结构可以表示为:
(a1,a2,a3,…,an)
表中元素的个数n定义为线性表的长度(n≥0),n=0的表称为空表。
线性表的结构特征是:数据元素呈线性关系。在线性表中必存在唯一的一个被称为“第一个”的数据元素;必存在唯一的一个被称为“最后一个”的数据元素;除第一个元素外,每个元素都有且只有一个前驱元素;除最后一个元素外,每个元素都有且只有一个后继元素。所有数据元素ai在同一个线性表中必须是相同的数据类型。
线性表按其存储结构可分为顺序表和链表。用顺序存储结构存储的线性表称为顺序表;用链式存储结构存储的线性表称为链表。
线性表的基本运算
(1)在两个确定的元素之间插入一个新的元素。
(2)删除线性表中某个元素。
(3)查找线性表中的一个元素,需要时,还可找到元素进行值的更新。
顺序表和一维数组
在计算机中,表示线性表最简单的办法是用一组地址连续的存储单元依次存放线性表的数据元素。这种顺序分配存储方式的表,称为顺序表。这种结构的特点是将表的数据元素按其逻辑顺序依次存放到一组地址连续的存储单元中。这样,逻辑上相邻元素的存储地址也是相邻的,所以线性表的逻辑关系隐含在存储单元的邻接关系中。同时数据元素的数据类型是相同的,因此每一个元素占用同样大小的存储单元。
高级语言里的一维数组就是用顺序方式存储的线性表,因此也常用一维数组来称呼顺序表。一维数组的各元素下标恰好与元素在线性表中的序号一一对应。
链表
在顺序表中插入或删除元素时,都不可避免地要作元素的移动,每进行一次插入或删除,都要移动近乎一半的元素。又由于顺序表是一组地址连续的存储单元,对于长度可变的线性表,必须按其可能达到的最大长度预先分配空间,这可能由于估计不足造成一部分空间太长而得不到充分利用,也可能因空间过短而造成溢出。链表恰能有效地克服这些缺点。链表一般有单链表、双向链表和循环链表等。
l.单链表(线性链表)
单链表就是链式存储的线性表,其结点除信息域外还含有一个指针域,用来指出其后继结点的位置。结点的结构如下图所示。单链表的最后一个结点没有后继结点,它的指针域为空(记为NULL或^)。另外还需要设置一个指针head,指向单链表的第一个结点。
在单链表中,如何实现“插入”和“删除”?
假设我们要在线性表的两个数据元素a和b之间插入一个数据元素x,已知head为头节点并指向节点a,p也指向a,s指向节点x。
上述指针修改为s->next=p->next p->next=s
反之,若要删除线性表中的数据元素x,已知head为头节点并指向节点a,p也指向a,s指向节点x。
上述指针修改为p->next=p->next->next
2.循环链表
循环链表是表结构形式上和单链表稍有不同的一种链表,如下图所示。循环链表和单链表的差别仅在于链表中最后一个结点的指针域不为“NULL”,而是指向第一个结点,整个链表成为一个由链指针相链结的环,故称之为循环链表。其插入、删除算法和单链表没有多大区别。
3.双向链表
在实际应用中,链表结点可根据需要来设定。对于线性表来说,除了设有指向后继结点的指针外,还可设一个指向前驱结点的指针。我们称这种含有两个指针域的结点构成的链表为双向链表。其结构如下图所示。
栈
栈是一种只能在栈顶的一端进行进栈(push)或出栈(pop)操作的线性数据结构。栈的表尾称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈。栈的主要特点是“后进先出”(Last In First Out-LIFO),即后进栈的元素先处理。
假设栈S=(a1,a2,…,an),则称a1为栈底元素,an为栈顶元素。栈中元素按a1,a2,…,an的次序进栈,退栈的第一个元素应为栈顶元素。
栈的基本操作除了在栈顶进行插入或删除外,还有栈的初始化、判空及取栈顶元素等。
栈的表示和实现
栈包括两种存储结构:
(1)顺序栈
栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时设指针top指向栈顶元素的当前位置。空栈的栈顶指针值为零。假设用一维数组s(1:arrmax)表示栈,则对非空栈,s[1]为最早进栈的元素,s[top]为最晚进栈的元素,即栈顶元素。当top=arrmax时栈满,此时若有元素入栈则产生“越界”的错误,称为上溢,反之,top=0为栈空。
假设在程序中需设两个栈,并共享一维数组空间。可以利用“栈底位置不变”的特点,将两个栈的栈底分别设在数组空间的两端,然后各自向中间伸展,仅当两个栈的栈顶相遇时才可能发生上溢。
(2)链栈
链表作存储结构,利用指针来唯一确定。对于栈链,不会产生单个栈满而其余栈空的情形,只有当整个可用空间都被占满,new(p)过程无法实现时才会产生上溢。因此,多个链栈也可以共享空间。
树
树型结构是一类重要的非线性数据结构。在此类结构中,元素之间存在着明显的分层或嵌套关系,它们通常以各种形式的链表作存储结构,树和二叉树是最常用的树型结构。
l.树的定义和术语
在我们周围存在着很多可以用树结构来描述的实际问题。如图所示的树形结构。从图可见,树形结构类似一棵倒长的树,结构中含有一个类似“树根”的结点和若干类似“树叶”的结点以及若干分支结点。
树的形式化定义是:树(Tree)是由一个或多个结点组成的有限集合T,其中有一个特定的称为根(Root)的结点;其余结点可分为m(m≥0)个互不相交的有限集T1,T2,T3,…Tm,其中每一个集合本身又是一棵树,且称为根的子树(Subtree)。
(A(B(E(J,K),F),C(G),D(H(J),I)))
就是用表表示的那棵树。我们约定,每进一层次加一个括号,同层次用逗号分开。
术语
结点(node) 包含数据项及指向其它结点的分支。例如上图中的树共有12个结点。
结点的度(degree) 结点所拥有的子树棵数。例如上图的树中,根A的度为3,结点E的度为2,结点F,G,I,J,K,L的度为0。
叶(leaf)结点 即度为0的结点,又称为终端结点。例如上图的树中,{F,G,I,J,K,L}构成树的叶结点。
分支(branch)结点 除叶结点外的其它结点,又称为非终端结点。例如上图中,A,B,C,D,E,H就是分支结点。
子女(child)结点 若结点x有子树,则子树的根结点即为结点x的子女。例如上图中,结点A有3个子女,结点B有2个子女,结点L没有子女。
双亲(parent)结点 若结点x有子女,它即为子女的双亲。例如上图中,结点B,C,D,E,H等有一个双亲。
兄弟(sibling)结点 同一双亲的子女互称为兄弟。例如上图中,B,C,D为兄弟,E,F也为兄弟,但F,G,H不是兄弟。
祖先(ancestor)结点 从根结点到该结点所经分支上的所有结点。例如上图中,L的祖先为A,D,H。
子孙(descendant)结点 某一结点的子女,以及这些子女的子女都是该结点的子孙。
结点所处层次(level) 简称结点的层次,即从根结点所经路径上的分支条数。例如上图中,根结点在第0层,它的子女在第1层。
树的高度(depth) 树中结点所处的最大层次。空树的高度为-1,只有一个根结点的树的高度为0,上图中的树的高度为3。
树的度(degree) 树中结点的度的最大值。上图中的树的度为3。
有序树 树中结点的各棵子树是有序的,即为有序树。
无序树 树中结点的各棵子树之间的次序是不重要的,可以互相交换位置。
森林(forest) 是m(m≥0)棵树的集合。
2. 树的存储结构
直观地看,树的存储结构可以采用具有多个指针域的多重链表,结点中指针域的个数应由树的度来决定。例如,上图中树的度为3,可以采用含有3个指针域的结点的多重链表作存储结构。但在实际应用中,这种存储结构并不方便,一般将树转化为二叉树表示,再进行处理。
3.树的遍历
根据树的递归定义,有两种遍历树的方法:
先根(次序)遍历;若树中只有一个根节点,则访问树的根节点;否则,首先访问树的根节点,然后依次先根遍历每棵子树。
后根(次序)遍历;若树中只有一个根节点,则访问树的根节点;否则,首先依次后根遍历每一棵子树,然后访问树的根节点。
二叉树
l.二叉树的定义和术语
二叉树是另一种重要的树形结构,其结构定义为,二叉树(Binary Tree)是m(m≥0)个结点的有限集,它或为空树(m=0),或由一个根结点和两棵分别称为根的左子树和右子树的互不相交的二叉树组成,如下图所示。
需要注意的是,二叉树不是树的特殊情况,尽管树和二叉树的概念之间有许多关系,但它们是两个概念。树和二叉树之间最主要的差别是,二叉树的结点的子树要区分左子树和右子树,即使在结点只有一棵子树的情况下也要明确指出该子树是左子树还是右子树。
2.树的二叉树表示
在树(树林)与二叉树之间有一个自然的一一对应关系:每一棵树都能唯一地转换到它所对应的二叉树。有一个自然的方式把树和树林转换成对应的二叉树:凡是连接起来,对每个非终端结点,除其最左孩子外,删去该结点与其他孩子结点的连线,再以根结点为轴心,顺时针旋转45°。
对上图所示树用上述方法处理后稍加倾斜,就得到对应的二叉树。在树所对应的二叉树中,一个结点的左孩子是它原来在树里的第一个孩子,右孩子是它在原来树中的下一个兄弟。树的二叉树表示对于树的存储和运算有很大的意义,可以把对树的许多处理转移到对应的二叉树中去做,方便了树的存储和处理。
3.二叉树的性质
性质1 在二叉树的第i层上至多有2i-1个结点(i≥1)
性质2 深度为k的二叉树至多有2k-1个结点(k≥1)
性质3 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
完全二叉树和满二叉树,是两种特殊形态的二叉树。一棵深度为k且有2k-1个结点的二叉树称为满二叉树,这种树的特点是每一层上的结点数都是最大结点数。
可以对满二叉树的结点进行连续编号,约定编号从根结点起,自上而下,自左至右,由此可引出完全二叉树的定义。深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。显然,这种树的特点是:(1)叶子结点只可能在层次最大的两层上出现;(2)对任一结点,若其右分支下的子孙的最大层次为l,则其做分支下的子孙的最大层次必为l或l+1。
性质4 具有n个结点的完全二叉树的深度为log2n+1。
二叉树的遍历
在二叉树的一些应用中,常常要求在树中查找具有某种特征的结点,或者对树中全部结点逐一进行某种处理。这就提出了一个遍历二叉树的问题。即如何按某条搜索路径巡访树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。二叉树由三个基本单元组成:根结点、左子树和右子树。因此,依次遍历这三部分,便完成了整个二叉树的遍历。假如以L、D、R分别表示遍历左子树,访问根结点、遍历右子树,则可有DLR、LDR、LRD、DRL、RDL、RDL、RLD六种遍历二叉树的方案。若限定先左后右,则只有三种情况,即先(根)序遍历,中(根)序遍历和后(根)序遍历。下面是表达式a+b*(c-d)-e/f三种遍历过程演示过程。
二叉树的遍历算法
先序遍历二叉树的算法:
若二叉树不空,则依次进行下列操作:
访问根结点
先序遍历左子树
先序遍历右子树
中序遍历二叉树的算法:
若二叉树不空,则依次进行下列操作:
中序遍历左子树
根结点
中序遍历右子树
后序遍历二叉树的算法:
若二叉树不空,则依次进行下列操作:
后序遍历左子树
后序遍历右子树
根结点
查找
计算机操作的数据有两种类型,一种是数据量并不大,但计算异常复杂,科学计算就属于这一类型;另一类是数据量很大,计算相对来说比较简单,如只做一些查询、登记、统计分析等,所谓“数据处理”属于这种类型。在数据处理领域中,一个最基本的操作是数据定位,如在一张学生学籍情况统计表中查找指定学生的信息等,因此查找就成为非数值计算中很重要的一类算法。查找算法与数据元素的组织方式有密切的关系,组织方式不同,查找的方法就不一样。查找是数据处理中最基本、最重要的操作之一。
基本概念
查找与下一节介绍的排序都与关键字有关。关键字是数据元素中可以唯一标识一个数据元素的数据项,比如学号、身份证号等,但姓名、成绩、年龄等则不行,因为缺少唯一性。比如姓名可能相重,年龄可能相同等。查找是根据给定的关键值,在一组数据中确定其关键字等于给定值的数据元素的过程。确切定义为,给定一个值X,在含有n个记录的文件中进行搜索,寻找一个其关键字等于给定的X值的记录,如找到,则输出记录或记录在文件中的相对位置称查找成功;否则输出查找不成功的信息称查找失败。
查找算法
查找算法种类很多。衡量一个查找算法的主要标准是查找过程中对关键字进行的平均比较次数,或称平均检索长度,以n的函数形式表示,n是数据结构中的结点个数(或文件记录个数)。
顺序查找
顺序查找是针对线性表的最简单的查找方法。顺序查找的方法是:用待查关键字值与线性表中各结点的关键字值逐个比较,直到找出相等的关键字值;或者找遍所有结点都找不到符合要求项,即查找失败。顺序查找的优点是对线性表的结点逻辑次序无要求,对线性表的存储结构无要求(顺序存储、链接存储皆可)。它的缺点是平均检索长度长,为n/2。
二分法查找
顺序查找适用面广,对被查找的数据没什么要求。不过,如果被查找的数据已经按顺序排好了,再使用顺序查找就显得笨拙了。如果线性表结点是按关键字值排好序的,且线性表以顺序方式存储,我们可以采用二分查找。这是一种效率较高的线性表查找方法。
二分法查找的方法是:
用要查找的关键码值X与线性表中间位置结点的关键码W比较,这时有三种可能:
X=W,此时已经查找成功,查找结束。
X>W,表明X只能在表的后半部分,所以取表的后半部分进行查找。
X<W,表明X只能在表的前半部分,取表的前半部分进行查找。
二分法查找的优点是平均检索长度小,为1og2n。粗略地可以这样看,每经过一个关键码值比较,则查找范围缩小一半,因此经过log2n次比较就可完成查找过程。
二分法查找的缺点是它要求文件必须按关键字有序,并且只适用于顺序方式存储的顺序表。若初始文件无序,且不再进行记录的插入和删除(即文件一次形成后不再变动),则比较好的办法是,先对文件进行排序,使文件按关键字有序,然后用二分查找法。但若文件中的记录不断变动,二分法就不适用了。另外,在 n很小时(如n<30),二分法查找也不比顺序查找优越。
排序
在计算机计算和数据处理过程中,都会直接或间接地涉及到数据的排序问题。此外,在系统软件或应用软件的设计过程中,也不可避免地会遇到排序问题,在数据库和知识库管理系统中,排序应用更为广泛。据统计,在一些商业计算机系统中,排序操作占CPU的时间可高达15%~75%。可见,排序是计算机数据处理中的一种重要运算。如何进行排序,特别是高效率地进行排序是计算机应用中的重要问题之一。
基本概念
排序就是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字排列的有序序列。可以这样描述:
假设含有n个记录的文件{R1,R2,…,Rn},其相应的关键字为{K1,K2,…,Kn} ,需要确定一种排列P(1),P(2),…,P(n)使其相应的关键字满足如下的递增(或递减)关系:
KP(1)≤KP(2)≤…≤KP(n)或KP(1)≥KP(2)≥…≥KP(n)
称为排序。
排序可分为内排序和外排序两类。所谓内排序指当文件的数据量不太大时,排序过程中的全部信息放在内存中处理的排序方法。当文件的数据量较大,以致于内存不足以存放全部信息时,排序过程中需要在内、外存之间不断地进行数据交换才能达到排序的目的,这种排序称为外排序。内排序是排序的基础,方法也很多。
下面介绍几类最常用的内排序方法。
选择排序
选择排序的基本思想是,每次从待排序的记录中选出关键字最小(或最大)的记录,顺序放在已排序的记录序列的最后,直到全部排完为止。直接选择排序是一种很简单的排序方法,它的做法是:首先在所有记录中选出关键码最小的记录,把它与第一个记录交换,然后在其余的记录中再选出关键字码次最小的记录与第二个记录交换,依此类推,直到所有记录排序完成。
冒泡排序
冒泡排序也是一种简单的排序方法。具体作法是:将待排序的记录按从后向前的顺序顺次两两比较,若为逆序则进行交换。将序列照此方法从头到尾处理一遍称作一趟起泡,一趟起泡的效果是将关键码值最小的记录交换到了前面位置,即该记录的顺序起始位置。若某一趟起泡过程中没有任何交换发生,则排序过程结束。
插入排序
插入排序的基本思想是:每步将一个待排序的记录,按其关键码值的大小插入到前面已排序的文件的适当位置上,直到全部插完为止。
1.直接插入排序
在已排好的序列中用顺序法查找插入位置,找到后将该位置原来的记录及其后面所有记录顺序后移一个位置,空出该位置来插入新记录。用直接插入法排序n个记录的文件,关键码比较次数为n2量级,记录移动次数也为n2量级。
2.二分法插入排序
在已排好序的序列中使用二分法查找插入位置,找到插入位置后和直接插入排序法同样处理。二分法插入排序将关键字比较次数降为nlog2n量级,记录移动次数仍为n2量级。