第1关:希尔排序的实现
任务描述
本关任务:编写代码实现希尔排序。
相关知识
为了完成本关任务,你需要掌握: 1.如何实现希尔排序; 2.希尔排序的算法分析。
希尔排序
对于插入排序,最好的情况是列表已经基本有序,此时比较次数的时间复杂度是O(n)
。列表越接近有序,插入排序的比较次数就越少。因此,希尔排序以插入排序为基础,将待排序的列表划分为一些子列表,再对每一个子列表执行插入排序,从而实现对插入排序性能的改进。
希尔排序又叫缩小增量排序,划分子列表的特定方法是希尔排序的关键。我们并不是将原始列表直接分成若干个含有连续元素的子列表,而是首先确定一个增量 i 来作为子列表的划分间隔,然后把每间隔为 i 的所有元素选出来组成子列表。在希尔排序的过程中,每一趟排序都将增量不断减小,随着子列表数量的越来越少,无序表整体越来越接近有序,从而能够减少整体排序的比较次数。
图 1 展示的是以 3 为增量的希尔排序。
转存失败重新上传取消
图1 以 3 为增量的希尔排序
若对这个含有 9 个数据项的列表以 3 为间隔来划分,则会分成三个子列表:
-
将列表中下标为 0、3、6 的数据项分成一组,得到子列表 [54,17,44];
-
将列表中下标为 1、4、7 的数据项分成一组,得到子列表 [26,77,55];
-
将列表中下标为 2、5、8 的数据项分成一组,得到子列表 [93,31,20]。
然后分别对每一个子列表执行插入排序,得到如图 2 所示的列表。
转存失败重新上传取消
图2 对每个子列表排序后的结果
这三次插入排序的过程可描述为:
-
对于子列表 [54,17,44],首先将 17 与 54 进行比较,17 小于 54,于是将 17 插入到 54 之前。然后将 44 与 54、17 比较,于是将 44 插入到 17 与 54 之间,最终得到有序列表 {17,44,54};
-
对于子列表 [26,77,55],首先将 77 与 26 进行比较,77 大于 26,于是将 77 插入到 26 之后(不需要移动位置)。然后将 55 与 77、26 比较,于是将 55 插入到 26 与 77 之间,最终得到有序列表 {26,55,77};
-
对于子列表 [93,31,20],首先将 31 与 93 进行比较,31 小于 93,于是将 31 插入到 93 之前。然后将 20 与 93、31 比较,于是将 20 插入到 31 之前,最终得到有序列表 {20,31,93}。
这就完成了第 1 趟希尔排序,虽然这个列表还没有完全排好序,但经过这一趟对子列表的排序之后,列表中的每个元素更加靠近它最终应该处在的位置。
希尔排序的最后一趟排序一定是将增量减少到 1,图 3 是对图 2 中得到的列表以 1 为增量进行希尔排序,即执行标准的插入排序过程。
转存失败重新上传取消
图3 以 1 为增量的排序
通过之前对子列表进行的排序,列表比最开始更加接近有序,此时再进行标准插入排序,能够在一定程度上减少比较和移动的次数。此时,仅需要再进行四次移动就可以完成排序。最后一次插入排序过程中的移动操作有:
-
将插入项 20 与 26 进行比较,20 小于 26,于是将 26 向右移动一个位置;再将 20 与 17 进行比较,20 大于 17,最终将 20 插入到 17 与 26 之间;
-
将插入项 31 与 55 进行比较,31 小于 55,于是将 55 向右移动一个位置;再将 31 与 44 进行比较,31 小于 44,于是将 44 向右移动一个位置;再将 31 与 26 进行比较,31 大于 26,最终将 31 插入到 26 与 44 之间;
-
将插入项 54 与 55 进行比较,54 小于 55,于是将 55 向右移动一个位置;再将 54 与 44 进行比较,54 大于 44,最终将 54 插入到 44 与 55 之间。
对于含有 n 个数据项的列表,希尔排序的增量一般从 n/2 开始,之后的每趟减少到 n/4、n/8……直到 1。图 4 展示了对含有 9 个数据项的列表以 4 为增量划分子列表的一个示例。
转存失败重新上传取消
图4 以 4 为增量的情况
此外,增量序列中的值不应该有除 1 之外的公因子,否则可能会造成前面某一趟分在同一组已经比较过的数据项,在本趟继续分在同一组,此时这些数据项再次相互比较毫无意义,同时还会增加算法的时间,例如 8、4、2、1 这样的序列就不要选取(8、4、2 有公因子 2)。
希尔排序的算法分析
可能你会觉得希尔排序并不会比插入排序好,因为它最后一步执行了一次完整的插入排序。但事实上,最后的一次排序并不需要很多次的比较和移动,因为已经在之前对子列表的排序中实现了部分排序,这使得最后的排序非常高效。
希尔排序的复杂度分析十分复杂,大致是介于O(n)
和O(n2)
之间。使用某些增量值时,它的时间复杂度为O(n2)
。通过改变增量的大小,比如将增量保持在2k−1
(1、3、7、15、31 等),希尔排序的时间复杂度可以达到O(n3/2)
。
编程要求
根据提示,在右侧编辑器中的 Begin-End 区间补充代码,根据希尔排序的算法思想完成shellSort
和gapInsertionSort
方法,从而实现对无序表的排序。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确的数值,只有所有数据全部计算正确才能通过测试:
测试输入:
54,26,93,17,77,31,44,55,20
输入说明:输入为需要对其进行排序的无序表。
预期输出:
增量为 4 : [20, 26, 44, 17, 54, 31, 93, 55, 77]
增量为 2 : [20, 17, 44, 26, 54, 31, 77, 55, 93]
增量为 1 : [17, 20, 26, 31, 44, 54, 55, 77, 93]
输出说明:输出的是对无序表进行希尔排序的每一趟排序的结果,以列表的形式展现。其中增量的取值从 n/2 开始,之后的每趟减少到 n/4……直到 1。在本例测试数据中,数据项个数为 9,则增量序列为:4、2、1。
测试输入:
49,38,65,97,76,13,27
预期输出:
增量为 3 : [27, 38, 13, 49, 76, 65, 97]
增量为 1 : [13, 27, 38, 49, 65, 76, 97]
提示:
for i in range(0, 30, 5): # 步长为 5
print(i, end=" ")
print('\n')
for j in range(1, 30, 5):
print(j, end=" ")
输出:
0 5 10 15 20 25
1 6 11 16 21 26
'''请在Begin-End之间补充代码, 完成shellSort和gapInsertionSort函数''' # 希尔排序 def shellSort(alist): sublistcount = len(alist) // 2 # 设定初始增量为n/2 while sublistcount > 0: # 不断缩小增量,进行多趟排序 for startposition in range(sublistcount): # 每进行一次循环就对某一个子列表进行排序 # 调用gapInsertionSort函数对子列表进行排序 # ********** Begin ********** # gapInsertionSort(alist,startposition,sublistcount) # ********** End ********** # print("增量为", sublistcount, ":", alist) sublistcount = sublistcount // 2 # 带间隔的插入排序 def gapInsertionSort(alist, start, gap): for i in range(start + gap, len(alist), gap): # 循环的次数表示插入排序的趟数 currentvalue = alist[i] # 当前插入项的值 position = i # 当前插入项所在的位置 # 当 position-gap 位置有数据项 且 当前插入项小于 position-gap 位置的数据项, # 就不断地进行以下操作 # 将 position-gap 位置的数据项在子列表中向右移动一个位置 # position 指向 position-gap 位置 # ********** Begin ********** # while position>=gap and alist[position-gap]>currentvalue: alist[position]=alist[position-gap] position=position-gap # ********** End ********** # alist[position] = currentvalue # 找到当前插入项的插入位置
第1关:快速排序的实现
任务描述
本关任务:编写代码实现快速排序。
相关知识
为了完成本关任务,你需要掌握: 1.如何实现快速排序; 2.快速排序的算法分析。
快速排序
快速排序使用了和归并排序一样的分而治之策略,它的思路是依据一个“基准值”数据项来把列表分为两部分:小于基准值的一部分和大于基准值的一部分,然后每部分再分别进行快速排序,这是一个递归的过程。基准值的作用在于协助拆分列表,一般选择列表的第 1 项作为基准值。基准值在最后排序好的列表里的实际位置,我们通常称之为分割点,是用来对拆分成的两部分分别进行快速排序的关键位置点。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
-
找到基准值的位置,设置左标记 leftmark 和右标记 rightmark。
-
不断地移动左右标记,进行多次比较和交换: ① 左标一直向右移动,遇到比基准值大的就停止; ② 右标一直向左移动,遇到比基准值小的就停止; ③ 把左右标记所指的数据项交换。
-
继续移动,直到左标记移到右标记的右侧,停止移动。这时右标记所指的位置就是基准值应处的位置,将基准值和这个位置的数据项交换。此时,基准值左边部分的数据项都小于或等于基准值,右边部分的数据项都大于或等于基准值。
-
然后递归地对左边和右边的部分进行快速排序,当待排序部分只有一个数据项时,递归过程结束。
以下为快速排序的一个简单示例。首先将图 1 中列表的第 1 个数据项 54 作为基准值,然后设置两个位置标记 leftmark 和 rightmark,左标记 leftmark 指向列表的第一项 26,右标记 rightmark 指向列表的最后一项 20。
转存失败重新上传取消
图1 快速排序初始状态
接下来需要不断地把左标记向右移动,直到它指向了一个比基准值更大的数据项。同时不断地把右标记向左移动,直到它指向了一个比基准值更小的数据项。最后交换左、右标记位置的数据项。
首先看左标记,左标记当前所指的 26 小于基准值 54,则继续右移。右移一个位置后指向的 93 大于基准值 54,则停止移动。当前列表状态如图 2 所示。
转存失败重新上传取消
图2 左标记指向的 93 大于基准值 54
接下来看右标记,右标记当前所指的 20 小于基准值 54,则停止移动。当前列表状态如图 3 所示。
转存失败重新上传取消
图3 右标记指向的 20 小于基准值 54
相对于最终的分割点,当前左、右标记所指的两个数据项正位于错误的位置,因此需要对其进行交换。在此示例中,需要将 93 和 20 进行交换,交换后的列表状态如图 4 所示。
转存失败重新上传取消
图4 将 93 和 20 进行交换
重复执行以上步骤:
-
左标记当前所指的 20 小于基准值 54,则继续右移;右移一个位置后指向的 17 小于基准值 54,则继续右移;右移一个位置后指向的 77 大于基准值 54,则停止移动。
-
右标记当前所指的 93 大于基准值 54,则继续左移;左移一个位置后指向的 55 大于基准值 54,则继续左移;左移一个位置后指向的 44 小于基准值 54,则停止移动。
-
当前左、右标记的位置如图 5 所示,将所指向的 77 和 44 进行交换。
转存失败重新上传取消
图5 左、右标记分别指向 77 和 44
接下来继续移动左右标记:
-
左标记当前所指的 44 小于基准值 54,则继续右移;右移一个位置后指向的 31 小于基准值 54,则继续右移;右移一个位置后指向的 77 大于基准值 54,则停止移动。
-
右标记当前所指的 77 大于基准值 54,则继续左移;左移一个位置后指向的 31 小于基准值 54,则停止移动。
如图 6 所示,此时右标记移动到了左标记的前方,则右标记所在的位置就是分割点的位置。
转存失败重新上传取消
图6 右标记小于左标记
将基准值 54 与分割点位置的数据项 31 交换后,第 1 趟排序结束,当前列表状态如图 7 所示。可以看到,列表被分成两部分,左部分的所有数据项都比基准值 54 小,右部分的所有数据项都比基准值 54 大。接下来递归地对这两部分再执行快速排序过程,直到列表中只剩一个数据项。
转存失败重新上传取消
图7 第 1 趟排序结果
快速排序的递归算法的“递归三要素”可总结如下:
-
基本结束条件:列表中仅有 1 个数据项时,自然是排好序的;
-
缩小规模:根据基准值将列表分为两部分,最好的情况是分为相等规模的两半;
-
调用自身:将拆分成的两部分分别调用自身进行排序。
快速排序的算法分析
我们可以将快速排序分为两个过程来分析:
-
拆分的过程,如果对列表的拆分总是发生在列表的中央,那么时间复杂度就是
O(logn)
; -
移动的过程,每次左右标记移动时都需要将所指的数据项与基准值进行比较,所以时间复杂度是
O(n)
。
综合考虑,时间复杂度为O(nlogn)
。但是,如果基准值所在的分割点过于偏离中部,造成左右两部分数量不平衡,则会使算法效率降低。最坏的情况是,拆分成的某一部分始终没有数据,这时时间复杂度就退化到O(n2)
。
编程要求
在右侧编辑器中的 Begin-End 区间补充代码,根据快速排序的算法思想完成quickSortHelper
和partition
方法,从而实现对无序表的排序。其中quickSortHelper
方法用于对从 first 到 last 位置的数据项所在的列表进行快速排序;partition
方法用于对列表进行拆分,同时返回分割点位置。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确的数值,只有所有数据全部计算正确才能通过测试:
测试输入:
54,26,93,17,77,31,44,55,20
输入说明:输入为需要对其进行排序的无序表。
预期输出:
[17, 20, 26, 31, 44, 54, 55, 77, 93]
输出说明:输出的是对无序表进行快速排序后的结果,以列表的形式展现。
测试输入:
49,38,65,97,76,13,27
预期输出:
[13, 27, 38, 49, 65, 76, 97]
'''请在Begin-End之间补充代码, 完成quickSortHelper和partition函数''' # 快速排序 def quickSort(alist): quickSortHelper(alist,0,len(alist)-1) # 指定当前排序列表开始(first)和结束(last)位置的快速排序 def quickSortHelper(alist,first,last): if first<last: # 当列表至少包含两个数据项时,做以下操作 # 调用partition函数对当前排序列表进行拆分,返回分割点splitpoint splitpoint = partition(alist,first,last) # 递归调用quickSortHelper函数对拆分得到的左部分和右部分进行快速排序 # ********** Begin ********** # quickSortHelper(alist,first,splitpoint-1) quickSortHelper(alist,splitpoint+1,last) # ********** End ********** # # 拆分列表 def partition(alist,first,last): pivotvalue = alist[first] # 选定基准值为列表的第一个数据项 leftmark = first+1 # 左标 rightmark = last # 右标 done = False while not done: # 将左标向右移动,直至遇到一个大于基准值的数据项 while leftmark <= rightmark and alist[leftmark] <= pivotvalue: leftmark = leftmark + 1 # 将右标向左移动,直至遇到一个小于基准值的数据项 # ********** Begin ********** # while alist[rightmark]>=pivotvalue and rightmark>=leftmark: rightmark=rightmark-1 # ********** End ********** # # 右标小于左标时,结束移动 if rightmark < leftmark: done = True # 否则将左、右标所指位置的数据项交换 else: # ********** Begin ********** # temp=alist[leftmark] alist[leftmark]=alist[rightmark] alist[rightmark]=temp # ********** End ********** # # 将基准值就位 temp = alist[first] alist[first] = alist[rightmark] alist[rightmark] = temp return rightmark # 返回基准值位置,即分割点
第1关:前序、中序、后序遍历
- 任务描述
- 相关知识
- 编程要求
from binaryTree import BinaryTree
'''请在Begin-End之间补充代码, 完成 preorder()、inorder()、postorder()'''
# 前序遍历
def preorder(tree):
if tree: # 如果当前树的根结点为空,就递归结束
# ********** Begin ********** #
print(tree.key,end=' ')
preorder(tree.leftChild)
preorder(tree.rightChild)
# ********** End ********** #
# 中序遍历和后序遍历跟前序遍历的语句是一样的,只是次序不一样
# 中序遍历
def inorder(tree):
if tree != None:
# ********** Begin ********** #
inorder(tree.leftChild)
print(tree.key,end=' ')
inorder(tree.rightChild)
# ********** End ********** #
# 后序遍历
def postorder(tree):
if tree != None:
# ********** Begin ********** #
postorder(tree.leftChild)
postorder(tree.rightChild)
print(tree.key,end=' ')
# ********** End ********** #
第2关:后序遍历法重写表达式求值
- 任务描述
- 相关知识
- 编程要求
- 测试说明
import operator from parsetree import buildParseTree '''请在Begin-End之间补充代码, 完成函数postordereval()''' # 后序遍历法进行表达式求值 def postordereval(parseTree): opers = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv} res1 = None # 用来存储左子树求得的值 res2 = None # 用来存储右子树求得的值 if tree: # 如果当前树根结点不为空才进行以下求值操作 # ********** Begin ********** # fn=opers[tree.getRootVal()] fn(res1,res2) res1=postordereval(tree.leftChild) res2=postordereval(tree.rightChild) # ********** End ********** # if res1 and res2: # 如果左子树和右子树都成功返回值 # ********** Begin ********** # return tree.key # ********** End ********** # else: # ********** Begin ********** # return # ********** End ********** # # ( ( 7 + 4 ) * 9 ) pt = buildParseTree(input())
第3关:中序遍历生成全括号中
- 任务描述
- 相关知识
- 编程要求
- 测试说明
-
class Stack(): # 创建空列表实现栈 def __init__(self): self.__list = [] # 判断是否为空 def is_empty(self): return self.__list == [] # 压栈,添加元素 def push(self,item): self.__list.append(item) # 弹栈,弹出最后压入栈的元素 def pop(self): if self.is_empty(): return else: return self.__list.pop() class BinaryTree: # 创建左右子树为空的根结点 def __init__(self,rootObj): self.key = rootObj # 成员key保存根结点数据项 self.leftChild = None # 成员leftChild初始化为空 self.rightChild = None # 成员rightChild初始化为空 # 把newNode插入到根的左子树 def insertLeft(self,newNode): if self.leftChild == None: self.leftChild = BinaryTree(newNode) # 左子树指向由newNode所生成的BinaryTree else: t = BinaryTree(newNode) # 创建一个BinaryTree类型的新结点t t.leftChild = self.leftChild # 新结点的左子树指向原来根的左子树 self.leftChild = t # 根结点的左子树指向结点t # 把newNode插入到根的右子树 def insertRight(self,newNode): if self.rightChild == None: self.rightChild = BinaryTree(newNode) # 右子树指向由newNode所生成的BinaryTree else: t = BinaryTree(newNode) t.rightChild = self.rightChild # 新结点的右子树指向原来根的右子树 self.rightChild = t # 取得右子树 def getRightChild(self): return self.rightChild # 返回值是一个BinaryTree类型的对象 # 取得左子树 def getLeftChild(self): return self.leftChild # 设置根结点的值 def setRootVal(self,obj): self.key = obj # 取得根结点的值 def getRootVal(self): return self.key '''请在Begin-End之间补充代码, 完成表达式解析树的建立''' # 创建表达式解析树 def buildParseTree(fpexp): fplist = fpexp.split() pStack = Stack() # 存储树结点的栈 eTree = BinaryTree('') # 创建一个空的二叉树 pStack.push(eTree) # 把刚建立的结点入栈,该结点将最后出栈 currentTree = eTree # 把当前结点设成刚才建立的结点 for i in fplist: # 对每个单词进行从左到右扫描 # 表达式开始 if i == '(': # 创建一个当前结点的左孩子结点 # 将当前结点入栈,这样等下上升的时候才知道返回到哪里 # 将当前结点下降到左孩子结点 # ********** Begin ********** # currentTree.insertLeft('') pStack.push(currentTree) currentTree=currentTree.getLeftChild() # ********** End ********** # # 操作数 elif i not in ['+', '-', '*', '/', ')']: # 将当前结点的根值设置成把i转化为整数后的结果 # 出栈一个结点作为当前结点的父结点 # 将当前结点上升到 父结点 # ********** Begin ********** # currentTree.key=int(i) parent=pStack.pop() currentTree=parent # ********** End ********** # # 操作符 elif i in ['+', '-', '*', '/']: # 将当前结点的根值设置成操作符i # 创建一个当前结点的右孩子结点 # 将当前结点入栈 # 将当前结点下降到右孩子结点 # ********** Begin ********** # currentTree.setRootVal(i) currentTree.insertRight('') pStack.push(currentTree) currentTree=currentTree.getRightChild() # ********** End ********** # # 表达式结束 elif i == ')': # 出栈上升,返回到父结点 # ********** Begin ********** # parent=pStack.pop() currentTree=parent # ********** End ********** # else: raise ValueError return eTree # 中序遍历法生成全括号中缀表达式 def printexp(tree): sVal = "" # 用来存储中缀表达式字符串 if tree: # 如果当前树根结点不为空才进行以下操作 # ********** Begin ********** # if tree.getLeftChild(): sVal='('+printexp(tree.getLeftChild()) sVal = sVal + str(tree.getRootVal()) # ********** End ********** # if tree.getRightChild(): # ********** Begin ********** # sVal = sVal + printexp(tree.getRightChild())+')' # ********** End ********** # return sVal pt = buildParseTree(input()) # print(printexp(pt))
第1关:序列化二叉树
- 任务描述
- 相关知识
- 编程要求
- 测试说明
from binaryTree import BinaryTree # 对二叉树进行前序序列化 def serialize(tree): sVal = "" # 用来存储序列化字符串 if tree: # 如果当前树根结点不为空才进行以下操作 # 按前序遍历的顺序将各个结点值放入到序列化字符串中,同时在每个值后加上字符'!' # ********** Begin ********** # sVal=str(tree.key)+"!" sVal+=serialize(tree.leftChild) sVal+=serialize(tree.rightChild) # ********** End ********** # else: # 空结点位置放入'#!' # ********** Begin ********** # return '#!' # ********** End ********** # return sVal
第2关:反序列化二叉树
- 任务描述
- 相关知识
- 编程要求
- 测试说明
class Stack(): # 创建空列表实现栈 def __init__(self): self.__list = [] # 判断是否为空 def is_empty(self): return self.__list == [] # 压栈,添加元素 def push(self,item): self.__list.append(item) # 弹栈,弹出最后压入栈的元素 def pop(self): if self.is_empty(): return else: return self.__list.pop() class BinaryTree: # 创建左右子树为空的根结点 def __init__(self,rootObj): self.key = rootObj # 成员key保存根结点数据项 self.leftChild = None # 成员leftChild初始化为空 self.rightChild = None # 成员rightChild初始化为空 # 把newNode插入到根的左子树 def insertLeft(self,newNode): if self.leftChild == None: self.leftChild = BinaryTree(newNode) # 左子树指向由newNode所生成的BinaryTree else: t = BinaryTree(newNode) # 创建一个BinaryTree类型的新结点t t.leftChild = self.leftChild # 新结点的左子树指向原来根的左子树 self.leftChild = t # 根结点的左子树指向结点t # 把newNode插入到根的右子树 def insertRight(self,newNode): if self.rightChild == None: self.rightChild = BinaryTree(newNode) # 右子树指向由newNode所生成的BinaryTree else: t = BinaryTree(newNode) t.rightChild = self.rightChild # 新结点的右子树指向原来根的右子树 self.rightChild = t # 取得右子树 def getRightChild(self): return self.rightChild # 返回值是一个BinaryTree类型的对象 # 取得左子树 def getLeftChild(self): return self.leftChild # 设置根结点的值 def setRootVal(self,obj): self.key = obj # 取得根结点的值 def getRootVal(self): return self.key '''请在Begin-End之间补充代码, 完成表达式解析树的建立''' # 创建表达式解析树 def buildParseTree(fpexp): fplist = fpexp.split() pStack = Stack() # 存储树结点的栈 eTree = BinaryTree('') # 创建一个空的二叉树 pStack.push(eTree) # 把刚建立的结点入栈,该结点将最后出栈 currentTree = eTree # 把当前结点设成刚才建立的结点 for i in fplist: # 对每个单词进行从左到右扫描 # 表达式开始 if i == '(': # 创建一个当前结点的左孩子结点 # 将当前结点入栈,这样等下上升的时候才知道返回到哪里 # 将当前结点下降到左孩子结点 # ********** Begin ********** # currentTree.insertLeft('') pStack.push(currentTree) currentTree=currentTree.getLeftChild() # ********** End ********** # # 操作数 elif i not in ['+', '-', '*', '/', ')']: # 将当前结点的根值设置成把i转化为整数后的结果 # 出栈一个结点作为当前结点的父结点 # 将当前结点上升到 父结点 # ********** Begin ********** # currentTree.key=int(i) parent=pStack.pop() currentTree=parent # ********** End ********** # # 操作符 elif i in ['+', '-', '*', '/']: # 将当前结点的根值设置成操作符i # 创建一个当前结点的右孩子结点 # 将当前结点入栈 # 将当前结点下降到右孩子结点 # ********** Begin ********** # currentTree.setRootVal(i) currentTree.insertRight('') pStack.push(currentTree) currentTree=currentTree.getRightChild() # ********** End ********** # # 表达式结束 elif i == ')': # 出栈上升,返回到父结点 # ********** Begin ********** # parent=pStack.pop() currentTree=parent # ********** End ********** # else: raise ValueError return eTree flag = -1 def deserialize(s): global flag flag = flag + 1 lst = s.split('!') # flag每次加1,从0开始对字符进行判断 root = None # 新建一个树对象来反序列化字符串 if lst[flag] != '#': # 往树中存值,可递归输入s是因为flag是不断增大的 # 反序列过程中先对根结点进行操作,然后才是左右子树 # ********** Begin ********** # root=BinaryTree(int(lst[flag])) # ********** End ********** # print(root.key, end=' ') # 打印出先序反序列化重构二叉树的过程 # ********** Begin ********** # root.leftChild=deserialize(s) root.rightChild=deserialize(s) # ********** End ********** # return root
第1关:图抽象数据类型的实现
- 任务描述
- 相关知识
- 编程要求
- 测试说明
# -*- coding: utf-8 -*- '''请在Begin-End之间补充代码, 完成Vertex类和Graph类''' class Vertex: def __init__(self,key): self.id = key # 顶点的键值 self.connectedTo = {} # 顶点的邻接列表 # 增加一条连接顶点nbr的边,边上的权重为weight def addNeighbor(self,nbr,weight=0): self.connectedTo[nbr] = weight # 将顶点字符串化 def __str__(self): return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo]) # 得到与该顶点所连接的其他顶点 def getConnections(self): # ********** Begin ********** # return self.connectedTo.keys() # ********** End ********** # # 返回顶点的key def getId(self): # ********** Begin ********** # return self.id # ********** End ********** # # 返回连接到顶点nbr的边的权重 def getWeight(self,nbr): # ********** Begin ********** # return self.connectedTo[nbr] # ********** End ********** # class Graph: def __init__(self): self.vertList = {} # 初始化主列表为空 self.numVertices = 0 # 初始化顶点数量 # 将顶点key插入图中 def addVertex(self,key): # 顶点数量加一 # 新建一个键值为key的顶点newVertex # ********** Begin ********** # self.numVertices = self.numVertices + 1 newVertex = Vertex(key) # ********** End ********** # self.vertList[key] = newVertex return newVertex # 查找顶点n def getVertex(self,n): if n in self.vertList: # ********** Begin ********** # # 通过key查找顶点 return self.vertList[n] # ********** End ********** # else: return None def __contains__(self,n): return n in self.vertList # 添加从顶点f到顶点t的一条边,边上权重设置为cost def addEdge(self,f,t,cost=0): # 首先判断这两个顶点是否存在,若不存在就调用addVertex方法,将它加入到图中 # ********** Begin ********** # if f not in self.vertList: nv=self.addVertex(f) # ********** End ********** # if t not in self.vertList: nv = self.addVertex(t) # 在这条边的起始顶点f相关联的列表上,调用addNeighbor方法将边添加进去 # ********** Begin ********** # self.vertList[f].addNeighbor(self.vertList[t],cost) # ********** End ********** # # 返回图中所有顶点列表 def getVertices(self): return self.vertList.keys() # 迭代的特殊方法,使用for循环取出图中顶点 def __iter__(self): return iter(self.vertList.values())
第1关:词梯问题
- 任务描述
- 相关知识
- 编程要求
- 测试说明
from graphs import Graph, Vertex '''请在Begin-End之间补充代码, 完成buildGraph()''' def buildGraph(wordFile): d = {} g = Graph() wfile = open(wordFile,'r') # 打开单词列表文件 # 把单词分成几组,每组单词之间相差一个字母 for line in wfile: word = line[:-1] for i in range(len(word)): bucket = word[:i] + '_' + word[i+1:] # 如果d中存在标签为bucket的桶,就把该单词word添加到对应的桶中 # 否则,创建一个标签为bucket的桶,且将word以列表形式存入 # ********** Begin ********** # if bucket in d: # 4字母单词可属于4个桶 d[bucket].append(word) else: d[bucket] = [word] # ********** End ********** # # 在同一个存储桶中为单词添加顶点和边 for bucket in d.keys(): for word1 in d[bucket]: for word2 in d[bucket]: # 对桶中两个不相等的单词调用addEdge()进行添加边操作 # ********** Begin ********** # if word1 != word2: g.addEdge(word1, word2) # ********** End ********** # return g
第2关:广度优先搜索
- 任务描述
- 相关知识
- 编程要求
- 测试说明
from graphs import Graph, Vertex from queue import Queue '''请在Begin-End之间补充代码, 完成bfs和traverse函数''' def bfs(g,start): start.setDistance(0) # 设置距离为0 start.setPred(None) # 设置前驱结点为None vertQueue = Queue() vertQueue.enqueue(start) # 将当前结点入队 while (vertQueue.size() > 0): # 若队列不为空,循环执行以下操作 currentVert = vertQueue.dequeue() # 出队一个结点作为当前顶点 for nbr in currentVert.getConnections(): # 对当前顶点邻接列表中的顶点进行迭代检查 # 若颜色是白色,则该结点未被探索 if (nbr.getColor() == 'white'): # 将nbr其设置为灰色'gray' # nbr的距离被设置为当前结点的距离加一 # nbr的前驱结点被设置为当前结点currentVert # nbr入队,被加入队尾 # ********** Begin ********** # nbr.setColor('gray') nbr.setDistance(currentVert.getDistance()+1) nbr.setPred(currentVert) vertQueue.enqueue(nbr) # ********** End ********** # currentVert.setColor('black') # 通过前驱结点链接来打印出整个词梯。 def traverse(y): x = y while (x.getPred()): # 打印顶点x的key # 前驱结点成为新的x # ********** Begin ********** # print(x.getId()) x = x.getPred() # ********** End ********** # print(x.getId())
第1关:骑士周游问题
- 任务描述
- 相关知识
- 编程要求
- 测试说明
from graphs import Graph, Vertex '''请在Begin-End之间补充代码, 完成genLegalMoves、knightGraph和knightTour函数''' # 合法走棋位置函数 def genLegalMoves(x, y, bdSize): # 存储八个合法走棋位置 newMoves = [] # 马走日8个格子的坐标偏移值 moveOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1), (1, -2), (1, 2), (2, -1), (2, 1)] for i in moveOffsets: newX = x + i[0] newY = y + i[1] # 调用legalCoord方法判断newX和newY是否走出棋盘 # 只有落在棋盘里的才通过append加到newMoves里 # ********** Begin ********** # if legalCoord(newX,bdSize) and legalCoord(newY,bdSize): newMoves.append((newX,newY)) # ********** End ********** # return newMoves # 确认不会走出棋盘 def legalCoord(x, bdSize): if x >= 0 and x < bdSize: # 不得超出正方形棋盘的边界 return True else: return False # 构建走棋关系图 def knightGraph(bdSize): # 建立空图ktGraph ktGraph = Graph() # 遍历每个格子 for row in range(bdSize): for col in range(bdSize): # 将每个格子都编号为nodeId nodeId = posToNodeId(row, col, bdSize) # 单步合法走棋 newPositions = genLegalMoves(row, col, bdSize) # 对每个位置进行判断 for e in newPositions: nid = posToNodeId(e[0], e[1], bdSize) # 将顶点和形成的边加到图ktGraph中 # ********** Begin ********** # ktGraph.addEdge(nodeId,nid) # ********** End ********** # return ktGraph # 根据棋盘行、列确定索引值 def posToNodeId(row, col, bdSize): return row * bdSize + col def knightTour(n, path, u, limit): # n:层次; path:路径; u:当前顶点; limit:搜索总深度 u.setColor('gray') # 当前顶点设为灰色,表示正在探索 path.append(u) # 当前顶点加入路径 if n < limit: nbrList = list(u.getConnections()) # 对当前顶点连接的所有合法移动逐一深入 i = 0 done = False while i < len(nbrList) and not done: # 选择白色未经过的顶点深入 # 层次加1,递归调用knightTour深入 # ********** Begin ********** # if nbrList[i].getColor()=='white': done=knightTour(n+1,path,nbrList[i],limit) # ********** End ********** # i = i + 1 # 都无法完成总深度,回溯,试本层下一个顶点 if not done: path.pop() u.setColor('white') else: done = True return done
第2关:骑士周游算法改进
- 任务描述
- 相关知识
- 编程要求
- 测试说明
from graphs import Graph, Vertex '''请在Begin-End之间补充代码, 完成orderByAvail和knightTourBetter函数''' # 合法走棋位置函数 def genLegalMoves(x, y, bdSize): newMoves = [] moveOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1), (1, -2), (1, 2), (2, -1), (2, 1)] for i in moveOffsets: newX = x + i[0] newY = y + i[1] if legalCoord(newX, bdSize) and legalCoord(newY, bdSize): newMoves.append((newX, newY)) return newMoves # 确认不会走出棋盘 def legalCoord(x, bdSize): if x >= 0 and x < bdSize: return True else: return False # 构建走棋关系图 def knightGraph(bdSize): ktGraph = Graph() for row in range(bdSize): for col in range(bdSize): nodeId = posToNodeId(row, col, bdSize) newPositions = genLegalMoves(row, col, bdSize) for e in newPositions: nid = posToNodeId(e[0], e[1], bdSize) ktGraph.addEdge(nodeId, nid) return ktGraph # 根据棋盘行、列确定索引值 def posToNodeId(row, col, bdSize): return row * bdSize + col def orderByAvail(n): resList = [] for v in n.getConnections(): if v.getColor() == 'white': c = 0 for w in v.getConnections(): # 若w未被搜索过,颜色是白色,则c的值加1 # ********** Begin ********** # if w.getColor()=='white': c=c+1 # ********** End ********** # resList.append((c, v)) # 对有合法移动目标格子数量的顶点进行从小到大排序 # ********** Begin ********** # resList.sort(key=lambda x:x[0]) # ********** End ********** # return [y[1] for y in resList] def knightTourBetter(n, path, u, limit): # use order by available function u.setColor('gray') path.append(u) if n < limit: # 调用orderByAvail函数,将当前结点的有序合法移动位置存入nbrList # ********** Begin ********** # nbrList=orderByAvail(u) # ********** End ********** # i = 0 done = False while i < len(nbrList) and not done: # 选择白色未经过的顶点深入 # 层次加1,递归调用knightTourBetter深入 # ********** Begin ********** # if nbrList[i].getColor()=='white': done=knightTourBetter(n+1,path,nbrList[i],limit) # ********** End ********** # i = i + 1 if not done: # prepare to backtrack path.pop() u.setColor('white') else: done = True return done
第3关:通用深度优先搜索
- 任务描述
- 相关知识
- 编程要求
- 测试说明
from graphs import Graph '''请在Begin-End之间补充代码, 完成DFSGraph中的dfs和dfsvisit函数''' class DFSGraph(Graph): def __init__(self): super().__init__() self.time = 0 # 时间实例变量 self.resList = [] # 存储遍历序列 def dfs(self): for aVertex in self: aVertex.setColor('white') aVertex.setPred(-1) for aVertex in self: # 如果顶点的颜色为白色'white',调用dfsvisit函数探索顶点 # ********** Begin ********** # if aVertex.getColor()=='white': self.dfsvisit(aVertex) # ********** End ********** # def dfsvisit(self, startVertex): startVertex.setColor('gray') self.resList.append(startVertex) self.time += 1 startVertex.setDiscovery(self.time) for nextVertex in startVertex.getConnections(): # 如果顶点nextVertex的颜色为白色'white',则表示未被探索 # 设置其前驱为startVertex # 递归调用dfsvisit函数进行更深层次的探索 # ********** Begin ********** # if nextVertex.getColor()=='white': nextVertex.setPred(startVertex) self.dfsvisit(nextVertex) # ********** End ********** # startVertex.setColor('black') self.time += 1 startVertex.setFinish(self.time)