[C/C++基础知识] 一篇就让你彻底搞懂qsort快速排序的文章
? ? ? ? 最近在做LeetCode的題目、面試和筆試后發(fā)現(xiàn)經(jīng)常考察快速排序的知識(shí)。通過(guò)這篇文章介紹,能讓你徹底的了解和學(xué)習(xí)快排,主要從一下三個(gè)部分進(jìn)行介紹:
? ? ? ? 一.C語(yǔ)言實(shí)現(xiàn)qsort快速排序
? ? ? ? 二.快速排序的原理及手寫快排源碼
? ? ? ? 三.LeetCode關(guān)于Two Sum的快排實(shí)現(xiàn)
參考文獻(xiàn):
? ? ? ?《算法分析與設(shè)計(jì)》關(guān)于分治法那章內(nèi)容
? ? ? ? 如何利用C語(yǔ)言中的qsort庫(kù)函數(shù)實(shí)現(xiàn)快速排序 - by:stpeace
? ? ? ? 白話經(jīng)典算法系列之六 快速排序 快速搞定 - by:MoreWindows
? ? ? ? https://leetcode.com/problems/two-sum/
? ? ? ??[leetcode] Two Sum ?by:陽(yáng)光島主
? ? ? ?
一. C語(yǔ)言實(shí)現(xiàn)qsort快速排序
? ? ? ? 這段介紹參考百度百科,編譯器函數(shù)庫(kù)自帶的快速排序函數(shù)qsort。使用qsort()排序并用 bsearch()搜索是一個(gè)比較常用的組合,使用方便快捷。
? ? ? ? 頭文件:stdlib.h
? ? ? ? 用 法: void qsort(void *base, int nelem, int width, int (*fcmp)(const void *, const void *));
? ? ? ? 參數(shù): 1 待排序數(shù)組首地址
? ? ? ? ? ? ? ? ? ?2 數(shù)組中待排序元素?cái)?shù)量
? ? ? ? ? ? ? ? ? ?3 各元素的占用空間大小
? ? ? ? ? ? ? ? ? ?4 指向函數(shù)的指針,用于確定排序的順序
?
? ? ? ??整數(shù)快排
? ? ? ??int cmp1(const void *a, const void *b) ?
? ? ? ? { ?
? ? ? ? ? ? ? ?return *(int*)a - *(int*)b; ?
? ? ? ? }?
? ? ? ? qsort(num, len, sizeof(int), cmp1);
?
#include <stdio.h> #include <stdlib.h> //整數(shù)從小到大排序 int cmp1(const void *a, const void *b) { return *(int*)a - *(int*)b; } //整數(shù)從大到小排序 int cmp2(const void *a, const void *b) { return *(int*)b - *(int*)a; } int main() { int num[8] = {4,11,2,-3,15,4,0,7}; int len = 8; int i; printf("整數(shù)從小到大排序:\n"); qsort(num, len, sizeof(int), cmp1); for(i = 0; i < len; i ++) { printf("%d ", num[i]); } printf("\n\n"); printf("整數(shù)從大到小排序:\n"); qsort(num, len, sizeof(int), cmp2); for(i = 0; i < len; i ++) { printf("%d ", num[i]); } printf("\n"); system("PAUSE");return 0; }? ? ? ? 運(yùn)行結(jié)果如下圖所示:
? ? ? ? 二維數(shù)組快排
? ? ? ??int cmp(const void *a, const void *b) ?
? ? ? ? { ?
? ? ? ? ? ? ?return ((int*)a)[0] - ((int*)b)[0]; ?
? ? ? ? }
? ? ? ? qsort(num, len, sizeof(int)*2, cmp);
?
#include <stdio.h> #include <stdlib.h> //二維數(shù)組按照num[i][0]從小到大排序 int cmp(const void *a, const void *b) { return ((int*)a)[0] - ((int*)b)[0]; } int main() { int num[6][2] = {4, 1,11, 2,2, 3,-3, 4,15, 5,0, 7,}; int len = 6; int i; printf("排序前:\n"); for(i = 0; i < len; i ++) { printf("%4d %4d\n", num[i][0], num[i][1]); } printf("\n"); qsort(num, len, sizeof(int)*2, cmp); printf("排序后:\n"); for(i = 0; i < len; i ++) { printf("%4d %4d\n", num[i][0], num[i][1]); } printf("\n"); system("PAUSE");return 0; }? ? ? ? 運(yùn)行結(jié)果如下圖所示,其中num[i][0]和num[i][1]同時(shí)移動(dòng)。
?
?
? ? ? ??字符串快排
? ? ? ? int cmp(const void *a, const void *b) ?
? ? ? ? { ?
? ? ? ? ? ? ?return *(char*)a - *(char*)b; ?
? ? ? ? } ??
? ? ? ? qsort(str, sum, sizeof(char)*10, cmp);
?
#include <stdio.h> #include <stdlib.h> int cmp(const void *a, const void *b) { return *(char*)a - *(char*)b; } int main() { char str[7][10] = {"Monday","Tuesday","Wednesday","Thursday","Friday","Staturday","Sunday"}; int sum = 7; int i; printf("排序前:\n"); for(i = 0; i < sum; i++) { printf("%s\n", str[i]); } printf("\n"); qsort(str, sum, sizeof(char)*10, cmp); printf("排序后:\n"); for(i = 0; i < sum; i++) { printf("%s\n", str[i]); } printf("\n"); system("PAUSE");return 0; }? ? ? ? 同int類型一樣,輸出如下所示:
?
? ? ? ??double快排
? ? ? ??int cmp(const void *a, const void *b) ?
? ? ? ? { ?
? ? ? ? ? ? return *(double*)a > *(double*)b ? 1 : -1; ?
? ? ? ? } ?
? ? ? ? qsort(num, sum, sizeof(double), cmp);?
?
#include <stdio.h> #include <stdlib.h> int cmp(const void *a, const void *b) { return *(double*)a > *(double*)b ? 1 : -1; } int main() { double num[7] = {12.2324,-4.3457,0.00000,5.64375,1.25879,0.00001,7.85435}; int sum = 7; int i; printf("排序前:\n"); for(i = 0; i < sum; i++) { printf("%10f\n", num[i]); } printf("\n"); qsort(num, sum, sizeof(double), cmp); printf("排序后:\n"); for(i = 0; i < sum; i++) { printf("%10f\n", num[i]); } printf("\n"); system("PAUSE");return 0; }? ? ? ? 輸出如下圖所示:
?
? ? ? ??結(jié)構(gòu)體排序
? ? ? ? int compare1(const void *a,const void *b) ?
? ? ? ? { ?
? ? ? ? ? ? return ((Student*)a)->score - ((Student*)b)->score; ?
? ? ? ? }
? ? ? ? qsort(s, N, sizeof(Student), compare1);
?
#include <stdio.h> #include <stdlib.h> #define N 6 typedef struct { char name[15]; int score; }Student; int compare1(const void *a,const void *b) { return ((Student*)a)->score - ((Student*)b)->score; } int compare2(const void *a,const void *b) { return *(((Student*)a)->name) - *(((Student*)b)->name); } int main() { Student s[N] = { "Zhang San", 94, "Li Si", 80, "You", 94, "I", 100, "He", 72, "She", 60 }; int i; printf("按照分?jǐn)?shù)排序:\n");qsort(s, N, sizeof(Student), compare1); for(i = 0; i < N; i++) { printf("%-15s : %d\n", s[i].name, s[i].score); } printf("\n"); qsort(s, N, sizeof(Student), compare2); printf("按照姓名排序:\n");for(i = 0; i < N; i++) { printf("%-15s : %d\n", s[i].name, s[i].score); } system("PAUSE");return 0; }? ? ? ? 代碼輸出如下圖所示:
?
?
?
二. 快速排序原理及手寫快排源碼
? ? ? ? 由于MoreWindows寫的白話文系列太好,所以這部分主要參考他的文章和《算法設(shè)計(jì)與分析》關(guān)于分治法那章內(nèi)容。強(qiáng)烈推薦大家閱讀系列文章,原文鏈接:
? ? ? ??http://blog.csdn.net/morewindows/article/details/6684558
? ? ? ? 分治法的思想將一個(gè)大問(wèn)題分割成小規(guī)模問(wèn)題各個(gè)擊破,分而治之,常用遞歸解決。常用的包括二分查找、歸并排序和快速排序等應(yīng)用。其中思想如下所示:
? ? ? ? divide-and-conquer(P)
? ? ? ? {
? ? ? ? ? ? ? ? ? if(|P|<=n0) adhoc(p); ? ? ? ? ? ? //解決小規(guī)模問(wèn)題
? ? ? ? ? ? ? ? ? ? ? ? ? ? divide P into smaller subinstances P1,P2…Pk;
? ? ? ? ? ? ? ? ? for(i=1; i<=k; i++) ?{
? ? ? ? ? ? ? ? ? ? ? ? ? ? yi = divide-and-conquer(Pi); //分解
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? return merge(y1,y2…yk); ? ? ? ? //合并子算法
? ? ? ? }
? ? ? ? 關(guān)于快速排序,由于它的時(shí)間復(fù)雜度在O(N*logN)效率較高,經(jīng)常使用。該算法的基本思想:
? ? ? ? 1. 先從數(shù)列中取出一個(gè)數(shù)作為基準(zhǔn)數(shù)。
? ? ? ? 2. 分區(qū)過(guò)程,將比這個(gè)數(shù)大的全放到它右邊,小于或等于它的全放到它的左邊。
? ? ? ? 3. 再對(duì)左右區(qū)間重復(fù)第二步,直到各區(qū)間只有一個(gè)數(shù)。
? ? ? ? MoreWindows對(duì)快速排序作了進(jìn)一步的說(shuō)明:挖坑填數(shù)+分治法?
? ? ? ? 以一個(gè)數(shù)組作為示例,取區(qū)間第一個(gè)數(shù)為基準(zhǔn)數(shù)。
? ? ? ? 初始時(shí),i = 0;?j = 9;? ?X = a[i] = 72
? ? ? ? 由于已經(jīng)將a[0]中的數(shù)保存到X中,可以理解成在數(shù)組a[0]上挖了個(gè)坑,可以將其它數(shù)據(jù)填充到這來(lái)。
? ? ? ? 從j開(kāi)始向前找一個(gè)比X小或等于X的數(shù)。當(dāng)j=8,符合條件,將a[8]挖出再填到上一個(gè)坑a[0]中。a[0]=a[8]; i++;? 這樣一個(gè)坑a[0]就被搞定了,但又形成了一個(gè)新坑a[8],這怎么辦了?
? ? ? ? 簡(jiǎn)單,再找數(shù)字來(lái)填a[8]這個(gè)坑。這次從i開(kāi)始向后找一個(gè)大于X的數(shù),當(dāng)i=3,符合條件,將a[3]挖出再填到上一個(gè)坑中a[8]=a[3]; j--; 數(shù)組變?yōu)?#xff1a;
? ? ? ? i = 3;?? j =7;?? X=72
? ? ? ? 再重復(fù)上面的步驟,先從后向前找,再?gòu)那跋蚝笳摇?br /> ? ? ? ??從j開(kāi)始向前找,當(dāng)j=5,符合條件,將a[5]挖出填到上一個(gè)坑中,a[3] = a[5]; i++;
? ? ? ? 從i開(kāi)始向后找,當(dāng)i=5時(shí),由于i==j退出。此時(shí),i = j = 5,而a[5]剛好又是上次挖的坑,因此將X填入a[5]。數(shù)組變?yōu)?#xff1a;
? ? ? ? 可以看出a[5]前面的數(shù)字都小于它,a[5]后面的數(shù)字都大于它。因此再對(duì)a[0…4]和a[6…9]這二個(gè)子區(qū)間重復(fù)上述步驟就可以了。
? ? ? ? 對(duì)挖坑填數(shù)進(jìn)行總結(jié):
? ? ? ? 1. i =L; j =R; 將基準(zhǔn)數(shù)挖出形成第一個(gè)坑a[i]。
? ? ? ? 2. j--由后向前找比它小的數(shù),找到后挖出此數(shù)填前一個(gè)坑a[i]中。
? ? ? ? 3. i++由前向后找比它大的數(shù),找到后也挖出此數(shù)填到前一個(gè)坑a[j]中。
? ? ? ? 4. 再重復(fù)執(zhí)行2,3二步,直到i==j,將基準(zhǔn)數(shù)填入a[i]中。
? ? ? ? 照著這個(gè)總結(jié)很容易實(shí)現(xiàn)挖坑填數(shù)的代碼:
? ? ? ? 分治法:
? ? ? ?挖坑填數(shù):
int AdjustArray(int s[], int l, int r) //返回調(diào)整后基準(zhǔn)數(shù)的位置 { int i = l, j = r; int x = s[l]; //s[l]即s[i]就是第一個(gè)坑 while (i < j) { // 從右向左找小于x的數(shù)來(lái)填s[i] while(i < j && s[j] >= x) j--; if(i < j) { s[i] = s[j]; //將s[j]填到s[i]中,s[j]就形成了一個(gè)新的坑 i++; } // 從左向右找大于或等于x的數(shù)來(lái)填s[j] while(i < j && s[i] < x) i++; if(i < j) { s[j] = s[i]; //將s[i]填到s[j]中,s[i]就形成了一個(gè)新的坑 j--; } } //退出時(shí),i等于j。將x填到這個(gè)坑中。 s[i] = x; return i; }? ? ? ?PS:去一些大公司面試,經(jīng)常會(huì)讓你手寫快排、二叉樹非遞歸遍歷、鏈表翻轉(zhuǎn)等
?
三. LeetCode關(guān)于Two Sum的快排實(shí)現(xiàn)
? ? ? ? 下面講一個(gè)經(jīng)典的題目。做過(guò)LeetCode的肯定做個(gè)這個(gè)題目,LeetCode的第一題Two Sum,求兩個(gè)數(shù)的和。鏈接:https://leetcode.com/problems/two-sum/
Given an array of integers, find two numbers such that they add up to a specific target number.
The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.
You may assume that each input would have exactly one solution.
Input:?numbers={2, 7, 11, 15}, target=9
Output:?index1=1, index2=2
? ? ? ? 找到兩個(gè)數(shù)字和為target,并輸出兩個(gè)值的下標(biāo)。由于數(shù)組可能是無(wú)序狀態(tài),故需要排序后輸出即可 。但你需要注意一下幾點(diǎn):
? ? ? ? 1.首先想到的方法是兩層循環(huán)遍歷普通排序,時(shí)間復(fù)雜度O(N^2)會(huì)TLE
? ? ? ? 2.輸出下標(biāo)不是從0開(kāi)始,故i+1即可
? ? ? ? 3.輸出時(shí)負(fù)數(shù)需要判斷小的在前
? ? ? ? ? ?易錯(cuò):Input:[-1,-2,-3,-4,-5] -8 ?Output:[5,3] ?Expected:[3,5]
? ? ? ? 4.錯(cuò)誤 Input:[3,2,4] 6 ?Output:[1,3] ?Expected:[2,3]輸出原始位置,所以使用快速排序怎樣解決呢?方案一采用結(jié)構(gòu),方案二采用二維數(shù)組調(diào)用qsort:
? ? ? ? ? ? ? (1)先定義結(jié)構(gòu)NODE存儲(chǔ)<值,下標(biāo)>在進(jìn)行qsort排序
? ? ? ? ? ? ? (2)再一層循環(huán)前后兩個(gè)指針移動(dòng)遍歷求和,時(shí)間復(fù)雜度O(N*logN)
? ? ? ? ?由于結(jié)果唯一,可以通過(guò)左右下標(biāo)移動(dòng)實(shí)現(xiàn),如果sum<target移動(dòng)左下標(biāo)
? ? ? ? 5.通過(guò)Hash實(shí)現(xiàn)時(shí)間復(fù)雜度O(n)
? ? ? ? 代碼如下: //定義結(jié)構(gòu) 因?yàn)樾枰獢?shù)組值和下標(biāo)同時(shí)排序 typedef struct NODE {int value;int pos; }node;//調(diào)用cmp排序?qū)崿F(xiàn)結(jié)構(gòu)排序 int cmp(const void *a, const void *b) {return ((node *)a)->value - ((node *)b)->value; }int* twoSum(int* nums, int numsSize, int target) {int *result;int i,j;int sum;node *numNode;result=(int*)malloc(sizeof(int)*2);numNode=(node*)malloc(sizeof(node)*numsSize);for(i=0; i<numsSize; i++) {numNode[i].value=nums[i]; numNode[i].pos=i;}qsort(numNode,numsSize,sizeof(node),cmp);for(i=0,j=numsSize-1; i<j; ) {sum=numNode[i].value+numNode[j].value;if(sum==target) {if(numNode[i].pos<numNode[j].pos) { //小在前result[0]=numNode[i].pos+1;result[1]=numNode[j].pos+1;break;}else {result[0]=numNode[j].pos+1;result[1]=numNode[i].pos+1;break;}}else if(sum<target) { //左移i++;} else { //右移j--;}}return result; }
? ? ? ? 當(dāng)然你也可以調(diào)用unordered_map實(shí)現(xiàn)哈希表O(n)算法:
?
class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {vector<int> vec;unordered_map<int,int> map;for(int i=0; i<nums.size(); i++){if(map.find(target-nums[i]) != map.end()){vec.push_back(map[target-nums[i]]+1);vec.push_back(i+1);return vec;}map[nums[i]]=i;}} };
? ? ? ? 希望文章對(duì)你有所幫助吧!如果有錯(cuò)誤或不足之處,還請(qǐng)海涵。最后分析一個(gè)今天的故事:
? ? ? ? 今天去一個(gè)小公司面試(百度百科沒(méi)有該公司詞條),那個(gè)Leader感覺(jué)很不尊重人,一方面總是不屑的搖頭與嘲笑人,一方面又總和旁邊的人聊天,還不斷打斷人說(shuō)話。確實(shí)我普通話帶有西南口音,但是我就不明白了,難道這能影響到我寫程序嗎?雖然他直言不諱的說(shuō)我編程語(yǔ)言不說(shuō)精通,連熟悉都達(dá)不到,但是我前面都說(shuō)了我不精通任何東西,只是一般。讓我給他講知識(shí)圖譜,他說(shuō)要用通俗的話,那我就舉例子喬布斯、蘋果等,他又說(shuō)沒(méi)難度,那我就講神經(jīng)網(wǎng)絡(luò),他說(shuō)他不會(huì)編程。我不知道怎么說(shuō)了~他居然還說(shuō)百度可能僅僅是通過(guò)傳統(tǒng)的搜索引擎現(xiàn)在返回一個(gè)值,如姚明的身高,而沒(méi)有真正去做這個(gè)知識(shí)圖譜,我不知道怎么吐槽。
? ? ? ? ?僅僅面試了10分鐘,我就想走了,后面的問(wèn)題我都說(shuō)不知道,問(wèn)我Python的優(yōu)勢(shì),我說(shuō)腳本語(yǔ)言、開(kāi)發(fā)快、語(yǔ)言簡(jiǎn)潔,面向?qū)ο蟆?/strong>他非說(shuō)是面向過(guò)程的,和我爭(zhēng)論怎么是面向?qū)ο蟮?#xff0c;我說(shuō)舉個(gè)例子建房子,他又說(shuō)那是老師教的,我讓你講實(shí)例。好吧!我承認(rèn)我比較弱,但是Python是面向?qū)ο筮@種常識(shí)都不知道,我只能呵呵了~
? ? ? ? ?然后他說(shuō)你這也不會(huì),那也不會(huì),那就簡(jiǎn)單講講你認(rèn)為自己最優(yōu)秀的地方。我就說(shuō)我最大的優(yōu)點(diǎn)就是堅(jiān)持,不論10年,5年也好,我能做出東西來(lái);同時(shí)以后還是想會(huì)貴州當(dāng)老師。他最后說(shuō)我們要的人還是有要求的,明確告訴你不會(huì)給你offer,我當(dāng)時(shí)也回了句:我也不會(huì)來(lái)的。
? ? ? ? 最后離開(kāi)的時(shí)候我給HR說(shuō)我就是很不爽他,就沒(méi)心思答了,而且我表達(dá)能力也不好,但還是非常感謝你的推薦。最后當(dāng)?shù)弥菄?guó)內(nèi)最好的大學(xué)畢業(yè)的時(shí)候,我也非常震驚。我脾氣只有這么好了,二十幾年的光陰里沒(méi)怎么生過(guò)氣,當(dāng)時(shí)確實(shí)生氣。只恨臉皮薄,當(dāng)時(shí)沒(méi)損他幾句,但那又顯得不夠大度。
? ? ? ? 其實(shí)我想說(shuō):面試本身就是一個(gè)相互學(xué)習(xí)的過(guò)程,確實(shí)很多基礎(chǔ)知識(shí)我都不記得了,可能也不善言表,但是你一個(gè)面試官不尊重面試人員,那我就是看不起你。不論你賺再多的錢,再有本事,學(xué)校再厲害,我就是看不起,做人似乎比工作更重要吧!我也大大小小參加了不少的面試,阿里、360、百度都有,各種大牛也遇到過(guò),馬云、吳恩達(dá)、劉知遠(yuǎn)的視頻和講座也看過(guò)不少,清華北大也都有同學(xué),但是沒(méi)見(jiàn)過(guò)這么不尊重人的,面試過(guò)程中又嘲諷又聊天的,各種秀英語(yǔ),還吐槽我普通話。
? ? ? ? 先做人,后做事吧!看著比我年輕幾歲,這種秀優(yōu)越感只能理解為不成熟。
? ? ? ? 如果他們公司都是這樣招人的,那早晚也會(huì)毀在他們手上的,雖然他們公司現(xiàn)在有很大的潛力;如果我還在他說(shuō)下干活,遇到個(gè)東西整天吐槽你做得不夠好,給他說(shuō)代碼他又說(shuō)不會(huì),我也早晚會(huì)辭職的。總之,以后如果我走到當(dāng)面試官那一步了,最起碼要尊重面試人員,因?yàn)槟闶窃趯へ?cái),而不是秀優(yōu)越感!而且我都不知道哪里來(lái)的優(yōu)越感,學(xué)校嗎?好吧!但我還是看不起你這個(gè)人~
? ? ? ? 當(dāng)然面試也讓我知道了自己還存在很多不足,尤其是底層的算法很多都忘了,最近扎扎實(shí)實(shí)看書和做LeetCode吧!自己技術(shù)還是不夠精通,非常致命,包括同步異步通訊,線程進(jìn)程代碼等~但還是感謝那個(gè)熱心的HR吧!以后也盡量別參加這種沒(méi)有筆試,通過(guò)獵頭直接去小公司面試了。
? ? ? (By:Eastmount 2015-10-11 清晨6點(diǎn)?http://blog.csdn.net/eastmount/)
??
總結(jié)
以上是生活随笔為你收集整理的[C/C++基础知识] 一篇就让你彻底搞懂qsort快速排序的文章的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [python学习] 专题七.网络编程之
- 下一篇: C语言中能运算符重载吗,C++语言中什么