1. 程式人生 > >Java實現的排序演算法及比較 [冒泡,選擇,插入,歸併,希爾,快排]

Java實現的排序演算法及比較 [冒泡,選擇,插入,歸併,希爾,快排]

      排序演算法的穩定性:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,
而在排序後的序列中,ri仍在rj之前,則稱這種排序演算法是穩定的;否則稱為不穩定的。

氣泡排序:穩定

	public void bubbleSort(){//O(n^2)  //每一趟的比較一次就可能交換一次
		for(int i=0;i<nElements;i++){
			for(int j=0;j<nElements-i-1;j++){/////
				if(array[j]>array[j+1]){
					swap(j,j+1);
				}
			}
		}
	}
       private void swap(int one, int two){
		int temp;
		temp = array[one];
		array[one] = array[two];
		array[two] = temp;
	}

選擇排序:不穩定
	public void selectSort(){ //O(n^2) 每一趟,比較次數同上,但最多隻進行一次交換,所以比冒泡快。
		//int maxLocal = 0;
		for(int i=0;i<nElements;i++){
			int minIndex = i;
			for(int j=i+1;j<nElements;j++){
				if(array[minIndex] > array[j]){
					minIndex = j;
				}
			}
			if(minIndex != i){
				swap(minIndex,i);
			}
		}
	}

插入排序:穩定

public void insertSort(){// O(n^2) 比冒泡快一倍,比選擇排序略快,資料基本有序時,速度較快,但資料逆序時,不如冒泡快。
		int key;		//儲存要插入的元素
		int j;			//儲存當前元素的位置
		int i;			//儲存當前元素的前一個元素的位置,從後面向前比較
		for(j=1;j<nElements;j++){
			key = array[j];
			i=j-1;
			while(i>=0 && key<array[i]){	//如果插入元素前的元素比它大,並且陣列未比較到第一個元素前
				array[i+1] = array[i];	//將大的元素向後移動,第一次移動時覆蓋掉key值所在陣列中的位置;
	                        i--;			//繼續向前比較
			}				//找到插入位置時;
			array[i+1] = key;		//插入到不比key大的值的後面。
		}
	}



二路歸併排序:穩定    //O(nlogn) 

    public int[] mergeSort(int []array1, int []array2){//假定陣列全部被資料元素填滿,沒有空餘項;
        int []array3 = new int[array1.length + array2.length];//
        int array1Index = 0;
        int array2Index = 0;
        int array3Index = 0;
        
        while(array1Index < array1.length && array2Index < array2.length){
            if(array1[array1Index] < array2[array2Index]){
                array3[array3Index++] = array1[array1Index++];
            }else{
                array3[array3Index++] = array2[array2Index++];
            }
        }
        while(array1Index < array1.length){
            array3[array3Index++] = array1[array1Index++];
        }
        while(array2Index < array2.length){
            array3[array3Index++] = array2[array2Index++];
        }
        
        return array3;
    }

多路歸併排序的遞迴實現:

歸併排序的定義

歸併排序演算法採用的是分治演算法,即把兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序的序列分成若干個子序列,每個子序列都是有序的,然後把有序子序列合併成整體有序序列,這個過程也稱為2-路歸併.注意:歸併排序的一種穩定排序,即相等元素的順序不會改變.

歸併排序的原理

常見的排序主要有兩種,一種是先把待排序的序列一次分割,使子序列的長度減小至1,然後在合併,另外一種是把待排序兩兩分組排序然後在合併。

歸併排序實現的示例程式碼:

歸併排序的時間複雜度

歸併排序的最好、最壞和平均時間複雜度都是O(nlogn),而空間複雜度是O(n)比較次數介於(nlogn)/2和(nlogn)-n+1,賦值操作的次數是(2nlogn)。因此可以看出,歸併排序演算法比較佔用記憶體,但卻是效率高且穩定的排序演算法。

希爾排序:不穩定, 比插入排序快

public static void shellSort(int []array){//假定陣列長度即資料項的個數,即陣列所有資料項均被填滿,沒有空餘項;
	int interval = 1;                 // 間隔,初始值為1;
	while(interval < array.length/3){
		interval = interval * 3 + 1; //計算間隔的最大值;
	}
	while(interval > 0){                               //對於每一個間隔進行分組;
		for(int i = interval; i<array.length;i++){ //對每個元素按組分別進行插入排序;
			int temp = array[i];               //將待插入排序的元素取出;
			int groupSub = i-interval;         //標記當前組的最後一個比較元素的下標(從後向前比較);
			while( groupSub >= 0 && array[groupSub] > temp){    //若待排序元素的值比排好序的陣列的最後一個元素小且比較元素存在
				array[groupSub+interval] = array[groupSub] ;//將比較元素後移;
				groupSub = groupSub - interval;             //繼續跟前面的元素比較
			}
			array[groupSub+interval] = temp;                   //找到插入位置,將排序值插入到不符合比較條件的元素的後面一個位置;
		}//for
		interval = (interval-1)/3;    //計算下一個更小的間隔值,重新按其劃分,重新分組排序;
	}//while
}//shellSort

快速排序:不穩定,平均效果最好

public static void quickSort(int []array,int left, int right){ //O(N*logN)
		if(left < right){
			int key = array[left];//將樞紐的值設定為當前陣列的left標記值,
					      //而不是0下標值,遞迴呼叫時left~right指向將要快排的陣列片段
			int i = left;
			int j = right;
			while(i < j){
				while(i < j && array[j] > key){//從右端找到比key小的值移到左邊,未找到則j下標--左移
					j--;
				}
				if(i < j){                    //若不是因為比較過頭,則說明找到了比key小的值,則將其填到左邊i坑裡
					array[i++] = array[j];//i從下一個開始繼續找
				}
				while(i<j && array[i] < key){ //從左端找到比key大的值移到右邊,未找到則i++右移
					i++;
				}
				if(i < j){                    //若不是因為比較過頭,則說明找到了比key大的值,則將其填到右邊j坑裡
					array[j--] = array[i];//j從下一個開始繼續找
				}
			}
			array[i] = key;                       //跳出迴圈,則說明i==j,最後一個坑留給key
			quickSort(array, left, i-1 );
			quickSort(array, i+1, right);
		}
	}
快速排序的時間效能取決於快速排序遞迴的深度,可以用遞迴樹來描述遞迴演算法的執行情況。如圖9‐9‐7所示,它是{50,10,90,30, 70,40,80,60,20}在快速排序過程中的遞迴過程。由於我們的第一個關鍵字是50,正好是待排序的序列的中間值,因此遞迴樹是平衡的,此時效能也比較好。
 
圖9-9-7
在最優情況下,Partition每次都劃分得很均勻,如果排序n個關鍵字,其遞迴樹的深度就為.log2n.+1(.x.表示不大於x的最大整數),即僅需遞迴log2n次,需要時間為T(n)的話,第一次Partiation應該是需要對整個陣列掃描一遍,做n次比較。然後,獲得的樞軸將陣列一分為二,那麼各自還需要T(n/2)的時間(注意是最好情況,所以平分兩半)。於是不斷地劃分下去,我們就有了下面的不等式推斷。
  1. T(n)≤2T(n/2) +n,T(1)=0  
  2. T(n)≤2(2T(n/4)+n/2) +n=4T(n/4)+2n  
  3. T(n)≤4(2T(n/8)+n/4) +2n=8T(n/8)+3n  
  4. ……  
  5. T(n)≤nT(1)+(log2n)×nO(nlogn) 

也就是說,在最優的情況下,快速排序演算法的時間複雜度為O(nlogn)。

在最壞的情況下,待排序的序列為正序或者逆序,每次劃分只得到一個比上一次劃分少一個記錄的子序列,注意另一個為空。如果遞迴樹畫出來,它就是一棵斜樹。此時需要執行n‐1次遞迴呼叫,且第i次劃分需要經過n‐i次關鍵字的比較才能找到第i個記錄,也就是樞軸的位置,因此比較次數為 ,最終其時間複雜度為O(n2)。

平均的情況,設樞軸的關鍵字應該在第k的位置(1≤k≤n),那麼:

 

由數學歸納法可證明,其數量級為O(nlogn)。

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

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

總結