排序问题二(快排、堆、归并)

简介

三种常见的快速的排序方法介绍。

快速排序

取一个元素p(可以直接指定为第一个元素),使元素p归位置,左边的数都比p小,右边的数都比p大。通过递归完成排序。(最常用的排序,效率高)
时间复杂度:O(nlogn)
快排问题:
最坏情况:O(n2) 例如(9,8,7,6,5,4,3,2,1) 加入随机化初始p(随机数选取index,和最左边的数交换位置,人为重组一下数组,尽量避免最坏情况)
递归最大深度问题(默认为9999)

快速排序

import sys
sys.setrecursionlimit(1000000)

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)


def partition(data, left, right):
	p = data[left]
	while left < right:
		while left < right and data[right] >= p: # 找小于p的值
			right -= 1          # 往左走一步
		data[left] = data[right]  # 把右边的值放到左边位置上
		print(data)
		while left < right and data[left] <= p:  # 找大于p的值
			left += 1           # 往右走一步
		data[right] = data[left]  # 把左边的值放到右边位置上
		print(data)	
	data[left] = p              # 把p归位
	print(data)
	return left
堆排序

堆是一种特殊的完全二叉树结构。
大根堆:一个完全二叉树,满足任一节点都比其子节点都大。
小根堆:一个完全二叉树,满足任一节点都比其子节点都小。
在这里插入图片描述

堆的向下调整性质:假设根节点的左右子树都是堆,但自身不是堆,可以通过一次向下的调整来将其编程一个堆。

堆排序过程(大根堆)

(1) 堆建立。(从最后一个子树开始调整)
(2) 堆顶是最大元素。
(3) 去掉堆顶,将堆最后一个元素放到堆顶,此时通过一侧调整重新使堆有序。
(4) 堆顶元素为第二大元素。
(5) 重复步骤3,直到堆变空。

时间复杂度:O(nlogn), 实际表现略慢于堆排序。
python 内置堆排序模块(小根堆)
堆排序

def sift(li, low, high):
	# 堆最后一个元素位置high, low堆顶位置
	i = low  # 根节点位置
	j = 2 * i + 1 # 左节点
	temp = li[i] # 堆顶存起来
	while j <= high: # 只要j位置有节点就一直循环
		if j+1 <= hight and li[j+1] > li[j]: # 如果右节点存在并且比左节点大
			j = j + 1 # j指向右节点
		if li[j] > temp:
			li[i] = li[j]
			i = j  # i j 位置替换
			j = 2 * i + 1 # j网下继续调整
		else:
			li[i] = temp  # 把temp放到父节点的位置
			break
	else:
		li[i] = temp # 把temp放到叶子节点


def heap_sort(li):
	n = len(li)
	for i in range((n-1-1)//2, -1, -1): # 从最后一个节点的父节点的位置网前遍历
		# i 表示建堆的时候调整的部分的根的下标
		sift(li, i, n-1)
	# 建堆完成
	for i in range(n-1, -1, -1):
		# i 指当前堆的最后一个元素的位置
		li[0], li[i] = li[i], li[0]
		sift(li, 0, i-1) # i-1是新的high


# python内置模块使用
import heapq
import random

li = list(range(100))
random.shuffle(li)
print(li)

heapq.heapify(li) # 建小根堆
print(li)

n = len(li)
new_li = list()
for i in range(n):
	value = heapq.heappop(li) # 弹出最小的元素
	new_li.append(value)
print(li)
归并排序

归并: 假设一个列表两段分别有序,将其合成一个有序列表的操作。
归并排序过程:
(1) 分解: 将列表越分越小,直至分成一个元素。
(2) 终止条件: 一个元素是有序的。
(3) 合并: 将两个有序列表归并,列表越来越大。
关键点:递归
时间复杂度:O(nlogn) 空间复杂度: O(n)

在这里插入图片描述

# 归并
def merge(li, low, mid, high):
	i = low
	j = mid + 1
	temp_list = list()
	while i <= mid and j <= high: # 只要左右两边都有数就循环
		if li[i] < li[j]:
			temp_list.append(li[i])
			i += 1
		else:
			temp_list.append(li[j])
			j += 1
	# while执行完,肯定有一部分没有数了
	while i <= mid: # 如果左边还有数
		temp_list.append(li[i])
		i += 1
	while j <= high: # 如果右边还有数
		temp_list.append(li[j])
		j += 1
	
	li[low:high+1] = temp_list


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. 三种排序时间复杂度都是O(nlogn)
  2. 一般情况下,就运行时间而言: 快排 < 归并 < 堆
  3. 缺点:(1)快排:极端情况下 效率低。(2)归并:需要二外的内存开销。 (3)堆排序: 在三种排序中速度相对慢一些。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值