算法基础——十种常用排序算法的Java及Python实现

  • Post author:
  • Post category:java


概述

排序算法不用多说了,程序员算法基础必须要掌握的,现在总结一下加深记忆。下图是常见八大排序算法的分类、名称、时间空间复杂度,以及稳定性。
除了以上八大排序算法外,还有两种在输入数组符合特定条件下的高效率排序算法:

计数排序

:假设输入数组中每一个元素都是

[0,k]区间

内的整数。对于一般情况,k取数组中的最大值。这是一种稳定排序,当k=O(n)时,时间复杂度O(k+n)=

O(n)

,但当k很大时,带来空间的代价是昂贵的。

桶排序

:假设输入数组的中每一个元素

均匀分布

在[0,1)区间上。跟计数排序一样是一种稳定排序,因为对输入数据作了假设,速度较快。当不符合均匀分布时只要满足以下条件也能使得时间复杂度为

O(n)

:所有桶的大小的平方和与总的元素数呈线性关系。

代码

以下是经典八大排序算法的Java及Python代码,都是基于经典算法书籍《算法导论》里的伪代码实现的,我在关键语句部分附上了注释。
按照上图中的顺序分别介绍八大排序算法的实现(升序),前面是Java,后面是Python。Java的排序函数写在了一个类里,Python的排序函数则直接写出来了。

直接插入排序

public class InsertSort {
	//插入排序,最好情况O(n),最坏情况O(n^2),稳定原址排序
	public int[] Sort(int[] arr){

		for(int i=0;i<arr.length;i++){
			int key = arr[i];
			int j = i-1;
			for(;j>=0 && arr[j]>key;j--)
				arr[j+1] = arr[j];//注意!这里不是交换,而是把比key大的都往后挪
			arr[j+1] = key;//key再插入合适的位置
		}
		return arr;
	}
}

def insertSort(arr):
    #插入排序,最坏为O(n^2),最好为O(n)
    #升序排列一个数组,降序排列将while语句中改为arr[i]<key即可
    if(arr==None or len(arr)<2):
        return arr
    for j in range(1,len(arr)):
        key = arr[j]#待插入的数
        i = j-1
        while(i>=0 and arr[i]>key):
            #从后往前比较待插入的数和当前数,将arr[j-1]、arr[j-2]...向右移动直到找到arr[j]的适当位置
            arr[i+1] = arr[i]
            i -= 1
        arr[i+1] = key#遍历完将arr[j]插入到该位置
    return arr

希尔排序

public class ShellSort {
	// 希尔排序,最好情况O(n),最坏情况O(n^2),平均情况比直接插入排序要好,平均情况为O(n^1.3)
	//不稳定,原址排序
	public int[] Sort(int[] arr) {
		int n = arr.length;
		for (int gap = n; gap > 0; gap/=2) {
			
			for (int i = gap;i < n;i++){
				if (arr[i] < arr[i-gap]){//如果比前面的元素小,则以步长为gap进行插入排序
					int key = arr[i];
					int j = i-gap;
					while (j>=0 && arr[j] > key){
						arr[j+gap] = arr[j];
						j -= gap;
					}
					arr[j+gap] = key;
				}
			}
		}
		return arr;
	}
}
def shellSort(arr):
    #改进版的插入排序-希尔排序,最坏为O(n^2),最好为O(n),平均为O(n^1.3)
    gap = len(arr)/2
    while gap > 0:
        
        for i in range(gap,len(arr)):
            if arr[i] < arr[i-gap]:#后面的元素比前面的小,以gap为步长进行插入排序
                key = arr[i]
                j = i - gap
                while j>=0 and arr[j]>key:
                    arr[j+gap] = arr[j]
                    j -= gap
                arr[j+gap] = key
        #步长减一半     
        gap /= 2

    return arr

直接选择排序

public class SelectSort {
	//选择排序,最坏和最好都为O(n^2),不稳定,原址排序
	public int[] Sort(int[] arr) {
		int n = arr.length;
		if (n<2)
			return arr;
		
		for(int i=0;i<n-1;i++){
			int index = i;
			for(int j=i+1;j<n;j++){
				if(arr[j]<arr[index]){
					index = j;//找出剩余元素中最小那个数的下标
				}
			}
			//将剩余元素中最小那个数跟当前数交换
			int tmp = arr[i];
			arr[i] = arr[index];
			arr[index] = tmp;
		}
		
		return arr;
	}
}
def selectSort(arr):
    #选择排序,最坏为O(n^2)
    #升序排列一个数组,降序排列将while语句中改为arr[i]<key即可
    if(arr==None or len(arr)<2):
        return arr
    for j in range(len(arr)):
        
        index = j
        for i in range(j,len(arr)):
            if arr[i] < arr[index]:
                index = i#找出剩余的最小元素
                
        #未排序中最小的元素跟arr[j]交换
        tmp = arr[j]
        arr[j] = arr[index]
        arr[index] = tmp
        
    return arr

堆排序

public class HeapSort {
	//维护一个最大堆,使其以i节点的元素为根
	public void maxHeapify(int[] arr,int i,int heap_size){
		int l = 2*i+1;//左孩子节点
		int r = l+1;
		
		int largest = i;
		if(l<heap_size && arr[l]>arr[largest])
			largest = l;
		if(r<heap_size && arr[r]>arr[largest])
			largest = r;
		
		if(largest != i){
			//保证i节点为最大,并维护以largest节点为根的最大堆
			int tmp = arr[i];
			arr[i] = arr[largest];
			arr[largest] = tmp;
			maxHeapify(arr,largest,heap_size);
		}
	}
	
	public void buildMaxHeap(int[] arr){
		int heap_size = arr.length;
		int mid = (heap_size-1)/2;
		//从中间节点开始把arr建为最大堆(只需遍历数组的一半),最后使得arr[0]为最大堆的根
		for(int i=mid;i>=0;i--)
			maxHeapify(arr,i,heap_size);
	}
	
	public int[] Sort(int[] arr){
		//先把数组建造为最大堆
		buildMaxHeap(arr);
		
		//从后往前遍历,根据最大堆的性质arr[0]最大
		for(int i = arr.length-1;i>=0;i--){
			int tmp = arr[0];
			arr[0] = arr[i];
			arr[i] = tmp;
			int heap_size = i;
			maxHeapify(arr,0,heap_size);
		}
		
		return arr;
	}
}
def maxHeapify(arr,i,heap_size):
    #维护一个最大堆,让arr[i]的值在最大堆中逐级下降,使得以i为根节点的子树是最大堆,O(lgn)
    l = 2*i+1#下标i从0开始,因此左孩子节点的下标是2*i+1
    r = l + 1#右孩子节点

    largest = i

    if l<heap_size and arr[l]>arr[largest]:#注意heap_size初始化为len(arr),这里判断应为l<heap_size
        largest = l
    if r<heap_size and arr[r]>arr[largest]:
        largest = r
                
    if largest != i:#i不是最大堆的根节点,就交换值,并且让largest为根节点的子树保持最大堆
        arr[largest],arr[i] = arr[i],arr[largest]
        maxHeapify(arr,largest,heap_size)

            
def buildMaxHeap(arr):
    #自顶向上,将arr转换为最大堆
    heap_size = len(arr)
    mid = int((heap_size-1)/2)
    for i in range(mid,-1,-1):
        maxHeapify(arr,i,heap_size)
        

def heapSort(arr):
    
    buildMaxHeap(arr)#时间复杂度O(n)
    size = len(arr)    
    
    #n-1次调用maxHeapify,时间复杂度O(nlgn)
    for i in range(size-1,-1,-1):
        
        arr[i],arr[0] = arr[0],arr[i]#从后往前存储,根据最大堆性质,arr[0]是当前最大堆的最大值
        heap_size = i
        maxHeapify(arr,0,heap_size)


冒泡排序

public class PopSort {
	public int[] Sort(int[] arr) {
		int n = arr.length;
		if(n<2)
			return arr;
		
		for(int i=0;i<n;i++)
			for(int j=n-1;j>i;j--){
				if(arr[j-1]>arr[j]){
					int tmp = arr[j];
					arr[j] = arr[j-1];
					arr[j-1] = tmp;
				}
			}
		
		return arr;
	}
}
def popSort(arr):
    n = len(arr)
    if n < 2:
        return arr
    
    for i in range(n):
        for j in range(n-1,i,-1):
            if arr[j-1] > arr[j]:
                arr[j-1],arr[j] = arr[j],arr[j-1]

    return arr

快速排序

public class QuickSort {
	
	public int getPartition(int[] arr,int low,int high){

		int tmp;
		int index = low - 1;		
		for(int i = low;i<high;i++){
			//arr[index]及其以前的都比arr[high]小
			if(arr[i]<=arr[high]){
				index++;
				tmp = arr[i];
				arr[i] = arr[index];
				arr[index] = tmp;
			}
		}
		//把arr[high]放到合适位置
		tmp = arr[index+1];
		arr[index+1] = arr[high];
		arr[high] = tmp;
		
		return index+1;
	}
	
	public void Sort(int[] arr,int low,int high) {
		//选一个基准元素将arr分成左边小右边大的两部分,注意high是最大下标
		if(low<high){
			int mid = getPartition(arr,low,high);
			Sort(arr,low,mid-1);
			Sort(arr,mid+1,high);
		}
	}
}
def quickSort(arr,low,high):
    #随机选择基准元素的快速排序,达到期望时间复杂度O(nlgn)
    #注意high是最大下标
    if low < high:
        mid = getPartition(arr,low,high)
        #对基准元素两边的数组快排
        quickSort(arr,low,mid-1)
        quickSort(arr,mid+1,high)
        
def getPartition(arr,low,high):
    #随机选择一个数并与arr[high]交换,防止最坏情况
    #将arr[high]作为基准进行排序
    rand = random.randint(low,high)
    arr[high],arr[rand] = arr[rand],arr[high]
    
    index = low - 1
    for i in range(low,high):
        if arr[i] <= arr[high]:#保证index前面的数都比key小
            index += 1
            arr[index],arr[i] = arr[i],arr[index]
    arr[index+1],arr[high] = arr[high],arr[index+1]
    
    return index+1

归并排序

public class MergeSort {
	
	public void Sort(int[] arr,int low,int high){
		if(low<high){
			//注意high是最大下标
			int mid = (int)((low+high)/2);
			Sort(arr,low,mid);
			Sort(arr,mid+1,high);
			mergeArr(arr,low,mid,high);
		}
	}
	
	public void mergeArr(int[] arr,int low,int mid,int high){
		int n1 = mid-low+1;//注意左边数组的长度
		int n2 = high-mid;
		int[] arr1 = new int[n1+1];
		int[] arr2 = new int[n2+1];
		
		int i = 0;
		int j = 0;
		//把mid左右两边的数分别放在两个数组里
		for(;i<n1;i++)
			arr1[i] = arr[low+i];
		for(;j<n2;j++)
			arr2[j] = arr[mid+1+j];
		//放置无穷大的值作为哨兵值,简化边界处理
		arr1[n1] = Integer.MAX_VALUE;
		arr2[n2] = Integer.MAX_VALUE;
		i = 0;
		j = 0;
		for(int k=low;k<=high;k++){
			//比较左右两个数组的元素,将小的依次放入arr[k]
			if(arr1[i]<=arr2[j]){
				arr[k] = arr1[i];
				i++;
			}
			else{
				arr[k] = arr2[j];
				j++;
			}
		}
	}
}
def mergeSort(arr,p,r):
    #p17,归并排序,O(nlgn).注意r是数组的最大下标
    if p<r:
        q = int((p+r)/2)
        mergeSort(arr,p,q)
        mergeSort(arr,q+1,r)
        Merge(arr,p,q,r)
        
def Merge(arr,p,q,r):
    #合并两个有序数组,arr1[p...q]和arr2[q+1...r]
    n1 = q-p+1#左边数组的长度(因为q中位数是向下取整,所以q-p+1)
    n2 = r-q#右边数组的长度
    arr1 = []
    arr2 = []
    for i in range(n1):
        arr1.append(arr[p+i])
    for j in range(n2):
        #注意j的范围
        arr2.append(arr[q+1+j])
    arr1.append(float('inf'))#放置一个无穷大的数作为“哨兵值”
    arr2.append(float('inf'))
    #print(arr1,arr2)
    i,j = 0,0
    for k in range(p,r+1):#注意k的范围
        if arr1[i] <= arr2[j]:
            arr[k] = arr1[i]
            i += 1
        else:
            arr[k] = arr2[j]
            j += 1

基数排序

这个用Java写太麻烦了,各种麻烦的ArrayList操作~~所以只贴Python版

def cntDigit(arr,radix):  
    #获取数组元素的最大位数  
    maxnum = arr[0]  
    for x in arr:  
        if x > maxnum:  
            maxnum = x  
      
    cnt = 0  
    while(maxnum != 0):  
        maxnum //= radix  
        cnt += 1
    print("max radix="+str(cnt))  
    return cnt  
  
def radixSort(arr,radix=10):  
    k = cntDigit(arr,radix)#获取最大位数  
    bucket = [[] for i in range(radix)] #10个桶 
    for i in range(1,k+1):  
        for j in arr:  
            #bucket[x]存储从低到高第i位为x的数,如数组中的543,i=1时存在bucket[3]里  
            bucket[j//(radix**(i-1)) % (radix)].append(j)  
        del arr[:]#先初始化arr  
        #print(bucket)  
        for z in bucket:#当前位数的数组按顺序放入arr中  
            arr += z  
            del z[:]
        #print(arr)
    return arr
arr = [431,234,560,102,322,676,148,329,543,89,7]
sortedArr = radixSort(arr)
print(sortedArr)


计数排序

def countSort(a):
    k = a[0]
    #得到最大值k
    for x in a:
        if x > k:
            k = x
    b = [0 for i in range(len(a))]#b存放排序的输出
    c = [0 for i in range(k+1)]#c是计数数组
    
    for x in a:
        #c[x]记录了数组a中x出现的次数
        c[x] += 1
    for i in range(1,k+1):
        #[i]记录了小于等于i的元素的数量
        c[i] = c[i] + c[i-1]
    for x in a:
        #小于等于x的元素有c[x]个,将x放在第c[x]的位置
        #注意下标从0开始
        b[c[x]-1] = x
        c[x] -= 1
    return b


桶排序

import random

def bucketSort(a):
    n = len(a)
    b = [[] for i in range(n)]#产生n个链表
    for i in range(n):
        #第i个链表存放的是半开区间[i/10,(i+1)/10]的值
        buc = int(n*a[i])
        b[buc].append(a[i])
    res = []
    for list in b:
        insertSort(list)#插入排序
        res += list
    return res

def insertSort(arr):
    #对每一个链表进行插入排序
    if(arr==None or len(arr)<2):
        return arr
    for j in range(1,len(arr)):
        key = arr[j]#待插入的数
        i = j-1
        while(i>=0 and arr[i]>key):
            #从后往前比较待插入的数和当前数,将arr[j-1]、arr[j-2]...向右移动直到找到arr[j]的适当位置
            arr[i+1] = arr[i]
            i -= 1
        arr[i+1] = key#遍历完将arr[j]插入到该位置
    return arr

#测试用例:
#产生一个[0,1)区间均匀分布的数组
ranArr = [random.random() for i in range(10)]
print(ranArr)
res = bucketSort(ranArr)
print(res)



版权声明:本文为buptlrw原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。