当前位置: 首页 > news >正文

Python进阶指南7:排序算法和树

1.排序算法

1.1算法稳定性

所谓排序,使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作

排序算法,就是如何使得记录按照要求排列的方法

排序算法在很多领域是非常重要

在大量数据的处理方面:一个优秀的算法可以节省大量的资源。

在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析

在这里插入图片描述

举个例子:
在这里插入图片描述

把以上数据进行升序排列:

在这里插入图片描述

☆ 假定在待排序的记录序列中,存在多个具有相同的关键字的记录

☆ 若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的, 否则称为不稳定的

记忆:具有相同关键字的纪录经过排序后,相对位置保持不变,这样的算法是稳定性算法

再来看一个例子:

在这里插入图片描述

升序排序后的结果

在这里插入图片描述

无序数据具有2个关键字,

先按照关键字1排序,

若关键字1值相同,再按照关键字2排序。

稳定性算法具有良好的作用。

1.2排序算法

排序犹如一把将混乱变为秩序的魔法钥匙,使我们能以更高效的方式理解与处理数据。

无论是简单的升序,还是复杂的分类排列,排序都向我们展示了数据的和谐美感。

☆ 冒泡排序
冒泡思路

冒泡排序(bubble sort)通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。

可视化展示网站:https://visualgo.net/zh/sorting

冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换二者。遍历完成后,最大的元素会被移动到数组的最右端。

在这里插入图片描述

第一轮第一步排序:

在这里插入图片描述

第一轮第二步排序:

在这里插入图片描述

第一轮第三步排序:

在这里插入图片描述

第一轮第四步排序:

在这里插入图片描述

第一轮第五步排序:

在这里插入图片描述

第一轮最终排序结果:

在这里插入图片描述

重复以上步骤,直至完成最终排序。

冒泡步骤

设列表的长度为 n ,冒泡排序的步骤如上图所示。

  1. 首先,对 n 个元素执行“冒泡”,将列表的最大元素交换至正确位置
  2. 接下来,对剩余 n−1 个元素执行“冒泡”,将第二大元素交换至正确位置
  3. 以此类推,经过 n−1 轮“冒泡”后,前 n−1 大的元素都被交换至正确位置
  4. 仅剩的一个元素必定是最小元素,无须排序,因此列表排序完成。

在这里插入图片描述

代码实现
# 冒泡排序
def bubble_sort(my_list):# 获取列表的元素个数list_length = len(my_list)"""以[5,3,4,7,2]注意:排完序以后,小的在前,大的在后。当前遍历到的元素与后一个元素进行对比和交互第一轮循环:i=0,list_length=5内层循环:j元素的索引初始值=0,j要循环到【索引为3】结束j=3,元素是7,后一个元素的索引=j+1结合上面的注意实现,得到内层循环要list_length-1第一轮循环的结果:[3,4,5,2,7]第二轮循环:i=1,list_length=5,列表中的最后的元素7不用再纳入下一次循环内层循环:j元素的索引初始值=0,j要循环到【索引为2】结束因此最终内层循环的range的表达式要写出list_length-1-1第二轮循环的结果:[3,4,2,5,7]第三轮循环:i=2,list_length=5,列表中的最后的元素5,7不用再纳入下一次循环内层循环:j元素的索引初始值=0,j要循环到【索引为1】结束因此最终内层循环的range的表达式要写出list_length-1-2综上所述:因此最终内层循环的range的表达式要写出list_length-1-i"""for i in range(list_length):# 外层循环控制循环的次数for j in range(list_length-1-i):# 内层循环控制每轮中各个元素值的大小对比if my_list[j]>my_list[j+1]:# 交互两个元素的值my_list[j],my_list[j+1] = my_list[j+1],my_list[j]if __name__ == '__main__':my_list = [5,3,4,7,2]bubble_sort(my_list)print(my_list)
效率优化

我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 flag 来监测这种情况,一旦出现就立即返回。

经过优化,冒泡排序的最差时间复杂度和平均时间复杂度仍为 O(n2) ;但当输入数组完全有序时,可达到最佳时间复杂度 O(n)

# 冒泡排序
def bubble_sort(my_list):# 获取列表的元素个数list_length = len(my_list)"""以[5,3,4,7,2]注意:排完序以后,小的在前,大的在后。当前遍历到的元素与后一个元素进行对比和交互第一轮循环:i=0,list_length=5内层循环:j元素的索引初始值=0,j要循环到【索引为3】结束j=3,元素是7,后一个元素的索引=j+1结合上面的注意实现,得到内层循环要list_length-1第一轮循环的结果:[3,4,5,2,7]第二轮循环:i=1,list_length=5,列表中的最后的元素7不用再纳入下一次循环内层循环:j元素的索引初始值=0,j要循环到【索引为2】结束因此最终内层循环的range的表达式要写出list_length-1-1第二轮循环的结果:[3,4,2,5,7]第三轮循环:i=2,list_length=5,列表中的最后的元素5,7不用再纳入下一次循环内层循环:j元素的索引初始值=0,j要循环到【索引为1】结束因此最终内层循环的range的表达式要写出list_length-1-2综上所述:因此最终内层循环的range的表达式要写出list_length-1-i"""# 是否有元素交互。False表示没有,True表示有flag = Falsefor i in range(list_length-1):print(f"外层循环{i}")# 外层循环控制循环的次数for j in range(list_length-1-i):# 内层循环控制每轮中各个元素值的大小对比if my_list[j]>my_list[j+1]:# 交互两个元素的值my_list[j],my_list[j+1] = my_list[j+1],my_list[j]flag = True# 为什么能够直接在这里判断然后结束循环。核心原因是冒泡排序的第一轮循环会得到最大值# 如果第一轮循环的过程中,都没有发生交换。那么后续更加不可能有交换if not flag:breakif __name__ == '__main__':# my_list = [5,3,4,7,2]my_list = [1,2,3,4]bubble_sort(my_list)print(my_list)
☆ 选择排序

选择排序(selection sort)的工作原理非常简单:开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾。

排序思路

设列表的长度为 n ,选择排序的算法流程如下图所示:

第一轮:

在这里插入图片描述

第一轮:

在这里插入图片描述

第二轮:

在这里插入图片描述

第二轮:

在这里插入图片描述

第三轮:

在这里插入图片描述

第三轮:

在这里插入图片描述

第四轮:
在这里插入图片描述

第四轮:

在这里插入图片描述

第五轮:

在这里插入图片描述

第五轮:
在这里插入图片描述

完成排序,最终结果:

在这里插入图片描述

排序步骤
  1. 初始状态下,所有元素未排序,即未排序(索引)区间为 [0,n−1] 。
  2. 选取区间 [0,n−1] 中的最小元素,将其与索引 0 处的元素交换。完成后,数组前 1 个元素已排序。
  3. 选取区间 [1,n−1] 中的最小元素,将其与索引 1 处的元素交换。完成后,数组前 2 个元素已排序。
  4. 以此类推。经过 n−1 轮选择与交换后,数组前 n−1 个元素已排序。
  5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。
代码实现

def select_sort(my_list):# 获取列表的元素个数list_length = len(my_list)for i in range(list_length-1):# 外层循环控制排序轮次"""以[5, 3, 4, 7, 2]为例。list_length固定值为5第一轮:i=0,临时的最小值初始化是5,那么需要拿3,4,7,2与该值进行大小比较因此,j的初始化索引=1,直到=list_length-1结束第一轮的结果:[2,3,4,7,5]    第二轮:i=1,临时的最小值初始化是3,那么需要拿4,7,5与该值进行大小比较因此,j的初始化索引=2,直到=list_length-1结束所以:内层循环的range中表达式range(i+1,list_length)"""# 临时的最小值的索引min_index = ifor j in range(i+1,list_length):# 内层循环用来在剩余的数据中找到最小值# 比较大小。如果当前的元素值比临时的最小值要小,更新最小值的索引if my_list[j]<my_list[min_index]:min_index = j# 经过上面的循环以后能够找到本轮的最小值索引,然后调整元素的索引if min_index!=i:my_list[min_index],my_list[i] = my_list[i],my_list[min_index]if __name__ == '__main__':my_list = [5, 3, 4, 7, 2]# my_list = [1, 2, 3, 4]select_sort(my_list)print(my_list)

2.树

2.1树的基本概念

树是一种一对多关系的数据结构,主要分为:

  1. 多叉树
    1. 每个结点有0、或者多个子节点
    2. 没有父节点的结点成为根节点
    3. 每一个非根节点有且只有一个父节点
    4. 除了根节点外,每个子节点可以分为多个互不相交的子树
  2. 二叉树
    1. 每个结点有0、1、2 个子节点
    2. 没有父节点的结点成为根节点
    3. 每一个非根节点有且只有一个父节点
    4. 除了根节点外,每个子节点可以分为多个互不相交的子树

2.2树的相关术语

在这里插入图片描述

2.3二叉树的种类

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.4二叉树的存储

顺序存储、链式存储。树在存储的时候,要存储什么?

  1. 结点关系

如果树是完全二叉树、满二叉树,可以使用顺序存储。大多数构建出的树都不是完全、满二叉树,所以使用链式存储比较多。

class TreeNode:def __init__(self):self.item = valueself.parent = 父亲self.lchild = 左边树self.rchild = 右边树

对于树而言,只要拿到根节点,就相当于拿到整棵树。

完全二叉树适合顺序结构存储,但其插入删除元素效率较差。
在这里插入图片描述

大多数的二叉树都是使用链式结构存储。

2.5树的应用场景_数据库索引

在这里插入图片描述

在这里插入图片描述

2.6二叉树的概念和性质

2.7广度优先遍历

class Node(object):"""节点类"""def __init__(self, item):self.item = itemself.lchild = Noneself.rchild = Noneclass BinaryTree(object):"""二叉树"""def __init__(self, node=None):self.root = nodedef add(self, item):"""添加节点"""passdef bradh_travel(self):"""广度优先遍历"""pass
  1. 深度优先遍历:沿着某一个路径遍历到叶子结点,再从另外一个路径遍历,直到遍历完所有的结点
  2. 广度优先遍历:按照层次进行遍历

2.8添加节点思路分析

  1. 初始操作:初始化队列、将根节点入队、创建新结点
  2. 重复执行:
    • 获得并弹出队头元素
      1. 如果当前结点的左右子结点不为空,则将其左右子节点入队
      2. 如果当前结点的左右子节点为空,则将新结点挂到为空的左子结点、或者右子节点

2.9遍历方法的实现

class Node(object):"""节点类"""def  __init__(self, item):self.item = itemself.lchild = Noneself.rchild = Noneclass BinaryTree(object):"""完全二叉树"""def __init__(self, node=None):self.root = nodedef add(self, item):"""添加节点"""# 初始操作:初始化队列if self.root == None:self.root = Node(item)return# 队列queue = []# 根节点入队queue.append(self.root)while True:# 从头部取出数据node = queue.pop(0)# 判断左节点是否为空if node.lchild == None:node.lchild = Node(item)returnelse:queue.append(node.lchild)if node.rchild == None:node.rchild = Node(item)returnelse:queue.append(node.rchild)def breadh_travel(self):"""广度优先遍历"""if self.root == None:return# 队列queue = []# 添加数据queue.append(self.root)while len(queue)>0:# 取出数据node = queue.pop(0)print(node.item, end="")# 判断左右子节点是否为空if node.lchild is not None:queue.append(node.lchild)if node.rchild is not None:queue.append(node.rchild)
if __name__ == '__main__':tree = BinaryTree()tree.add("A")tree.add("B")tree.add("C")tree.add("D")tree.add("E")tree.add("F")tree.add("G")tree.add("H")tree.add("I")tree.breadh_travel()
A
B
C
D
E
F
G
H
I

2.10二叉树的三种深度优先遍历

先序遍历:先访问根节点、再访问左子树、最后访问右子树

中序遍历:先访问左子树、再访问根节点、最后访问右子树

后序遍历:先访问左子树、再访问右子树、最后访问根节点

  1. 无论那种遍历方式,都是先访问左子树、再访问右子树
  2. 碰到根节点就输出、碰到左子树、右子树就递归 注意:左子树右子树是一棵树所以递归;根节点是一个节点所以打印输出

2.11二叉树的三种深度优先遍历代码实现

class Node(object):"""节点类"""def  __init__(self, item):self.item = itemself.lchild = Noneself.rchild = Noneclass BinaryTree(object):"""完全二叉树"""def __init__(self, node=None):self.root = nodedef add(self, item):"""添加节点"""if self.root == None:self.root = Node(item)return# 队列queue = []# 从尾部添加数据queue.append(self.root)while True:# 从头部取出数据node = queue.pop(0)# 判断左节点是否为空if node.lchild == None:node.lchild = Node(item)returnelse:queue.append(node.lchild)if node.rchild == None:node.rchild = Node(item)returnelse:queue.append(node.rchild)def breadh_travel(self):"""广度优先遍历"""if self.root == None:return# 队列queue = []# 添加数据queue.append(self.root)while len(queue)>0:# 取出数据node = queue.pop(0)print(node.item, end="")# 判断左右子节点是否为空if node.lchild is not None:queue.append(node.lchild)if node.rchild is not None:queue.append(node.rchild)def preorder_travel(self, root):"""先序遍历 根 左 右"""if root is not None:# 先访问根节点print(root.item, end="")# 递归再访问左子树self.preorder_travel(root.lchild)# 递归访问右子树self.preorder_travel(root.rchild)def inorder_travel(self, root):"""中序遍历 左 根 右"""if root is not None:self.inorder_travel(root.lchild)print(root.item, end="")self.inorder_travel(root.rchild)def postorder_travel(self, root):"""后序遍历 根 左 右"""if root is not None:self.postorder_travel(root.lchild)self.postorder_travel(root.rchild)print(root.item, end="")if __name__ == '__main__':tree = BinaryTree()tree.add("0")tree.add("1")tree.add("2")tree.add("3")tree.add("4")tree.add("5")tree.add("6")tree.add("7")tree.add("8")tree.add("9")tree.preorder_travel(tree.root)print()tree.inorder_travel(tree.root)print()tree.postorder_travel(tree.root)

在这里插入图片描述

2.12二叉树由遍历结果反推二叉树的结构

我们需要知道先序遍历结果和中序遍历结果、或者后序遍历结果和中序遍历结果才能够确定唯一一棵树。

只知道先序遍历、后序遍历结果,不能保证确定唯一的一棵树。

通过先序遍历可以确定哪个元素是根节点,通过中序遍历可以知道左子树都有那些结点、右子树都有那些结点。

http://www.dtcms.com/a/390603.html

相关文章:

  • 深入理解 HashMap的数据结构
  • ArcGIS前后两期数据库对比工具
  • React18学习笔记(三) ReactRouter----React中的路由
  • [cesium] vue3 安装cesium方法
  • 埃文科技亮相华为全联接大会2025 联合鲲鹏发布AI使能平台解决方案 共筑AI产业新生态
  • Linux 桌面环境GNOME 49 释出
  • react/umi,浏览器tab设置
  • langchain-PipelinePromptTemplate
  • git 本地仓库与远程仓库链接
  • 绘想 - 百度推出的AI视频创作平台
  • 穿越像素的凝视:深度解析视频中的人物与动物识别算法技术
  • OpenHarmony 4.0 Release源码下载、编译及烧录
  • 大模型提示词Prompt工程:2-全攻略+最佳实践框架+原理解析+实战案例库+七招要诀
  • 大模型微调——Prompt-Tuning
  • code2prompt 快速生成项目 Markdown 文档(结合大模型进行问答)
  • UIKit-CAGradientLayer
  • K8s LoadBalancer服务深度解析
  • Windows 系统开发 iOS 与安卓应用全流程指南,附 PC 前端工具链
  • CentOS 7 系统 “cannot find a valid baseurl for repo base7x86_64” 报错完整解决方案
  • centos7通过kubeadm安装k8s1.27.1版本
  • kubesphere(k8s)如何设置存储类的默认路径
  • 在 k8s 上部署 Kafka 4.0 3节点集群
  • k8s 部署 EMQX 5.8.6 静态三节点集群
  • UVa1374/LA3621 Power Calculus
  • 以 NoETL 重塑 AI-Ready 的数据底座,Aloudata 获评 IDC 面向生成式 AI 的数据基础设施核心厂商
  • 声音转文字API平台推荐
  • Vue3: watch watchEffect
  • 梯度提升算法及其在回归与分类中的应用实战
  • 【自然语言处理与大模型】大模型应用开发四个场景
  • 深度神经网络-传播原理