leetcode算法学习笔记-桶排序与排序内容总结(python版)

b站左老师课程对应链接:3.详解桶排序以及排序内容大总结_哔哩哔哩_bilibili

在完全二叉树中,i位置的左子位置为2i+1,右子位置为2i+2,父位置为(i-1)/2。

heapinsert

用户依次输入值可以保证组成的数组为大根堆。也可以认为是给较大值找位置。

过程描述:初始化heapsize=0,用户依次输入一个值,heapsize+1,输入后与自己的父位置值比较,如果大于父位置值则交换位置。

def heapinsert(arr, i):
    while arr[i] > arr[int((i-1)/2)]:
        arr[i], arr[int((i-1)/2)] = arr[int((i-1)/2)],  arr[i]
        i = int((i-1)/2)

heapify

用于当把最大值从数组中抽离后,将数组重新排列成大根堆。也可以认为是给较小值找位置。

过程描述:最大值从数组中删除后,将数组的最后一个值复制到最大值位置(0位),heapsize-1。i初始化为0位,从0位(i)开始与子数中的最大值比较,当子数中的最大值大于父值,则交换位置,并使i=子数最大值本来的位置。继续重复上步,直到当子数中的最大值不大于父值或i下方没有子数。

def heapify(arr, i, heapsize):
    left = int(i * 2 + 1) #左子数位置

    while left < heapsize: #用于判断下方是否有子数,当左子数位置>=heapsize时,越界,即没有左子数,也就没有右子数。
        #筛选子数最大值位置,只有当右子数位置在界内,即有右子数,且值大于左子数,子数最大值位置才为右子数
        largest = left + 1 if (left + 1 < heapsize and arr[left+1] > arr[left]) else left
        #比较父与最大子数的大小,把大的下标给largest
        largest = largest if arr[largest] > arr[i] else i
        #当i为最大值时,就不用继续往下比较了,已经满足大根堆要求
        if largest == i:
            break
        #其余情况需要互换位置,并将i = largest
        arr[largest], arr[i] = arr[i], arr[largest]
        i = largest
        #新左子数,即原来i位置的子数的左子数
        left = int(i * 2 + 1)

变换任意位置的值,将这个值与原值对比,小于原值使用heapify,大于原值使用heapinsert。

时间复杂度:O(logN):更换的数只在二叉树层之间变换,因此最坏的结果是logN

使用堆来排序数组

过程描述:从数组中逐一加载数,每次加一个数都使用heapinsert处理,将所有数按大根堆排序后,再将最大值与后一个值位置互换,使用heapify操作处理。如此循环直到heapsize=0。

class heapsort:
    def __init__(self, arr):
        self.arr = arr

    def compare_num(self):
        for i in range(len(self.arr)):
            self.heapinsert(i)
        heapsize = len(self.arr)
        while heapsize > 0:
            self.arr[0], self.arr[heapsize - 1] = self.arr[heapsize - 1], self.arr[0]
            heapsize -= 1
            self.heapify(0, heapsize)

        return self.arr

    def heapinsert(self, i):
        while self.arr[i] > self.arr[int((i-1)/2)]:
            self.arr[i], self.arr[int((i-1)/2)] = self.arr[int((i-1)/2)], self.arr[i]
            i = int((i-1)/2)

    def heapify(self, i, heapsize):
        left = int(i * 2 + 1)  # 左子数位置

        while left < heapsize:  # 用于判断下方是否有子数,当左子数位置>=heapsize时,越界,即没有左子数,也就没有右子数。
            # 筛选子数最大值位置,只有当右子数位置在界内,即有右子数,且值大于左子数,子数最大值位置才为右子数
            largest = left + 1 if (left + 1 < heapsize and self.arr[left + 1] > self.arr[left]) else left
            # 比较父与最大子数的大小,把大的下标给largest
            largest = largest if self.arr[largest] > self.arr[i] else i
            # 当i为最大值时,就不用继续往下比较了,已经满足大根堆要求
            if largest == i:
                break
            # 其余情况需要互换位置,并将i = largest
            self.arr[largest], self.arr[i] = self.arr[i], self.arr[largest]
            i = largest
            # 新左子数,即原来i位置的子数的左子数
            left = int(i * 2 + 1)

if __name__ == '__main__':
    arr = [1,3,9,7,2,5,8,0]
    print(heapsort(arr).compare_num())

 时间复杂度:O(N*LogN)

空间复杂度:O(1)

将循环加载数步骤简化,直接导入整个数组进行操作。

过程描述:从数组最后一个数反向遍历数组,每个数heapify后,向上一个数继续迭代,直到i=0

class heapsort:
    def __init__(self, arr):
        self.arr = arr

    def compare_num(self):
        for i in range(len(self.arr)-1, -1, -1):
            self.heapify(i, len(self.arr))
        heapsize = len(self.arr)
        while heapsize > 0:
            self.arr[0], self.arr[heapsize - 1] = self.arr[heapsize - 1], self.arr[0]
            heapsize -= 1
            self.heapify(0, heapsize)

        return self.arr

    def heapinsert(self, i):
        while self.arr[i] > self.arr[int((i-1)/2)]:
            self.arr[i], self.arr[int((i-1)/2)] = self.arr[int((i-1)/2)], self.arr[i]
            i = int((i-1)/2)

    def heapify(self, i, heapsize):
        left = int(i * 2 + 1)  # 左子数位置

        while left < heapsize:  # 用于判断下方是否有子数,当左子数位置>=heapsize时,越界,即没有左子数,也就没有右子数。
            # 筛选子数最大值位置,只有当右子数位置在界内,即有右子数,且值大于左子数,子数最大值位置才为右子数
            largest = left + 1 if (left + 1 < heapsize and self.arr[left + 1] > self.arr[left]) else left
            # 比较父与最大子数的大小,把大的下标给largest
            largest = largest if self.arr[largest] > self.arr[i] else i
            # 当i为最大值时,就不用继续往下比较了,已经满足大根堆要求
            if largest == i:
                break
            # 其余情况需要互换位置,并将i = largest
            self.arr[largest], self.arr[i] = self.arr[i], self.arr[largest]
            i = largest
            # 新左子数,即原来i位置的子数的左子数
            left = int(i * 2 + 1)

if __name__ == '__main__':
    arr = [1,3,9,7,2,5,8,0]
    print(heapsort(arr).compare_num())




数组变为大根堆步骤的时间复杂度:O(N):二叉树最底层子节点有N/2个,这些节点没有子节点,不用heapify操作,时间复杂度为N/2*1,倒数第二层子节点需要遍历和heapify操作,时间复杂度为N/4*2,以此类推。加和后为O(N)。

整体时间复杂度仍为O(N*LogN)

一个几乎有序数组的排序:

几乎有序数组:把数组中每个数排好序,每个元素移动距离不超过k,k相对于整个数组长度较小。

过程描述:可以使用小根堆处理,遍历数组前k+1个值,将这k+1个数转化为小根堆,之后再向后一位,也就是1~k+2范围的值再转换为小根堆,以此类推,到最后7个值只要逐步减小变为小根堆的范围,就可以获得有序数组。

时间复杂度:O(N*Logk)

补充材料:python中可以直接调用的堆算法

heapq.heappush(heapitem)

将 item 的值加入 heap 中,保持heap为小根堆。

import heapq
heap = []
arr = [1, 3, 6, 2, 5, 8, 3, 0, 4, 7]
for i in arr:
    heapq.heappush(heap, i)
print(heap)
#结果:[0, 1, 3, 2, 5, 8, 6, 3, 4, 7]

heapq.heappop(heap)

弹出并返回 heap 的最小的元素,保持heap为小根堆。如果堆为空,抛出IndexError 。使用heap[0] ,可以只访问最小的元素而不弹出它。

import heapq
heap = []
h = []
arr = [1, 3, 6, 2, 5, 8, 3, 0, 4, 7]
for i in arr:
    heapq.heappush(heap, i)
for _ in range(len(heap)):
    h.append(heapq.heappop(heap))
print(h)
#结果:[0, 1, 2, 3, 3, 4, 5, 6, 7, 8]

heapq.heappushpop(heapitem)

将 item 放入堆中,然后弹出并返回 heap 的最小元素。该组合操作比先调用heappush()再调用 heappop()运行起来更有效率。如果堆为空,行为类似于调用heappush(),并且返回None。

import heapq

heap = []
arr = [1, 3, 6, 2, 5, 8, 3, 5, 4, 7]
for i in arr:
    heapq.heappush(heap, i)

print(heapq.heappushpop(heap, 0))

#结果:0

heapq.heapify(x)

将list x 转换成小根堆,就地操作,也就是直接在list内操作,线性时间内。

import heapq

arr = [1, 3, 6, 2, 5, 8, 3, 5, 4, 7]
heapq.heapify(arr)
print(arr)

#结果:[1, 2, 3, 3, 5, 8, 6, 5, 4, 7]

heapq.heapreplace(heapitem)

弹出并返回 heap 中最小的一项,同时推入新的 item。 堆的大小不变。 如果堆为空则引发IndexError。

利用可调用函数直接运行堆的方法称为黑盒方法。黑盒方法就是用户输入一个值,黑盒返回一个值。但如果要从堆内部改变一个值就不推荐这种方法了,相对于我们手写的方法,黑盒方法处理这种问题,效率较差。

利用黑盒方法实现完成几乎有序数组的排序:

import heapq

def sort_almost_sorted_array(arr, k):
    # 创建一个最小堆
    min_heap = []
    result = []

    # 将前 k+1 个元素加入堆中
    for i in range(min(k + 1, len(arr))):
        heapq.heappush(min_heap, arr[i])

    # 从 k+1 到数组结束
    for i in range(k + 1, len(arr)):
        # 弹出堆顶元素(最小元素)并加入结果
        result.append(heapq.heappop(min_heap))
        # 将当前元素加入堆
        heapq.heappush(min_heap, arr[i])

    # 将堆中剩余的元素全部弹出并加入结果
    while min_heap:
        result.append(heapq.heappop(min_heap))

    return result


# 示例数组
arr = [3, 2, 1, 6, 5, 4, 9, 8, 7]
k = 3
sorted_arr = sort_almost_sorted_array(arr, k)
print(sorted_arr)

?按课中方法做?

比较器

用于单个参数有多个变量的比较场景,对其中一个变量进行排序。

用于学生信息排序

sorted()实现

class Student:
    def __init__(self, name, id, age):
        self.name = name
        self.id = id
        self.age = age

if __name__ == '__main__':
    # 创建学生对象
    student1 = Student('A', 1, 25)
    student2 = Student('B', 3, 30)
    student3 = Student('C', 2, 18)

    # 将学生对象放入列表
    students = [student1, student2, student3]

    # 使用 sorted() 函数按学生ID排序
    sorted_students = sorted(students, key=lambda student: student.id)

    print('ID are sorted in ascending order.')

    # 输出排序结果
    for student in sorted_students:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

#################################################################################

    sorted_students = sorted(students, key=lambda student: student.age)

    print('Ages are sorted in ascending order.')

    for student in sorted_students:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

###################################################################################

    sorted_students = sorted(students, key=lambda student: student.age, reverse=True)

    print('Ages are sorted in descending order.')

    for student in sorted_students:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

比较器方法 functools.cmp_to_key()实现

from functools import cmp_to_key

class Student:
    def __init__(self, name, student_id, age):
        self.name = name
        self.id = student_id
        self.age = age

class IdAscendingSorter:
    @staticmethod
    def compare(a, b):
        return a.id - b.id

class IdDescendingSorter:
    @staticmethod
    def compare(a, b):
        return b.id - a.id

class AgeAscendingSorter:
    @staticmethod
    def compare(a, b):
        return a.age - b.age

class AgeDescendingSorter:
    @staticmethod
    def compare(a, b):
        return b.age - a.age

if __name__ == '__main__':
    # 创建学生对象
    student1 = Student('A', 1, 25)
    student2 = Student('B', 3, 30)
    student3 = Student('C', 2, 18)

    # 将学生对象放入列表
    students = [student1, student2, student3]

    # 使用 sorted() 函数按学生ID排序(升序)
    sorted_students = sorted(students, key=cmp_to_key(IdAscendingSorter.compare))

    print('ID are sorted in ascending order:')
    for student in sorted_students:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

    # 使用 sorted() 函数按学生ID排序(降序)
    sorted_students_desc = sorted(students, key=cmp_to_key(IdDescendingSorter.compare))

    print('\nID are sorted in descending order:')
    for student in sorted_students_desc:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

    # 使用 sorted() 函数按学生年龄排序(升序)
    sorted_students_name_asc = sorted(students, key=cmp_to_key(AgeAscendingSorter.compare))

    print('\nNames are sorted in ascending order:')
    for student in sorted_students_name_asc:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

    # 使用 sorted() 函数按学生年龄排序(降序)
    sorted_students_name_desc = sorted(students, key=cmp_to_key(AgeDescendingSorter.compare))

    print('\nNames are sorted in descending order:')
    for student in sorted_students_name_desc:
        print(f"Name: {student.name}, ID: {student.id}, Age: {student.age}")

桶排序

与之前的方法有区别,之前的方法都是基于比较的排序,该方法不基于比较。

计数排序

假设目标数组有N种值,生成一个N个值的额外数组,key代表目标数组中的值,value代表目标数组中该值出现的次数。遍历目标数组后,统计并体现再额外数组中。

如一组年龄数组年龄分布从0~150。0岁有5人,就在辅助数组0位置输入值5,1岁有8人,就在辅助数组1位置输入值8,依次类推,再按照0000011111111的对应人名进行排序。

该方法时间复杂度为O(N)

但当数组中值范围分布极大时,额外数字内存占用也会极大。所以需要分析数据状况来使用。

基数排序

设置0~9的容器,将目标数组按十进制最高位数补零,按顺序遍历数组,将个位数对应的值将目标数组内值放入容器,遍历完并放入容器后,再从0容器开始按顺序取出到目标数组中,之后按十位百位千位...一直到最高位重复步骤。

如有数组[12,22,3,74,100]

目标数组按十进制最高位数补零[012,026,003,074,100]

0容器:100

1容器:无

2容器:012,022

3容器:003

4容器:074

回传目标数组:[100,012,022,003,074]

......

[003,012,022,074,100]

实质上是从最高位开始到最低位优先级。

这种方法只能用于有限进制的数字,对没有进制或无限进制的对象无法排序。

这里可以推导出一个规律:按照基数排序方法,每位排序时,先进容器的会先出容器。

因为python中没有容器(桶)这一概念,因此,我们这里使用另一种方式来还原桶进出的过程。

过程描述:设置十进制位中每位(个十百千...)中0~9每个数在该位上出现的频率表count,设置一个辅助数组help用于存放十进制中每位的排序情况,这个辅助数组可以视作容器。从左往右依次遍历数组,先将个位出现的数用count统计频率,遍历统计完后将从左往右累加,这时count中显示的数c就是个位为x(0~9)的数最后应该处于第几个位置,换句话说,就是个位x的数最后出容器的位置为c-1位,所以这里可以倒序遍历数组来更方便的完成出桶步骤。具体流程是,从右往左遍历数组,看个位数字,将count中对应位的数-1,得到的数就是该数在个位出容器步骤的位置。后面的十位百位...也按这个步骤来。

举例

[013,021,011,052,062]

常规个位进出容器

0容器:无

1容器:021,011

2容器:052,062

3容器:013

出容器数组[021,011,052,062,013]

按过程描述方法

count[0,2,2,1,0...]

累加count[0,2,4,5,5,5...]代表个位每个数字最后出容器是第几个位置.。

从左往右遍历数组013个位是3,3在count对应值是5,也就在第5个位置,将count-1对应数组第4位,放到辅助数组变为[0位,1位,2位,3位,013]。

继续遍历062个位是2,2在count对应值是4,也就在第4个位置,将count-1对应数组第3位,放到辅助数组变为[0位,1位,2位,062,013]。

继续遍历052个位是2,2在count对应值是3,也就在第3个位置,将count-1对应数组第2位,放到辅助数组变为[0位,1位,052,062,013]。

....

可以看出最后结果与常规思路一致,只是换了流程顺序。

class RadixSort:
    def __init__(self, arr, L, R, digit):
        self.arr = arr  # 输入的数组
        self.L = L  # 数组的起始索引
        self.R = R  # 数组的结束索引
        self.digit = digit  # 总的位数

    def get_digit(self, number, d):
        """获取数字 number 在第 d 位上的值"""
        return (number // 10 ** (d - 1)) % 10  # 以0作为最低位

    def counting_sort(self, d):
        """基于第 d 位的计数排序"""
        # 计数器
        count = [0] * 10
        output = [0] * (self.R - self.L + 1)

        # 统计当前位数的出现频率
        for i in range(self.L, self.R + 1):
            digit_value = self.get_digit(self.arr[i], d)
            count[digit_value] += 1

        # 改变 count 用于位置计算
        for i in range(1, 10):
            count[i] += count[i - 1]

        # 构建输出数组
        for i in range(self.R, self.L - 1, -1):  # 倒序遍历以保持稳定性
            digit_value = self.get_digit(self.arr[i], d)
            output[count[digit_value] - 1] = self.arr[i]
            count[digit_value] -= 1

        # 将输出数组复制回原数组中
        for i in range(self.L, self.R + 1):
            self.arr[i] = output[i - self.L]

    def sort(self):
        # 对每一位进行排序
        for d in range(1, self.digit + 1):
            self.counting_sort(d)


if __name__ == '__main__':
    # 测试基数排序
    arr = [170, 45, 75, 90, 802, 24, 2, 66]
    L = 0
    R = len(arr) - 1
    max_digit = 3  # 以 802 为例,最多 3 位

    sorter = RadixSort(arr, L, R, max_digit)
    sorter.sort()

    print("Sorted array:", sorter.arr)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值