数据结构-栈
对于程序设计来说:\r\n 编程语言是工具;\r\n 数据结构是基础;\r\n 算法设计是方法。
程序=数据结构 算法
数据结构
数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合。
数据结构的内涵\r\n“操作”的对象:数据。\r\n数据与数据间的关系。\r\n针对数据的基本操作。
数据结构的形式定义 \r\ndata _ structure=(D,S)\r\nD:数据元素的有限集;\r\nS:D上关系的有限集;
数据结构相关概念
数据(data) 计算机科学中指所有能输入到计算机中并被程序处理的符号总称。例如数值、字符、图像、声音都属于数据的范畴。\r\n\r\n数据元素(data element) 是数据的基本单位 ,在程序中作为一个整体进行考虑。\r\n 有时一个数据元素有若干数据项。\r\n\r\n数据对象(data object)是性质相同的数据元素的集合,是数据的一个子集。
逻辑结构
前述“关系”即结构(sructure),指数据之间的逻辑关系,又称逻辑结构。
物理结构
1 顺序结构:借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。 \r\n 逻辑上关联的数据元素,物理存储结构中相邻。 \r\n2 链式结构 :借助元素存储地址的指针(pointer)表示数据元素之间的逻辑关系 。\r\n 逻辑上关联的数据元素,物理存储结构中不一定相邻。 \r\n用高级语言中的数据类型(数组、动态变量)来描述\r\n逻辑结构、物理结构密切相关\r\n算法的设计取决于所选定的数据(逻辑)结构,而算法的实现依赖于所采用的存储结构。
由n个数据元素的有限序列\r\n除头元素外,每个元素都有一个前趋\r\n除尾元素外,每个元素都有一个后继
线性结构
线性表
线性表是最常用且最简单的一种数据结构,它是由有限个数据元素组成的有序集合。\r\n每个数据元素有一个数据项或者含多个数据项。
线性表的两种存储方式
1、顺序存储结构\r\n顺序存储结构是指用一组地址连续的存贮单元依次存储线性表的元素,通常用数组实现。数组的物理实现是一块连续的存储空间,它是按首址(表中第1个元素的地址)十位移来访问每一个元素。
2、链式存储结构\r\n 在链式存储结构的线性表中,逻辑上相邻的两元素,其物理位置不要求相邻。采用动态指针。数组元素和动态变量的类型一般采用记录类型,数据域存储结点的值,指针域存储后件的数组下标或地址。最后一个结点的指针域的值为0或nil。
链表的插入与删除
无需移动任何元素
线性表的具体实现
顺序存储结构\r\n用数组类型:\r\n list: array [1..maxlen] of elemtp;\r\n\r\n链式存储结构\r\n 用指针类型 和 动态变量:\r\n pointer = nodetype ^ ;\r\n nodetype = record\r\n data : elemtp ;\r\n next : pointer ;\r\n end;
顺序存储与链式存储操作的对比
限定仅在表尾进行插入和删除操作的线性表,又称为后进先出(last in first out )线性表 (LIFO结构)\r\n \r\n 栈顶——表尾\r\n 栈底——表头
栈
通常栈可以用顺序的方式存储(数组),分配一块连续的存储区域存放栈中的表目,并用一个变量t指向当前栈顶(如下图)。
栈的实现(一)
Const\r\n m=栈表目数的上限;\r\nType\r\n stack=array[1..m] of stype; {栈类型}\r\nVar \r\n s:stack;{栈}\r\n top: integer; {栈顶指针}\r\n
栈的实现(二)
const\r\n m=栈表目数的上限;\r\n type \r\n stack=record\r\n elem: array[1..m] of elemtp;\r\n top:0..m; {栈顶指针}\r\n end;\r\n Var \r\n s:stack;{栈}
TOP=0 表示栈空\r\nTop=m 表示栈满
栈的基本操作
栈的基本操作包括四种\r\n初始化(init)、\r\n进栈(push)、\r\n出栈(pop)、\r\n读取栈顶元素(top)。
1) 过程init(s,t) —初始化\r\nprocedure init;\r\n begin\r\n t:=0;\r\n end;\r\n2)、过程push(x)—往栈s中压入一个值为x的数据:\r\nprocedure push (var s:stack; x:stype; var t:integer);\r\n begin\r\n if t=m then writeln(‘overflow’) {上溢}\r\n else begin t←t 1;s[t]←x;end;{else} {x入栈}\r\nend;{Push}
3)函数pop(s,t)—从栈中弹出一个表目\r\n function pop (var s:stack; var t:integer):stype;\r\n begin\r\n if t=0 then writeln (‘underflow’) {下溢}\r\n else begin pop←s[t]; t←t-1; end;{else} {栈顶元素出栈}\r\n end;{pop}\r\n4)函数top(s,t)—读栈顶元素\r\n function top (s:stack; t:integer):stype;\r\n begin\r\n if t=0 then writeln (‘stack empty’)\r\n else top←s[t]; {返回栈顶元素}\r\n end;{top}
栈的应用
1、(1998) 栈S初始状态为空,现有5个元素组成的序列{1,2,3,4,5},对该序列在栈S上一次进行如下操作(从序列中的1开始,出栈后不再进栈):进栈、进栈、进栈、出栈、进栈、出栈、进栈。问出栈的元素序列是______\r\n\r\n(A) {5,4,3,2,1} (B) {2,1} (C) {2,3} (D) {3,4}
【样例3输入:】\r\n{ [ } ]\r\n【样例3输出:】\r\nno\r\n【样例4输入:】\r\n< >>>\r\n【样例4输出:】\r\nno
2、括号匹配\r\n【问题描述:】\r\n判断包含有括号{,[,<,(,),>,],}的字符串是否是合法匹配。\r\n例如以下是合法的括号匹配:\r\n(), [ ], (()), ([ ]), ()[ ], ()[()]\r\n以下是不合法括号匹配的:\r\n(, [, ], )(, ([ ]}, ([(),{( })\r\n【输入:】\r\n一行,字符串(长度范围:[1,200])。\r\n【输出:】\r\n如果字符串中括号匹配是合法的输出“yes”,不合法的输出“no”。
【样例1输入:】\r\n{ [ ] } < >\r\n【样例1输出:】\r\nyes\r\n【样例2输入:】\r\n{ [ ] < >\r\n【样例2输出:】\r\nno
i:=1; t:=0;tt: 0;\r\n while i<=n do\r\n begin\r\n case s[i] of\r\n '(','[','{','<':push(s[i]);\r\n ')':if pop<>'(' then begin tt:=1;break;end;\r\n '}':if pop<>'{' then tt:=1;break;end; ']':if pop<>'[' then tt:=1;break;end; '>':if pop<>'<' then tt:=1;break;end; end;\r\n inc(i);\r\n end;\r\n if (t<>0) or (tt<>0) then writeln('No') \r\n else writeln('Yes');\r\nend.
var\r\n s:string;\r\n a:array[0..200] of char;\r\n n,i,t,tt:integer;\r\n procedure push(k:char);\r\n begin\r\n inc(t); a[t]:=k;\r\n end;\r\n function pop:char;\r\n begin\r\n pop:=a[t]; dec(t);\r\n end;\r\nbegin\r\n readln(s);\r\n a[0]:=' '; n:=length(s);
算法1:
var\r\n f:array[char] of char;\r\n a:array[0..200] of char;\r\n s:string; ch:char;\r\n p,i,n:longint;\r\n function pop:char;\r\n begin pop:=a[p];dec(p); end;\r\n procedure push(x:char);\r\n begin inc(p); a[p]:=x; end;\r\n procedure print(k:integer);\r\n begin writeln(k); halt;end;\r\nbegin\r\n f['[']:=']';\r\n f['(']:=')';\r\n f['{']:='}';\r\n f['<']:='>';
算法2:
readln(s);\r\n p:=0; i:=1; n:=length(s);\r\n while i<=n do\r\n begin\r\n if ord(f[s[i]])<>0 then push(s[i]) //左括号进栈\r\n else //右括号判断\r\n begin\r\n if p=0 then print(0); //有多余的右括号\r\n if f[pop]<>s[i] then print(0); //和前面的不匹配\r\n end;\r\n inc(i);\r\n end;\r\n if p=0 then print(1) else print(0);\r\nend.
栈的应用3——表达式求值
输入一个表达式,该表达式含有“ ”、“-”、“*”、“/”、“(”、“)”和操作数\r\n输入以‘@’结束。输出该表达式的值。\r\n\r\n分析:\r\n由于一个表达式含操作数、运算符和括号,因此只能采用字符串类型输入,而字符是不能进行数值计算的。在这种情况下,计算机又如何计算表达式的值呢。一般方法是:\r\n\r\n中缀表达式→等价的后缀表达式→计算后缀表达式的值\r\n
中缀表达式和后缀表达式的特征
中缀表达式:中缀表达式与通常的表达式一样,运算符位于两个操作之间,计算规则为\r\n①先括号内、后括号外;\r\n②无括号或同层括号内先*、/、后 、-;\r\n③同级运算按由左至右顺序进行。\r\n为了计算方便,输入的中缀表达式串常以‘@’为结束标志。\r\n例如3*(5–2) 7@\r\n \r\n后缀表达式:后缀表达式中不再引入括号,运算符放在两个运算对象之后。所有计算按运算符出现的顺序,严格地由左而右进行(不再考虑运算符的优先规则)。为了计算方便,以‘.’为操作数的结束标志,以‘@’为后缀表达式的结束标志。\r\n例如3*(5-2) 7@ 对应的后缀表达式为 3.5.2.-*7. 。后缀表达式是简化运算顺序的一种表达式。
中缀表达式的运算规则比较多,包括优先级、左右次序和括号;尤其是括号可以改变优先顺序,这就只能在计算的每一步,用肉眼去扫描、观察和比较整个表达式中谁的优先级高,就先计算那部分。但用计算机去做就很麻烦,而且效率不高。\r\n所以,计算机科学中是把中缀表达式先转换成后缀表达式,在利用计算机来计算后缀表达式的值。后缀表达式又称“逆波兰式”,在后缀表达式中,不存在括号,也不存在优先级的差别,计算过程完全按照运算符出现的先后顺序进行,整个计算过程仅需一遍扫描即可完成。
中缀表达式转化成后缀表达式的规则是:\r\n只要将每个运算符移到它的两个运算对象的后面,再删去所有括号,便得到与之等价的后缀表达式
栈的应用-中缀转后缀
输入一个中缀表达式,编程输出其后缀表达式,要求输出的后缀表达式的运算次序与输入的中缀表达式的运算次序相一致。为简单起见,假设输入的中缀表达式由+(加)、-(减)、×(乘)、/(除)四个运算符号以及左右圆括号和大写英文字母组成,其中算术运算符遵守先乘除后加减的运算规则。假设输入的中缀表达式长度不超过80个字符,且都是正确的,即没有语法错误,并且凡出现括号其内部一定有表达式,即内部至少有一个运算符号。以下是一个运行实例。\r\n输入:X A*(Y-B)-Z/F\r\n输出:XAYB-* ZF/-
设置两个栈:操作数栈(ovs)和运算符栈(ops),用来分别存放表达式中的操作数和运算符。开始时操作数栈为空,运算符栈中放入一个特殊的标志运算符号#号,并在表达式的末尾加上一个#号,并规定#号的优先级最低,然后从左向右扫描表达式,凡遇操作数便一律进栈;若遇运算符,则判断其优先级是否大于运算符栈栈顶元素的优先级。若小,则栈顶运算符退栈,并从操作数栈中弹出两个操作数(操作数为后缀表达式)进行后缀变换处理,处理结果进操作数栈,重复刚才的比较,直到栈顶运算符的优先级小于等于当前运算符的优先级,此时,若当前运算符的优先级大于栈顶运算符的优先级,则当前运算符进栈,继续扫描;若当前运算符的优先级等于栈顶运算符的优先级,则弹出栈顶运算符,继续扫描。扫描完该表达式后运算符栈为空,操作数栈中只有一个元素,该元素就是所要求的后缀表达式。
X A*(Y-B)-Z/F
以下程序中的数组f用来存放运算符之间的优先级关系,1(>)表示前面的运算符优先于后面的运算符,-1(<)表示后面的运算符优先于前面的运算符,0(=)表示前面的运算符的优先级与后面的运算符相同,2(error)表示这两个运算符如果在扫描中相遇的话,意味着该表达式是错误的。需要补充的是:左括号的优先级是最高的,右括号的优先级是除#号以外最低的,而里层的左括号比外层的左括号更优先,但左括号和右括号的优先级则是相等的,这样定义的目的是为了消去括号。以上规律列表如下:< p="">
P2
P1
X A*(Y-B)-Z/F
上述算法还可用于求一个表达式的值和判断一个表达式是否有错等等。\r\nprogram ex2;\r\nconst max=100;\r\n op_set:set of char=[‘ ’,‘-’,‘*’,‘/’];{运算符集合}\r\n letter:set of char=[‘A’..‘Z’];{运算数集合}\r\nvar expression,result:string;{中缀表达式,后缀表达式,字符串}\r\nprocedure scan(expression:string);{本过程完成中缀的扫描及到后缀的转换}\r\n var i,top1,top2:integer;{操作数栈和操作符栈的栈顶指针}\r\n ovs:array [1..max] of string[max];{操作数栈}\r\n ops:array [1..max] of char;{操作符栈}\r\n f:array['#'..'/','#'..'/'] of shortint;\r\n{数组f用来存放运算符之间的优先级关系}
begin\r\n f[' ',' ']:=1; f[' ','-']:=1; f[' ','*']:=-1; f[' ','/']:=-1; f[' ','(']:=-1; f[' ',')']:=1; f[' ','#']:=1;\r\n f['-',' ']:=1; f['-','-']:=1; f['-','*']:=-1; f['-','/']:=-1; f['-','(']:=-1; f['-',')']:=1; f['-','#']:=1;\r\n f['*',' ']:=1; f['*','-']:=1; f['*','*']:=1; f['*','/']:=1; f['*','(']:=-1; f['*',')']:=1; f['*','#']:=1;\r\n f['/',' ']:=1; f['/','-']:=1; f['/','*']:=1; f['/','/']:=1; f['/','(']:=-1; f['/',')']:=1; f['/','#']:=1;\r\n f['(',' ']:=-1;f['(','-']:=-1;f['(','*']:=-1; f['(','/']:=-1; f['(','(']:=-1; f['(',')']:=0; f['(','#']:=2;\r\n f[')',' ']:=2; f[')','-']:=2; f[')','*']:=2; f[')','/']:=2; f[')','(']:=2; f[')',')'):=2; f[')','#'):=2;\r\nf['#',' ']:=-1;f['#','-']:=-1;f['#','*']:=-1; f['#','/']:=-1; f['#','('):=-1; f['#',']']:=2; f['#','#']:=0;\r\n expression:=expression ‘#’;{表达式的末尾放一个“#”}\r\n ops[1]:=‘#’; {操作符栈先放入一个“#”}\r\n top1:=0;{初始化操作数栈}\r\n top2:=1; {操作符栈中放入一个“#”}
for i:=1 to length(expression) do{在中缀表达式的长度范围内执行循环}\r\n begin\r\n if expression[i] in letter {如果扫描到的中缀表达式的内容是操作数}\r\n then begin top1:=top1 1; \r\n ovs[top1]:=expression[i] {操作数进栈}\r\n end\r\n else begin\r\n while f[ops[top2],expression[i]]=1 do\r\nbegin ovs[top1-1]:=ovs[top1-1] ovs[top1] ops[top2];\r\n top1:=top1-1;\r\n top2:=top2-1 \r\n end; {若遇运算符,则判断其优先级是否大于运算符栈栈顶元素的优先级。若小,则栈顶运算符退栈,并从操作数栈中弹出两个操作数(操作数为后缀表达式)进行后缀变换处理,处理结果进操作数栈重复刚才的比较,直到栈顶运算符的优先级大于等于当前运算符的优先级。}\r\nif f[ops[top2],expression[i]]=0\r\n then top2:=top2-1\r\n else begin top2:=top2 1;\r\n ops[top2]:=expression[i] end;\r\n{若当前运算符的优先级大于栈顶运算符的优先级,则当前运算符进栈,继续扫描;若当前运算符的优先级等于栈顶运算符的优先级,则弹出栈顶运算符,继续扫描。}\r\nend\r\n end;\r\n result:=ovs[1] {扫描完该表达式后运算符栈为空,操作数栈中只有一个元素,该元素就是所要求的后缀表达式。}\r\nend;
begin{main}\r\n write('Input a expression:');\r\n readln(expression);\r\n scan(expression);\r\n writeln('The result is: ',result)\r\nend.\r\n测试输入:A*(X Y)/(B-Z)\r\n输出:AXY *BZ-/
将中缀表达式转换为等价的后缀表达式(方法2)
在中缀表达式中,运算符出现次序与计算顺序不一致,实际计算顺序不仅要看它的出现次序,还要受运算符的优先级和括号的影响。如果把一个中缀表达式中所有的计算顺序都按计算规则用嵌套括号的形式表示出来,其转换过程就要清楚的多。例如中缀表达式3*(5–2) 7@可以改写成((3*(5–2)) 7)@。这时可以看出,只要将每对括号中的运算符移到相应括号的后面,再删去所有括号,便得到与之等价的后缀表达式3.5.2.*7. @ 。\r\n为了将中缀表达式转换为等价的后缀表达式,需要从左至右扫描中缀表达式,并使用一个栈s2来存放中缀表达式中的开括号‘(’和暂时不能参与计算的运算符,显然s2栈的元素类型为char。为了方便表达式值的计算,在转换后的后缀表达式中,每一个操作数尾添加一个‘.’。例如计算3*(5–2) 7@
const\r\n m=1000;\r\ntype stack=array[1..m]of char;\r\nvar a,e:string; {后缀表达式和中缀表达式}\r\n s:stack; {栈,存放暂不参与计算的运算符}\r\n t,i:integer; {栈顶指针、中缀表达式的字符指针}\r\n w:char;\r\n fi,fo:text;\r\nprocedure push(x:char);\r\nbegin\r\n t:=t 1;\r\n s[t]:=x;\r\nend;\r\nfunction pop:char;\r\nbegin\r\n pop:=s[t];\r\n t:=t-1;\r\nend;\r\nfunction top(s:stack;t:integer):char;\r\nbegin\r\n top:=s[t];\r\nend;
begin\r\n assign(fi,'zzh.in');\r\n reset(fi);\r\n assign(fo,'zzh.out');\r\n rewrite(fo);\r\n read(fi,e);\r\n a:='';i:=1;t:=0; {后缀表达式初始化为空} {从中缀表达式的第一个字符开始分析,栈空}\r\n while e[i]<>'@' do begin {由左而右扫描处理中缀表达式的每一个字符} \r\n case e[i]of\r\n '0'..'9':begin {操作数进入后缀表达式}\r\n while e[i]<>'.' do begin\r\n a:=a e[i];\r\n i:=i 1;\r\n end;\r\n a:=a '.'; {添加操作数的结尾标志}\r\n end;\r\n '(':push('('); {‘(’入栈}\r\n ')':begin {s栈中栈顶至’(’间的所有运算符相继出栈,进入后缀表达式} \r\n w:=pop;\r\n while w<>'(' do begin\r\n a:=a w;\r\n w:=pop;\r\n end;\r\n end;
' ','-':begin {s栈中,栈顶至’(’前的所有运算符相继出栈,进入后缀表达式,e[i]进入s栈}\r\n if t<>0 then begin\r\n w:=top(s,t);\r\n while w<>'(' do begin\r\n a:=a w; w:=pop;\r\n if t=0 then break else w:=top(s,t);\r\n end;\r\n end;\r\n push (e[i]);\r\n end;\r\n '*','/':begin {s栈中栈顶至第1个’ ’或’-’前的所有运算符相继出栈,进入后缀表达式,e[i]进入s栈}\r\n if t<>0 then begin\r\n w:=top(s,t);\r\n while (w='*')or(w='/')do begin\r\n a:=a w; w:=pop;\r\n if t=0 then break\r\n else w:=top(s,t);\r\n end;\r\n end;\r\n push(e[i]);\r\n end;\r\n end;\r\n i:=i 1; {准备扫描处理中缀表达式的下一个字符}\r\n end;
while t<>0 do a:=a pop; {s栈中的运算符相继出栈,进入后缀表达式}\r\n\r\n a:=a '@';\r\n writeln(fo,a);\r\n close(fi);\r\n close(fo);\r\nend.
栈的应用4-计算后缀表达式
所谓后缀表达式是指这样的一种表达式:式中不再引入括号,运算符放在两个运算对象之后。所有计算按运算符出现的顺序,严格地由左而右进行(不再考虑运算符的优先规则)。例如\r\n 3*(5-2) 7 对应的后缀表达式为 3.5.2.-*7. @\r\n其中‘@’为后缀表达式的结束标记,’.’为操作数的结束符。\r\n 3.5.2.-*7. \r\n =3.3.*7. \r\n =9.7. \r\n =16\r\n 输入:\r\n 后缀表达式A(假定A合乎文法,不需判错);\r\n 输出:\r\n 表达式的值。
后缀表达式求值\r\n【问题描述:】\r\n 所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。\r\n 1) 运算符号: 、-、*、/。/ 运算只取整数部分。采用 div 即可\r\n 2) 一个完整的运算数以”.”作为结束符号。\r\n 3) 运算的中间结果与最后的结果数据范围:[-1000000000,1000000000]。\r\n 如:3*(5–2) 72 对应的后缀表达式为:3.5.2.-*72. \r\n 运算过程:3.5.2.-*72. =3.3.*72. =9.72. =81\r\n【输入:】一行: 后缀表达式(长度不超过100)。\r\n【输出:】 表达式的值。\r\n【样例输入:】\r\n3.5.2.-*72. \r\n【样例输出:】\r\n81
方法:利用栈结构
算法分析:\r\n假设后缀的表达式为A,操作数和计算结果存放在栈S中,s的类型为integer。\r\n从左向右处理A中的每一个字符:\r\n如果遇到一个操作数,就送入栈s中;\r\n如果遇到一个运算符,就从栈s中取出栈顶的两个操作数进行计算,然后将计算结果重新压栈。\r\n直到最后一个运算符处理结束,这时栈s顶的元素即是计算结果。\r\n例如计算后缀表达式3.5.2.-*7. 的值
3.5.2.-*7.
const m=100;\r\ntype stack=array[1..m] of integer; {定义栈类型}\r\nvar s:stack; {栈}\r\n a:string; {后缀表达式,字符串}\r\n t,i,j,k,len:integer; {t—栈顶指针;i—后缀表达式的字符指针;k、j—操作数值}\r\nprocedure push(x:integer);{进栈}\r\n begin\r\n t:=t 1;\r\n s[t]:=x;\r\n end;\r\nfunction pop:integer;{出栈}\r\n begin\r\n pop:=s[t];\r\n t:=t-1;\r\n end;
begin\r\n readln(a); {读入后缀表达式}\r\n len:=length(a); {后缀表达式的长度}\r\n i:=1; {后缀表达式字符指针初始化}\r\n t:=0; {栈初始化}\r\n while i<=len do {处理后缀表达式,循环直至结束}\r\n begin\r\n case a[i] of
'0'..'9':begin\r\n k:=0;\r\n while a[i]<>’.’ do \r\n begin {从s中取出一个完整的操作数k}\r\n k:=10*k ord(a[i])-48; {ord(‘0’)=48}\r\n i:=i 1;\r\n end;\r\n push(k);{把操作数k压栈}\r\n end;\r\n ' ':push(pop pop);\r\n {从栈s中取出栈顶的两个数进行加法运算,然后将结果在压栈}
'-':begin {从栈s中取出栈顶的两个数进行减法运算,然后将结果在压栈}\r\n j:=pop;\r\n push(pop-j);\r\n end;\r\n '*':push(pop*pop); \r\n {从栈s中取出栈顶的两个数进行乘法运算,然后将结果在压栈}\r\n '/':begin {从栈s中取出栈顶的两个数进行除法运算,然后将结果在压栈}\r\n j:=pop;\r\n push(pop div j);\r\n end;\r\nend;{case}
i:=i 1;\r\n end;{while}\r\n writeln(pop); {取出栈顶元素,即时表达式的最后结果}\r\nend.