头歌实践平台(Python数据结构)(7-13)

第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 区间补充代码,根据希尔排序的算法思想完成shellSortgapInsertionSort方法,从而实现对无序表的排序。

测试说明

平台会对你编写的代码进行测试,比对你输出的数值与实际正确的数值,只有所有数据全部计算正确才能通过测试:

测试输入:

 
  1. 54,26,93,17,77,31,44,55,20

输入说明:输入为需要对其进行排序的无序表。

预期输出:

 
  1. 增量为 4 : [20, 26, 44, 17, 54, 31, 93, 55, 77]
  2. 增量为 2 : [20, 17, 44, 26, 54, 31, 77, 55, 93]
  3. 增量为 1 : [17, 20, 26, 31, 44, 54, 55, 77, 93]

输出说明:输出的是对无序表进行希尔排序的每一趟排序的结果,以列表的形式展现。其中增量的取值从 n/2 开始,之后的每趟减少到 n/4……直到 1。在本例测试数据中,数据项个数为 9,则增量序列为:4、2、1。

测试输入:

 
  1. 49,38,65,97,76,13,27

预期输出:

 
  1. 增量为 3 : [27, 38, 13, 49, 76, 65, 97]
  2. 增量为 1 : [13, 27, 38, 49, 65, 76, 97]

提示:

 
  1. for i in range(0, 30, 5): # 步长为 5
  2. print(i, end=" ")
  3. print('\n')
  4. for j in range(1, 30, 5):
  5. print(j, end=" ")

输出:

 
  1. 0 5 10 15 20 25
  2. 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 项作为基准值。基准值在最后排序好的列表里的实际位置,我们通常称之为分割点,是用来对拆分成的两部分分别进行快速排序的关键位置点。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

  1. 找到基准值的位置,设置左标记 leftmark 和右标记 rightmark。

  2. 不断地移动左右标记,进行多次比较和交换: ① 左标一直向右移动,遇到比基准值大的就停止; ② 右标一直向左移动,遇到比基准值小的就停止; ③ 把左右标记所指的数据项交换。

  3. 继续移动,直到左标记移到右标记的右侧,停止移动。这时右标记所指的位置就是基准值应处的位置,将基准值和这个位置的数据项交换。此时,基准值左边部分的数据项都小于或等于基准值,右边部分的数据项都大于或等于基准值。

  4. 然后递归地对左边和右边的部分进行快速排序,当待排序部分只有一个数据项时,递归过程结束。

以下为快速排序的一个简单示例。首先将图 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. 基本结束条件:列表中仅有 1 个数据项时,自然是排好序的;

  2. 缩小规模:根据基准值将列表分为两部分,最好的情况是分为相等规模的两半;

  3. 调用自身:将拆分成的两部分分别调用自身进行排序。

快速排序的算法分析

我们可以将快速排序分为两个过程来分析:

  • 拆分的过程,如果对列表的拆分总是发生在列表的中央,那么时间复杂度就是O(logn)

  • 移动的过程,每次左右标记移动时都需要将所指的数据项与基准值进行比较,所以时间复杂度是O(n)

综合考虑,时间复杂度为O(nlogn)。但是,如果基准值所在的分割点过于偏离中部,造成左右两部分数量不平衡,则会使算法效率降低。最坏的情况是,拆分成的某一部分始终没有数据,这时时间复杂度就退化到O(n2)

编程要求

在右侧编辑器中的 Begin-End 区间补充代码,根据快速排序的算法思想完成quickSortHelperpartition方法,从而实现对无序表的排序。其中quickSortHelper方法用于对从 first 到 last 位置的数据项所在的列表进行快速排序;partition方法用于对列表进行拆分,同时返回分割点位置。

测试说明

平台会对你编写的代码进行测试,比对你输出的数值与实际正确的数值,只有所有数据全部计算正确才能通过测试:

测试输入:

 
  1. 54,26,93,17,77,31,44,55,20

输入说明:输入为需要对其进行排序的无序表。

预期输出:

 
  1. [17, 20, 26, 31, 44, 54, 55, 77, 93]

输出说明:输出的是对无序表进行快速排序后的结果,以列表的形式展现。

测试输入:

 
  1. 49,38,65,97,76,13,27

预期输出:

 
  1. [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关:前序、中序、后序遍历

  2. 任务描述
  3. 相关知识
  4. 编程要求
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)

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值