失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > python实现堆排序用类的方法_GitHub - lil-q/sorting-algorithm-python: 十种排序

python实现堆排序用类的方法_GitHub - lil-q/sorting-algorithm-python: 十种排序

时间:2023-06-20 16:20:37

相关推荐

python实现堆排序用类的方法_GitHub - lil-q/sorting-algorithm-python: 十种排序

排序

分类

评价排序算法的几个指标:

时间复杂度:包括平均时间复杂度、最坏时间复杂度和最好时间复杂度。一般而言,好的性能是$$O(nlog_2n)$$,坏的性能是$$O(n^2)$$。对于一个排序理想的性能是O(n),但平均而言不可能达到。基于比较的排序算法对大多数输入而言至少需要$$O(nlog_2n)$$。

空间复杂度:内存使用量

稳定性: 稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。

依据排序的方法:插入、交换、选择、合并等等。

本文介绍了以下几种排序,推荐

排序方法

平均时间复杂度

最坏时间复杂度

最好时间复杂度

空间复杂度

稳定性

冒泡排序

$$O(n^2)$$

$$O(n^2)$$

O(n)

O(1)

稳定

选择排序

$$O(n^2)$$

$$O(n^2)$$

$$O(n^2)$$

O(1)

不稳定

插入排序

$$O(n^2)$$

$$O(n^2)$$

O(n)

O(1)

稳定

堆排序

$$O(nlog_2n)$$

$$O(nlog_2n)$$

$$O(nlog_2n)$$

O(1)

不稳定

归并排序

$$O(nlog_2n)$$

$$O(nlog_2n)$$

$$O(nlog_2n)$$

O(n)

稳定

快速排序

$$O(nlog_2n)$$

$$O(n^2)$$

$$O(nlog_2n)$$

$$O(log_2n)$$

不稳定

希尔排序

$$O(n^{1.3})$$

$$O(n^2)$$

O(n)

O(1)

不稳定

计数排序

O(n+m)

O(n+m)

O(n+m)

O(n+m)

稳定

基数排序

O(n*k)

O(n*k)

O(n*k)

O(n+k)

稳定

桶排序

O(n+k)

$$O(n^2)$$

O(n)

O(n+k)

稳定

均按从小到大排列

k代表数值中的"数字"个数

n代表数据规模

m代表数据的最大值减最小值

冒泡排序

数据分区:(无序区,有序区)。

从无序区透过交换找出最大元素放到有序区前端。

流程

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码

def bubble_sorted(nums):

size = len(nums)

for i in range(size-1):

for j in range(size-1-i):

if nums[j] > nums[j+1]:

nums[j],nums[j+1]=nums[j+1],nums[j]

return nums

但是,该算法的最优时间复杂度并不是O(n),而是$$O(n^2)$$。需改写才能实现最优理想状态:

def bubble_sorted(nums):

size = len(nums)

for i in range(size-1):

didSwap=False

for j in range(size-1-i):

if nums[j] > nums[j+1]:

nums[j],nums[j+1]=nums[j+1],nums[j]

didSwap=True

if didSwap==False:

return

return nums

选择排序

数据分区:(有序区,无序区)。

在无序区里找一个最小的元素跟在有序区的后面。对数组:比较得多,换得少。

流程

初始状态:无序区为R[1..n],有序区为空;

第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;

n-1趟结束,数组有序化了。

代码

def selection_sorted(nums):

size = len(nums)

for i in range(size-1):

min_index = i

for j in range(i, size):

if nums[j] < nums[min_index]:

min_index = j

nums[i], nums[min_index] = nums[min_index], nums[i]

return nums

插入排序

数据分区:(有序区,无序区)。

把无序区的第一个元素插入到有序区的合适的位置。对数组:比较得少,换得多。

流程

从第一个元素开始,该元素可以认为已经被排序;

取出下一个元素,在已经排序的元素序列中从后向前扫描;

如果该元素(已排序)大于新元素,将该元素移到下一位置;

重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

将新元素插入到该位置后;

重复步骤2~5。

代码

def insertion_sorted(nums):

size = len(nums)

for i in range(1,size):

cur_num=nums[i]

pre_index=i-1

while nums[pre_index]>cur_num and pre_index>=0:

nums[pre_index+1]=nums[pre_index]

pre_index-=1

nums[pre_index+1]=cur_num

return nums

堆排序

数据分区:(最大堆,有序区)。

从堆顶把根卸出来放在有序区之前,再恢复堆。 关于堆

流程

最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点

创建最大堆(Build Max Heap):将堆中的所有数据重新排序

堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

代码

def heap_sorted(nums):

# 调整最大堆

def adjust_heap(idx, max_len):

left = 2 * idx + 1

right = 2 * idx + 2

max_loc = idx

if left < max_len and nums[max_loc] < nums[left]:

max_loc = left

if right < max_len and nums[max_loc] < nums[right]:

max_loc = right

if max_loc != idx:

nums[idx], nums[max_loc] = nums[max_loc], nums[idx]

adjust_heap(max_loc, max_len)

# 建堆

n = len(nums)

for i in range(n // 2 - 1, -1, -1):

adjust_heap(i, n)

# 排序

for i in range(1, n):

nums[0], nums[-i] = nums[-i], nums[0]

adjust_heap(0, n - i)

return nums

归并排序

把数据分为两段,从两段中逐个选最小的元素移入新数据段的末尾。

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

流程

把长度为n的输入序列分成两个长度为n/2的子序列;

对这两个子序列分别采用归并排序;

将两个排序好的子序列合并成一个最终的排序序列。

代码

递归法(Top-down):

def merge(left, right):

result = []

while left and right:

if left[0] <= right[0]:

result.append(left.pop(0))

else:

result.append(right.pop(0))

if left:

result += left

if right:

result += right

return result

def merge_sort(L):

if len(L) <= 1:

return L

mid = len(L) // 2

left = L[:mid]

right = L[mid:]

left = merge_sort(left)

right = merge_sort(right)

return merge(left, right)

迭代法(Bottom-up)

重写merge(),实现O(1)

def merge_iter(nums,l1,l2,r2):

r1=l2-1

while r1>=l1 and r2>=l2:

if nums[r1]>nums[r2]:

tmp=nums[r2] # 暂存较小值

nums[r2]=nums[r1]

tmp_r1=r1-1

# 将前半数组的大于tmp的值后移一个单位

while nums[tmp_r1]>tmp and tmp_r1>=l1:

nums[tmp_r1+1]=nums[tmp_r1]

tmp_r1-=1

nums[tmp_r1+1]=tmp

r2-=1

这里参考了leetcode 88.合并两个有序数组,采用双指针从后往前合并两个有序数组(其实也就是一个数组切片的前一半和后一半),实现了空间复杂度O(1)。

方法一:使用生成器

# 生成器产生2的幂

def powerOfTwo(max):

x=1

while x<=max:

yield x

x*=2

# 方法一:用生成器产生1,2,4,8...

def merge_sorted_iter(nums):

sortedList=[]

n=len(nums)

for i in powerOfTwo(n):

for j in range(0,n,i*2):

merge_iter(nums,j,min(j+i,n-1),min(j+2*i-1,n-1))

return nums

方法二:使用位运算

# 方法二:用位操作产生1,2,4,8...

def msi(nums):

length = len(nums)

step = 1

# 步长为1,2,4,8,...,一直合并下去

while step <= length:

offset = step << 1

for index in range(0, length, offset):

merge_iter(nums, index, min(index+step, length-1), min(index+offset-1, length-1))

step = offset

快速排序

数据分区:(小数,基准元素,大数)。

在区间中随机挑选一个元素作基准,将小于基准的元素放在基准之前,大于基准的元素放在基准之后,再分别对小数区与大数区进行排序。

流程

从数列中挑出一个元素,称为 “基准”(pivot);

重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

代码

import random

def quick_sorted(nums):

if len(nums) <= 1:

return nums

left, right, mid = [], [], []

pivot = random.choice(nums)

for num in nums:

if num == pivot:

mid.append(num)

elif num < pivot:

left.append(num)

else:

right.append(num)

return quick_sorted(left) + mid + quick_sorted(right)

需要$${\Omega (n)}$$的额外存储空间,也就跟归并排序一样不好。额外需要的存储空间,在实际实现时,也会极度影响速度和缓存的性能 。下面是原地排序的代码, 平均可以达到$$O(\log n)$$的空间复杂度。

def quick_sorted_inp(nums, first, last):

if first >= last:

return

mid_value = nums[first]

low = first

high = last

while low < high:

while low < high and nums[high] >= mid_value:

high -= 1

nums[low] = nums[high]

while low < high and nums[low] < mid_value:

low += 1

nums[high] = nums[low]

nums[low] = mid_value

quick_sorted_inp(nums, first, low-1)

quick_sorted_inp(nums, low+1, last)

希尔排序

也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率

但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

流程

选择一个增量序列$$t_1$$,$$t_2$$,…,$$t_k$$,其中$$t_i$$>$$t_j$$,$$t_k$$=1;

按增量序列个数k,对序列进行k 趟排序;

每趟排序,根据对应的增量$$t_i$$,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

代码

def shell_sorted(nums):

n = len(nums)

# 初始步長,理论上只要最终步长为1任何步长序列都可以工作

gap = n // 2

while gap > 0:

for i in range(gap, n):

# 每个步長進行插入排序

temp = nums[i]

j = i

# 插入排序

while j >= gap and nums[j - gap] > temp:

nums[j] = nums[j - gap]

j -= gap

nums[j] = temp

# 得到新的步長

gap = gap // 2

return nums

实际上使用1,2,4,8...的增量序列有时会在gap=1时浪费很多时间,[Mark Allen Weiss]指出,最好的增量序列是 Sedgewick提出的 (1, 5, 19, 41, 109,...),该序列的项来自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 这两个算式。使用 Sedgewick增量 的希尔排序的完整C语言程序

计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

流程

找出待排序的数组中最大和最小的元素;

统计数组中每个值为i的元素出现的次数,存入数组C的第i项;

对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);

反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

代码

def counting_sorted(nums):

# 计数排序针对非负整数,初始化最大值为-1,也可以设为nums[0]

maxValue=-1

for num in nums:

if num>maxValue:

maxValue=num

bucket = [0]*(maxValue+1)

for num in nums:

bucket[num]+=1

index=0

for i in range(len(bucket)):

while bucket[i]>0:

nums[index] = i

index+=1

bucket[i]-=1

return nums

基数排序

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

流程

取得数组中的最大数,并取得位数;

从最低位开始取每个位组成radix数组;

对radix进行计数排序(利用计数排序适用于小范围数的特点);

代码

def radix_sorted(nums):

# 基数排序针对非负整数,初始化最大值为-1,也可以设为nums[0]

maxValue=-1

for num in nums:

if num>maxValue:

maxValue=num

# 求最高位数n

n=len(str(maxValue))

# 进行n次排序

for k in range(n):

s=[[] for i in range(10)]

for i in nums:

s[i//(10**k)%10].append(i)

nums=[a for b in s for a in b]

return nums

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

流程

设置一个定量的数组当作空桶;

遍历输入数据,并且把数据一个一个放到对应的桶里去;

对每个不是空的桶进行排序;

从不是空的桶里把排好序的数据拼接起来。

代码

def bucket_sorted(nums):

if not nums:return nums

maxValue=nums[0]

minValue=nums[0]

for num in nums:

if num>maxValue:

maxValue=num

elif num

minValue=num

# 将数据映射到桶中

bucketSize=100 # 设定桶的大小

bucketCount=(maxValue-minValue)//bucketSize+1 # 计算桶的数量

buckets=[[] for _ in range(bucketCount)]

for num in nums:

buckets[(num-minValue)//bucketSize].append(num)

# 对桶内排序,这里使用插入排序,也可以递归桶排序。

res=[]

for k in buckets:

res=res+insertion_sorted(k)

return res

# 插入排序

def insertion_sorted(nums):

size = len(nums)

for i in range(1,size):

cur_num=nums[i]

pre_index=i-1

while nums[pre_index]>cur_num and pre_index>=0:

nums[pre_index+1]=nums[pre_index]

pre_index-=1

nums[pre_index+1]=cur_num

return nums

总结

桶排序、基数排序和计数排序都属于非比较类排序,其中计数排序和基数排序都用到了桶排序的思想。计数排序一共分了0,1,2...maxValue一共maxValue+1个桶,每个桶表示一个数;而基数排序则分了十个桶,从第一位开始递归桶排序。冒泡排序,选择排序,插入排序都是比较两个数然后交换。堆排序,归并排序,快速排序则是运用了分治的思想。

参考

python实现堆排序用类的方法_GitHub - lil-q/sorting-algorithm-python: 十种排序算法的python实现及复杂度分析...

如果觉得《python实现堆排序用类的方法_GitHub - lil-q/sorting-algorithm-python: 十种排序》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。