本文主要是用python实现几种常用的比较排序算法,包括冒泡排序、选择排序、插入排序(二分插入排序和希尔排序)、堆排序、归并排序和快速排序,主要参考的博客:常用排序算法总结(一),参考博客已经总结的非常nice了,里面有排序过程的动态效果图和细致的分析,只不过是使用C++实现的,本文则是python实现的简化版,排序均指从小到大排序,想看细致分析的可以参考C++版本的。本文错误之处还望不吝指正。常见的比较排序算法性能分析表:
1:冒泡排序(Bubble Sort)
基本思想:从左到右,比较相邻的两个元素,选择最大的元素逐步移至最右边,依次循环,直到选择出较大的n-1个数为止。
# __author__ = 'czx'
# coding=utf-8
def swap(A,i,j): # 元素A[i]和元素A[j]交换
tmp =A[i]
A[i]=A[j]
A[j] =tmp
def bubblesort(A): # 冒泡排序
n = len(A)
for j in range(n-1): # 只需循环n-1次,每次都是选择最大的数后移
for i in range(n-1-j): # 这里只需要对n-1-j个数据进行排序,因为有j个较大值已经排序好了
if A[i] > A[i+1]: # 相邻元素比较,前者大则后移
swap(A,i,i+1)
def cocktailsort(A): # 鸡尾酒排序:从左到右选最大,从右到左选最小
n = len(A)
left = 0
right = n-1
while left < right : # 对数组A[left,right]进行排序,直到left==right
for i in range(left,right): # 从左到右选最大
if A[i] > A[i+1]:
swap(A,i,i+1)
right = right - 1 # 选好了最大值时,right-1
for i in range(right,left,-1): # 从右到左选最小
if A[i-1] > A[i]:
swap(A,i-1,i)
left = left + 1 # 选好了最小值时,left+1
if __name__=='__main__':
A = [6,12,5,11,3,9,10,1,8,7,2,4]
print A
cocktailsort(A) # 选择排序算法,bubblesort 或者 cocktailsort
print A
2:选择排序(Selection Sort)
基本思想:选择最小的元素放在数组最前端,作为已排序的部分,对于未排序的部分,同样选择未排序中的最小的放在未排序部分的最前端,也就是已排序的末端。
# __author__ = 'czx'
# coding=utf-8
def swap(A,i,j): # 元素A[i]和元素A[j]交换
tmp =A[i]
A[i]=A[j]
A[j] =tmp
def selectionsort(A): #选择排序
n = len(A)
for i in range(n-1): # 同样只需要依次选择n-1个未排序的最小的值即可
min = i # 记住最小值的下标,用于交换
for j in range(i+1,n): # 已排好i-1个元素,从第i个元素开始比较选择最小值
if A[min] > A[j]: # 如果找到更小的值,记下该更小值的下标
min = j
if min != i: # 只有在下标和每次初始i不同时才交换位置
swap(A,min,i)
if __name__=='__main__':
A = [6,12,5,11,3,9,10,1,8,7,2,4]
print A
selectionsort(A)
print A
3:插入排序(Insertion Sort)
基本思想:想象打扑克,手上抓第一张牌的时候只有一张,抓第二张的时候跟第一张比较该放在左边还是右边,依次循环,每抓到一张牌,我们就选择合适的位置放这张牌,使得手上的牌是有序的。那么对于排序,我们从左到右选择元素(新抓到的牌)依次和已经排序好的元素(手上的牌)比较,选择合适的位置插入新牌(从手牌右端最大的逐个向左比较)。其实好多玩扑克的人都不按照从小到大的顺序放牌的,哈哈,我就是。
# __author__ = 'czx'
# coding=utf-8
def swap(A,i,j): # 元素A[i]和元素A[j]交换
tmp =A[i]
A[i]=A[j]
A[j] =tmp
def insertionsort(A): # 插入排序
n = len(A)
for i in range(1,n): # 抓牌(初始时候默认手上有一张牌,即元素A[0])
tmp = A[i] # 记下抓到什么牌
j = i - 1 # 和之前的i张牌进行比较
while j>=0 and A[j]>tmp: # 从右到左比较,新抓的牌较小时,手牌后移
A[j+1] = A[j]
j =j -1
A[j+1]=tmp # 找到合适的位置时插入手牌
def binaryinsertsort(A): # 二分插入排序:逐个比较移动太麻烦?那就二分比较吧
n = len(A)
for i in range(1,n): # 取n-1次元素(初始时候默认手上已经有一个元素)
tmp = A[i]
left = 0 # 左端
right = i-1 # 右端
while left<=right:
mid =(left+right)/2 # 中间一个的下标
if A[mid]>tmp: # 比较新元素和已排序列的中间元素,如果中间元素比新元素大
right = mid - 1 # 选取左半序列作为新的二分查找序列
else:
left = mid + 1 # 否则选取右半序列
j = i - 1
while j>=left: # 向右移动较大的元素
A[j+1] = A[j]
j = j - 1
A[left]=tmp # 正确位置插入新元素
def shellsort(A): # 希尔排序:利用‘插入排序对已经排序好的序列效率高(比较移动的次数少)’的特性
step = 0
n = len(A)
while step<=n/3: # 选取一系列增量step
step = 3*step + 1
while step>=1:
for i in range(step,n): # 从step之后逐个选值
j = i-step
tmp = A[i]
while j>=0 and A[j]>tmp: # 和对应step之前的对应元素比较,如果step当前值较小
A[j+step] = A[j] # 数据后移
j = j-step # 以step递减
A[j+step] = tmp # 插入正确的位置
step = (step-1)/3 # 递减增量
if __name__=='__main__':
A = [6,12,5,11,3,9,10,1,8,7,2,4]
print A
shellsort(A) # 排序方法:insertionsort 或者 binaryinsertsort 或者shellsort
print A
4:归并排序(Merge Sort)
基本思想:分治,将待排序数组不断细分至单个元素为一个小数组,两两合并成较大的数组,依次合并成一个数组。实现的方法有递归和非递归的。
# __author__ = 'czx'
#coding:utf-8
def merge(A,left,mid,right): # 归并以mid为界的左右子序列
len = right-left+1 # 序列长度
tmp = [] # 临时空间,存储左右子序列的比较排序结果
i = left # 左序列起点
j = mid + 1 # 有序列起点
while i<=mid and j<=right: # 直到有任意一个序列的所有元素比较完成
if A[i]<=A[j]:
tmp.append(A[i]) # 选取较小的值加入tmp
i = i + 1
else:
tmp.append(A[j])
j = j + 1
while i <= mid: # 右子序列元素比较完成后直接将左子序列元素加入到tmp
tmp.append(A[i])
i = i + 1
while j <= right: # 左子序列元素比较完成后直接将右子序列元素加入到tmp
tmp.append(A[j])
j = j + 1
for k in range(len): # 将排序好的tmp赋值给A[left,right],实现子序列A[left,right]的排序
A[left]=tmp[k]
left = left +1
def mergesortrecursion(A,left,right): # 递归实现归并排序
if left==right:
return
mid =(left+right)/2 # 通过mid将A一分为二
mergesortrecursion(A,left,mid) # 以mid为界,归并A的左半序列
mergesortrecursion(A,mid+1,right) # 归并A的右半部分
merge(A,left,mid,right) # 对左半子序列A[left,mid]和右半子序列A[mid+1,right]归并
def mergesortiteration(A,len): # 非递归实现并归排序
i = 1 # i表示待归并子序列大小,初始化为1
while i<len: # 子序列大小不超过A的长度,但会大于等于A长度的一半
left = 0 # left:初始化为0
while left + i < len: # 直到当前子序列只包含不到两个更小的子序列,比如i=8时,右侧只剩3个元素
mid = left + i - 1 # mid:相邻两子序列中间分界
if mid + i <len: # right:右侧子序列长度可能越界,比如不是完全二分的情况
right = mid + i
else:
right = len-1
merge(A,left,mid,right) # 归并操作
left = right +1 # 归并完前面两个子序列后归并后面的两个子序列
i = i * 2 # 每次翻倍,比如归并1和1大小的子序列后变为2,此时便归并2和2子序列
if __name__ == '__main__':
A = [6,12,5,11,3,9,10,1,8,7,2,4]
print A
#mergesortrecursion(A, 0, len(A)-1)
mergesortiteration(A,len(A))
print A
5:堆排序(Heap Sort)
基本思想:根据最大堆(最小堆)的性质实现排序功能,初始化堆,不断用堆尾数据替换堆顶数据并将堆顶数据输出,堆减小至1,实现最后的排序。堆树相关知识可参考:堆树(最大堆和最小堆)。
# __author__ = 'czx'
# coding=utf-8
def swap(A,i,j): # 元素A[i]和A[j]交换
tmp =A[i]
A[i]=A[j]
A[j] =tmp
def heapify(A,i,size): # 父亲节点i的调整(递归)
left = 2*i+1 # 左节点
right = 2*i+2 # 右节点
max = i # 最大节点值的下标
if left<size and A[left]>A[max]: # 如果有左节点且左节点值较大
max = left # 最大下标变为left
if right<size and A[right]>A[max]: # 如果有右节点且右节点值较大
max = right # 最大下标为right
if max!=i: # 当前节点不符合最大堆性质时进行调整
swap(A,i,max) # 先交换数据位置使得符合性质
heapify(A,max,size) # 调整对应的子节点使得其符合最大堆性质
def buildheap(A,n): # 建堆,堆树是一个完全二叉树结构
i = n/2-1 # 根据完全二叉树性质找到最末尾节点的父亲节点,然后调整该节点,使得符合最大堆性质
while i>=0: # 自下往上调整直到整棵树的堆顶
heapify(A,i,n) # 数组A,父亲节点i,堆的大小n
i = i - 1
def heapsort(A,n): # 堆排序
buildheap(A,n) # 初始化建堆
size = n # 堆的大小n
while size > 1:
size = size - 1 # 堆元素减一
swap(A,0,size) # 数据元素下标从0开始到n-1,所以size-1放在数据交换之前
heapify(A,0,size) # 调整堆
if __name__=='__main__':
A = [6,12,5,11,3,9,10,1,8,7,2,4]
print A
heapsort(A,len(A))
print A
6:快速排序(Quick Sort)
基本思想:每次选取一个基准(比如将待排序数组的最右端数值作为基准),将小于基准值的数放在最左端,大于基准数的值放在最右端,依次递归实现快速排序。
# __author__ = 'czx'
# coding=utf-8
def swap(A,i,j): # 交换元素A[i]和A[j]
tmp =A[i]
A[i]=A[j]
A[j] =tmp
def partition(A,left,right): 数组的A[left]到A[right]作为待比较的子序列
pivot = A[right] # 最右端的值作为基准
tail = left -1 # 当前子序列左边序列的尾端
i = left # 最左端开始和基准值比较
while i<right: # 直到最右端
if A[i]<=pivot: # 如果当前值比基准值小
tail = tail + 1 # 将数据放在左边子序列的尾端(连接起来)
if tail !=i: # 如果tail即使i处的值,便无需交换
swap(A,tail,i) # 否则,tail一定小于i,并交换两者的位置
i = i + 1
swap(A,tail+1,right) # 确定此时左子序列的队尾位置,并与基准值交换
return tail + 1 # 返回当前左子序列队尾下标,作为下次递归的分界点
def quicksort(A,left,right): # 快速排序
if left>=right: # 递归出口,当要排序的子序列的左端left<=right右端
return
pivot = partition(A,left,right) # 返回基准值的下标索引
quicksort(A,left,pivot-1) # 根据下标索引,对左子序列进行快速排序
quicksort(A,pivot+1,right) # 对右边子序列进行快速排序
if __name__=='__main__':
A = [6,12,5,11,3,9,10,1,8,7,2,4]
print A[:-1]
print A
quicksort(A,0,len(A)-1)
print A