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

【Python 算法零基础 4.排序 ⑦ 桶排序】

草木不争高,争的是生生不息

                                        —— 25.5.26

选择排序回顾

① 遍历数组从索引 0 到 n-1n 为数组长度)。

② 每轮确定最小值假设当前索引 i 为最小值索引 min_index。从 i+1 到 n-1 遍历,若找到更小元素,则更新 min_index

③ 交换元素若 min_index ≠ i,则交换 arr[i] 与 arr[min_index]

'''
① 遍历数组:从索引 0 到 n-1(n 为数组长度)。② 每轮确定最小值:假设当前索引 i 为最小值索引 min_index。从 i+1 到 n-1 遍历,若找到更小元素,则更新 min_index。③ 交换元素:若 min_index ≠ i,则交换 arr[i] 与 arr[min_index]。
'''def selectionSort(arr: List[int]):n = len(arr)for i in range(n):min_index = ifor j in range(i+1, n):if arr[j] < arr[min_index]:min_index = jif min_index != i:arr[i], arr[min_index] = arr[min_index], arr[i]return arr

冒泡排序回顾

① 初始化设数组长度为 n

② 外层循环遍历 i 从 0 到 n-1(共 n 轮)。

③ 内层循环对于每轮 i,遍历 j 从 0 到 n-i-2

④ 比较与交换若 arr[j] > arr[j+1],则交换两者。

⑤ 结束条件重复步骤 2-4,直到所有轮次完成。

'''
① 初始化:设数组长度为 n。② 外层循环:遍历 i 从 0 到 n-1(共 n 轮)。③ 内层循环:对于每轮 i,遍历 j 从 0 到 n-i-1。④ 比较与交换:若 arr[j] > arr[j+1],则交换两者。⑤ 结束条件:重复步骤 2-4,直到所有轮次完成。
'''
def bubbleSort(arr: List[int]):n = len(arr)for i in range(n):for j in range(n-i-1):if arr[j] > arr[j+1]:arr[j], arr[j+1] = arr[j+1], arr[j]return arr

插入排序回顾

① 遍历未排序元素从索引 1 到 n-1

② 保存当前元素将 arr[i] 存入 current

③ 元素后移从已排序部分的末尾(索引 j = i-1)向前扫描,将比 current 大的元素后移。直到找到第一个不大于 current 的位置或扫描完所有元素。

④ 插入元素将 current 放入 j+1 位置。

'''
① 遍历未排序元素:从索引 1 到 n-1。② 保存当前元素:将 arr[i] 存入 current。③ 元素后移:从已排序部分的末尾(索引 j = i-1)向前扫描,将比 current 大的元素后移。直到找到第一个不大于 current 的位置或扫描完所有元素。④ 插入元素:将 current 放入 j+1 位置。
'''
def insertSort(arr: List[int]):n = len(arr)for i in range(n):current = arr[i]j = i - 1while current < arr[j] and j >0:arr[j+1] = arr[j]j -= 1arr[j + 1] = currentreturn arr

计数排序回顾

① 初始化设数组长度为 n,元素最大值为 r。创建长度为 r+1 的计数数组 count,初始值全为 0。

② 统计元素频率遍历原数组 arr,对每个元素 x,将 count[x] 加 1。

③ 重构有序数组初始化索引 index = 0。遍历计数数组 count,索引 v 从 0 到 r,若 count[v] > 0,则将 v 填入原数组 arr[index],并将 index 加 1。count[v] - 1,重复此步骤直到 count[v] 为 0。

④ 结束条件当计数数组遍历完成时,排序结束。

'''
输入全为非负整数,且所有元素 ≤ r① 初始化:设数组长度为 n,元素最大值为 r。创建长度为 r+1 的计数数组 count,初始值全为 0。② 统计元素频率:遍历原数组 arr,对每个元素 x,将 count[x] 加 1。③ 重构有序数组:初始化索引 index = 0。遍历计数数组 count,索引 v 从 0 到 r,
若 count[v] > 0,则将 v 填入原数组 arr[index],并将 index 加 1。
count[v] - 1,重复此步骤直到 count[v] 为 0。④ 结束条件:当计数数组遍历完成时,排序结束。
'''def countingSort(arr: List[int], r: int):# count = [0] * len(r + 1)count = [0 for i in range(r + 1)]for x in arr:count[x] += 1index = 0for v in range(r + 1):while count[v] > 0:arr[index] = vindex += 1count[v] -= 1return arr

归并排序回顾

Ⅰ、递归分解列表

① 终止条件:若链表为空或只有一个节点(head is None 或 head.next is None),直接返回头节点。

② 快慢指针找中点:初始化 slow 和 fast 指针,slow 指向头节点,fast 指向头节点的下一个节点。fast 每次移动两步,slow 每次移动一步。当 fast 到达末尾时,slow 恰好指向链表的中间节点。

③ 分割链表:将链表从中点断开,head2 指向 slow.next(后半部分的头节点)。将 slow.next 置为 None,切断前半部分与后半部分的连接。

④ 递归排序子链表:对前半部分(head)和后半部分(head2)分别递归调用 mergesort 函数。

Ⅱ、合并两个有序列表

① 创建虚拟头节点创建一个值为 -1 的虚拟节点 zero,用于简化边界处理。使用 current 指针指向 zero,用于构建合并后的链表。

② 比较并合并节点:遍历两个子链表 head1 和 head2,比较当前节点的值:若 head1.val <= head2.val,将 head1 接入合并链表,并移动 head1 指针。否则,将 head2 接入合并链表,并移动 head2 指针。每次接入节点后,移动 current 指针到新接入的节点。

③ 处理剩余节点:当其中一个子链表遍历完后,将另一个子链表的剩余部分直接接入合并链表的末尾。

④ 返回合并后的链表:虚拟节点 zero 的下一个节点即为合并后的有序链表的头节点。

'''
Ⅰ、递归分解列表① 终止条件:若链表为空或只有一个节点(head is None 或 head.next is None),直接返回头节点。② 快慢指针找中点:初始化 slow 和 fast 指针,slow 指向头节点,fast 指向头节点的下一个节点。fast 每次移动两步,slow 每次移动一步。当 fast 到达末尾时,slow 恰好指向链表的中间节点。③ 分割链表:将链表从中点断开,head2 指向 slow.next(后半部分的头节点)。将 slow.next 置为 None,切断前半部分与后半部分的连接。④ 递归排序子链表:对前半部分(head)和后半部分(head2)分别递归调用 mergesort 函数。Ⅱ、合并两个有序列表① 创建虚拟头节点:创建一个值为 -1 的虚拟节点 zero,用于简化边界处理。使用 current 指针指向 zero,用于构建合并后的链表。② 比较并合并节点:遍历两个子链表 head1 和 head2,比较当前节点的值:若 head1.val <= head2.val,将 head1 接入合并链表,并移动 head1 指针。否则,将 head2 接入合并链表,并移动 head2 指针。每次接入节点后,移动 current 指针到新接入的节点。③ 处理剩余节点:当其中一个子链表遍历完后,将另一个子链表的剩余部分直接接入合并链表的末尾。④ 返回合并后的链表:虚拟节点 zero 的下一个节点即为合并后的有序链表的头节点。
'''
def mergesort(self, head: ListNode):if head is None or head.next is None:return headslow, fast = head, head.nextwhile fast and fast.next:slow = slow.nextfast = fast.next.nexthead2 = slow.nextslow.next = Nonereturn self.merge(self.mergesort(head), self.mergesort(head2))def merge(self, head1: ListNode, head2: ListNode):zero = ListNode(-1)current = zerowhile head1 and head2:if head1.val <= head2.val:current.next = head1head1 = head1.nextelse:current.next = head2head2 = head2.nextcurrent = current.nextcurrent.next = head1 if head1 else head2return zero.next

快速排序回顾

Ⅰ、分区函数 Partition

① 随机选择基准元素:根据左右边界下标随机选择基准元素(选择的是元素并非下标),将基准元素赋值变量进行后续比较

② 交换基准元素:将基准元素移动到最左边,将基准元素存储在变量中,

③ 分区操作:对于基准元素右边的元素,找到第一个小于基准元素的值,移动到最左边;对于基准元素左边的元素,找到第一个大于基准元素的值,移动到最右边

④ 返回基准元素的最终位置:循环执行完毕后,基准元素左边的值都小于它,基准元素右边的值都大于它

Ⅱ、递归排序函数

① 定义递归终止条件:当左索引小于右索引时,结束递归

② 分区操作: 执行第一次分区操作,找到基准元素

③ 递归调用分区函数:将基准元素的左边、右边部分分别传入递归函数进行排序

'''
Ⅰ、分区函数 Partition① 随机选择基准元素:根据左右边界下标随机选择基准元素(选择的是元素并非下标),将基准元素赋值变量进行后续比较② 交换基准元素:将基准元素移动到最左边,将基准元素存储在变量中,③ 分区操作:对于基准元素右边的元素,找到第一个小于基准元素的值,移动到最左边;对于基准元素左边的元素,找到第一个大于基准元素的值,移动到最右边④ 返回基准元素的最终位置:循环执行完毕后,基准元素左边的值都小于它,基准元素右边的值都大于它Ⅱ、递归排序函数① 定义递归终止条件:当左索引小于右索引时,结束递归② 分区操作: 执行第一次分区操作,找到基准元素③ 递归调用分区函数:将基准元素的左边、右边部分分别传入递归函数进行排序
'''def Partition(arr, left, right):idx = random.randint(left, right)arr[left], arr[idx] = arr[idx], arr[left]l = leftr = rightx = arr[l]while l < r:while l < r and x < arr[r]:r -= 1if l < r:arr[l], arr[r] = arr[r], arr[l]l += 1while l < r and x > arr[l]:l += 1if l < r:arr[l], arr[r] = arr[r], arr[l]r -= 1return ldef quickSort(arr, l, r):if l >= r:returnnode = self.quickSort(l, r)self.quickSort(arr, l, node-1)self.quickSort(arr, node+1, r)return arr

一、引言

        桶排序(Bucket Sort)是一种基于计数的排序算法,工作原理是将数据分到有限数量的桶子里,然后每个桶再分别排序(可以使用别的排序算法或是以递归方式继续使用桶排序进行排序)


二、算法思想

        第一步:设置固定数量的空桶。

        第二步:把数据放到对应的桶中。

        第三步:对每个不为空的桶中数据进行排序。

        第四步:拼接不为空的桶中数据,得到结果。


三、算法分析

1.时间复杂度

        桶排序属于一种组合排序,因为分入桶中的元素,可以采用任意一种排序,所以时间复杂度还是取决于每个桶中的元素,采用什么排序。


2.空间复杂度

        由于采用了额外的桶空间,并且每个元素都需要放入桶中,最多存储的个数就是元素个数,所以空间复杂度为O(n)


3.算法的优点

        ① 排序速度快:适用于一定范围内的整数或浮点数排序,不需要比较元素之间的大小,因此排序速度较快。

        ② 稳定性:桶排序是一种稳定的排序算法。


4.算法的缺点

        ① 占用内存空间:当数据范围较大时,需要的桶数量较多,会消耗较多的内存空间。

        ② 不适合大规模数据:桶排序是一种线性排序,不适合大规模数据排序。


四、实战练习

451. 根据字符出现频率排序

给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。

返回 已排序的字符串 。如果有多个答案,返回其中任何一个。

示例 1:

输入: s = "tree"
输出: "eert"
解释: 'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

示例 2:

输入: s = "cccaaa"
输出: "cccaaa"
解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:

输入: s = "Aabb"
输出: "bbAa"
解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

提示:

  • 1 <= s.length <= 5 * 10^5
  • s 由大小写英文字母和数字组成

思路与算法

Ⅰ、桶排序

① 初始化桶和频率数组: 创建字符长度+1的桶bucket,索引 i 表示频率为 i 的字符列表;长度为max的频率数组count,用于记录每个字符的出现次数

② 统计字符频率:通过 ord(char) 获取字符的ASCII码,作为频率数组的索引

③ 将字符按照频率放入桶中:遍历频率数组,将每个字符以频率作为索引放入数组中

④ 返回桶数组:返回桶数组,其中每个桶包含对应频率的字符列表

Ⅱ、频率排序

① 调用桶排序生成桶数组:桶数组 bucket 的索引表示频率,每个桶存储对应频率的字符列表

② 逆序遍历桶数组生成结果字符串:从最高频率(len(s))开始,确保先处理频率高的字符。对于频率为 i 的每个字符,重复 i 次并拼接到结果字符串。

class Solution:def bucketSort(self, arr, max):n = len(arr)bucket = [ [] for i in range(n + 1)]count = [0 for i in range(max)]for i in range(n):count[ord(arr[i])] += 1for i in range(max):temp = count[i]bucket[temp].append(chr(i))return bucketdef frequencySort(self, s: str) -> str:bucket = self.bucketSort(s, 256)ans = ''# 逆序遍历桶for i in range(len(s), 0, -1):for j in bucket[i]:for k in range(i):ans += jreturn ans

相关文章:

  • PythonWeb项目开发脚手架
  • 无线错误排查、排错命令
  • sctpscan:用于发现 SCTP 网络扫描器!全参数详细教程!Kali Linux教程!
  • unity开发棋牌游戏
  • CppCon 2014 学习:Defensive Programming Done Right.
  • windows 缓冲区溢出实验关于VS的一些配置 (逆向与漏洞分析 (魏强) (Z-Library))
  • 【存储基础】数据存储基础知识
  • 【基于阿里云搭建数据仓库(离线)】DataWorks中删除节点
  • 每日八股文6.2
  • 激光雷达的强度像和距离像误差与噪声分析(2)2025.6.2
  • 软件测评师教程 第2章 软件测试基础 笔记
  • Linux 中 m、mm、mmm 函数和 make 的区别
  • 【C++/Linux】TinyWebServer前置知识之IP协议详解
  • UE特效Niagara性能分析
  • String和StringBuilder和StringBuffer
  • Java从入门到精通 - 常用API(一)
  • Java函数式编程(中)
  • Vortex GPGPU的github流程跑通与功能模块波形探索(四)
  • 6个月Python学习计划 Day 12 - 字符串处理 文件路径操作
  • RocketMQ 消息发送核心源码解析:DefaultMQProducerImpl.send () 方法深度剖析
  • 做面料哪个网站好/下载优化大师安装桌面
  • 建个外国网站/广告宣传方式有哪些
  • 汾阳做网站/东莞推广服务
  • wordpress 用户分组管理/郑州官网关键词优化公司
  • wordpress自动生产/上海seo服务
  • 怎样用服务器做网站/企业网站托管