【Python 算法零基础 4.排序 ⑧ 基数排序】

没人能打败我,我会抽出自己的骨,打断自己的筋,重新生长

                                                                                                —— 25.5.29

选择排序回顾

① 遍历数组从索引 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 = 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

② 外层循环遍历 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 - 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。

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

'''
输入全为非负整数,且所有元素 ≤ 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 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 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

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

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

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

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

Ⅱ、递归排序函数

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

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

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

'''
Ⅰ、分区函数 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

桶排序回顾

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

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

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

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

'''
桶排序回顾

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

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

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

④ 返回桶数组:返回桶数组,其中每个桶包含对应频率的字符列表
'''
def bucketSort(arr, max_val):  # 移除 max_val 表示字符编码最大值(如 256)
    n = len(arr)
    # 初始化桶:索引范围 [0, max_val-1]
    bucket = [[] for _ in range(max_val)]
    
    # 分布:按字符编码放入桶
    for char in arr:
        bucket[ord(char)].append(char)  # 索引 = 字符编码值
    
    # 合并桶(索引升序即字符升序)
    sorted_arr = []
    for b in bucket:
        sorted_arr.extend(b)  # 每个桶内元素已按插入顺序排列
    
    return sorted_arr  # 返回排序后的一维数组

一、引言

        基数排序(Radix Sort)是一种非比较型排序算法,它根据数字的每一位来进行排序。通常用于整数排序,基数排序的基本思想是通过对所有元素进行若干次“分配”和“收集”操作来实现排序。


二、算法思想 

        第一步:获取待排序元素的最大值,并确定其位数

        第二步:最低位(个位)开始,依次对所有元素进行“分配”“收集”操作,先进先出。

        第三步:在每一位上,根据该位上数字的值将元素分配到相应的桶中。

        第四步:对每个桶中的元素进行顺序收集,得到排序后的部分结果。

        重复上述步骤,直到对所有位都进行了排序。 


三、算法分析

1.时间复杂度

        它的时间复杂度为 O(d * (n+r)),其中 d 是数字的位数n 是待排序元素的数量r基数
(radix)。

        基数排序的时间复杂度主要取决于数字的位数待排序元素的数量。对于位数较少的情况,基数排序的效率较高,因为它不需要进行元素间的比较。然而,当数字的位数较多时,可能需要较多的"分配”和“收集”操作,导致时间复杂度增加。


2.空间复杂度

        空间复杂度方面,基数排序需要额外的存储空间来存储桶。具体的空间复杂度取决于使用的桶的数量和存储桶的方式。

        某种意义上来说,基数排序就是另一种特别的桶排序


3.算法的优点

        ① 排序效率高:基数排序不需要进行元素间的比较,因此对于一些特殊情况(如排序范围有限、元素具有特定的顺序等),它可能比比较型排序算法更有效。

        ② 对定长序列更有效:基数排序可以很容易地用于排序具有固定宽度的数字序列,如电话号码、身份证号码等


4.算法的缺点

        ① 额外的空间:基数排序需要额外的空间来存储桶,对于大型数据集可能会消耗较多的内存。

        ② 额外的处理:对于复杂的数据类型或非整数类型,可能需要进行额外的处理来实现基数排序


四、实战练习

912. 排序数组

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

    示例 1:

    输入:nums = [5,2,3,1]
    输出:[1,2,3,5]
    

    示例 2:

    输入:nums = [5,1,1,2,0,0]
    输出:[0,0,1,1,2,5]
    

    提示:

    • 1 <= nums.length <= 5 * 104
    • -5 * 104 <= nums[i] <= 5 * 104

    思路与算法

    ① 初始化参数和辅助数组设置最大元素数MAX_N为 50000,最大位数MAX_T为 8,进制BASE为 10;计算并存储基数的各次幂(如 10⁰, 10¹, ..., 10⁷)到数组PowOfBase

    ② 预处理数组元素为每个元素加上BASE^(MAX_T-1)(即 10⁷),确保所有元素变为正数;这一步是为了处理可能的负数输入,将其转换为正数进行排序

    ③ 按位进行多轮排序从最低有效位(个位)开始,逐位进行处理(共进行MAX_T轮)

            分配阶段对每个元素,计算当前位上的数字(通过整除和取模运算);将元素分配到对应的桶(0-9)中

            收集阶段按桶的顺序(0 到 9)依次收集元素;将收集的元素依次放回原数组,覆盖原有的顺序

    ④ 恢复原始数值排序完成后,从每个元素中减去BASE^(MAX_T-1)(即 10⁷);恢复元素的原始值,完成排序过程

    class Solution:
    
        MAX_N = 50000
        MAX_T = 8
        BASE = 10
    
        def RedixSort(self, arr):
            n = len(arr)
            PowOfBase = [1 for i in range(self.MAX_T)]
            for i in range(1, self.MAX_T):
                PowOfBase[i] = PowOfBase[i - 1] * self.BASE
    
            for i in range(n):
                arr[i] += PowOfBase[self.MAX_T - 1]
    
            pos = 0 
            while pos < self.MAX_T:
                RedixBucket = [ [] for i in range(self.BASE)]
    
                for i in range(n):
                    rdx = arr[i] // PowOfBase[pos] % self.BASE
                    RedixBucket[rdx].append( arr[i] )
                
                top = 0
                for i in range(self.BASE):
                    for rb in RedixBucket[i]:
                        arr[top] = rb
                        top += 1
                
                pos += 1
    
            for i in range(n):
                arr[i] -= PowOfBase[self.MAX_T - 1]
    
        def sortArray(self, nums: List[int]) -> List[int]:
            self.RedixSort(nums)
            return nums

    【Python 算法零基础 4.排序 ⑧ 基数排序】_第1张图片

    你可能感兴趣的:(Python常见算法,算法,排序算法,数据结构)