第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 = ywhile (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 + coldef 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 + coldef 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)
🤝 期待与你共同进步
🌱 亲爱的读者,非常感谢你每一次的停留和阅读!你的支持是我们前行的最大动力!🙏
🌐 在这茫茫网海中,有你的关注,我们深感荣幸。你的每一次点赞👍、收藏🌟、评论💬和关注💖,都像是明灯一样照亮我们前行的道路,给予我们无比的鼓舞和力量。🌟
📚 我们会继续努力,为你呈现更多精彩和有深度的内容。同时,我们非常欢迎你在评论区留下你的宝贵意见和建议,让我们共同进步,共同成长!💬
💪 无论你在编程的道路上遇到什么困难,都希望你能坚持下去,因为每一次的挫折都是通往成功的必经之路。我们期待与你一起书写编程的精彩篇章! 🎉
🌈 最后,再次感谢你的厚爱与支持!愿你在编程的道路上越走越远,收获满满的成就和喜悦!
关于Python学习指南
如果想要系统学习Python、Python问题咨询,或者考虑做一些工作以外的副业,都可以扫描二维码添加微信,围观朋友圈一起交流学习。
我们还为大家准备了Python资料和副业项目合集,感兴趣的小伙伴快来找我领取一起交流学习哦!
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
包括:Python激活码+安装包、Python web开发,Python爬虫,Python数据分析,人工智能、自动化办公等学习教程。带你从零基础系统性的学好Python!
👉Python所有方向的学习路线👈
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(全套教程文末领取)
👉Python学习视频600合集👈
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
温馨提示:篇幅有限,已打包文件夹,获取方式在:文末
👉Python70个实战练手案例&源码👈
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉Python大厂面试资料👈
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
👉Python副业兼职路线&方法👈
学好 Python 不论是就业还是做副业赚钱都不错,但要学会兼职接单还是要有一个学习规划。
👉 这份完整版的Python全套学习资料已经上传,朋友们如果需要可以扫描下方CSDN官方认证二维码或者点击链接免费领取【保证100%免费
】