我們聊聊快排吧

2022-07-03 08:45:13 字數 3672 閱讀 9818

最近一直在看《程式設計珠璣》第二版這一本書,裡面的東西真的很實用,以前也看過不少講解快排的書,但是在程式設計珠璣上看到的講解是我見過最好理解,也是最詳細的,從效率和空間以及實現等各個方面都做了詳細說明,並比較了幾種變形的快排的效率,所以在這把我看到的內容寫出來記錄,留著以後忘了的時候看。

1.1.插入排序

首先說一下插入排序,這個會在最後的變形快排中用到,插入排序類似於整理撲克牌的方式,假設之前的序列已經有序,當拿到乙個新的數字的時候,只要將其插入到之前的合適位置即可,直到所有資料都被處理完,整個數列就有序了,由插入排序的思想可以分析當原序列基本有序時,插入排序交換的次數是非常少的,所以對於基本有序的序列來說採用插入排序是非常高效的,資料型別對程式程式結構的選擇是起到很大的決定性的。插入排序的**如下:

1

insort()

2

1.2.最簡單的快排程式還是說一下快排的基本原理吧,快排的思想基於分治法,首先將陣列分成兩個小部分,使陣列的前一部分值都小於某乙個哨兵值t,後半部分都大於t,然後再遞迴的進行快排這兩個子陣列,直到陣列的元素只剩下乙個。在最簡單的快排中這樣的哨兵值設定為字陣列中的第乙個值x[l].

最簡單的快排劃分陣列部分的偽**如下:

1 m = l

2for i =[l,u]

3if x[i]

4 swap(x,++m,i);

下面利用圖示來進行講解,這樣更直觀。

如上圖所示,假設這是進行了幾次簡單快排後的陣列的狀態,當掃瞄位置到位置 i 時,如果x[i] < 哨兵值x[l]則需要交換m的下乙個位置和位置i的值,m的下乙個位置指向的是第乙個比哨兵值x[l]大的位置.

當迴圈終止的時候情況如下:

如上圖所示此時整個陣列的所有元素都已經劃分好了,此時m指向的是最後乙個比t小的元素的位置,所以只需要交換一下哨兵位置和m位置的值就得到了陣列的有序序列,如下圖所示。

下面附上簡單快排程式的完整的**:

1

void qsort1(int l,intu)2

78int i,m =l;

9for(i = l + 1; i <= u, i++)

1015}16

17swap(x,l,m);

1819 qsort1(l,m - 1

);20 qsort1(m + 1

,u);

21 }

1.3.雙向劃分的快排程式雙向劃分就是設定兩個游標分別從陣列的左側,和右側開始,左側每次找到比哨兵值t(現在為x[0])大的位置,右側每次找到比哨兵值t小的位置,如果兩個下標沒有交叉就交換他們的值,直到,兩個游標產生交叉。這種思想可以避免在陣列中所有元素都相同時產生平方時間的演算法,而是比較差不多n*logn的次數即可,同時可以減少總的比較次數。

雙向劃分的圖示如下:

雙向劃分的快排程式的**如下:

1

void qsort2(int l,intu)2

78int t = x[l],i = l,j =u;910

while (i

11

2223 x[i] =t;

2425 qsort2(l,i - 1

);26 qsort2(i + 1

,u);

27 }

1.4.哨兵隨機,小陣列不處理快排當陣列已經按公升序排好序時,會導致快排是o(n2)時間複雜度的,所以使用哨兵值隨機劃分,可以避免這種情況,方法是把x[l]和x[l..u]中的乙個隨機項來進行交換,然後在設定x[l]為劃分陣列哨兵值,實現函式為swap(x[l],x[rand(l,u)])。

而且當快排進行劃分到每個子陣列很小時,原來的快排程式花費了大量的時間來排序這小很小的子陣列,如果這時用1.1中所介紹的插入排序來排序陣列會非常有效,因為當用快排排序到後面的小陣列階段時,陣列已經基本有序了,而插入排序對基本有序的陣列排序是非常快的,在《程式設計珠璣》第二版這本書中,作者做了實驗,驗證出這個小陣列的值邊界設定為50時,然後再呼叫插入排序,變形過得快排程式處理最快。

**如下:

1

#define samll_cutoff 5023

void qsort3(int l,intu)4

1011

swap(x,l,rand(l,u));

12int t = x[l],i = l,j =u;

1314

while (i

15

2627 x[i] =t;

2829 qsort3(l,i - 1

);30 qsort3(i + 1

,u);

31 }

對該快排函式呼叫時方式如下:

1 qsort3(0,n-1

);2 insort();

1.5.作者進行了效率對比如下表:程式時間(納秒)

c庫函式qsort

137n logn

qsort1

60 n logn

qsort2

44n logn

qsort3

36n logn

c++庫函式sort

30n logn

l1.6.線性選擇問題(選出陣列中第k小的元素)

線性選擇問題可以基於堆排序或者快排做,以前在計算機演算法設計與分析那本書上看過這個問題,就放在這裡一起記錄吧。

解決思路:

由於每次快排將元素分為左右兩部分,左邊都比哨兵值小,右邊都比哨兵值大,所以可以基於這個特性,在每次快排後對哨兵最後交換的位置m進行判斷,如果m比k大說明第k小的元素在左邊,只需在左邊子陣列繼續邊遞迴找第k小的元素即可,如果m比k小則說明第k小的值在右邊,則需要在右邊子陣列中找第k-m小的元素(由於左側的m個元素都比k小),直到遞迴結束後返回x[k-1](因為陣列下標從0開始)即可。

基於快排的線性選擇**如下:

1

void kth_select(int l,int

u,int k)28

9swap(x,l,rand(l,u));

10int t = x[l],i = l,j =u;

1112

while (i

13

2425 x[i] =t;

2627

if(j

28

31else

if(j >k)

3235 }

該函式的呼叫如下,kth_select(0,u - 1,k-1);(陣列下標從0開始)

我們來聊聊物件導向吧 一

前言 什麼是物件導向,對於初學者其實就是個噩夢,但是其實物件導向,也不可怕,我個人理解是 變數把具有一定作用的資料存起來,以便後面使用,而函式則是將經常使用的功能封裝起來以便後面使用,而物件導向的思想其實把擁有重複使用的變數和函式組裝起來的一種思想,分隔成一小塊塊的 以便管理和日後的維護.繼承 子類...

快排2 經典快排和荷蘭國旗快排

基礎知識見 建議先閱讀基礎知識,並自己手推一遍 演算法原理 第一步 取陣列最後乙個數作為num,將陣列中的 num的數放在陣列的左邊,num的數放在陣列的右邊,這是可以理解為分成了兩個陣列 第二步 然後將 num的部分當成乙個陣列,繼續第一步 num的部分同理 第三步 若陣列的大小 2,則結束。流程...

聊聊位運算吧

先說說 什麼是位運算,程式中的所有數在計算機記憶體中都是以二進位制的形式儲存的。位運算就是直接對整數在記憶體中的二進位制位進行操作。說白的位運算就是將整數在記憶體中的二進位制的進行運算。運算符號 意義運算物件型別 運算結果型別 物件數例項 位邏輯非運算 整型,字元型整型1 a 位邏輯與運算 2a b...