算法的穩定性:如果待排序的兩個元素Ri,Rj,其對應的關鍵字keyi=keyj,且在排序前Ri在Rj的前面,如果排序后Ri還在Rj的前面,則稱這種排序算法是穩定的,否則稱排序算法是不穩定的。
內部排序和外部排序:內部排序是指在排序期間,元素全部存放在內存中的排序。外部排序是指排序期間元素無法全部同時存放在內存中,必須在排序過程中根據要求不斷地在內外存之間移動的排序。
1.插入排序
1)插入排序:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排好序的子序列中,直到全部記錄插入完成。
時間復雜度:O(n2),空間復雜度:O(1).穩定性:穩定的排序方法。
2)希爾排序:將待排序表分割成若個形如L[i,i+d,i+2d,i+3d,.....i+kd]的特殊子表,分別進行直接插入排序。當整個表呈基本有序時,在對全體記錄進行一次直接插入排序。
過程:先去一個小於n的步長d1,把表中全部記錄分成d1個組,所有距離為d1的倍數的記錄放在同一組中,在各組中進行直接插入排序。然后取第二個步長d2<d1.重復上述過程,直到di=1,即所有記錄在同一組中,再進行直接插入排序。
增量求法:目前不統一,一般采用d1=n/2,,
時間復雜度:當n在某個特定范圍時為
不穩定排序
2.交換排序
冒泡排序:將設待排序表長為n,從后往前兩兩比較相鄰元素的值,若為逆序,則交換他們,直到序列比較完。此為一趟冒泡。結果為將最小的元素交換到待排序的第一個位置。下一趟冒泡時,前一趟確定的最小元素不再參與比較,待排序列減少一個元素,每趟排序吧最小元素放到最終位置,這樣最多做n-1趟冒泡就把所有元素排好。
- 快速排序:快速排序是對冒泡排序的一種改進。其基本思想是基於分治法的:在待排序L[1....n]中任意取一個元素pivot作為基准,通過一趟排序將待排序表划分為獨立的兩部分L[1...k-1],L[k+1....n]使得L[1....k-1]中所有元素小於等於pivot,L[k+1...n]中所有元素大於pivot,則pivot則放置在最終位置上L(k),這個過程稱為一趟快速排序。而后分別遞歸的對兩個子表重復上述過程,直到每一部分內只有一個元素或空為止(所有元素放置在最終位置上)
過程:首先假定划分算法已知,記為partition(),返回上述中的k,L(k)已經在最終位置上,所以可以先對表進行划分,而后對表調用同樣的排序操作。遞歸的調用快速排序算法進行排序。程序結構如下:
1)兩個下標分別從首,尾向中間掃描的方法
假設每次都是以當前表中第一個元素作為樞紐值對表進行划分,則必須將表中比樞紐值大的元素向右移動,比樞紐值小的元素向左移動,使得一趟partition()操作后,表的元素被樞紐值一分為二。
若初始序列3,8,7,1,2,5,6,4排序過程如下:
2 8 7 1 2 5 6 4
2 8 7 1 8 5 6 4
2 1 7 1 8 5 6 4
2 1 7 7 8 5 6 4
2 1 3 7 8 5 6 4 //A[high]A[low]
2)兩個指針索引一前一后逐步向后掃描
快速排序是所有內部排序算法中平均性能最優的排序算法。在快速排序算法中,並不產生有序子序列,但每一趟排序后將一個元素(基准元素)放在其最終位置上。當初始排序表基本有序或基本逆序是,就得到最壞情況下的時間復雜度O(n2).
A快排一次排序的應用
A)區分數組中大小寫字母(編寫函數,讓小寫字母在所有大寫字母之前)
b)給定含n個元素的整型數組a,包含0和非0,對數組進行排序,使排序后滿足1.排序后的所有0元素在前,非零元素在后,且非零元素排序前后相對位置不變,不能使用額外的存儲空間。
c)荷蘭國旗問題
D)輸入n個整數,輸出其中最小的k個。
思路1:將輸入的n個數排序,這樣排在最前面的k個數就是最小的k個數。
思路2:假設最小的k個數中最大的為A。在快排中,先在數組中隨機選擇一個數字,然后調整數組中數字的順序,使得比選中數字小的數字排在他的左邊,比選中數字大的排在他的右邊(快排一次)
若選中的數字下表剛好是k-1(從0開始),那么這個數字(A)加上左側的k-1個數就是最小的k個數。如果他的小標大於k-1,則A位於他的左側,我們可以在他的左邊部分的數組中查找。若小標小於k-1,那么A應該位於他的右邊,我們可以接着在他的右邊部分中尋找。(發現這是一個遞歸問題,但是我們找到的k個數不一定是有序的)
3.選擇排序
思想:每一趟在后面n-i+1(i=1,2..n-1)個待排序元素中選取關鍵字最小的元素,作為有序子序列的第i個元素,直到n-1趟做完,待排序元素只剩下1就不用再選了。
1)簡單選擇排序
空間復雜度:O(1)。時間復雜度:元素移動較少不超過3(n-1)(一次swap三次元素移動)。最好移動0次(此時表已經有序)。但是元素間比較的次數與序列的初始狀態無關,始終為n(n-1)/2次。時間復雜度為O(n2).
2)堆排序
堆排序是一種樹形選擇排序方法,在排序過程中將L[1..n]視為一棵完全二叉樹的順序村粗結構。利用完全二叉樹中雙親結點和孩子結點之間的內在關系,在當前無序區中選擇關鍵字最大(或最小)的元素。
堆排序的實質是構建初始堆,對初始序列建堆,就是一個反復篩選的過程。
A)根據初始關鍵字序列(20,18,22,16,30,19)構建初始大根堆。
在元素個數為n的序列上建堆,其時間復雜度為O(n),這說明可以在線性時間內,將一個無序數組建成一個大頂堆。
B)堆排序的思想
由於堆本身的特點(以大頂堆為例),堆頂元素就是最大值。輸出堆頂元素后,通常將堆底元素放入堆頂,此時根節點已不滿足堆的性質,將堆頂元素向下調整繼續保持大頂堆性質,輸出堆頂元素,重復,直到僅剩一個元素為止。
C)堆的插入和刪除
刪除堆頂元素時,先將堆的最后一個元素與堆頂元素交換,有序性質破壞,需要堆根結點進行向下調整。
對堆進行插入操作時,先將新結點放在堆的末端,再對這個新結點執行向上調整操作,大頂堆插入操作如下圖所示:
向上調整算法如下所示:
D)堆排序的應用(最小k個數)
輸入n個整數,輸出其中最小的k個.(用堆排序來解決,適合處理海量數據)
思路:首先讀入k個數創建一個大小為k的大頂堆,然后依此讀入剩余數據,如果當前數據比大頂堆的堆頂小,則用這個數代替當前堆頂元素,並調整時期保持大頂堆性質,如果當前數據比堆頂大,則此數不可能為最小的k個整數之一,故拋棄此數。(時間復雜度:O(nlogk))
4.歸並排序
- 二路歸並排序(內部排序,基於分治算法的,使用輔助空間)
含義:將兩個或兩個以上的有序表組合成一個新的有序表。假定待排序表含有n個記錄,則可視為n個有序子表,每個子表長度為1,兩兩歸並,得到
過程:分解:將n個元素的待排序表分成各含n/2個元素的子表,采用二路歸並算法對兩個子表遞歸的進行排序。
合並:合並兩個已排序的子表得到排序結果。
Merge()的功能時將前后相鄰的兩個有序表歸並為一個有序表的算法。設兩段有序表A【low...mid】A[mid+1...high]存放在同一順序表中相鄰的位置上,先將他們復制到輔助數組B中,每次從對應B中的兩個段取出一個記錄進行關鍵字比較,將較小者放入A中,當輸入B中有一段超出其表長,則將另一段剩余部分直接復制到A中。
A)合並兩個排好序的鏈表(連個遞增排序鏈表,合並他們使新鏈表結點仍然是按照遞增排序的)
b)給定有序數組a,b.已知數組a末尾有足夠空間容納b,請實現將b合並到a中。函數頭如下:
Void merge(int a[],int b[],int n,int m)//n為數組a的元素個數,m為數組b的元素個數
思路:先計算總元素個數,從數組末尾(最大元素)開始歸並。
C)原地歸並排序(二叉歸並排序 內部排序,不適用輔助空間)
原地歸並排序不需要輔助數組即可歸並。關鍵在merge這個函數。假設有兩段遞增的子數組arr[begin....mid-1]和arr[mid...end],但整個數組不是遞增的。其中i=begin,j=mid,k=end.
然后把i到mid-1的部分和mid到j-1的部分對調(可通過三次逆序實現)較小部分就調到前面去了,此時數組變為0 1 2 3 4 5 6 9 7 8(前面有序了,后面又是兩個遞增子數組,繼續迭代即可)
D)多路歸並排序(外部排序)
外部排序是指大文件的排序,即待排序的記錄存儲在外部存儲器上,待排序的文件無法一次裝入內存,需要在內存和外部存儲器之間進行多次數據交換,以達到排序整個文件的目的。
思路:外部排序最常用的算法是多路歸並排序,即將源文件分解成多個能夠一次性裝入內存的部分,分別把每一部分調入內存完成排序,然后對已排序的子文件進行歸並排序。
從二路到多路,增大k可以減少外存信息讀寫時間,但k個歸並段中選擇最小的記錄需要比較k-1次,為了降低選出每個記錄需要的比較次數k,引入敗者數。
敗者樹可視為一棵完全二叉樹,每個葉結點存放各歸並段在歸並過程中當前參加比較的記錄,內部結點用來記憶左右子樹中的失敗者,讓勝者網上繼續進行比較,一直到根節點。如果比較兩個數,大的為失敗者,小的為勝利者,則根節點指向的數為最小數。
圖中第一個葉子結點為b0.k路歸並的敗者樹深度為
案例:有20個有序數組,每個數組有500個unsigned int元素,降序排序。要求從這10000個元素中選出最大的500ge.
思路:依此從20個有序數組中選擇一個當前元素,兩兩比較,然后找出最大的數,循環500次,即可選擇出500個最大的數。但是這里每選擇一個最大元素,需要比較19次,效率低。
改進方法1:利用堆,從20個數組中各取一個數,並記錄每個數的來源數組,建立一個含有20個元素的大頂堆。此時堆頂就是最大元素,去除堆頂元素,並從堆頂元素的來源數組中取下一個元素加入堆,調整堆后再取最大值,一直這樣進行500次即可。時間復雜度
改進方法2:利用敗者樹。從20個數組中各取一個數,並記錄每個數的來源數組,建立一個20路歸並的敗者樹。此時敗者樹輸出的就是最大的數,然后從最大數的來源數組繼續取下一個數加入敗者樹,繼續比較,直到輸出500個數為止。時間復雜度為
不同排序算法的比較
總結:
1.比較次數和初始排列無關的是選擇排序。
2.在初始序列基本有序的情況下,最優的是插入排序,此時插入排序時間復雜度為O(n),其次是冒泡排序,時間復雜度也為O(n).快速排序此時性能最差,時間復雜度為
3.堆排序對初始數據集的排列順序不敏感,在最好,最壞和平均情況下,堆排序的時間復雜度為
4.節儉排序,一對數字不進行兩次或兩次以上的比較。包括(插入排序,歸並排序)
5.基於比較的排序算法時間復雜度的下界(最好的時間復雜度)為: