排序算法分析


我們都知道,在應屆面試的時候,問到最多的就是快速排序,快速排序是最經典、最常用的排序算法,因為它的平均效率最優,也最穩定。

快速排序使用了分治的算法思想,分治算法本身理解起來很符合人類的思路(遞歸是很容易被理解的),其最關鍵的一步,就是划分了,算法導論上介紹了一種划分方法,和我在數據結構課上學的略有不同,昨晚,我把它們全都用java實現了。

這個是算法導論的版本:

 1 /**
2 * 快速排序的划分方法一:算法導論版本 sit的下一個比最后一個大
3 *
4 * @param begin
5 * @param end
6 * @return
7 */
8 private int QuickMergeA(int begin, int end) {
9 System.out.print("初始:" + arr);
10 System.out.print(begin + " " + end);
11 int anchor = (int) arr.get(end);
12 int sit = begin - 1;
13 int temp;
14 for (int i = begin; i < end; i++) {
15 if ((int) arr.get(i) <= anchor) {
16 sit++;
17 temp = (int) arr.get(i);
18 arr.set(i, (int) arr.get(sit));
19 arr.set(sit, temp);
20 }
21 }
22 temp = (int) arr.get(sit + 1);
23 arr.set(sit + 1, anchor);
24 arr.set(end, temp);
25 System.out.print(arr);
26 System.out.println(sit + 1);
27 return sit + 1;
28 }

不難看出,這個版本的划分思路是,從數組一端往另一端推進,對於比指定元素小的值,均放在左側,比指定元素大的值,放在右側。

然后我們再看我數據結構課本上學過的版本:

/**
* 快速排序的划分方法二:數據結構課本版本
*
*
@param begin
*
@param end
*
@return
*/
private int QuickMergeB(int begin, int end) {
System.out.print(
"初始:" + arr);
System.out.print(begin
+ " " + end);
int anchor = arr.get(end);
int send = end - 1;
int temp;
int tbegin = begin;
while (begin <= send) {
while (arr.get(begin) <= anchor && begin <= end - 1) {
begin
++;
}
while (send >= tbegin && arr.get(send) > anchor) {
send
--;
}
if (begin < send) {
temp
= arr.get(begin);
arr.set(begin, arr.get(send));
arr.set(send, temp);
}
if (begin >= send) {
temp
= arr.get(begin);
arr.set(begin, arr.get(end));
arr.set(end, temp);
}
}
System.out.print(arr);
System.out.println(send
+ 1);
return send + 1;
}

可以看出,這種思路是兩端推進的手段,兩端同時推進,同樣是左邊存放比指定元素小的值,右邊存放比指定元素大的值。

 

這兩種方法遍歷數組的推進方法雖然不同,但是其效率是一致的,划分一次都相當於是要遍歷一次子數組。其划分的包裝入口也是相似的:

/**
* 快速排序入口
*
*
@param begin
*
@param end
*/
private void QuickSequence(int begin, int end) {
// if (begin < end) {
// int q = QuickMergeA(begin, end);
// this.QuickSequence(begin, q - 1);
// this.QuickSequence(q + 1, end);
// }
if (begin < end) {
int q = QuickMergeB(begin, end);
this.QuickSequence(begin, q - 1);
this.QuickSequence(q + 1, end);
}

}

需要指明的是,測試上面方法的過程中,應該把arr數組設置為靜態的,在java中,實現類似於C++的地址訪問的情況,我經常使用靜態變量。

 

最古老的排序算法是插入排序,之前學習數據結構的時候,對於各種排序算法總是混淆,比如對於快速排序和插入排序的算法思路都明白,但是總是把快速排序當成插入排序,插入排序當成快速排序。也許是當時兩節課學了冒泡排序、快速排序、插入排序、堆排序等太多的排序算法吧,加上編程經驗少造成的。

 

其實插入排序很容易和快速排序區分開。因為插入排序是真的“插入”。插入排序的基礎是左側數據已經排序准確,你要做的是把下一個數插入到有序數組的指定位置,你可以腦補撲克牌。

/**
* 插入排序
*/
private void InsertSequence() {
for (int i = 0; i < arr.size(); i++) {
int p = 0;
while (p < i && arr.get(p) < arr.get(i)) {
p
++;
}
if (p < i) {
int temp = arr.get(i);
for (int k = i; k > p; k--) {
arr.set(k, arr.get(k
- 1));
}
arr.set(p, temp);
}
}
}

插入排序的代碼很短,但是我們的經驗是,短的代碼代表思路簡單,不代表效率高。其實是這樣的,插入排序的效率是N^2,而快速排序的效率則是NlogN。插入排序是最朴素的排序算法,也是相對較慢的排序算法。

 

學數據結構的課程時,我非常喜歡堆排序。或許是因為它是最形象的排序算法。其實,堆排序利用特有的數據結構,高效的做了這么一件事,每次找出最小的數,然后從數組中刪掉這個數,繼續查找。

 1 private void MaintainHeap(int length) {
2 int temp;
3 for (int i = length / 2 - 1; i >= 0; i--) {
4 if ((arr.get(i) < arr.get((i + 1) * 2 - 1))
5 || ((i + 1) * 2 < length && arr.get(i) < arr
6 .get((i + 1) * 2))) {
7 if ((i + 1) * 2 < length) {
8 if (arr.get((i + 1) * 2) < arr.get((i + 1) * 2 - 1)) {
9 temp = arr.get(i);
10 arr.set(i, arr.get((i + 1) * 2 - 1));
11 arr.set((i + 1) * 2 - 1, temp);
12 } else {
13 temp = arr.get(i);
14 arr.set(i, arr.get((i + 1) * 2));
15 arr.set((i + 1) * 2, temp);
16 }
17
18 } else {
19 temp = arr.get(i);
20 arr.set(i, arr.get((i + 1) * 2 - 1));
21 arr.set((i + 1) * 2 - 1, temp);
22 }
23
24 }
25 }
26
27 System.out.println(arr);
28 }
29
30 private void Heapsequence(int length) {
31 for (int i = length; i > 0; i--) {
32 this.MaintainHeap(i);
33 int temp = arr.get(0);
34 arr.set(0,arr.get(i - 1));
35 arr.set(i - 1, temp);
36 }
37 }

堆排序的重要步驟是堆的維護。對於最大堆而言,要確保特定個元素構建的堆中,每個節點的值都大於其孩子節點,這通常的方法是,從最后一個有子節點的節點開始,遍歷到根節點,對於這其間的每一個結點,如果其子節點存在大於當前節點的節點,則把最大的子節點同當前節點交換,並同時維護交換后的子節點的狀態(這個子節點的子節點可能在交換后比子節點的值大)。

 

除了上面的排序算法,我們常見的還有冒泡排序之類。同時,我在算法導論上,還接觸過計數排序、基數排序、桶排序等線性排序算法。因為其思路比較簡單,所以這里只給出一般思路:

計數排序顧名思義,使用了一個計數數組,對於每一個值,初始計數為0,每新增一個,該計數就增加一,最后形成一個排序序列。計數排序的效率一般,但是由於其穩定,常常作為其它排序算法基本過程的算法使用。

基數排序是另外一種比較有意思的排序算法,它先從最低位對數據進行排序,然后再依次上升到最高位,而對每個數位的排序,他通常使用計數排序。

桶排序是效率最高的排序算法,但是它對數據要求較高,同時是一種以空間換時間的排序算法。它的方案是先new一個足夠大的數組,然后對待排序數組遍歷,將新數組鍵等於待排序數組中值的位置置為一(多個則繼續增一),然后遍歷新數組,即可得到待排序數組的順序。這種算法不適合離散性數據,也不適合不知道范圍的數據排序。

 

對於常規排序算法,冒泡排序適合只有少數數據不在正確位置上的數據,快速排序的平均效率最高,堆排序性能極不穩定,插入排序的效率較差。

 

以上。

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com