参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure
1.行编辑程序
1.1问题描述
一个简单的行编辑程序的功能是:接收用户从终端的输入的数据,并存入用户的数据区。而用户在终端可能输入差错,所以每接收一个字符就立即存入数据区显然是不合适的。此时就需要有一些“回退”或者“撤销”的操作,即我们需要维护一个“输入缓冲区”,用以接收用户的一行数据,然后逐行存入用户数据区。当用户输入时,允许用户输入出差错,并能及时更正。例如:
1.当用户发现刚刚输入的一个字符是错的,可以补进一个"#"表示退格符,用以表示前面输入的那个字符无效;
2.当用户发现键入的行内差错较多或难以补救时,可以键入"@"表示退行符,用以表示当前一整行均无效。
1.2代码实现:
#coding=utf-8
def lineEdit():
#利用栈stack作为一个用户数据输入缓冲区,从终端接受一行数据并传送至调用过程的数据区
stack = []
line = raw_input("")
#'$'为全文结束符,line不是空串
while len(line)!=0:
for i in range(len(line)):
ch = line[i]
if ch == '$':
break;
elif ch == '#':
stack.pop()
elif ch == '@':
#清空stack
stack = []
else:
#有效字符进栈
stack.append(ch)
#一行输入处理完了,将从栈底到栈顶的字符传送至调用过程的数据区,该过程用打印语句代替
print ''.join(stack)
#清空栈
satck = []
#读入下一行
line = raw_input("")
def main():
lineEdit()
if __name__ == '__main__':
main()
1.3演示
2.括号匹配检验
2.1问题描述
三种括号:()、[ ]、{ },依次读入括号串,后遇到的左括号总是“越急迫的渴望得到匹配”,反而先遇到的左括号的急迫程度低,这与栈的特点相吻合。算法的基本思想是:左括号总是入栈,栈顶的左括号的急迫程度最高,如果接下来读取的字符是左括号,则该左括号入栈,成为最高急迫渴望得到匹配,而之前的左括号的急迫程度均降低一层;若接下来读取的字符是右括号,则栈顶的左括号得到匹配,出栈‘以此类推,直到栈为空,则括号是完全匹配的。
算法流程图如下:
2.2代码实现
#coding=utf-8
def isMatch(topElem, curChar):
matcher = {')':'(', ']':'[', '}':'{'}
return topElem == matcher[curChar]
def isParenthesisMatching():
parenthesisStr = raw_input("请输入待检验的括号串:")
#维护一个顺序栈stack
stack = []
for i in range(len(parenthesisStr)):
ch = parenthesisStr[i]
if ch == '(' or ch == '[' or ch == '{':
stack.append(ch)
elif ch == ')' or ch == ']' or ch == '}':
#栈是否为空,如果为空,则右括号多余
if len(stack) == 0:
return False
#与栈顶元素匹配,则出栈
elif isMatch(stack[-1], ch):
stack.pop()
#与栈顶元素不匹配,则入栈
else:
stack.append(ch)
#print stack
#如果栈为空,则是完全匹配
return len(stack) == 0
def main():
print isParenthesisMatching()
if __name__ == '__main__':
main()
2.3演示
3.数制转换
3.1问题描述
将一个十进制数N转化为d进制数,转化过程是用N不断除以d,将所得的余数记录下来,直至商为0,产生的余数逆向组合起来便是对应的d进制数了。这个过程刚好和栈的后进先出的特点相吻合。
算法:
1.初始化一个空栈
2.N不断除以d,求余数,至商为0(循环)
3.产生的余数依次入栈
4.出栈的顺序刚好是转化好的数(即从栈顶到栈底)
3.2代码实现
#coding=utf-8
#数值转换
def conversion():
stack = []
n = input("请输入一个待转换的十进制数字:")
d = input("想转化为几进制?")
if n == 0:
return 0
while n:
stack.append(n%d)
n/=d
stack.reverse()
return ''.join(map(str, stack))
def main():
print conversion()
if __name__ == '__main__':
main()
3.3演示
4.Hanoi塔问题
4.1问题描述
古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上(如图)。有一个和尚想把这64个盘子从A座移到B座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求打印移动的步骤。
汉诺塔问题的求解示意图:
分析:
1.多个函数嵌套调用的内部执行过程
通常,当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要先完成三件事:
(1)将所有的实在参数、返回地址等信息传递给被调用函数保存;
(2)为被调用函数的局部变量分配存储区;
(3)将控制转移到被调用函数的入口。
而从被调用函数返回调用函数之前,系统也应完成三件工作:
(1)保存被调函数的计算结果;
(2)释放被调函数的数据区;
(3)依照被调函数保存的返回地址将控制转移到调用函数。
当有多个函数构成嵌套调用时,按照“后调用先返回”的原则,上述函数之间的信息传递和控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,每当从一个函数退出时,就释放它的存储区,则当前正运行的数据区必在栈顶。
2.递归函数的内部执行过程
在计算机内部,一个递归函数的调用过程类似于多个函数的嵌套调用,只不过调用函数和被调用函数是同一个函数。为了保证递归函数的正确执行,系统需设立一个工作栈。具体地说,递归调用的内部执行过程如下:
⑴ 运行开始时,首先为递归调用建立一个工作栈,其结构包括值参、局部变量和返回地址;
⑵ 每次执行递归调用之前,把递归函数的值参和局部变量的当前值以及调用后的返回地址压栈;
⑶ 每次递归调用结束后,将栈顶元素出栈,使相应的值参和局部变量恢复为调用前的值,然后转向返回地址指定的位置继续执行。
这些执行过程都有系统的工作栈来维护。
4.2代码实现
Hanoi问题的递归算法
#coding=utf-8
#count为全局变量,记录搬动次数
count = 0
def move(x, n, z):
#搬动操作
global count
count +=1
print "%d. 将%d号盘子从塔座%s搬到%s" %(count, n, x, z)
#将x塔座上按直径由小到大且自上而下编号为1--n的n个圆盘按规则搬到塔座z上,y可作为辅助塔座
def hanoi(n, x, y, z):
if n == 1:
move(x, n, z)
else:
hanoi(n-1, x, z, y)
move(x, n, z)
hanoi(n-1, y, x, z)
def main():
hanoi(3, 'a', 'b', 'c')
if __name__ == '__main__':
main()
4.3演示
我们为Hanoi问题的核心递归算法语句编上号,如下
1 def hanoi(n, x, y, z):
2 if n == 1:
3 move(x, n, z)
else:
4 hanoi(n-1, x, z, y)
5 move(x, n, z)
6 hanoi(n-1, y, x, z)
那么上述汉诺塔算法在执行过程中,工作栈的变化如下图所示,其中栈元素的结构为(返回地址,n值,A值,B值,C值),返回地址对应算法中语句的行号,图的序号对应递归调用和返回的序号。
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure