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

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

------ 25.5.26

选择排序回顾

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

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

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

python 复制代码
'''
① 遍历数组:从索引 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 = i
        for j in range(i+1, n):
            if arr[j] < arr[min_index]:
                min_index = j
        if min_index != i:
            arr[i], arr[min_index] = arr[min_index], arr[i]
    return arr

冒泡排序回顾

① 初始化:设数组长度为 n

② 外层循环:遍历 i0n-1(共 n 轮)。

③ 内层循环:对于每轮 i,遍历 j0n-i-2

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

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

python 复制代码
'''
① 初始化:设数组长度为 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

插入排序回顾

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

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

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

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

python 复制代码
'''
① 遍历未排序元素:从索引 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 - 1
        while current < arr[j] and j >0:
            arr[j+1] = arr[j]
            j -= 1
        arr[j + 1] = current
    return 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。

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

python 复制代码
'''
输入全为非负整数,且所有元素 ≤ 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] += 1
    index = 0
    for v in range(r + 1):
        while count[v] > 0:
            arr[index] = v
            index += 1
            count[v] -= 1
    return arr

归并排序回顾

Ⅰ、递归分解列表

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

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

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

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

Ⅱ、合并两个有序列表

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

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

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

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

python 复制代码
'''
Ⅰ、递归分解列表

① 终止条件:若链表为空或只有一个节点(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 head
    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    head2 = slow.next
    slow.next = None
    return self.merge(self.mergesort(head), self.mergesort(head2))

def merge(self, head1: ListNode, head2: ListNode):
    zero = ListNode(-1)
    current = zero
    while head1 and head2:
        if head1.val <= head2.val:
            current.next = head1
            head1 = head1.next
        else:
            current.next = head2
            head2 = head2.next
        current = current.next
    current.next = head1 if head1 else head2
    return zero.next

快速排序回顾

Ⅰ、分区函数 Partition

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

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

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

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

Ⅱ、递归排序函数

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

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

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

python 复制代码
'''
Ⅰ、分区函数 Partition

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

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

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

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

Ⅱ、递归排序函数

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

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

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

def Partition(arr, left, right):
    idx = random.randint(left, right)
    arr[left], arr[idx] = arr[idx], arr[left]
    l = left
    r = right
    x = arr[l]
    while l < r:
        while l < r and x < arr[r]:
            r -= 1
        if l < r:
            arr[l], arr[r] = arr[r], arr[l]
            l += 1

        while l < r and x > arr[l]:
            l += 1
        if l < r:
            arr[l], arr[r] = arr[r], arr[l]
            r -= 1
    return l

def quickSort(arr, l, r):
    if l >= r:
        return
    node = 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 次并拼接到结果字符串。

python 复制代码
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])] += 1
        for i in range(max):
            temp = count[i]
            bucket[temp].append(chr(i))
        return bucket

    def 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 += j
        return ans
        
相关推荐
码农之王15 分钟前
记录一次,利用AI DeepSeek,解决工作中算法和无限级树模型问题
后端·算法
落羽的落羽3 小时前
【C++】二叉搜索树
开发语言·数据结构·c++·学习
编程绿豆侠3 小时前
力扣HOT100之二分查找: 34. 在排序数组中查找元素的第一个和最后一个位置
数据结构·算法·leetcode
Shan12053 小时前
找到每一个单词+模拟的思路和算法
数据结构·算法
纳于大麓3 小时前
数据结构-栈
数据结构
国家不保护废物3 小时前
微信红包算法深度解析:从产品思维到代码实现
javascript·算法·面试
小明同学013 小时前
[C++入门]简化的艺术---对模版的初步探索
开发语言·c++·算法
kaiaaaa4 小时前
算法训练第八天
算法
LL_xjbt4 小时前
代码随想录刷题day29
算法·leetcode·动态规划
叶子2024225 小时前
守护进程实验——autoDL
人工智能·算法·机器学习