1、第十二课 数据结构线性表12.0 数据结构概述12.1 线性表12.2 栈12.3 队列12.4 串12.0 数据结构概述是否掌握数据结构,是区分一个程序设计人员水平高低的标志,也是在高中阶段信息学竞赛中,能否获得一等奖的分水岭。无论在国内还是国外, 数据结构一直是大学计算机专业的一门专业基础课,但是,对于学习程序设计的中学生,在学习完一门程序设计语言后,为了学习更多的算法以进一步提高编程能力, 数据结构就成了必须突破的对象。掌握数据结构的目的,是充分利用数据之间的内在联系,降低数据的检索、插入、删除、更新、排序等操作的时间复杂度,从而提高算法的效率。从而,避免了大量的穷举式操作。一、什么是数
2、据结构:简而言之,就是“数据+结构” 。1、 数据:数据是客观事物的符号表示。是我们在解决问题的过程中,对客观事物对象所进行的定量、定性的数值或文字表达,以便于我们输入计算机中进行模拟、计算、处理。例如:我们要对这次期中考试成绩进行总分计算、名次排序、优秀率等统计,就需要将考试结果用分数量化,并加上姓名等信息后,输入到计算机中进行处理,我们将所有输入的这些内容,成为一个“期中考试成绩数据” 。数据元素、数据项:数据项:数据中补课分割的最小单元。例如:上例中的某个学生的语文成绩。数据元素:是数据的基本单位,是由若干个数据项组成。例如:上例中某一个学生的考试各科成绩。2、 结构:数据中各数据元素之
3、间的关系,分为逻辑结构和物理结构。逻辑结构:数据之间的内在关系。例如:一家人参加旅行团去某地旅游,在旅行车上,无论他们是否坐在一起,他们之间的父子、母子等内在关系依然存在。物理结构:数据在计算机中的存储方式,又称为存储结构。物理结构的种类:顺序存储、链式存储、索引存储、散式存储。顺序存储:把逻辑相邻的结点存储在物理相邻的存储单元内链式存储:结点之间的逻辑关系由附加指针来表示。索引存储:存储结点信息的同时,建立索引表。散列存储:按结点的关键字直接计算出存储地址。物理结构与逻辑结构之间的关系:(1) 逻辑结构是具体问题抽象出来的数学模型,物理结构是逻辑结构的计算机语言的实现。(2) 有什么样的逻辑
4、结构,就应该选择相应的物理结构。(3) 选择的物理结构要方便基于逻辑结构上的数据运算。(4) 物理结构不等同于逻辑结构,反之,逻辑结构也不等于物理结构,因此,对数据结构的设计,不仅要进行逻辑结构的设计,也还要进行物理结构的设计。数据的运算:各种逻辑结构上的运算集合。常用的有:检索、插入、删除、更新、排序。二、数据结构的分类:序号 名称 结构特点 常用物理结构1 集合 无任何关系的一组数据例如:互不相识的若干人乘坐同一辆公交车。顺序索引2 线性表 元素间存在严格的一对一的关系例如:按顺序排列的 26 个英文字母,B 前面只能是 A,后面只能是 C。顺序散列3 树 元素间严格的一对多关系例如:磁盘
5、中的文件夹和文件。顺序索引、散列4 图 元素间为多对多的关系例如:一个班级同学之间的关系。一个人可以有多个朋友,也可以是多个人的朋友。顺序索引12.1 线性表一个线性表是 n 个数据元素的有限序列。线性表是最常用且最简单的一种数据结构。一、线性表的结构特点在数据元素的非空有限集中,(1)存在唯一的一个被称做“第一个”的数据元素;(2)存在唯一的一个被称做“最后一个”的数据元素;(3)除第一个之外,集合中的每个数据元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。a1 ai-1 ai ai+1 anai是 ai+1的直接前驱元素,a i+1是 ai的直接后继元素。线性表
6、中元素的个数 n 定义为线性表的长度,为 0 时称为空表。在非空表中的每个数据元素都有一个确定的位置。a i是第 i 个元素,把 i 称为数据元素 ai在线性中的位序。二、线性表的操作:1、 求长度:L:=a0;2、 清空:a0:=0;3、 判断线性表是否为空:(a0=0)=true4、 线性表存在且未满:a0b)and(finda0 then find:=0;end;三、线性表应用实例:1、 顺序表倒置:算法思路:把第一个元素与最后一个元素交换,把第二个元素与倒数第二个元素交换。一般地,把第 i 个元素与第 n-i 个元素交换,i 的取值范围是 0 到 n/2(n 为顺序表的长度) 。pro
7、cedure invert;var i,tmp:integer;beginfor i:=1 to a0 div 2 dobegintmp:=an-i+1;an-i+1:=ai;ai:=tmp;end;end;2、 两顺序表合并:procedure merger(a,b:var;var c:arr);var p1,p2,p,i:integer;beginfillchar(c,sizeof(c),0);p1:=1; p2:=1; p:=0;while (p1bb0 thenbegininc(b0); bb0:=ai;end;end;12.2 栈一、栈的定义:栈(Stack)是操作限定在表的尾端进行
8、的线性表。二、栈的结构特点:1、栈是一种特殊的线性表。2、栈的一端(栈底)是封闭的,禁止进行任何操作。3、栈的中间不允许插入和删除等操作,但可以发生查找等操作。4、 栈的插入和删除等操作全部在另一端(栈顶)进行。从以上特点可以看出,先入栈的元素将被压 在后入栈的元素底下,只有后入栈的元素出栈后才能出栈。因此,栈又称为“先进后出表(LIFO) ”或“后进先出表(FOLI) ”。如右图,表的底端(栈底)是封闭的,进栈和出栈操作都只能在栈顶进行。我们只需要一个栈顶指针就可以了。当 top=0 时,称为空栈。三、栈的物理结构:一般采用数组存储,a1 为栈底,an一端为栈顶,可以很方便地实现栈顶的进栈操
9、作和出栈操作。四、栈的基本运算:1、 初始化空栈:top:=0;2、 求栈的长度:L:=top;3、 判断栈是否溢出:(topmaxn)=true4、 判断栈是否为空:(top=0)=true5、 进栈操作:if top0 thenbeginb:=atop;dec(top);end;7、 取栈顶元素:(只取栈顶数据,不改变栈的大小)if top0 dobegininc(top);atop:=n mod 8;n:=n div 8;end;end;2、行编辑:一个简单的行编辑程序的功能是:接受用户从终端输入的程序或数据,并存入用户的数据区。允许用户输入出错时可以及时更正。可以约定为退格符,以表示前
10、一个字符无效,为退行符,表示当前行所有字符均无效。procedure editline(var st:arr);var top:integer;begintop:=0;repeatread(inf,ch);case ch of#:if top dobegincode:=1;while (scode=0)and(scode()and(ch()and(sftops() dobegincha:=pops;a:=popd;b:=popd;c:=jisuan(b,a,cha);pushd(c);end;dec(tops);end;end elsebeginss:=copy(s,1,code-1);del
11、ete(s,1,code-1);val(ss,a,code);pushd(a);end;end;while tops=max then 回溯 p:=false;endif ;until p=true;until step=0;回溯过程如下:Procedure BACK;step:=step-1 ;if step0 then 栈顶结点出栈else p:=true;procedure back; /回朔过程begindec(step);if step0 then begin栈顶结点出栈;p:=false;end else p:=true;end;/procedure dfs(step);begin
12、step:=0;repeatinc(step);j:=0; p:=false;repeatinc(j);if 新产生结点复合条件 thenbegin新产生结点入栈;if 子结点是目标结点 then 输出else p:=true;end else if j=max then back;until p;until step=0;Borland/Turbo Pascal 编译器是 16 位的编译器,最多只能设置 64KB 字节的栈空间,因此,使用模拟递归进行回朔是非常必要的。在 Freepascal 是 32 位的编译器,理论上栈空间是没有限制的,所以,现在已经不提倡使用结构复杂的模拟递归了。12.
13、3 队列(Queue)一、队列的定义:队列是只允许在一端进行加入,而在另一端进行删除的运算受限的线性表。(1)允许删除的一端称为队头(Front) 。(2)允许插入的一端称为队尾(Rear ) 。(3)当队列中没有元素时称为空队列。(4)队列亦称作先进先出(First In First Out)的线性表,简称为 FIFO 表。队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(即不允许“加塞“) ,每次离开的成员总是队列头上的(不允许中途离队) ,即当前“ 最老的“成员离队。二、队列的操作:为了便于队头和队尾位置的记录,需要开设两个下标指针变量:头指针:head,指向当前队头元素的前一
14、个 位置(即最后出队的元素位置) 。尾指针:tail, 指向当前队尾元素所在的位置。1、初始化队列(置队空):head:=0; tail:=0;2、判断队列是否为空:head=tail=true3、 判断是否已满(即是否溢出)队列溢出有两种:1 2 m m-1 m-2 maxn假性溢出: * * * . . . *head tail 此时,如果有新元素需要入队,由于数组以使用到最大下标无法入队,二实际上数组中仍有队头之前的 m-1 个空位置。这种溢出称为假性溢出。假性溢出的克服方法:采用环形数组,即使 tail 到达maxn 时,自动返回到 1。1 2 m m-1 m-2 maxn真性溢出:h
15、ead tail或:1 2 m m-1 m-2 maxntail head 第一种情况为链式队列,队头指针指向 1,而队尾指针已到达 maxn,当再有元素需要入队时,因无法入队而发生溢出,其解决办法只有扩充 maxn 的值。第二种情况为使用循环队列,队头指针已循环到队尾指针的左边,并已把 head 前面的空位置占满,如果再有元素入队,则会发生叠加到队头元素上。其解决办法有二:扩充 maxn 和附加备用队列。判断是否溢出操作:链式队列:tailmaxn=true循环队列:(head=tail)and(ring_headmaxn thenbegininc(ring_head);head:=(hea
16、d-1) mod maxn+1;end;deq:=ahead;end;/procedure join(d:integer);begininc(tail);if tailmaxn thenbegininc(ring_tail);tail:=(tail-1)mod maxn+1;end;atail:=d;end;/procedure main;begind:=deq; bd:=1;for i:=2 to n dobeginfor j:=1 to i dobegind:=deq;join(d);end;d:=deq;bd:=i;end;end;/procedure print;beginrewrit
17、e(outf);for i:=1 to n-1 do write(outf,bi, ); write(outf,bn);close(outf);end;/begininit;main;print;end. 12.4 串串是一种特殊的线性数据。一般对于串的操作既可以按数组的方式进行,也可以使用专用函数或过程,其时间代价基本相同。(1) 求串长 O(1) length(2) 两串连接 O(n) 加法远算(3) 复制子串 O(n) copy(4) 插入子串 O(n) insert(5) 删除子串 O(n) delete(6) 数转串 O(n) str(7) 串转数 O(n) val(8) 串匹配 O
18、(mn) pos从以上看出,串的匹配算法时间复杂度非常大。这里学习对串的模式匹配的算法改进。一、 pos 函数的算法效率低下的原因:传统的字符串模式匹配算法(BF 算法)就是对于主串和模式串双双自左向右,一个一个字符比较,如果不匹配,主串和模式串的位置指针都要回溯。这样的算法时间复杂度为O(nm) ,其中 n 和 m 分别为串 s 和串 t 的长度。例如:在串 S=”abcabcabdabba”中查找 T=” abcabd”先是比较 S1和 T1是否相等,然后比较 S2 和 T2是否相等我们发现一直比较到 S6 和 T6才不等。如图一:图一当这样一个失配发生时,T 下标必须回溯到开始,S 下标
19、回溯的长度与 T 相同,然后 S 下标增 1,然后再次比较。如图:图二极端的例子是:在 S=“AAAAAAAAB“(100 个 A)中查找 T=”AAAAAAAAAB”, 简单匹配算法每次都是比较到 T 的结尾,发现字符不同,然后 T 的下标回溯到开始,S 的下标也要回溯相同长度后增 1,继续比较,所以,时间复杂度为 O(mn)。二、 对传统算法的改良探讨图二中,S6和 T6失配后, S 和 T 的下标同时回朔是效率低下的根本原因(接下来的比较很多是无意义的) ,对比两个串发现,当发生失配时,下一次有效比较发生在S4和 T1(如图三),而真正有意义的比较则发生在 S6对 T3。图三通过考察得出
20、,最好的策略是:1、 S 串的下标不回朔,停留在上次失配的位置 i。2、 T 串回朔 k 个位置,使 T 串与 S 串中对齐第 i 个字符之前的子串相配。因此,只要能求出 T 串中每个位置的 k 值,就可以在模式匹配过程中,对 S 串从头扫描到尾(不回朔) ,当发生 Si和 Tj失配时,只将 T 串回朔 k 个位,接下来就可以继续比较 Si与 Tk,直到完全匹配或 S 串到末尾。就能实现 O(n)的效率。三、 KMP 算法:1、 求 k 值:当 Si与 Tj失配时, T1Tj-1与 Si-j+1Si-1一定已匹配成功,则两串的红色1 i-j+1 i-k+1 iST1 j-k+1 j区域完全相同
21、:Si-k+1Si-k+2Si-k+3Si-1=Tj-k+1Tj-k+2Tj-k+3Tj-1。(1)当 T 串回朔到下标为 k 的位置时,Tk与 Si对齐进行下一次比较,而 S 串中红色1 i-j+1 i-k+1 iST区域和 T 串中蓝色区域完全相同。即有:Si-k+1Si-k+2Si-k+3Si-1=T1 T2T3 Tk-1 (2)由(1) (2)两式得出:Tj-k+1Tj-k+2Tj-k+3 Tj-1= T1 T2T3Tk-1(3)即 T 串的蓝色区域和红色区域完全相同,由此得出,要求出 T 串中 j 位置的 k 值,只需要找出 j 位置前有多少个字符(红色区域)与自身串首的字符(蓝色区
22、域)相同即可。 0 当 j=1时Kj= maxk|1lp then main:=i-lpelse main:=0;end;/procedure print;beginwrite(outf,m); close(outf);end;/begininit;makenext(p);m:=main;print;end. 习题:1、 读入一个算术表达式,判断其括号是否配对正确。2、 给定一个栈、一个无序数据队列 A 和一个空队列 B,无序数据队列中的数据可以有两种操作,进栈(a 操作)或出栈进入到队列 B 中(b 操作) ,判断 A 栈中的数据能否通过 A、B 两种操作在 B 队列中排序,如能,输出操作序列,如不能,则输出-1。例一:输入:1 2 3 4 5输出:ababababab例二:输入:5 4 3 2 1输出:aaaaabbbbb例三:输入:5 2 4 3 1输出:-13、编程找出 s1 和 s2 的最长公共子串。 (串长10000) 。4、集合的前 N 个元素:编一个程序,按递增次序生成集合 M 的最小的 N 个数,M 的定义如下:(1)数 1 属于 M;(2)如果 X 属于 M,则 Y=2*X+1 和 Z=3*1 也属于 M;(3)此外再没有别的数属于 M。