詳解C語(yǔ)言快速排序三種方法的單趟實(shí)現(xiàn)
交換排序的思想
基本思想:所謂交換,就是根據(jù)序列中兩個(gè)記錄鍵值的比較結(jié)果來(lái)對(duì)換這兩個(gè)記錄在序列中的位置,交換排 序的特點(diǎn)是:將鍵值較大的記錄向序列的尾部移動(dòng),鍵值較小的記錄向序列的前部移動(dòng)。
冒泡排序的思想
冒泡排序比較簡(jiǎn)單,我們之前使用也很多,簡(jiǎn)單講解,就是比較兩個(gè)數(shù),如果比他大就交換,沒(méi)有他大就接著向后比,一直到數(shù)組結(jié)束,這是單趟排序,多趟就是控制好下標(biāo),進(jìn)行循環(huán)即可。
冒泡代碼實(shí)現(xiàn)
void Swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } void BubbleSort(int* a, int n) { assert(a); for (int j = 0; j < n - 1; ++j) { int exchange = 0;//定義變量,用于判斷是否交換過(guò) for (int i = 1; i < n - j; ++i) { if (a[i - 1] > a[i]) { Swap(&a[i - 1], &a[i]); exchange = 1; } } if (exchange == 0)//沒(méi)有交換過(guò)表示有序,直接跳出 { break; } } }
冒泡排序的特性
1. 冒泡排序是一種非常容易理解的排序
2. 時(shí)間復(fù)雜度:O(N^2)
3. 空間復(fù)雜度:O(1)
4. 穩(wěn)定性:穩(wěn)定
快速排序的整體框架
快速排序是Hoare于1962年提出的一種二叉樹結(jié)構(gòu)的交換排序方法,其基本思想為:任取待排序元素序列中 的某元素作為基準(zhǔn)值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準(zhǔn)值,右 子序列中所有元素均大于基準(zhǔn)值,然后最左右子序列重復(fù)該過(guò)程,直到所有元素都排列在相應(yīng)位置上為止。
// 假設(shè)按照升序?qū)rray數(shù)組中[left, right)區(qū)間中的元素進(jìn)行排序 void QuickSort(int array[], int left, int right) { if(right - left <= 1) return; // 按照基準(zhǔn)值對(duì)array數(shù)組的 [left, right)區(qū)間中的元素進(jìn)行劃分 int div = partion(array, left, right); // 劃分成功后以div為邊界形成了左右兩部分 [left, div) 和 [div+1, right) // 遞歸排[left, div) QuickSort(array, left, div); // 遞歸排[div+1, right) QuickSort(array, div+1, right); }
述為快速排序遞歸實(shí)現(xiàn)的主框架,發(fā)現(xiàn)與二叉樹前序遍歷規(guī)則非常像,同學(xué)們?cè)趯戇f歸框架時(shí)可想想二叉 樹前序遍歷規(guī)則即可快速寫出來(lái),后序只需分析如何按照基準(zhǔn)值來(lái)對(duì)區(qū)間中數(shù)據(jù)進(jìn)行劃分的方式即可。
簡(jiǎn)單理解:
我們先選出一個(gè)數(shù),然后把所有數(shù)據(jù)分為三部分,第一部分是大于這個(gè)數(shù)的部分,第二個(gè)部分是這個(gè)數(shù),第三個(gè)部分是大于這個(gè)數(shù)的。
然后,進(jìn)行遞歸求解,對(duì)于小于的部分,選一個(gè)數(shù),分為三部分。對(duì)于大于的部分,選一個(gè)數(shù),分為三部分進(jìn)行求解。
遞歸返回條件:首先是區(qū)間不存在返回,區(qū)間只有一個(gè)數(shù)返回。
快速排序單趟實(shí)現(xiàn)邏輯
1. hoare版本單趟實(shí)現(xiàn)(左右指針?lè)ǎ?/h3>
首先我們先學(xué)習(xí)下最經(jīng)典的左右指針?lè)ǎ?/p>
首先我們一般都會(huì)這兩個(gè)疑問(wèn)?(后續(xù)挖坑法和前后指針?lè)ㄍ恚?/p>
1.為什么要選左邊的數(shù)作為初識(shí)位置比較位置。
2.為什么要讓右邊先走?
我們之所以選取左邊,只是因?yàn)榉奖?,容易控制,你也可以選擇右邊,或者任意位置,都可以,只不過(guò)在代碼邏輯上稍微復(fù)雜點(diǎn),不容易控制。
我們讓右邊先走,是因?yàn)樽詈笪覀円?key位置的數(shù)據(jù)移動(dòng)到相遇位置,也就是key位置數(shù)據(jù)的正確位置,所以我們應(yīng)該保證讓左邊來(lái)遇到右邊,就是讓右邊的位置先到等著左邊。因?yàn)槲覀冞x取的是左邊的key,所以左邊的下標(biāo)就是少了 1 ,我們讓右邊先走就可以剛好彌補(bǔ)過(guò)來(lái)。反之,如果我們選取左邊為key,還讓左邊先走,那么我們最后就會(huì)發(fā)現(xiàn),這個(gè)位置的下標(biāo)就錯(cuò)了,不能保證左邊都是大于key的數(shù)了。
綜上:我們?nèi)绻x擇了左邊為key,那么就讓右邊先走,選擇右邊為key,就讓左邊先走。
我們看著示意圖實(shí)現(xiàn)下單趟排序代碼:
//前后指針?lè)▎翁伺判? int PartSort(int *a,int left,int right) { int keyi = left;//先保存最左側(cè)下標(biāo),作為keyi while (left < right) { //先讓右走,找小,并且不能越界 while (left < right && a[right] >= a[keyi]) { --right; } //再讓左走,找大,不越界。 while (left < right && a[left] <= a[keyi]) { ++left; } //交換左邊大的,和右邊小的 Swap(&a[left], &a[right]); } //循環(huán)完成,我們?cè)谧詈蠼粨Q下,相遇位置的和原來(lái)keyi位置的值 Swap(&a[keyi], &a[left]); //返回相遇位置的下標(biāo)是為進(jìn)行下一步遞歸。 return left; }
2.挖坑法單趟排序?qū)崿F(xiàn)
我們看下有點(diǎn)意思的挖坑法。
我們觀察發(fā)現(xiàn),還是先選取一個(gè)位置作為我們比較的數(shù)同時(shí)也是坑位,然后還是先讓右邊走,然后把數(shù)據(jù)放到坑中,形成一個(gè)新的坑,接著左邊走,再把數(shù)據(jù)放入坑中,形成新坑,最后,把我們選取位置的數(shù)據(jù)放到最后一個(gè)坑位上,就滿了。
其實(shí)在我們調(diào)試發(fā)現(xiàn),"挖坑" 只不過(guò)是形象描述了,其實(shí)乜有坑位,只是數(shù)據(jù)重復(fù),然后替換掉了。
圖示比較簡(jiǎn)單,我們嘗試實(shí)現(xiàn)下單趟排序:
//挖坑法 int PartSort2(int* a, int left, int right) { int key = a[left];//保存最左邊初始位置的值 while (left < right) { while (left < right && a[right] >= key) { --right; } a[left] = a[right];//產(chǎn)生一個(gè)坑位 while (left < right && a[left] <= key) { ++left; } a[right] = a[left];//上一個(gè)坑位填上,產(chǎn)生新的坑位 } a[left] = key;//把最后的坑位填上了。 return left;//返回最后相遇的下標(biāo),以便后序遞歸 }
3.前后指針?lè)?/h3>
前后指針?lè)ê妥笥抑羔橆愃频遣煌耆粯优丁?/p>
前后指針?lè)?,其?shí)就是定義兩個(gè)指針,一個(gè)是prev為初始位置,一個(gè)是cur為初始位置+1,cur是遇見(jiàn)大于初始位置的值停下,交換(prev+1)下標(biāo)的值,直到 cur 指針走到結(jié)尾,此時(shí)就交換prev指針和初始位置即可。
簡(jiǎn)單理解:
前后指針,就是不停的把大于初始位置的數(shù)據(jù)向后移動(dòng),最后一個(gè)指針走到末尾了,另一個(gè)指針此時(shí)的指向,剛好就是我們初始位置在整個(gè)數(shù)據(jù)中要排的位置。
需要注意的是:我們每次交換的都是prev+1的下標(biāo)值,如果 prev = cur 時(shí),此時(shí)我們不用交換,prev也不用++,只需要 cur++ 即可。
前后指針的單趟代碼實(shí)現(xiàn):
//前后指針?lè)? int PartSort3(int *a, int left, int right) { int prev = left;//后指針 int cur = left + 1;//前指針 int keyi = left;//初始位置 while(cur <= right)//當(dāng)cur小于等于最右邊時(shí)進(jìn)入循環(huán) { //當(dāng)cur找到比初始位置大的數(shù),如果此時(shí)cur不等于prev, //那么就交換cur和++prev,一定是前置++。 if (a[cur] < a[keyi] && prev != cur) { Swap(&a[cur], &a[++prev]); } cur++; } Swap(&a[prev], &a[keyi]);//最后交換prev和初始位置即可 return prev;//返回prev為了后續(xù)遞歸做鋪墊 }
以上就是詳解C語(yǔ)言快速排序三種方法的單趟實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于C語(yǔ)言快速排序單趟實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)點(diǎn)菜系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)點(diǎn)菜系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06C++實(shí)現(xiàn)含附件的郵件發(fā)送功能
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)含附件的郵件發(fā)送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05如何使用visual studio2019創(chuàng)建簡(jiǎn)單的MFC窗口(使用C++)
這篇文章主要介紹了如何使用visual studio2019創(chuàng)建簡(jiǎn)單的MFC窗口(使用C++),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03基于c語(yǔ)言知識(shí)點(diǎn)的補(bǔ)遺介紹
本篇文章是對(duì)c語(yǔ)言知識(shí)點(diǎn)的一些補(bǔ)遺進(jìn)行詳細(xì)的分析介紹,需要的朋友參考下2013-05-05