基于python算法入门(四)

学习目标:

1.快速排序
2.堆排序
3.归并排序


学习前奏:

1.快速排序

快速排序的思路:取一个元素p(第一个元素),使元素p归位,列表被p分为两部分,左边都比p小,右边都比p大,递归完成排序。

2.堆排序

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

3.归并排序

利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。


一:快速排序

快速排序-框架

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(li, left, right):
	tmp = li[left]
	while left<right:
		while li[right] >= tmp and left<right:  # 从右边找比tmp小的数
			right -= 1   # 往左走一步
		li[left]=li[right] # 把右边的值写到左边空位上
		while li[left] <= tmp and left < right:
			left +=1
		li[right]=li[left]   # 把左边的值写到右边空位上
		print(li)		
	li[left] = tmp  # 把tmp归位
	return left

def quick_sort(data,left,right):
	if left < right: #至少2个元素
		mid = partition(data,left,right)
		quick_sort(data,left,mid-1)
		quick_sort(data,mid+1,right)
		
	
li = [5,7,4,6,3,1,2,9,8]
quick_sort(li,0,len(li)-1)
print(li)

时间复杂度:O(nlogn) 每次partition的是n,总共logn层,所以时间复杂度为O(nlogn),你可以去用它和之前的lowb三人组做时间比较,效果就很明显了,同时也注意用cal_time时不要套在递归上,记得取个别名比较。不过快速排序中有递归的问题,也要修改递归深度,也会消耗一定的系统资源,而快速排序有最坏的情况,也是我之前说过排序都有一般时间复杂度,还有最坏情况。比如当li=[9,8,7,6,5,4,3,2,1],当li为倒序有序的列表时,它每次只少一个数,所以最坏情况为O(n2),不过是极为少数,因为你也可以解决最坏情况,比如打乱列表,shuffle,或者在随机化取一个数为mid。

二:堆排序

堆排序前传-树

树是一种数据结构 比如:目录结构(类如linux)
树是一种可以递归定义的数据结构
树由n个节点组成
如果n=0,那这是一棵空树;
如果n>0,那存在1个节点作为树的根节点,其
他节点可以分为m个集合,每个集合本身又是
棵树
基于python算法入门(四)_第1张图片
树的根节点:A 最上面开始
叶子节点:B,C,H,J,P,Q,L,M,N 最后没有分叉的
树的深度(高度):4 就是说最深有几层,显然4层
树的度:6 就是看最多分了几叉
孩子节点/父节点 :j是父节点,p和q是它的孩子节点,很明显。

堆排序前传-二叉树

二叉树:度不超过2的树
每个节点最多有2个孩子节点
两个孩子节点被区分为左孩子节点和右孩子节点

基于python算法入门(四)_第2张图片
满二叉树:一个二叉树,如果每一个层
的结点数都达到最大值,则这个二叉树
就是满二叉树。
完全二叉树:叶节点只能出现在最下层
和次下层,并且最下面一层的结点都集
中在该层最左边的若干位置的二叉树。
基于python算法入门(四)_第3张图片

堆排序前传-二叉树的存储方式

1.链式存储方式(后面在做讲解)
2.顺序存储方式 就是用列表来存

基于python算法入门(四)_第4张图片
1.父节点和左孩子节点的编号下标有什么关系?
0-1 1-3 2-5 3-7 4-9
i→2i+1
2.父节点和右孩子节点的编号下标有什么关系?
0-2 1-4 2-6 3-8 4-10
i→2i+2
3.通过孩子节点怎么找到父节点?
i 一> (i-1)//2

堆排序-什么是堆

堆:一种特殊的完全二叉树结构
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点少
基于python算法入门(四)_第5张图片

堆排序-堆的向下调整性质

假设根节点的左右子树都是堆,但根节点不满足堆的性质
可以通过一次向下的调整来将其变成一个堆。基于python算法入门(四)_第6张图片

基于python算法入门(四)_第7张图片

堆排序过程

1.建立堆。
2.得到堆顶元素,为最大元素
3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过-次调整重新使堆有序。
4.堆顶元素为第二大元素。
5.重复步骤3,直到堆变空。

向下调整函数的实现

def sift(li, low, high):
	# li 列表 low 堆的根节点  high最后一个元素的位置
	i = low   # i最开始指向根节点
	j = 2*i+1  # j开始为左孩子
	tmp = li[low]  # 把堆顶存起来
	while j<=high:  # 只要j位置有数
		if j+1 <= high and li[j+1] > li[j]:  # 如果右孩子有并且比较大
			j = j+1   # j指向右孩子
		if li[j] > tmp:
			li[i] = li[j]
			i = j
			j = 2*i+1
		else: # tmp更大,把tmp放到i的位置上
			li[i]=tmp   # 把tmp放到某一级领导位置上
			break
	else:
		li[i]=tmp  # 把tmp放在叶子节点

堆排序实现

def heap_sort(li):
	n = len(li)
	for i in range((n-2)//2, -1,-1):
		# i表示建堆的时候调整的下标
		sift(li,i,n-1) 
	print(li)
	# 堆排序完成
	# 挨个出数
	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
	

li = [i for i in range(100)]
import random
random.shuffle(li)
print(li)

heap_sort(li)

sift函数时间复杂度为O(logn),出现折半过程
堆排序的时间复杂度:O(nlogn)

堆的内置模块

import heapq  # q -->> queue表示优先队列   实现的是小根堆   先进先出是队列  出现小的先出或者大的先出为优先队列
import random

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

heapq.heapify(li)  # 建堆
n=len(li)
for i in range(n):
	print(heapq.heappop(li),end=',')

堆排序–topk问题

现在有n个数,设计算法得到前k大的数。(k

解决思路:
1.排序后切片 O(nlogn)
2.排序lowb三人组 O(kn)
3.堆排序思路 O(nlogk)
取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数。依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元
素,如果大干堆顶,则将堆顶更换为该元素、并目对堆进行一- 次调整;遍历列表所有元素后,倒序弹出堆顶。

topk实现

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

def topk(li,k):
	heap = li[0:k]
	for i in range((k-2))//2,-1,-1):
		sift(heap, i, k-1)
	# 1.建堆
	for i in range(k,len(li)-1):
		if li[i] > heap[0]:
			heap[0] = li[i]
			sift(heap,0,k-1)
	#2.遍历
	for i in range(k-1,-1,-1):
		heap[0],heap[i] = heap[i], heap[0]
		sift(heap,0,i-1)
	# 出数
	return heap

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

三:归并排序

归并排序–归并

假设现在的列表分两段有序,如何将其合成为一个有序列表
基于python算法入门(四)_第8张图片
这种操作就叫做归并

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执行完,肯定有一部分没数了
	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)

归并排序–使用归并

分解:将列表越分越小,直至分成一个元素。
终止条件:一个元素是有序的。
合并:将两个有序列表归并,列表越来越大。
基于python算法入门(四)_第9张图片

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

li=list(range(1000))
import random
random.shuffle(li)
print(li)
merge_sort(li,0,len(li)-1)
print(li)

时间复杂度:O(nlogn)
空间复杂度:O(n) 不属于原地排序,归并必须需要一定的空间 python中sort方法内部实现基于归并排序和插入排序的结合

NB三人组总结:

三种排序算法的时间复杂度都是O(nlogn)
一般情况下,就运行时间而言:
快速排序<归并排序<堆排序

三种排序算法的缺点:
快速排序:极端情况下排序效率低
归并排序:需要额外的内存开销
堆排序:在快的排序算法中相对较慢
基于python算法入门(四)_第10张图片

你可能感兴趣的:(算法,数据结构,python,排序算法,堆排序)