头歌实践平台(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)
    
    
    
    
    

🤝 期待与你共同进步

🌱 亲爱的读者,非常感谢你每一次的停留和阅读!你的支持是我们前行的最大动力!🙏

🌐 在这茫茫网海中,有你的关注,我们深感荣幸。你的每一次点赞👍、收藏🌟、评论💬和关注💖,都像是明灯一样照亮我们前行的道路,给予我们无比的鼓舞和力量。🌟

📚 我们会继续努力,为你呈现更多精彩和有深度的内容。同时,我们非常欢迎你在评论区留下你的宝贵意见和建议,让我们共同进步,共同成长!💬

💪 无论你在编程的道路上遇到什么困难,都希望你能坚持下去,因为每一次的挫折都是通往成功的必经之路。我们期待与你一起书写编程的精彩篇章! 🎉

🌈 最后,再次感谢你的厚爱与支持!愿你在编程的道路上越走越远,收获满满的成就和喜悦

关于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%免费

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值