高速排序(quick sort)也是一種交換排序,它在排序中採取了分治策略。
從待排序列中選取一元素作為軸值(也叫主元)。
將序列中的剩餘元素以該軸值為基準,分為左右兩部分。左部分元素不大於軸值,右部分元素不小於軸值。軸值終於位於兩部分的切割處。
對左右兩部分反覆進行這種切割,直至無可切割。
從高速排序的演算法思想能夠看出,這是一遞迴的過程。
要想徹底弄懂高速排序,得解決兩個問題:
怎樣選擇軸值?(軸值不同,對排序有影響嗎?)
怎樣切割?
問題一:軸值的選取?
軸值的重要性在於:經過切割應使序列盡量分為長度相等的兩個部分,這樣分治法才會起作用。若是軸值正好為序列的最值,切割後,元素統統跑到一邊兒去了,分治法就無效了。演算法效率無法提高。-看別人寫快排的時候,注意他軸值的選取哦。
問題二:怎樣切割?
這涉及到詳細的技巧和策略。在稍後的**中我們一一介紹。
直接選取第乙個元素或最後乙個元素為軸值。這也是國內眾多教材中的寫法。
舉個樣例:
原序列 4 8 12 1 9 6
下標 0 1 2 3 4 5
軸值 pivot=4
初始化 i j
i j i不動,移動j,while(i=pivot)j--;
移動元素
1 8 12 1 9 6
i j j不動,移動i,while(i
移動元素 1 8 12 8 9 6
i,j 再次移動j,i和j相遇,結束
最後一步 1 4 12 8 9 6 pivot歸位
軸值4的左右兩部分接著切割……
我想你一定看懂了,而且這軸值4,真的沒選好,由於切割後左部分僅僅有乙個元素。
有人稱上面的做法是:挖坑填數。這樣的描寫敘述真的非常形象。簡單解釋下:首先取首元素為軸值,用變數pivot儲存軸值,這就是挖了乙個坑。此時,a[0]就是乙個坑。接著移動j,把合適位置的j填入a[0],於是a[j]成了新的坑。舊的坑被填上,新的坑就出現。直到i和j相遇,這最後乙個坑,被pivot填上。至此完畢了第一趟切割……
看懂了,就動手寫程式吧!
void quicksort(int a, int n) //高速排序,版本號一
a[i] = pivot; //把軸值放到切割處
quicksort(a, i);
quicksort(a + i + 1, n - i -1);
}}
如今想想以最後乙個元素為軸值的**了,先別急著看,先動動手哦!**例如以下:
void quicksort(int a, int n)
a[i] = pivot; //把軸值放到切割處
quicksort(a, i);
quicksort(a + i + 1, n - i - 1);
}}
為了讓軸值pivot不至於無效(不讓pivot出現最值的情況)。我們能夠使用一些策略來改進pivot的選取。
策略一:
隨機選取序列中一元素為軸值。
int selectpivot(int a, int low, int high)
選取首尾元素不就是該策略的一種特例!
策略二:
隨機選取三數,取中位數。
int selectpivot(int a, int low, int high)
它的一種特例就是,選取原序列首、
尾、中間三數,取它們的中位數。
眼下看來基本經常使用的就這兩種策略。
只是我得吐槽一句:假設原序列中的元素本身就是隨機存放的,也就是說,各個元素出如今各個位置的概率一樣。那麼特別地選取首尾元素和隨機選取又有什麼差別呢?不知大家怎麼看?
還得補充一句:隨機選取軸值後,記得要把它和首或尾的元素交換哦。至於為什麼?***!
這也是《演算法導論》上的版本號。它的普遍做法是選取尾元素為pivot。重點是使用了乙個切割函式:partition()。
偽**與例如以下:
partition(a, low, high)
1. pivot <- a[high] //選取尾元素為軸值
2. i <- low-1 //把low-1賦值給i,下同
3. for j <- low to high-1 //j的變化範圍[low, high-1]
4. do if a[j] <= pivot
5. then i <- i+1
6. exchange a[i]<->a[j]
7. exchange a[i+1} <
-> a[high]
8. return i+1; //返回的是切割的位置
然後,對整個陣列進行遞迴排序:
quicksort(a, low, high)
1 if low < high
2 then q <- partition(a, low, high) //對元素進行切割就在這裡
3 quicksort(a, low, q - 1)
4 quicksort(a, q + 1, high)
假設你不習慣於看偽**,我來舉個樣例:(還是上面的序列)
原序列 4 8 12 1 9 6
下標 -1 0 1 2 3 4 5 軸值pivot是6
初始化 i j a[j]=a[0]=4<6,下一步先 i++;再swap(a[i],a[j]);隨後j++;
交換 4 8 12 1 9 6
i j 接著移動j
i j a[j]=a[3]=1<6,下一步…
交換 4 1 12 8 9 6
i j
i j
交換 4 1 6 8 9 12 最後一步 swap(a[i+1], a[high]);或者是 swap(a[i+1], a[j]);
所以最後返回的是 i+1
用大白話講講上面的排序過程:用兩個指標i,j,它們初始化為i=-1;j=0,接著讓j不斷地自增,遇到a[j]>pivot就與i交換,直到j指向末尾。
更直白的話:從頭開始遍歷原序列,遇到小於軸值的就交換到序列前面。
看懂了,就寫**了…
int partition(int a, int low, int high)
swap(a[++i], a[high]); //主元歸位
return i; //上面一步已經 ++i,所以這裡不用 i+1
}void quicksort(int a, int low, int high)
}void quicksort(int a, int n)
題外話:看到有的api設計是這種:quicksort(int a, int low, int high)。竟然讓使用者多寫乙個0!如此不為使用者考慮。應越簡潔越好。排序僅僅給陣列名和陣列大小,就可以。
對上面的流程再思考:看到初始化i=-1;你不認為奇怪嗎?為什麼i一定要從-1開始,細緻了解了i的作用,你會發現i本能夠從0開始。這種做法的partition()方法是這種:
int partition(int a, int low, int high)
swap(a[i], a[high]); //主元歸位
return i;
}
再思考:為什麼j不能指向high?若是更改if(a[j]
此時的partition()是這種:
int partition(int a, int low, int high)
return i-1; //這裡為什麼是i-1,得想明確?
}
至於有時候把quicksort()和partition()寫成乙個函式,那是再簡單只是的事情,你肯定會的。
上面用的都是遞迴的方法,把遞迴轉化非遞迴總是不簡單的,也總讓人興奮。這個版本號就是高速排序的非遞迴寫法;
void quicksort(int a, int low, int high)
if (mid + 1 < high)
//僅僅要棧不為空,說明仍有可切割的部分
while(!s.empty())
if (mid + 1 < h)
} }}
這個非遞迴的寫法是非常有意思的,非常須要技巧。細緻想想,你能明確的。
高速排序號稱高速搞定,時間複雜度是o(nlogn)。基本上是最優的排序方法。它的寫法不外乎以上三種,大同小異。看到這裡。你一定徹底了解了它。以上寫法,都經過了本人測試,不知道你的測試是否和我一樣?
若是有所幫助,頂乙個哦!
本專欄的資料夾
全部內容的資料夾
排序 交換排序
交換排序 利用交換位置進行排序 1 演算法思想 重複的走訪要排序的元素,依次比較相鄰兩個元素的大小,如果順序錯誤則交換這兩個元素的位置,直到不需要在比較 2 步驟 比較相鄰兩個元素,如果前乙個比後乙個大,則交換位置 從第乙個元素一直比較到最後乙個元素,這時最後乙個元素的值是最大的 減掉最後乙個元素,...
歸併 高速分揀
我們都知道stl排序庫函式最常用的 sort v.begin biend 這是由於在以公升序。我一般不喜歡用乙個迭代器,我一般用這種格式。排序的陣列,替代指標迭代器。sort a,a n 公升序排序 sort a,a n,cpm int cmp type a,type b 這裡我們來手動實現歸併排序...
交換排序演算法
快速排序 為啥叫快速排序,因為速度快,效率高 1.先找乙個數作為基準。作為基準的這個數,一趟排下來,左邊的數必小於它,右邊的數必大於它,也就是說,它找到了自己的位置。2.將兩個指標i,j分別指向表的起始 基準 和最後的位置。3.比較j指標的數字是否小於基準,j 直到j小於基準,交換位置 4.比較i指...