Python3实现快速排序、归并排序、堆排序

# -*- coding: utf-8 -*-
# @Time : 2019-03-26 16:46
# @Author : Jayce Wong
# @ProjectName : leetcode
# @FileName : sorting.py
# @Blog : https://blog.51cto.com/jayce1111
# @Github : https://github.com/SysuJayce

import random

def quick_sort(data):
"""
对于每一轮排序,先随机选取范围内的一个下标,该下标对应的值称为主元,然后将小于主元的值挪到主元
的左边,大于主元的值挪到主元的右边,即确保主元在正确的位置。然后下一轮只需要对主元左边的数组和
右边的数组分别排序即可,数组大小减为原来的一半。
每轮排序确定一个主元,该轮排序完成后待排序的两个数组的长度变为原来的一半,可以看做是一个树,
根节点是原数组,每一轮会分裂一次,每个节点被分裂成2个子节点,直到该节点长度为1,不需再进行排序
为止,这样就一共需要logN轮,每轮每部需要比较N次,即时间复杂度nlogn
快排是不稳定排序(相同大小的元素排序后不一定按照原顺序)
:param data: 待排序的数组
"""
def sort(start, end):
pivot = partition(start, end)
if pivot > start:
sort(start, pivot - 1)
if pivot < end:
sort(pivot + 1, end)

def partition(start, end):
idx = random.randint(start, end) # 随机选择一个idx
# 先将idx下标所在的值(主元)和末端的值交换
data[idx], data[end] = data[end], data[idx]
position = start # position是下一个小于主元的值应在的位置
for i in range(start, end):
# 如果一个值小于主元,则检查它是否在正确的位置,不正确的话则进行调整,使得它落到应在
# 的位置
if data[i] < data[end]:
if i != position:
data[position], data[i] = data[i], data[position]
position += 1
# 还原主元应在的位置
data[position], data[end] = data[end], data[position]
return position

if not data:
return
sort(0, len(data) - 1)

def merge_sort(data):
"""
先将数组不断进行对半分裂直到不可再分(最长子数组长度为1),然后进行归并,归并的时候每次从两个
数组中选择最小的元素。
归并排序是稳定算法,时间复杂度为nlogn
:param data: 待排序的数组
"""
def sort(start, end):
if start < end:
mid = (start + end) >> 1
sort(start, mid)
sort(mid + 1, end)
merge(start, mid, end)

def merge(start, mid, end):
p1, p2 = start, mid + 1
while p1 <= mid and p2 <= end:
if data[p1] < data[p2]:
temp.append(data[p1])
p1 += 1
else:
temp.append(data[p2])
p2 += 1
if p1 <= mid:
temp.extend(data[p1: mid + 1])
if p2 <= end:
temp.extend(data[p2: end + 1])

# 【需要将辅助数组中的数还原到data中】
while temp:
data[start] = temp.pop(0)
start += 1

if not data:
return
temp = [] # 建立全局辅助数组,避免递归过程不断创建
sort(0, len(data) - 1)

def heap_sort(data):
"""
堆排序是不稳定的一种排序算法,其时间复杂度是O(nlogn)

当需要升序排序的时候,构建最大堆,反之构建最小堆。

最大堆的定义是对于每一个非叶子节点,它的值大于等于它的子节点。最小堆的定义类似。

以升序排序为例,堆排首先是从最后一个非叶子节点开始往左往上构建最大堆,目的是减少重复性工作,
因为如果自顶向下构建最大堆的话难度大,而自底向上构建最大堆的话在对第x层的某个节点构建最大堆的
时候可以确保第x+1层及以下的树已满足最大堆的定义
:param data: 待排序的元素
"""
def adjustHeap(cur_idx, length):
"""

:param cur_idx: 待调整的子树的根节点位置
:param length: 树中剩余的元素个数。随着排序的进行,堆顶元素(根节点)会逐个被删除,
导致树中元素不断减少
"""
temp = data[cur_idx] # 先记录当前位置的元素
# 由于最大堆的定义是父节点元素大于等于其子节点元素,因此将当前位置的元素和它的子节点元素
# 进行大小比较,
k = 2 * cur_idx + 1 # 左子节点的位置
while k < length: # 只在树内遍历
# 如果右子节点的元素更大,则将k定位到右子节点,
# 因为父节点的值需要不小于最大子节点的值
if k + 1 < length and data[k] < data[k + 1]:
k += 1

# 如果子节点的元素大于根节点,则将子节点的值赋给父节点
# 如果这里不使用赋值而是交换的话,会有多余的操作(如果这次调整需要不止一次交换的话)
if data[k] > temp:
data[cur_idx] = data[k]
# 这时cur_idx保存的是temp可能要去到的位置,但是由于还有剩余的孙子节点没有比较
# 所以这里先不用交换,而是记录下temp可能要去到的位置,最后再将其放到正确的位置
cur_idx = k
# 如果上层的子节点已经小于父节点,那么孙子节点一定不会大于父节点,因为我们已经构建了
# 一个最大堆(在初始化构建最大堆时,我们是从最后一个非子节点开始自底向上构建的,所以
# 在处理上层节点的时候其下层节点已经是符合最大堆定义的了,因此也符合这里的break条件)
else:
break
# 检查剩余的子节点
k = 2 * k + 1

data[cur_idx] = temp # 将temp元素还原到正确的位置

if not data:
return

""" 初始化构建最大堆 """
# 从最后一个非叶子节点开始,往左遍历,当第x层遍历完之后从第x-1层的最右边开始往左遍历
# 每次调整该节点使得满足最大堆的要求
for i in range((len(data) >> 1) - 1, -1, -1):
adjustHeap(i, len(data))

# 从构建好的最大堆取出堆顶元素(也就是最大值),然后放到数组末尾(即将这个节点删除)
# 删除之后需要重新调整堆的结构以满足最大堆的定义。
# 由于上一步已经构建了一个最大堆,因此这里只需要对新的根节点的元素进行调整即可
for j in range(len(data) - 1, 0, -1):
data[j], data[0] = data[0], data[j]
adjustHeap(0, j)

def main():
data = []
for _ in range(10):
data.append(random.randint(0, 9))

print(data)
quick_sort(data)
merge_sort(data)
heap_sort(data)
print(data)

if __name__ == ‘__main__‘:
main()

原文地址:https://blog.51cto.com/jayce1111/2380394

时间: 2024-11-15 15:15:03

Python3实现快速排序、归并排序、堆排序的相关文章

快速排序、堆排序、归并排序比较

快速排序是二叉查找树(二叉查找树)的一个空间最优化版本.不是循序地把数据项插入到一个明确的树中,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中.这两个算法完全地产生相同的比较次数,但是顺序不同.对于排序算法的稳定性指标,原地分区版本的快速排序算法是不稳定的.其他变种是可以通过牺牲性能和空间来维护稳定性的. 快速排序的最直接竞争者是堆排序(Heapsort).堆排序通常比快速排序稍微慢,但是最坏情况的运行时间总是O(n log n).快速排序是经常比较快,除了introsort变化版本外

几种排序算法的C++实现——快速排序、堆排序、基数排序

排序算法是非常常见的面试笔试问题,考查的一个人的基本功,本文将一些排序做了C++的实现,就当是做个学习总结吧. 1.快速排序 快速排序的中心是填坑法,取一个数(这里选取第一个数)作为基准数temp,从队尾开始寻找第一个比基准数小的数a[j],交换a[j]和temp,然后队首开始查找第一个比temp大的数a[i],交换之,遍历的结果是当i>=j时,temp左边的数都小于temp,后边的数都大于temp,这个有点像归并排序.最后利用递归调用完成排序,代码如下: 1 void QuickSort(in

十大基础实用算法之快速排序和堆排序

快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要Ο(n log n)次比较.在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见.事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来. 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists). 算法步骤: 1 从数列中挑出一个元素,称为 "基准"(pi

单链表的排序 快速排序 归并排序 quicksort mergesort

原理都很简单,关键是某些边界能否正确写对: #include<iostream> #include<stdio.h> using namespace std; class Node { public: int val; Node* next; Node(int val = 0):val(val),next(NULL){ } }; Node* quicksort(Node* head, Node* tail) { Node *res1 = NULL, *res2 = NULL; No

快速排序 归并排序的非递归版本 备忘

首先,归并排序,分治,递归解决小的范围,再合并两个有序的小范围数组,便得到整个有序的数组. 这是很适合用递归来写的,至于非递归,便是从小到大,各个击破,从而使得整个数组有序.代码如下: void merge(vector<int> &A, int left, int mid, int right) { int i=left,j=mid+1; vector<int> tmp(right-left+1,0); int k=0; while(i<=mid&&

David MacKay:用信息论解释 &#39;快速排序&#39;、&#39;堆排序&#39; 本质与差异

这篇文章是David MacKay利用信息论,来对快排.堆排的本质差异导致的性能差异进行的比较. 信息论是非常强大的,它并不只是一个用来分析理论最优决策的工具. 从信息论的角度来分析算法效率是一件很有趣的事,它给我们分析排序算法带来了一种新的思路. 运用了信息论的概念,我们很容易理解为什么快排的速度那么快,以及它的缺陷在哪里. 由于个人能力不足,对于本文的理解可能还是有点偏差. 而且因为翻译的困难,这篇译文有很多地方并没有翻译出来,还是使用了原文的句子. 所以建议大家还是阅读原文Heapsort

快速排序、堆排序和归并排序的实现

1.快速排序 通过选择轴值,一次划分都能确定该轴值得位置,其时间复杂度最好的情况(每次划分都恰好将区间平分为等长的两半)下为Ο(nlgn),最差情况(每次划分将区间分成0与n-1)为O(n^2).其空间复杂度考虑递归的栈深为O(lgn). 1 /************************************************************************* 2 **File Name :quicksort.c 3 **Author : 4 **Contact :

常用的较优排序之快速排序,堆排序,归并排序

1.快速排序 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序.可以用递归和非递归的方法分别实现. int _QuickSort(int* a,int left,int right,int key) { while (left < right) { while (left < right&&a[left] <=a[key]) { left++; //找出比a[key]大的下标 }

三种排序算法(归并排序、快速排序,堆排序)

归并排序:建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为二路归并. 归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取. 工作原理: 1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后