1. 程式人生 > >1015:基礎排序演算法

1015:基礎排序演算法

目錄

一、幾種經典排序演算法及其時間複雜度級別

二、如何分析一個排序演算法?

三、氣泡排序

四、插入排序

五、選擇排序


總結:氣泡排序、插入排序和選擇排序

一、幾種經典排序演算法及其時間複雜度級別

冒泡、插入、選擇 O(n^2) 基於比較

快排、歸併 O(nlogn) 基於比較

計數、基數、桶 O(n) 不基於比較

二、如何分析一個排序演算法?

1.學習排序演算法的思路?

明確原理、掌握實現以及分析效能。

2.如何分析排序演算法效能?

從執行效率、記憶體消耗以及穩定性3個方面分析排序演算法的效能。

3.執行效率:

從以下3個方面來衡量

1)最好情況、最壞情況、平均情況時間複雜度

2)時間複雜度的係數、常數、低階:排序的資料量比較小時考慮

3)比較次數和交換(或移動)次數

4.記憶體消耗:通過空間複雜度來衡量。針對排序演算法的空間複雜度,引入原地排序的概念,原地排序演算法就是指空間複雜度為O(1)的排序演算法。

5.穩定性:如果待排序的序列中存在值等的元素,經過排序之後,相等元素之間原有的先後順序不變,就說明這個排序演算法時穩定的。

三、氣泡排序

1.排序原理

1)氣泡排序只會操作相鄰的兩個資料。

2)對相鄰兩個資料進行比較,看是否滿足大小關係要求,若不滿足讓它倆互換。

3)一次冒泡會讓至少一個元素移動到它應該在的位置,重複n次,就完成了n個數據的排序工作。

4)優化:若某次冒泡不存在資料交換,則說明已經達到完全有序,所以終止冒泡。

2.程式碼實現

/**
 * 氣泡排序
 * @param a 待排序陣列
 * @param n 陣列長度
 */
public static void bubbleSort(int[] a, int n) {
if(n<=0) return ;
	for (int i = 0; i < n; i++) {
		//標記一次冒泡是否存在資料交換,若存在,則改為true
		boolean tag = false;
		for (int j = 0; j < n-1-i; j++) {
			if(a[j] > a[j+1]){
				int temp = a[j];
				a[j] = a[j+1];
				a[j+1] = temp;
				tag = true;
			}
		}
		//若本次冒泡操作未發生資料交換,則終止冒泡操作
		if (tag == false) break;
	}
}

3.效能分析

1)執行效率:最小時間複雜度、最大時間複雜度、平均時間複雜度

最小時間複雜度:資料完全有序時,只需進行一次冒泡操作即可,時間複雜度是O(n)。

最大時間複雜度:資料倒序排序時,需要n次冒泡操作,時間複雜度是O(n^2)。

平均時間複雜度:通過有序度和逆序度來分析。

什麼是有序度?

有序度是陣列中具有有序關係的元素對的個數,比如[2,4,3,1,5,6]這組資料的有序度就是11,分別是[2,4][2,3][2,5][2,6][4,5][4,6][3,5][3,6][1,5][1,6][5,6]。同理,對於一個倒序陣列,比如[6,5,4,3,2,1],有序度是0;對於一個完全有序的陣列,比如[1,2,3,4,5,6],有序度為n*(n-1)/2,也就是15,完全有序的情況稱為滿有序度。

什麼是逆序度?逆序度的定義正好和有序度相反。核心公式:逆序度=滿有序度-有序度。

排序過程,就是有序度增加,逆序度減少的過程,最後達到滿有序度,就說明排序完成了。

氣泡排序包含兩個操作原子,即比較和交換,每交換一次,有序度加1。不管演算法如何改進,交換的次數總是確定的,即逆序度。

對於包含n個數據的陣列進行氣泡排序,平均交換次數是多少呢?最壞的情況初始有序度為0,所以要進行n*(n-1)/2交換。最好情況下,初始狀態有序度是n*(n-1)/2,就不需要進行互動。我們可以取箇中間值n*(n-1)/4,來表示初始有序度既不是很高也不是很低的平均情況。

換句話說,平均情況下,需要n*(n-1)/4次交換操作,比較操作可定比交換操作多,而複雜度的上限是O(n^2),所以平均情況時間複雜度就是O(n^2)。

以上的分析並不嚴格,但很實用,這就夠了。

2)空間複雜度:每次交換僅需1個臨時變數,故空間複雜度為O(1),是原地排序演算法。

3)演算法穩定性:如果兩個值相等,就不會交換位置,故是穩定排序演算法。

四、插入排序

1.演算法原理

首先,我們將陣列中的資料分為2個區間,即已排序區間和未排序區間。初始已排序區間只有一個元素,就是陣列的第一個元素。插入演算法的核心思想就是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間中的元素一直有序。重複這個過程,直到未排序中元素為空,演算法結束。

2.程式碼實現

/**
 * 插入排序
 * @param a 待排序陣列
 * @param n 表示陣列大小
 */
public static void insertSort(int[] a, int n) {
    if(n<=1) return;
    for(int i=1;i<n;i++){
        int value=a[i];
        int j=i-1;
        //找到插入位置
        for(;j>0;j--){
            if(a[j]>value){
                a[j+1]=a[j];//移動資料
            } else {
                break;
            }
        }
        a[j+1]=value;//插入資料
     }
}

3.效能分析

1)時間複雜度:最好、最壞、平均情況

如果要排序的陣列已經是有序的,我們並不需要搬移任何資料。只需要遍歷一遍陣列即可,所以時間複雜度是O(n)。如果陣列是倒序的,每次插入都相當於在陣列的第一個位置插入新的資料,所以需要移動大量的資料,因此時間複雜度是O(n^2)。而在一個數組中插入一個元素的平均時間複雜都是O(n),插入排序需要n次插入,所以平均時間複雜度是O(n^2)。

2)空間複雜度:從上面的程式碼可以看出,插入排序演算法的執行並不需要額外的儲存空間,所以空間複雜度是O(1),是原地排序演算法。

3)演算法穩定性:在插入排序中,對於值相同的元素,我們可以選擇將後面出現的元素,插入到前面出現的元素的後面,這樣就保持原有的順序不變,所以是穩定的。

五、選擇排序

1.演算法原理

選擇排序演算法也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,並將其放置到已排序區間的末尾。

2.程式碼實現

/**
 * 選擇排序
 * @param a 待排序陣列
 * @param n 陣列長度
 */
public static void selectSort(int[] a, int n) {
	if(n<=0) return;
    for(int i=0;i<n;i++){
        int min=i;
        for(int j=i;j<n;j++){
            if(a[j] < a[min]) min=j;
        }
        if(min != i){
            int temp=a[i];
            a[i]=a[min];
            a[min]=temp;
        }
    }
}

3.效能分析

1)時間複雜度:最好、最壞、平均情況

選擇排序的最好、最壞、平均情況時間複雜度都是O(n^2)。為什麼?因為無論是否有序,每個迴圈都會完整執行,沒得商量。

2)空間複雜度:

選擇排序演算法空間複雜度是O(1),是一種原地排序演算法。

3)演算法穩定性:

選擇排序演算法不是一種穩定排序演算法,比如[5,8,5,2,9]這個陣列,使用選擇排序演算法第一次找到的最小元素就是2,與第一個位置的元素5交換位置, 那第一個5和中間的5的順序就變數,所以就不穩定了。正因如此,相對於氣泡排序和插入排序,選擇排序就稍微遜色了。

六、思考

1.氣泡排序和插入排序的時間複雜度都是 O(n^2),都是原地排序演算法,為什麼插入排序要比氣泡排序更受歡迎呢?

氣泡排序移動資料有3條賦值語句,而選擇排序的交換位置的只有1條賦值語句,因此在有序度相同的情況下,氣泡排序時間複雜度是選擇排序的3倍,所以,選擇排序效能更好。

2.如果資料儲存在連結串列中,這三種排序演算法還能工作嗎?如果能,那相應的時間、空間複雜度又是多少呢?