Python数据结构学习-3种快速排序

排序

  • 快速排序、堆排序和归并排序都是常用的排序算法,它们各有其优点。
  • 快速排序( Q u i c k S o r t QuickSort QuickSort):平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),对于大量数据的排序效率较高;它是原地排序算法,不需要额外的存储空间,节省内存资源;内存使用上有效率,它最差运行情况的复杂度为 O ( n 2 ) O(n^2) O(n2),会在数据接近有序的情况下发生。
  • 堆排序( H e a p S o r t HeapSort HeapSort):时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),性能稳定;原地排序算法,不需要额外的存储空间;堆排序不适合小数据集,因为它的常数因子相对较大。同时,它的缓存性能也不好,因为它需要大量的随机访问。
  • 归并排序( M e r g e S o r t MergeSort MergeSort):时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),性能稳定;归并排序在处理链表和在外存中的大数据集时很有用,因为归并排序只需要少量的随机访问内存;归并排序需要额外的存储空间,这可能在处理大量数据时成为问题。

快速排序

  • 取一个元素 p p p(第一个元素),使元素 p p p归位。
  • 列表被 p p p分成两部分,左边都比 p p p小,右边都比 p p p大。
  • 递归完成排序
  • 视频链接link
    在这里插入图片描述
    5 5 5将列表分成左右两个部分 [ 2 , 1 , 4 , 3 ] , [ 6 , 7 , 9 , 8 ] [2,1,4,3],[6,7,9,8] [2,1,4,3],[6,7,9,8],左边挑选第一位 2 2 2归位,于是 2 2 2将子列表再次分成左右两部分 [ 1 ] , [ 4 , 3 ] [1],[4,3] [1],[4,3], [ 1 ] [1] [1]一个元素达到有序, [ 4 , 3 ] [4,3] [4,3] 4 4 4归位,变成 [ 3 , 4 ] [3,4] [3,4], 4 4 4左边为一个元素、右边没有元素均达到有序。
//QuickSort
def quick_sort(data, left, right):
	if left < right:  # 至少两个元素
		mid = partition(data, left, right)
		quick_sort(data, left, mid-1)
		quick_sort(data, mid+1, right)
//partition function(归位操作)
def partition(list, left, right):
	template = list[left]
	while left < right:
		while left < right and list[right] >= template:  # 从右边找比template小的数
			right -= 1  # 往左走一步
		list[left] = list[right]  # 把右边的值放到左边空位上
		while left < right and list[left] <= template:  
			left += 1
		list[right] = list[left]
	list[left] = template
	return left

此外,快速排序可能会有最坏的情况出现,比如列表 [ 5 , 4 , 3 , 2 , 1 ] [5,4,3,2,1] [5,4,3,2,1],在每次归位之后总会出现一侧没有元素,这样会导致每次归位截断之后,下次处理的数据量只比上一次少了一个,所以不会出现 O ( n l o g n ) O(nlogn) O(nlogn)的复杂度,而是 O ( n 2 ) O(n^2) O(n2)。所以一旦出现这种情况,由于数据量过大,可能超过递归的最大默认深度1000,可以通过设置 s y s . s e t r e c u r s i o n l i m i t sys.setrecursionlimit sys.setrecursionlimit来更改。
 为了解决这种最坏情况的发生,采用随机化版本的快速排序。先前归位操作选取的值都是第一个值,现在我们在待排序表种随机选取一个数,然后与表头的第一个元素交换,剩下的步骤同原先一样。或者同样推荐在排序之前将原表 s h u f f l e shuffle shuffle一次。


堆排序

下面的图表示完全二叉树:

a
b
c
d
e
f
g
h
i
  • 将完全二叉树的节点值存放在 l i s t = [ a , b , c , d , e , f , g , h , i ] list=[a,b,c,d,e,f,g,h,i] list=[a,b,c,d,e,f,g,h,i]中。
  • l i s t list list的父节点和左孩子节点的索引关系: ( 0 − 1 ) , ( 2 − 5 ) , ( 1 − 3 ) , ( 3 − 7 ) (0-1),(2-5),(1-3),(3-7) (01),(25),(13),(37),满足 i = 2 i + 1 i=2i+1 i=2i+1的关系。
  • l i s t list list的父节点和右孩子节点的索引关系: ( 0 − 2 ) , ( 1 − 4 ) , ( 2 − 6 ) , ( 3 − 8 ) (0-2),(1-4),(2-6),(3-8) (02),(14),(26),(38),满足 i = 2 i + 2 i=2i+2 i=2i+2的关系。
  • 堆是一种特殊的完全二叉树,当任一节点都比其孩子节点大,则称为大根堆。当满足任一节点都比其孩子节点小,则称为小根堆。
    大根堆和小根堆
  • 堆的向下调整:当根节点的左右子树都是堆时,可以通过一次向下调整来将其变换成一个堆。

堆排序步骤如下:

构造堆
根节点被排序
去掉堆顶,将堆最后一个元素放到堆顶,向下调整到有序
堆顶元素为第二大元素
重复第三步,直到空堆

代码如下:

//向下调整函数的实现
def sift(list, low, high)
	'''
	list:列表
	low:堆的根节点位置
	high:堆的最后一个元素的位置
	'''
	i = low  # i最开始指向根节点
	j = 2*i+1  # j最开始是根节点的左孩子节点
	template = list[low]  # 把堆顶存起来
	while j<=high: # 只要j的位置有数
		if j+1<=high and list[j+1] > list[j]:  # 如果有右孩子且右孩子节点更大
			j = j+1  # j指向右孩子节点
		if list[j] > template:  # 假如大于根节点
			list[i] = list[j]  # 交换
			i = j  # 交换完成,更新一下i和j(往下看一层)
			j = 2*i+1
		else:   # template更大,把template放到i的位置上
			list[i] = template
			break
	else:
		list[i] = template  # 把template放到叶子节点上
//实现堆排序
def heap_sort(list):
	n = len(list)
	for i in range((n-2)//2, -1, -1):
		# i表示建堆的时候调整的部分的根的下标索引
		sift(list, 1, n-1)  # 堆建立完成
		for i in range(n-1, -1, -1)  # 该循环执行完毕,堆空了,堆顶去除完毕
		# i 指向当前堆的最后一个元素
			list[0], list[i] = list[i], list[0]
			sift(list, 0, i-1)  # i-1是新的high

实际上在Python内部有内置模块“heapq”来实现堆排序:

import heapq
import random

liheapq = []
li = list(range(100))
random.shuffle(li)
heapq.heapify(li)  # 建小根堆
for i in range(len(li)):
	liheapq.append(heapq.heappop(li))  # 每次弹出最小元素
  • (堆排序的应用)topk问题:现有 n n n个数,设计算法找到前 k k k大的数。 ( k < n ) (k<n) (k<n)
     方法一:排序后切片的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)(所有都排一遍)。方法二:简单三种排序的复杂度为 O ( k n ) O(kn) O(kn)(只排 k k k个)。方法三:堆排序的复杂度为 O ( n l o g k ) O(nlogk) O(nlogk)

实现思路:

  • 取列表前 k k k个元素建立一个小根堆。堆顶就是目前取出的这 k k k个数里面的第 k k k大的数。
  • 从剩下的列表里向后依次遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;若大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整,将其再次调整为小根堆。
  • 最后遍历完成,完全二叉树上的 k k k个节点元素就是列表中前 k k k大的数。

代码实现:

import random

# 建立小根堆
def sift(list, low, high)
	i = low  
	j = 2*i+1 
	template = list[low] 
	while j<=high:
		if j+1<=high and list[j+1] < list[j]: 
			j = j+1  
		if list[j] < template:
			list[i] = list[j]  
			i = j  
			j = 2*i+1
		else:  
			list[i] = template
			break
	else:
		list[i] = template 

def topk(list, k):
	heap = list[0:k]
	for i inrange((k-2)//2, -1, -1):  # 建堆
		sift(heap, i, k-1)
	for i in range(k, len(list)-1):  # 遍历列表中剩下的所有元素
		if list[i] > heap[0];
			heap[0] = list[i]
			sift(heap, 0, k-1)
	for i in range(k-1, -1, -1)  # 该循环执行完毕,堆空了,堆顶去除完毕
		# i 指向当前堆的最后一个元素
			heap[0], heap[i] = heap[i], heap[0]
			sift(heap, 0, i-1)  # i-1是新的high
	return heap

list = list(range(1000))
random.shuffle(list)
print(topk(list, 10))

归并排序

  • 假设现在的列表分两段有序,将其合成为一个有序列表,这种操作称为一次归并(非原地排序,需要拿一个临时列表将每次弹出的最值存储起来)。
# 归并
def merge(li, low, mid, high):
	i = low
	j = mid+1
	ltmp = []
	while i <= mid and j<= high  # 只要左右两边都有数
		if li[i] < li[j]:
			ltmp.append(li[i])
			i +=1
		else:
			ltmp.append(li[j])
			j +=1
	# while执行完,肯定有一部分没数了
	# if i <= mid and j=high:
		# ltmp.extend(li[i:mid])
	# elif i = mid and j<=high:
		# ltmp.extend(li[j:len(li)-1])
	while i <= mid:
		ltmp.append(li[i])
		i += 1
	while j <= high:
		ltmp.append(li[j])
		j += 1
	li[low:high+1] = ltmp  # 把列表再写回去	

li = [2,4,5,7,1,3,6,8]	
merge(li, 0, 3, 7)
print(li)

一般情况的排序实现:
在这里插入图片描述
归并排序代码如下:

def merge_sort(li, low, high):
	if low < high:  # 至少有两个元素,递归
		mid = (low+high)//2
		merge_sort(li, low, mid)
		merge_sort(li, mid+1, high)
		merge(li, low, mid, high)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进阶的路遥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值