910 likes | 1.08k Views
计算机软件技术基础 (3). 算法与数据结构. 设计程序首先要研究要解决的问题,提出适当的 计算模型 并列出解决问题的方法和步骤。 模型一旦建立起来,就要选择合适的 算法 ,并将解题步骤表述出来;同时需要确定合适的 数据结构 本章着重讨论解决问题的核心 -- 算法 以及算法的处理对象 -- 数据的结构. 如何解决农夫过河问题 一个农夫带着一只狼、一只羊和一棵白菜,身处河南岸,要把东西全部运到北岸。约束条件是只有一条能容下他和一件物品的小船,只有农夫能撑船。不能单独留下羊和白菜,也不能单独留下羊和狼,狼不爱吃白菜。. 3 .1 算法.
E N D
计算机软件技术基础(3) 算法与数据结构
设计程序首先要研究要解决的问题,提出适当的计算模型并列出解决问题的方法和步骤。设计程序首先要研究要解决的问题,提出适当的计算模型并列出解决问题的方法和步骤。 • 模型一旦建立起来,就要选择合适的算法,并将解题步骤表述出来;同时需要确定合适的数据结构 • 本章着重讨论解决问题的核心 -- 算法以及算法的处理对象 -- 数据的结构 • 如何解决农夫过河问题 • 一个农夫带着一只狼、一只羊和一棵白菜,身处河南岸,要把东西全部运到北岸。约束条件是只有一条能容下他和一件物品的小船,只有农夫能撑船。不能单独留下羊和白菜,也不能单独留下羊和狼,狼不爱吃白菜。
3.1算法 • 解题过程的准确、完整的描述称作解该问题的算法 • 程序就是用计算机语言表述的算法,流程图就是图形化了的算法 • 著名计算机科学家、PASCAL语言发明者N·沃思(Niklaus Wirth)教授进一步提出如下的著名公式: 程序=算法+数据结构 ——不能离开数据结构去抽象地分析程序的算法,也不能脱离算法去孤立地研究程序的数据结构,而只能从算法与数据结构的统一上去认识程序
3.1.1 算法的两要素 算法由操作与控制结构两要素组成 1.操作 (1) 逻辑运算: “与”、“或”、“非”; (2) 算术运算: 加、减、乘、除; (3) 数据比较: 大于、小于、等于、不等于; (4) 数据传送: 输入、输出、赋值。
2. 控制结构 • 算法的控制结构,决定了各操作的执行次序。用流程图 可以形象地表示出算法的控制结构 • 任何复杂的算法都可以用顺序、选择、循环三种控制结构组合而成(1966年,Bohm和Jacopini证明)
3.1.2 算法的特征 1. 算法是由一套计算规则组成的一个过程 2. 组成算法的规则是确定的、可执行的 3. 每种算法必须有确定的结果,产生一个或多个输出 4. 每个算法必须有0个(自动生成初始数)或多个输入 5. 解答必须在有限步内得到,不能出现“死循环” 我们可以得出如下的结论:算法是一个过程,这个过程由一套明确的规则组成,这些规则指定了一个操作的顺序,以便用有限的步骤提供特定类型问题的解答
3.1.3算法的表示 算法设计一般是由粗到细的过程,一般可以使用下面几种类型的工具描述算法: • 自然语言 用自然语言描述算法通俗易懂,但它存在着难以克服的缺陷: • 易产生歧义性。自然语言往往要根据上下文才能判别其含义,不太严格。 • 语句比较繁琐冗长,并且很难清楚地表达算法的逻辑流程。如果算法中包含判断、循环处理,尤其是这些处理的嵌套层数增多,自然语言描述其流程既不直观又很难表达清楚。 • 当今的计算机尚不能处理用自然语言表示的算法
专用工具 • 常用的有流程图、PAD图和N-S图、伪代码等 • 程序设计语言 • 算法描述语言 • 为了便于转换成某种编程语言,一般采用准程序设计语言作算法描述语言。在本书中为类VB语言 继续
流程图是采用不同的几何图形来描述算法的逻辑结构,每个几何图形表示不同性质的操作流程图是采用不同的几何图形来描述算法的逻辑结构,每个几何图形表示不同性质的操作 常用流程图符号: 返回
3.1.4 常用算法 1.枚举法(穷举法) 基本思想是: • 先依据题目的部分条件确定答案的大致范围 • 在此范围内对所有可能的情况逐一验证,直到全部情况验证完 • 若某个情况使验证符合题目的条件,则为本题的一个答案;若全部情况验证完后均不符合题目的条件,则问题无解
2.迭代法 • 使一个复杂问题的求解过程转化为相对简单的迭代算式的重复执行过程 使用迭代法构造算法的基本方法是: • 首先确定一个合适的迭代公式,选取一个初始近似值以及解的误差 • 然后用循环处理实现迭代过程,终止循环过程的条件是前后两次得到的近似值之差的绝对值小于或等于预先给定的误差 • 并认为最后一次迭代得到的近似值为问题的解。
3.递归法 • 如果一个过程直接或间接地调用它自身,则称该过程是递归的 • 例:求阶乘。 • Func fac(n As Integer) • If n=1 then • fac=1 • Else • fac=n*fac(n-1) • Endif 递归过程必须有一个递归终止条件, 当n=0时定义为1,是阶乘递归定义的递归出口 递归则是从函数本身出发,逐次上溯调用其本身求解过程,直到递归的出口,然后再从里向外倒推回来,得到最终的值
4.递推法 • 所谓递推法,它的数学公式也是递归的。只是在实现计算时与递归相反。从给定边界出发逐步迭代到达指定计算参数。 例:求阶乘 • f(n)=n! =n×(n-1)! =n×f(n-1) • 要计算10!,可以从递推初始条件f(0)=1出发,应用递推公式f(n)=n×f(n-1)逐步求出f(1)、f(2)…、f(9)、最后求出f(10)的值 • 递推操作是提高递归函数执行效率最有效的方法,科技计算中最常见
5.分治法 • 解一个复杂的问题时,尽可能地把这个问题分解为较小部分,找出各个的解,然后再把各部分的解组合成整个问题的解,这就是所谓的分治法 6.回溯法 • 在那些涉及到寻找一组解的问题或者满足某些约束条件的最优解的问题中,有许多可以用回溯法来求解
回溯法的算法是: Proc Backtracking(succ : Boolean) 确定起始状态值走第一步 确定下一步还有几种可能 选一可能走下一步,记住可能和本步特征 做完新一步应做的事 While 目标未达到 do 确定下一步有几种可能 While 没有可能and 还有上一步 do 回退上一步 查有无下一可能 Enddo If 上一步没有了Then return (SUCC=FALSE) EndIf 选一可能走一步,记住可能和本步特征 做完新一步应做的事 Enddo return (SUCC=TRUE) End Backtracking
3.1.5 算法分析 • 评价一个算法是否完善,我们主要关心以下三个问题: • 算法的复杂度 • 时间复杂度----执行算法的计算工作量,即算法的时间代价。 衡量算法工作量的方法: • 执行算法所需时间作为算法工作量的度量。 • 算法程序中所执行的指令条数或语句条数作为度量。 • 语句执行的次数作为度量。 • 空间复杂度----执行算法所需的内存空间,包括: • 算法程序所占的空间 • 输入初始数据所占的空间 • 算法执行过程中所需的额外空间,包括: • 算法执行过程中的工作单元 • 数据结构所需的附加空间
算法的最优性----衡量算法的好坏主要依据算法的复杂度,特别是时间复杂度。通常总是在最坏的情况下分析算法的工作量。算法的最优性----衡量算法的好坏主要依据算法的复杂度,特别是时间复杂度。通常总是在最坏的情况下分析算法的工作量。 • 最优算法是指在解决一个问题时,如果在被研究的算法类中,没有一个算法比现有算法执行更少的基本运算,则称此算法是最优的。 • 快速算法的设计----与工程上常用的算法相比,其时间复杂度较小。 • 快速算法不一定是最优算法。
3.2 数据结构3.2.1 数据结构概述 。 1.数据结构的研究内容 数据的逻辑结构、数据的存储结构、数据的运算 数据的逻辑结构:Data-Structure = (D,R) 其中:D是数据元素的集合,R是D上关系的集合 一般将数据结构分为两大类:线性数据结构和非线性数据结构。线性数据结构有线性表、栈、队列、串、数组和文件;非线性数据结构有树和图 程序中的数据运算是定义在数据的逻辑结构上的,但运算的具体实现要在存储结构(物理结构)上进行。每种逻辑结构都有一个运算集合。常用的运算有检索、插入、删除、更新、排序等
2.研究方法 • 最基本的数据结构为表,其他数据结构都转化为表处理 • 方法: • 研究数据结构的逻辑结构的运算性质 • 研究数据结构的物理结构 • 根据物理结构和运算性质写算法(集合) • 逻辑结构和物理结构是相对的
3.2.2 线性表 • 线性表的逻辑结构是n个数据元素的有限序列: (a1, a2 ,a3,…an) n为线性表的长度(n≥0),n=0的表称为空表 • 数据元素呈线性关系.必存在唯一的称为“第一个”的数据元素;必存在唯一的称为“最后一个”的数据元素; • 除第一个元素外,每个元素都有且只有一个前驱元素; 除最后一个元素外,每个元素都有且只有一个后继元素。 • 所有数据元素ai在同一个线性表中必须是相同的数据类型
线性表按其存储结构可分为顺序表和链表。用顺序存储结构存储的线性表称为顺序表;用链式存储结构存储的线性表称为链表线性表按其存储结构可分为顺序表和链表。用顺序存储结构存储的线性表称为顺序表;用链式存储结构存储的线性表称为链表 • 线性表的基本运算主要有: (1)在两个确定的元素之间插入一个新的元素; (2)删除线性表中某个元素; (3)按某种要求查找线性表中的一个元素,需要时,还可找到元素进行值的更新
1.顺序表和一维数组 • 将线性表中的数据元素依次存放在某个存储区域中,所形成的表称为顺序表。一维数组就是用顺序方式存储的线性表,其下标可看成元素的相对地址 • 运算: (1) 插入,在线性表(a1, a2…,ai,ai+1…,an)的第i个位置插入元素x,算法如下: Sub Insert(ByRef A:Type,n,i,x) If(i<1) Or (i>n+1) Then ERROR(“位置不存在!”) Else For j=n Down To i A(j+1)=A(j) Next j Endif A(i)=x n=n+1 End
(2)删除:在表长为n的线性表(a1,a2,…ai-1,ai,ai+1…an)中删除第i个数据元素,通常还需将第i+1个至第n个元素向前推动一个位置,即(a1, a2 ,…,ai-1,ai+1,…,an),其算法描述如下: 顺序表的不足: PROC DELETE (VAR A,VAR n,I) If (i<1) Or (i>n) Then ERROR ('位置不存在!') ELSE FOR j=i TO n-1 A(j)=A(j+1) Next j n=n-1 Endif End • 在顺序表中插入或删除元素时,每进行一次插入或删除,都要移动近乎一半的元素。 • 对于长度可变的线性表,必须按可能达到的最大长度分配空间
2.链表 (1)单链表(线性链表):链式存储的线性表 • 结点除信息域外还含有一个指针域,用来指出其后继结点的位置 • 最后一个结点没有后继结点,指针它的指针域为空(记为NIL 或∧)。另外还需要设置一个指针head,指向单链表的第一个结点
(2)循环链表:循环链表和单链表的差别仅在于链表中最后一个结点的指针域不为“NIL”,而是指向头一个结点,成为一个由链指针链结的环(2)循环链表:循环链表和单链表的差别仅在于链表中最后一个结点的指针域不为“NIL”,而是指向头一个结点,成为一个由链指针链结的环 (3)双向链表:设有一个指向后继结点的指针和一个指向前驱结点的指针
栈(STACK)也是一种特殊的线性表,是一种“后进先出”的结构,它的运算规则受到一些约束和限定,故又称限定性数据结构栈(STACK)也是一种特殊的线性表,是一种“后进先出”的结构,它的运算规则受到一些约束和限定,故又称限定性数据结构 (1)栈的结构特点 栈是限定仅在表尾进行插入和删除运算的线性表,表尾称为栈顶(top),表头称为栈底(bottom) 栈的物理存储可以用顺序存储结构,也可以用链式存储结构 (2)栈的运算 设置一个空栈 判定栈是否为空 进栈、退栈 读取栈顶元素等 3.栈
应用 • 括号匹配 • (a*(b+c)+d) • 写出匹配括号的程序 • 思路: • 从左到右扫描字符串,把所遇到的左括号压入堆栈;每当遇到一个右括号,就把它与栈顶的左括号(如存在)匹配,同时从栈顶弹出该左括号。 • 十进制到八进制数的转换
栈的变形 • 在使用多个栈的时候,采用单个数组存在缺陷(?) • 空间利用率不高 • 可以在一个数组中表达多个堆栈。 • 尤其是在表达两个堆栈的时候
4.队列 (1)队列的结构特点 • 队列也是一种特殊的线性表。在实际生活中经常要靠排队来维护正常的社会秩序,在计算机程序设计中也有类似的问题。数据结构中的队列与生活中的“排队”极为相似,也是按“先来到先解决”的原则行事的,既不允许“加塞儿”,也不允许“中途离队”。“先入先出” • 队列(Queue)是限定所有的插入只能在表的一端进行,而所有的删除都在表的另一端进行的线性表 • 表中允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front) • 队列的操作是按先进先出的原则进行的 • 队列的物理存储可以用顺序存储结构,也可以用链式存储结构。
队列的运算 • 通常对队列进行的运算有:设置一个空队列;判定某个队列是否是空队列;插入一个新的队尾元素,简称入队列;删除队头元素,简称出队列;以及读取队头元素等。 • 如果进入队列的元素个数事先可以估计得到,则队列可以按顺序存储方式进行组织。当队列的容量无法预先估计时,可以采用右图所示的链表存储结构。 • “假溢出”问题:若采取每插入一个元素,队尾指针变量R的值加1,每删除一个元素,队头指示变量F的值加1的方法,则经过若干次插入、删除运算后,尽管队列中的元素个数小于存储空间的容量,但由于此时R可能已指向存储空间的末端,而无法再进行插入了。所以,在实现插入、删除运算时一般使用循环队列,即把队列的存储空间逻辑上看成一个环,当R指向存储空间的末端后,就把它重新置成指向存储空间的始端,如下图所示。
进队操作与出队操作结果示意图 • 显然,若队空间为N个元素,则进行N次进队操作后就会出现队满现象,而不论这中间是否进行过出队操作,即出队操作所腾出的空位不能重复利用。这是一个十分严重的问题,若不解决,无论分配多大的空间,也不能保证不发生溢出。所以,这种队列无实用价值。解决这问题的方法是在发生假溢出时,移动队中元素,在尾部让出空位。但这种方法效率差,一种更好的方法是使用循环结构──这就是所谓循环队列法。
循环队列 将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。采用循环队列结构后,有效地解决了“假溢出”的问题,避免了数据元素的移动。 • 插入一个新元素 rear = mod(rear,m)+1 • 删除一个元素 front = mod(front,m)+1 • front = rear 队列空或满 增加一个标志变量S
front = rear 队列空或满,增加一个标志变量S • 假设标志为S,其状态定义为 • 设初始状态F=R,S=0 • 插入一个元素后置S=1 删除操作 If S = 0 Then ERROR (“队空!”); Return; Else F =mod ( F,m ) + 1; If F = R Then S = 0; Endif 插入操作 If ( F = R ) and ( S = 1 ) Then ERROR (“队满!”); Return; Else R = mod ( R,m ) + 1; Q(R) = x; S = 1; Endif
3.2.3 树和二叉树 • 树型结构是一类重要的非线性数据结构。在此类结构中,元素之间存在着明显的分层或嵌套关系,它们通常以各种形式的链表作存储结构,树和二叉树是最常用的树型结构。
1.树和二叉树的定义和术语 • 树结构类似一棵倒长的树,结构中含有一个类似“树根”的结点和若干类似“树叶”的结点以及若干分支节点 • 树的形式化定义: • 树(Tree)是由一个或多个结点组成的有限集合T • 其中有一个特定的称为根的结点; • 其余结点可分为m(m≥0)个互不相交的有限集T1,T2,T3 ,…,Tm • 每一个集合本身又是一棵树,且称为根的子树
树的表示,每进一层次加一个括号,同层之间用逗号分开例如:(A(B(E,F),C(G),D(H,I,J))),表示下面的树树的表示,每进一层次加一个括号,同层之间用逗号分开例如:(A(B(E,F),C(G),D(H,I,J))),表示下面的树 • 一个节点的子树个数称为该节点的度(degree);树中各节点的度的最大值被定义为该树的度
父结点、子结点、边 • 兄弟 • 祖先、子孙 • 路径、路径长度 • 结点的层数、树的层数 • 树叶、分支节点
二叉树是另一种重要的树形结构,其结构定义为:二叉树(Binary Tree)是n(n≥0)个结点的有限集,它或为空树(n=0),或由一个根结点和两棵分别称为根的左子树和右子树的、互不相交的二叉树组成
树和二叉树之间最主要的差别是:二叉树的结点的子树要区分左子树和右子树,即使在结点只有一棵子树的情况下也要明确指出该子树是左子树还是右子树树和二叉树之间最主要的差别是:二叉树的结点的子树要区分左子树和右子树,即使在结点只有一棵子树的情况下也要明确指出该子树是左子树还是右子树
二叉树的特性 • 包含n个(n>0)元素的二叉树边数为n-1 • 若二叉树的高度为h(h>=0), 则该二叉树最少有h个元素,最多有2h-1个元素 • 包含n个元素的二叉树的最大高度为n,最小为[log2(n+1)] • 满二叉树 • 完全二叉树
2. 树的存储结构 • 树的存储结构可以采用具有多个指针域的多重链表,结点中指针域的个数应由树的度来决定 • 但在实际应用中,这种存储结构并不方便,一般将树转化为二叉树表示,进行处理
2. 二叉树的存储结构 • 通常使用具有2个指针域的链表,LC为左指针域,指向结点的左子树,RC为右指针域,指向结点的右子树。 • 亦可用数组的下标来模拟指针,即开辟三个一维数组DATA,LC和RC分别存放结点的元素及其左、右指针
树的二叉树表示方法 • 树的二叉树表示 • 在树(树林)与二叉树之间有一个自然的一一对应的关系,每一棵都能唯一地转换到它所对应的二叉树。 • 有一个自然的方式把树和树林转换成对应的二叉树:凡是兄弟就用线连接起来,对每个非终端结点,除其最左孩子外,删去该结点与其他孩子结点的连线,再以根结点为轴心,顺时针旋转45度
树的二叉树表示方法 • 树的二叉树表示 • 在树(树林)与二叉树之间有一个自然的一一对应的关系,每一棵都能唯一地转换到它所对应的二叉树。 • 有一个自然的方式把树和树林转换成对应的二叉树:凡是兄弟就用线连接起来,对每个非终端结点,除其最左孩子外,删去该结点与其他孩子结点的连线,再以根结点为轴心,顺时针旋转45度