1. 程式人生 > >總結幾種排序演算法的Java實現

總結幾種排序演算法的Java實現

1、氣泡排序

氣泡排序是一種交換排序,它的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止。


Java程式碼:

import java.util.Random;

public class BubbleSort {
		
	/**
	 * 改進的氣泡排序演算法
	 * 通過標誌位flag避免無謂的比較
	 */
	public static void bubbleSort( int[] data ){
		boolean flag = true;
		for( int i = 0; i < data.length && flag; i++ ){
			flag = false;
			for( int j = data.length - 1; j > i; j-- ){
				if( data[j] < data[j-1] ){
					swap( data, j - 1, j );
					flag = true;
				}
			}
		}
	}
	
	/**
	 * 交換陣列中的兩個數
	 */
	public static void swap( int[] data, int index1, int index2 ){
		int temp = data[index1];
		data[index1] = data[index2];
		data[index2] = temp;
	}

}


2、選擇排序

時間複雜度為O(n^2),但效能上優於氣泡排序。

選擇排序法就是通過n-i-1次關鍵字的比較,從n-i-1個關鍵字中選出關鍵字最小的記錄,並和第i個記錄交換。

Java程式碼:

import java.util.Random;

public class SelectSort {
		
	/**
	 * 選擇排序演算法
	 */
	public static void selectSort( int[] data ){
		for( int i = 0; i < data.length; i++ ){
			for( int j = i + 1; j < data.length; j++ ){
				if( data[j] < data[i] ){
					swap( data, i, j );
				}
			}
		}
	}
	
	/**
	 * 交換陣列中的兩個數
	 */
	public static void swap( int[] data, int index1, int index2 ){
		int temp = data[index1];
		data[index1] = data[index2];
		data[index2] = temp;
	}

}

3、插入排序

時間複雜度為O(n^2),但比冒泡和選擇排序的效能要好一些。

插入排序的基本操作是將一個記錄插入到已經排好序的有序表中,從而得到一個新的、記錄數增1的有序表。

Java程式碼:

import java.util.Random;

public class InsertSort {
		
	/**
	 * 插入排序演算法
	 */
	public static void insertSort( int[] data ){
		for( int i = 1; i < data.length; i++ ){
			for( int j = i; j > 0 && data[j] < data[j-1]; j-- ){
					swap( data, j - 1, j );
			}
		}
	}
	
	/**
	 * 交換陣列中的兩個數
	 */
	public static void swap( int[] data, int index1, int index2 ){
		int temp = data[index1];
		data[index1] = data[index2];
		data[index2] = temp;
	}

}


4、希爾排序

D.L.Shell於1959年提出的一種排序演算法,時間複雜度為O(n^k),1<k<2,是突破時間複雜度為O(n^2)的首批演算法之一。希爾排序所需要比較的記錄是跳躍式的移動,因此並不是一種穩定的排序演算法。

希爾排序可以看作是對插入排序的一種改進,採取跳躍分割的策略,將相距某個“增量”的記錄組成一個子序列,這樣保證在子序列內分別進行直接插入排序後得到的結果是基本有序,從而減少交換次數,提高排序效率。

Java程式碼:

import java.util.Random;

public class ShellSort {

    /**
     * 希爾排序
     * i為增量,增量排序的最後一個增量必須為1才行
     */
    public static void shellSort( int[] data ) {
        for( int inc = data.length / 2;  inc > 0; inc /= 2 ){
            for( int start = 0; start < inc; start++ ){
                insertSort( data, start, inc );
            }
        }
    }

    /**
     * 以start為起點,inc為間隔進行插入排序
     */
    private static void insertSort( int[] data, int start, int inc ) {
        for( int i = start + inc; i < data.length; i += inc ){
            for( int j = i; ( j >= inc ) && ( data[j] < data[j-inc] ); j -= inc ){
                swap( data, j, j - inc );
            }
        }
    }
 
}


5、堆排序
堆排序就是利用堆(假設利用大頂堆)進行排序的方法。它的基本思想是,將帶排序的序列構造成一個大頂堆。此時,整個序列的最大值就是堆頂的根節點。將它移走(將其與堆陣列的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的n-1個序列重新構造成一個堆,這樣就會得到n個元素中的次大值,如此反覆執行,便能得到一個有序序列了。

堆排序無論是最好、最壞和平均時間複雜度均為O(nlogn),空間複雜度上,它只有一個用來交換的暫存單元,也非常不錯。但由於記錄的比較和交換是跳躍式進行,因此堆排序也是一種不穩定的排序方法。


Java程式碼:

import java.util.Random;


public class HeapSort {

    /**
     * 堆排序
     */
    public static void heapSort( int[] data ) {
        for( int i = data.length / 2;  i >= 0; i-- ){
        	heapAdjust( data, i, data.length );
        }
        for( int i = data.length - 1; i > 0; i-- ){
        	swap( data, 0, i );
        	heapAdjust( data, 0, i );
        }
    }

    /**
     * 調整陣列data[start..end]中的關鍵字,使之成為一個大頂堆
     */
    private static void heapAdjust( int[] data, int start, int end ) {
        int temp,child;
        for( temp = data[start]; leftChild( start ) < end; start = child ){
        	child = leftChild( start );
        	//調整child,使其指向最大那個子節點
        	if( child != end - 1 && data[child] < data[child+1] )
        		child++;
        	//start既可是最初要調整的節點索引,也可是子節點索引
        	if( temp < data[child] )
        		data[start] = data[child];
        	else
        		break;
        }
        data[start] = temp;
    }
    
    /**
	 * 返回左子樹的索引
	 */
    private static int leftChild( int i )
    {
        return 2 * i + 1;
    }
    
    /**
	 * 交換陣列中的兩個數
	 */
	public static void swap( int[] data, int index1, int index2 ){
		int temp = data[index1];
		data[index1] = data[index2];
		data[index2] = temp;
	}
	
}

  6、歸併排序

歸併排序就是利用歸併的思想實現的排序方法。它的原理是假設初始序列含有n個記錄,則可以看成是n個有序的子序列,每個子序列的長度為1,然後兩兩歸併,得到[n/2]個長度為2或1的有序子序列;在兩兩歸併,……,如此重複,直至得到一個長度為n的有序序列為止,這種排序方法成為2路歸併排序。

歸併排序的最好、最壞、平均的時間複雜度都是O(nlogn)。由於歸併排序在歸併過程中需要與原始記錄序列同樣數量的儲存空間存放歸併結果以及遞迴時深度為logn的棧空間,因此空間複雜度為O(n+logn)。

另外,歸併排序的merge函式中有if( data[leftPos] <= data[rightPos] )的語句,這就是它需要兩兩比較,不存在跳躍,因此歸併排序是一種穩定的排序演算法。


Java程式碼:

import java.util.Random;


public class MergeSort {

    /**
     * 歸併排序
     */
    public static void mergeSort( int[] data ) {
    	int[] tempArray = new int[data.length];
    	mergeSort( data, tempArray, 0, data.length - 1 );
    }

    /**
     * 遞迴呼叫
     */
    private static void mergeSort( int[] data, int[] tempArray, int left, int right ) {
        if( left < right ){
        	int center = ( left + right ) / 2;
            mergeSort( data, tempArray, left, center );
            mergeSort( data, tempArray, center + 1, right );
            merge( data, tempArray, left, center + 1, right );
        }
    }
    
    /**
	 * 歸併演算法
	 */
    private static void merge( int[] data, int[] tempArray, int leftPos, int rightPos, int rightEnd )
    {
        int leftEnd = rightPos - 1;
        int tempPos = leftPos;
        int numElements = rightEnd - leftPos + 1;
        //主迴圈
        while( leftPos <= leftEnd && rightPos <= rightEnd ){
        	if( data[leftPos] <= data[rightPos] )
        		tempArray[tempPos++] = data[leftPos++];
        	else
        		tempArray[tempPos++] = data[rightPos++];
        }
        //複製剩餘部分到tempArray
        while( leftPos <= leftEnd )
    		tempArray[tempPos++] = data[leftPos++];
    	while( rightPos <= rightEnd )
    		tempArray[tempPos++] = data[rightPos++];
        //複製tempArray回原陣列
        for( int i = 0; i < numElements; i++, rightEnd--)
        	data[rightEnd] = tempArray[rightEnd];
    }
    
}

7、快速排序

快速排序的基本思想是:通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序的目的。

快速排序的最優和平均時間複雜度均為O(nlogn),最壞的時間複雜度為O(n^2)。

就空間複雜度來說,主要是遞迴造成棧空間的使用,最好情況,遞迴樹的深度為logn,其空間複雜度也就是O(logn),最壞情況,需要n-1次遞迴呼叫,其空間複雜度為O(n),平均情況,空間複雜度為O(logn)。

由於關鍵字的比較和交換是跳躍進行的,因此,快速排序是一種不穩定的排序方法。


import java.util.Random;

public class QuickSort {
		
	public static void quickSort( int[] data ){
		quickSort( data, 0, data.length - 1 );
	}
	
	/**
	 * 遞迴操作
	 */
	public static void quickSort( int[] data, int low, int high ){
		int pivotIndex;
		if( low < high ){
			pivotIndex = partition( data, low, high );
			quickSort( data, low, pivotIndex - 1 );
			quickSort( data, pivotIndex + 1, high );
		}
	}
	
	/**
	 * 分割操作(排序)
	 */
	public static int partition( int[] data, int low, int high ){
		int pivotValue = median3( data, low, high );
		while( low < high ){
			while( low < high && data[high] >= pivotValue )
				high--;
			swap( data, low, high );
			while( low < high && data[low] <= pivotValue )
				low++;
			swap( data, low, high );
		}
		return low;
	}
	
	/**
	 * 選取樞紐值
	 */
	public static int median3( int[] data, int low, int high ){
		int median = (low + high) / 2;
		if( data[low] > data[high] )
			swap( data, low ,high );
		if( data[median] > data[high] )
			swap( data, median, high );
		if( data[median] > data[low] )
			swap( data, low, median );
		return data[low];
	}
	
	/**
	 * 交換陣列中的兩個數
	 */
	public static void swap( int[] data, int index1, int index2 ){
		int temp = data[index1];
		data[index1] = data[index2];
		data[index2] = temp;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = new int[10000000];
		Random r = new Random();
		for( int i = 0; i < a.length; i++ ){
			a[i] = r.nextInt( 10000000 );
		}
		
		System.out.print( "陣列a排序前:" );
		for( int i = 0; i < 10; i++ )
			System.out.print( a[i] + " , " );
		
		long startTime = System.currentTimeMillis();
		quickSort( a );
		System.out.println( "\n耗費時間(毫秒):" + (System.currentTimeMillis() - startTime) );
		
		System.out.print( "陣列a排序後:" );
		for( int i = 0; i < 100; i++ )
			System.out.print( a[i] + " , " );
	}

}


8、桶排序

桶排序 (Bucket sort)或所謂的箱排序,是一個排序演算法,工作的原理是將陣列分到有限數量的桶子裡。每個桶子再個別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是 比較排序,他不受到 O(n log n) 下限的影響。
桶式排序不再是一種基於比較的排序方法,它是一種比較巧妙的排序方式,但這種排序方式需要待排序的序列滿足以下兩個特徵:
(1)待排序列所有的值處於一個可列舉的範圍之類;
(2)待排序列所在的這個可列舉的範圍不應該太大,否則排序開銷太大。
排序的具體步驟如下:
(1)對於這個可列舉範圍構建一個buckets陣列,用於記錄“落入”每個桶中元素的個數;
(2)將(1)中得到的buckets陣列重新進行計算,按如下公式重新計算:
buckets[i] = buckets[i] +buckets[i-1] (其中1<=i<buckets.length); 
桶式排序是一種非常優秀的排序演算法,時間效率極高,它只要通過2輪遍歷:第1輪遍歷待排資料,統計每個待排資料“落入”各桶中的個數,第2輪遍歷buckets用於重新計算buckets中元素的值,2輪遍歷後就可以得到每個待排資料在有序序列中的位置,然後將各個資料項依次放入指定位置即可。
桶式排序的空間開銷較大,它需要兩個陣列,第1個buckets陣列用於記錄“落入”各桶中元素的個數,進而儲存各元素在有序序列中的位置,第2個數組用於快取待排資料。
桶式排序是穩定的。
如果待排序資料的範圍在0~k之間,那麼它的時間複雜度是O(k+n)的
桶式排序演算法速度很快,因為它的時間複雜度是O(k+n),而基於交換的排序時間上限是nlg n。
但是它的限制多,比如它只能排整形陣列。而且當k較大,而陣列長度n較小,即k>>n時,輔助陣列C[k+1]的空間消耗較大(要求數字要分佈均勻,最大值和總個數差距不要太大)。
當陣列為整形,且k和n接近時, 可以用此方法排序。(有的文章也稱這種排序演算法為“計數排序”)


Java程式碼:

import java.util.Random;

public class BucketSort {
	
	/**
	* 桶式排序:
	* 桶式排序不再是基於比較的了,它和基數排序同屬於分配類的排序,
	* 這類排序的特點是事先要知道待排 序列的一些特徵。
	* 桶式排序事先要知道待排 序列在一個範圍內,而且這個範圍應該不是很大的。 
	* 比如知道待排序列在[0,M)內,那麼可以分配M個桶,第I個桶記錄I的出現情況,
	* 最後根據每個桶收到的位置資訊把資料輸出成有序的形式。
	* 這裡我們用兩個臨時性陣列,一個用於記錄位置資訊,一個用於方便輸出資料成有序方式,
	* 另外我們假設資料落在0到MAX,如果所給資料不是從0開始,你可以把每個數減去最小的數。 
	*/
	private static void bucketSort( int[] data,int max ){ 
		int[] temp = new int[data.length];//建立臨時陣列
		int[] count=new int[max];//建立桶
		
		//進桶,假如有重複的,那桶裡的值為重複的次數
		for( int i = 0; i < data.length; i++ ){
			count[data[i]]++;
		}
		//計算data陣列的索引值,data陣列索引值和前面count索引相加的值是相同的,重複的除外,但下面的方法也計算了重複的值
		for( int i = 1; i < max; i++ ){
			count[i] = count[i] + count[i-1];
		}
		//複製陣列,從data陣列的0位置開始,向temp的0位置開始拷貝data.length長的資料
		System.arraycopy(data, 0, temp, 0, data.length);
		//根據上一個for 迴圈計算的索引值進行計算,當碰到重複值的時候,--count[temp[k]]起了作用。
		for( int k = data.length - 1; k >= 0; k-- ){
			data[--count[temp[k]]] = temp[k];
		}
	}

        public static void main(String[] args) {
        	Random r = new Random();
        	int MAX = 10000000;
        	int[] keys = new int[MAX];
        	for(int i = 0; i < MAX; i++)
        	{
        		keys[i] = r.nextInt(99999);
        	}
        	System.out.println("排序之前:");
            for(int i=0;i<10;i++)
            {
               System.out.print(keys[i]+",");
            }
            bucketSort(keys,100000);//actually is 18, but 20 will also work
            System.out.println();
            System.out.println("排序之後:");
            for(int i=keys.length-1;i>keys.length-100;i--)
            {
               System.out.print(keys[i]+",");
            }
       }
       
}

9、基數排序

分配排序的基本思想:排序過程無須比較關鍵字,而是通過“分配”和“收集”過程來實現排序。它們的時間複雜度可達到線性階:O(n)。

一、兩種多關鍵碼排序方法

最高位優先法(MSD法)。先按k1排序,將序列分成若干子序列,每個子序列中的記錄具有相同的k1值;再按k2排序,將每個子序列分成更小的子序列;然後,對後面的關鍵碼繼續同樣的排序分成更小的子序列,直到按kd排序分組分成最小的子序列後,最後將各個子序列連線起來,便可得到一個有序的序列。前面介紹的撲克牌先按花色再按面值進行排序的方法就是MSD法 
最次位優先法(LSD法)。先按kd排序,將序列分成若干子序列,每個子序列中的記錄具有相同的kd值;再按kd-1排序,將每個子序列分成更小的子序列;然後,對後面的關鍵碼繼續同樣的排序分成更小的子序列,直到按k1排序分組分成最小的子序列後,最後將各個子序列連線起來,便可得到一個有序的序列。前面介紹的撲克牌先按面值再按花色進行排序的方法就是LSD法。

二、基於LSD方法的鏈式基數排序的基本思想

“多關鍵字排序”的思想實現“單關鍵字排序”。對數字型或字元型的單關鍵字,可以看作由多個數位或多個字元構成的多關鍵字,此時可以採用“分配-收集”的方法進行排序,這一過程稱作基數排序法,其中每個數字或字元可能的取值個數稱為基數。比如,撲克牌的花色基數為4,面值基數為13。在整理撲克牌時,既可以先按花色整理,也可以先按面值整理。按花色整理時,先按紅、黑、方、花的順序分成4摞(分配),再按此順序再疊放在一起(收集),然後按面值的順序分成13摞(分配),再按此順序疊放在一起(收集),如此進行二次分配和收集即可將撲克牌排列有序。


Java程式碼:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class RadixSort {
	
	/**
	 * 基數排序,預設基數radix為10
	 * @data待排陣列
	 * @size陣列中最大數位數
	 */
	public static void radixSort( int[] data ){
		
		//首先確定排序的趟數;
        int max = data[0];
        for( int i = 1; i < data.length; i++ ){
        	if( data[i] > max ){
               max = data[i];
            }
        }

        //判斷位數;   
        int size = 0;
        while( max > 0 ){
        	max /= 10;
        	size++;
        }

        int radix = 10;
        int[] temp = new int[data.length];//用於暫存元素  
        int[] count = new int[radix];//用於計數排序  
        int divide = 1;
        
        for( int i = 0; i < size; i++, divide *= radix ){
        	
        	System.arraycopy( data, 0, temp, 0, data.length );
        	Arrays.fill( count, 0 );
        	
        	for( int k = 0; k < temp.length; k++ ){
        		int tempKey = temp[k] / divide % radix;
        		count[tempKey]++;
        	}
        	
        	for( int m = 1; m < count.length; m++ ){
        		count[m] = count[m] + count[m-1];
        	}
        	
        	for( int n = data.length - 1; n >= 0; n-- ){
        		int tempKey = temp[n] / divide % radix;
        		data[--count[tempKey]] = temp[n];
        	}
        	
        }

	}
	
}