十大经典排序算法,python实现
比较排序:
快速排序,归并排序,堆排序,冒泡排序,(元素之间的次序依赖他们之间的比较,每个数必须和其他的数比较,才能确定自己的位置)
非比较排序:
基数排序,计数排序,桶排序,通过确定每个元素之前,应该有多少个元素来排序,计算arr[i]之前有多少个元素,则确定了arr[i]在拍苏数组中的位置,(非比较排序只要确定每个元素之前已有的元素即可,所有一次遍历即可解决,算法复杂度为O(n))
适用范围:
1.比较排序适用于一切需要排序的情况,
2.非比较排序时间复杂度低,但是需要占用空间来确定唯一位置,对数据规模和数据分布有一定的要求
n:数据规模
k:“桶”个数
in-place:占用常数内存,不占用额外内存
out-place:占用额外内存
1.冒泡排序:
遍历需要排序的数列,一次比较相邻两个元素,直到没有需要交换的数列
def bubbleSort (arr):
for i in range(1,len(arr)):
for j in range(0,len(arr)-1):
if arr[j] > arr[j]+1:
arr[j],arr[j+1]=arr[j+1],arr[j]
return arr
算法分析:
最佳情况:T(n) = O(n),最差情况:T(n) = O(n2),平均情况:T(n) = O(n2)
2. 选择排序:
是一种简单直观的排序算法,无论什么数据进去都是O(n2)的时间复杂度,所以需要用到它的时候数据规模越小越好,
算法步骤:
在未排序的序列中找到最大或最小的元素,存放到序列的起始位置,再从剩余的元素中继续寻找最大或最小元素,放在以排序的序列末尾,重复,
def selectionSort(arr):
for i in range(len(arr)-1):
min_index = i
for j in range(i +1,len(arr)):
if arr[j]<arr[min_index]:
min_index =j
if i != min_index:
arr[i],arr[min_index] = arr[min_index],arr[i]
return arr
算法分析:
最佳情况:T(n)=O(n2),最差情况:T(n)=O(n2),平均情况:T(n)=O(n2)
3.插入排序
是一种简单直观的排序算法,通过构建有序数列,对未排序数据,在一排序的序列中从后向前扫描,找到相应的位置并插入,
算法步骤:
将第一个待排序的元素看做一个有序的序列,把第二个元素当成未排序的序列,从头到尾依次扫描未排序的数列,将扫描的每个元素插入到有序序列的适当位置,
def insertionSort(arr):
for i in range(len(arr)):
pre_index = i-1
current = arr[i]
while pre_index >= 0 and arr[pre_index] > current:
arr[pre_index+1] = arr[pre_index]
pre_index -=1
arr[pre_index+1] = current
return arr
算法分析:
最佳情况:T(n) = O(n),最坏情况:T(n) = O(n2),平均情况:T(n) = O(n2)
4.希尔排序:
递减增量排序算法,是插入排序的改进版本,但希尔排序是非稳定排序算法
基本思想是将整个待排序的记录序列分割成若干子序列分别进行插入排序,待整个序列中的元素基本有序时,对全体元素进行依次插入排序,
def shellSort(arr):
import math
gap=1
while(gap < len(arr)/3):
gap = gap*3+1
while gap > 0:
for i in range(gap,len(arr)):
temp = arr[i]
j = i-gap
while j >=0 and arr[j] > temp:
arr[j+gap]=arr[j]
j-=gap
arr[j+gap] = temp
gap = math.floor(gap/3)
return arr
算法分析:
最佳情况:T(n) = O(nlog2 n),最坏情况:T(n) = O(nlog2 n),平均情况:T(n) = O(nlog2 n)
5.归并排序:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间
算法步骤:
将已有序的子序列合并,得到有序的序列,即先使每个子序列有序,再使子序列段间有序,再将两个有序表合并成一个有序表,称为2路归并
def mergeSort(arr):
import math
if(len(arr)<2):
return arr
middle = math.floor(len(arr)/2)
left, right = arr[0:middle], arr[middle:]
return merge(mergeSort(left), mergeSort(right))
def merge(left,right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0));
else:
result.append(right.pop(0));
while left:
result.append(left.pop(0));
while right:
result.append(right.pop(0));
return result
算法分析:
最佳情况:T(n) = O(n),最坏情况:T(n) = O(nlogn),平均情况:T(n) = O(nlogn)
6.快速排序:
是一种分而治之思想在排序算法上的典型应用,快速排序应该算是在冒泡排序基础上的递归分治法。
算法步骤:
首先选出一个元素称为‘基准’,重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面,在这个分区退出之后,该基准就处于数列的中间位置。把小于基准值元素的子数列和大于基准值元素的子数列排序
def quickSort(arr, left=None, right=None):
left = 0 if not isinstance(left,(int, float)) else left
right = len(arr)-1 if not isinstance(right,(int, float)) else right
if left < right:
partitionIndex = partition(arr, left, right)
quickSort(arr, left, partitionIndex-1)
quickSort(arr, partitionIndex+1, right)
return arr
def partition(arr, left, right):
pivot = left
index = pivot+1
i = index
while i <= right:
if arr[i] < arr[pivot]:
swap(arr, i, index)
index+=1
i+=1
swap(arr,pivot,index-1)
return index-1
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
算法分析:
最佳情况:T(n) = O(nlogn),最坏情况:T(n) = O(n2),平均情况:T(n) = O(nlogn)
7.堆排序:
利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
算法步骤:
创建一个堆 H,把堆首(最大值)和堆尾互换,把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置,重复直到堆的尺寸为 1
def buildMaxHeap(arr):
import math
for i in range(math.floor(len(arr)/2),-1,-1):
heapify(arr,i)
def heapify(arr, i):
left = 2*i+1
right = 2*i+2
largest = i
if left < arrLen and arr[left] > arr[largest]:
largest = left
if right < arrLen and arr[right] > arr[largest]:
largest = right
if largest != i:
swap(arr, i, largest)
heapify(arr, largest)
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
def heapSort(arr):
global arrLen
arrLen = len(arr)
buildMaxHeap(arr)
for i in range(len(arr)-1,0,-1):
swap(arr,0,i)
arrLen -=1
heapify(arr, 0)
return arr
算法分析:
最佳情况:T(n) = O(nlogn),最坏情况:T(n) = O(nlogn),平均情况:T(n) = O(nlogn)
8.计数排序:
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序是一种稳定的排序算法,计数排序适用额外的数组C,其中第i个元素是待排序数组A中值等于i的元素个数,然后根据数组C来找到A的位置
算法描述:
找出数组中的最大和最小元素,统计每个元素i出现的次数,存入C的第i项中,对所有的计数进行累加,反向填充目标数组,将每一个元素i放入新数组的第i项,每放入一个就将C(i)减去1
def countingSort(arr, maxValue):
bucketLen = maxValue+1
bucket = [0]*bucketLen
sortedIndex =0
arrLen = len(arr)
for i in range(arrLen):
if not bucket[arr[i]]:
bucket[arr[i]]=0
bucket[arr[i]]+=1
for j in range(bucketLen):
while bucket[j]>0:
arr[sortedIndex] = j
sortedIndex+=1
bucket[j]-=1
return arr
算法分析:
最佳情况:T(n) = O(n+k),最坏情况:T(n) = O(n+k),平均情况:T(n) = O(n+k)
9.桶排序:
是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定,在额外空间充足的情况下,尽量增大桶的数量,使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
算法步骤:
人为设置一个桶分类,作为每个桶所能放置锁哥不同数值,例如bucket_size == 5时,该桶可以放入{1,2,3,4,5}这几种数,遍历输入数据,将数据放入对应的桶对每个不为空的桶排序,数据拼接
def bucket_sort(arr):
buckets = [0] * ((max(arr) - min(arr)) + 1)
for i in range(len(arr)):
buckets[arr[i] - min(arr)] += 1
b = []
for i in range(len(buckets)):
if buckets[i] != 0:
b += [i + min(a)] * buckets[i]
return b
算法分析:
最佳情况:T(n) = O(n+k),最坏情况:T(n) = O(n+k),平均情况:T(n) = O(n2)
10.基数排序:
其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
算法步骤:
获取数组中最大的数,并获取位数,arr为原始数组,从低位开始取每个位组成radix数组,对radix排序,
def radix_sort(list, arr=3):
for i in range(arr):
s = [ [] for k in range(10)]
for j in list:
s[int(j / (10 ** i)) % 10].append(j)
re = [a for b in s for a in b]
return re
算法分析:
最佳情况:T(n) = O(nk),最坏情况:T(n) = O(nk),平均情况:T(n) = O(n*k)
基数排序 | 计数排序 | 桶排序三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;