演算法系列(五)排序演算法下篇--如何超越排序演算法下界

robert_chao發表於2016-06-01

概述

演算法系列(四)排序演算法中篇--歸併排序和快速排序一文中,我們介紹了歸併排序和快速排序,最壞的情況下,最快的排序演算法的時間複雜度是O(nlogn),是否有更好的演算法呢?到目前為止,沒有特殊的規則,O(nlogn)已經是最好的排序演算法了,也就是說通用排序演算法的時間複雜度下界就是O(nlogn)。如果限定一些規則,是可以打破這個下界的。下面說一下盡在O(n)時間內就能實現對陣列排序的演算法。

基於排序的規則

基於什麼樣的規則才能突破排序的下界呢?我們需要分析一下排序消耗的時間。排序需要遍歷,比較,交換。能否省略其中的一些步驟呢?這就是要定義的規則,通過規則減少排序步驟。下面舉一個最簡單的例子。
一組待排序的元素僅有1和2,沒有其它值,對這組數進行排序。
輸入A0,A1,A2,A3......An-1,Ai為1或者2
排序步驟
1、令k=0
2、令i從0到n-1依次取值,如果A[i]=1,k自增1
3、令i從0到k-1依次取值,將A[i]賦值為1
4、令i從k到n-1依次取值,將A[i]賦值為2
這樣我們完成了排序,花費的時間為O(n)
之前我們所說的演算法都是通過比較元素對來確定順序,那種排序叫做比較排序。凡是比較排序,通用下界為O(nlogn)
剛才所說的簡單例子,是一個簡單計數排序。下面詳細說明一下

使用基數排序超越排序下界

簡單例子每個元素僅有兩種可能的取值,擴充套件一下,如果每個元素有m個不同取值,只要取值是m個連續整數之內的整數,演算法是通用的。
首先,通過計算出有多少個元素的排序關鍵字等於某個值,隨後就能就算出有多少個元素的排序關鍵字小於每個可能的排序。

基本思想

計數排序的基本思想是對於給定的輸入序列中的每一個元素x,確定該序列中值小於x的元素的個數(此處並非比較各元素的大小,而是通過對元素值的計數和計數值的累加來確定)。一旦有了這個資訊,就可以將x直接存放到最終的輸出序列的正確位置上。

計數排序演算法詳細描述

該演算法需要三個基本方法

COUNT-KEY-EQUAL(A,n,m)

輸入 A 一個陣列,
         n 陣列A中的元素個數
         m陣列A中元素的取值範圍
輸出一個陣列equal[0......m],是equal[j]等於陣列A中元素值為j的元素個數
1、建立一個新陣列equal[0......m]
2、令equal陣列每個元素都為0
3、i從0到n-1依次取值,每次將equal[A[i]]的值自增1
4、返回equal

COUNT-KEY-LESS(equal,m)

輸入值 COUNT-KEY-EQUAL方法對應的值equal,m
輸出一個陣列less[0......m],less[j]=equal[0]+equal[1]+......+equal[j-1]
1、建立一個新陣列less[0...m]
2、令less[0]=0
3、j從1取到m,less[j]=less[j-1]+equal[j-1](這是普通的迭代演算法)
4、返回less

REARRANGE(A,less,n,m)

輸入 COUNT-KEY-EQUAL COUNT-KEY-LESS方法對應的A,less,n,m
輸出 陣列B,B中包含A中所有元素,並且已經排好序
1、建立新陣列B[0...n-1],next[0.....m]
2、j從0到m依次取值
令next[j]=less[j]+1
3、令i從0到n-1依次取值
key=A[i];index=next[key],B[index]=A[i],next[key]++
4、返回陣列B

程式碼實現

進行了邏輯整合,基本思路相同
package com.algorithm.sort;

/**
 * 計數排序
 * 
 * @author chao
 *
 */
public class CountSort {

	public static void main(String[] args) {
		int[] num = { 1, 1 };
		sort(num);
		for (int i = 0; i < num.length; i++)
			System.out.print(num[i] + " ");
	}

	/**
	 * 計數排序
	 * 
	 * @param num
	 */
	public static void sort(int[] num) {
		int len = num.length;
		int[] orign = new int[len];
		int max = 0;// 我們只對正整數排序
		for (int i = 0; i < len; i++) {
			orign[i] = num[i];
			if (num[i] > max) {
				max = num[i];
			}
		}
		max = max + 1;
		int[] count = new int[max];
		for (int i = 0; i < max; i++) {
			count[i] = 0;
		}
		for (int i = 0; i < len; i++) {
			count[num[i]]++;
		}
		int t1, t2;
		t1 = count[1];
		count[0] = count[1] = 0;
		for (int i = 2; i < max; i++) {
			t2 = count[i];
			count[i] = t1 + count[i - 1];
			t1 = t2;
		}
		int key, index;
		for (int i = 0; i < len; i++) {
			key = orign[i];
			index = count[key];
			num[index] = orign[i];
			count[key]++;
		}
	}
}

複雜度分析

計數排序的複雜性為O(n),但是有空間代價,如果最大數很大的話,空間代價非常大。
還有一種排序叫做基數排序,是基於計數排序的,有約束條件,時間複雜度為O(n)
桶排序,基數排序跟計數排序類似,不再詳細說明,堆排序(很常用,在樹形演算法分析中會再說明)
程式碼實現可以看github,地址https://github.com/robertjc/simplealgorithm
github程式碼也在不斷完善中,有些地方可能有問題,還請多指教

歡迎掃描二維碼,關注公眾賬號






相關文章