轉自本人知乎文章:https://zhuanlan.zhihu.com/p/30311662
現在,但凡規模大一點的互聯網公司招聘軟件相關的崗位,都會對數據結構和算法有一定要求。作為非科班出身的程序yuan,要想進好一點的公司,還是老老實實地把基礎打扎實吧。
說到排序,大家應該都不陌生,因為你生活中肯定有過網購吧,你在淘寶搜索寶貝的時候,遇到的就是排序,比如有按價格高低排序、按綜合排序、按信用高低排序。所以排序算法應該可以說是算法里面很重要的一個分支。
開始之前,先簡單介紹一下排序算法的幾個重要指標,這里,我盡量用自己理解的傻瓜式方法解讀:
(1)穩定性:當序列中存在兩個或兩個以上的關鍵字相等的時候,如果排序前序列中r1領先於r2,那么排序后r1如果仍舊領先r2的話,則是穩定的。(相等的元素排序后相對位置不變)
(2)不穩定性:當序列中存在兩個或兩個以上的關鍵字相等的時候,如果排序前序列中r1領先於r2,那么排序后r1如果落后r2的話,則是不穩定的。(相等的元素排序后相對位置發生改變)
(3)時間復雜度:算法的時間開銷是衡量其好壞的最重要的標志。高效率的算法應該具有更少的比較次數和記錄移動次數。
(4)空間復雜度:即執行算法所需要的輔助存儲的空間。
一、直接插入排序(插入類)
流程描述:遍歷序列中的關鍵字,每次取一個待排序的關鍵字,從待排序關鍵字的前一個關鍵字逐次向前掃描,如果掃描到的關鍵字大於待排序關鍵字,則把掃描到的關鍵字往后移一個位置。最后找到插入位置,將待排序關鍵字插入。
void InsertSort(int R[],int n) { int i,j int temp; for(i=1;i<n;++i) { temp=R[i]; //將待排關鍵字暫時存放在temp中 j=i-1; //待排關鍵字的前一個關鍵字序號 while(j>=0&&temp<R[j]) //從待排關鍵字的前一個關鍵字開始掃描,如果大於待排關鍵字,則往后移一個位置 { R[j+1]=R[j]; --j; } R[j+1]=temp; //找到插入位置,將temp中暫存的待排關鍵字插入 } }
最壞情況:整個序列是逆序的時候,則內層循環的條件temp<R[j]始終成立,此時對於每一次外層循環,內層循環次數每次達到最大值(即內層循環位i次),外層循環i取值為1~i-1,所以總的執行次數為n(n-1)/2 。
最好情況:整個序列為正序的時候。內層循環條件始終不成立,所以內層循環始終不執行,始終執行語句R[j+1]=temp。所以時間復雜度為O(n)。
空間復雜度:算法所需的輔助存儲空間不隨待排序列的規模變化而變化,是個常量,所以為O(1)。
二、折半插入排序(插入類)
過程描述:過程同直接插入排序,只是不同於直接插入排序時用順序查找,這里用的是折半查找。所以折半插入排序在查找過程上比直接插入排序節約不少時間。但是關鍵字移動次數和直接插入排序一樣。
最好情況時間復雜度:
最壞情況時間復雜度:
平均情況時間復雜度:
三、冒泡排序(交換類)
過程描述:通過一系列的交換動作實現排序。首先第一個關鍵字和第二個關鍵字比較,如果第一個關鍵字大,二者交換;然后第二個關鍵字和第三個關鍵字比較,如果第二個關鍵字大,二者交換,否則不交換。一直進行下去,知道最終最大的哪個關鍵字被交換到了最后,一趟冒泡排序完成。
void BubbleSort(int R[],int n) { int i,j,flag; int temp; for(i=n-1;i>=1;--i) { flag=0; //flag用來標記本趟排序是否發生了交換 for(j=1;j<=i;++j) { if(R[j-1]>R[j]) { temp=R[j]; R[j-1]=R[j]; R[j]=temp; flag=1; //flag=1表示本次排序發生了交換 } if(flag==0)//如果沒有發生交換,說明序列有序,排序結束 return; } }
最壞情況:序列逆序,此時內層循環if語句的條件始終成立,基本操作執行的次數為n-i。i取值為1~n-1,所以總的執行次數為(n-1+1)(n-1)/2=n(n-1)/2,所以時間復雜度為O(n^2)。
最好情況:序列正序。此時內層循環的條件比較語句始終不成立,不發生交換,內層循環執行n-1次,所以時間復雜度為O(n)。
平均情況:時間復雜度O(n^2)。
四、簡單選擇排序(選擇類)
void SelectSort(int R[],int n) { int i,j,k; int temp; for(i=0;i<n;++i) { k=i; for(j=i+1;j<n;++j) //從i后面的序列中挑選一個最小的關鍵字 { if(R[k]>R[j]) k=j; // temp=R[i]; R[i]=R[k]; R[k]=temp; } } }
五、希爾排序(插入類)
過程 描述:重點在增量的選取。如果增量為m,那么將下標為0、m、2m、3m的關鍵字分成一組,將下標為1、m+1、2m+1、3m+1等關鍵字分成另外一組,分別對這些組進行插入排序。這就是一趟希爾排序。
六、快速排序(交換類)
過程描述:每一趟選擇當前子序列中的一個關鍵字作為樞軸(一般選擇第一個關鍵字作為樞軸),將子序列中比樞軸小的移到樞軸前面,比樞軸大的移到樞軸后面,本趟交換完成后得到新的更短的子序列,成為下一趟交換的初始序列。一趟排序之后可以確定樞軸的最終位置。比樞軸小的全部在樞軸左邊,比樞軸大的全部在樞軸右邊。
void QuickSort(int R[],int high,int low) { int temp; int i=low,j=high; if(low<high) { temp=R[low]; while(i!=j) { while(j>i&&R[j]>=temp) --j; //從右往左掃描,找到一個小於樞軸temp的關鍵字 if(i<j) { R[i]=R[j]; //將右邊小於樞軸temp的關鍵字放在temp的左邊 ++i; //左邊序列號向右移一位 } while(i<j&&R[i]<temp) ++i;//從左向右掃描,找到一個大於樞軸關鍵字temp的關鍵字 if(i<j) { R[j]=R[i];//將左邊大於樞軸temp的關鍵字放在temp的右邊 --j; //右邊序號向左移動一位 } } R[i]=temp; QuickSort(R,low,i-1); QuickSort(R,i+1,high); } }
最好情況:時間復雜度為 ,待排序列越接近無序,本算法效率越高。
最壞情況:時間復雜度為 ,待排序列越接近有序,本算法效率越低。
平均情況:時間復雜度 。
空間復雜度:從頭到尾只用了temp這一個輔助存儲,所以為O(1)。
七、堆排序(選擇類)
把堆看成完全二叉樹,大根堆---父親大孩子小;小根堆---父親小孩子大。
過程描述:整個排序的過程就是不斷地將序列調整為堆。
以原始序列:49 38 65 97 76 13 27 49為例,調整為大根堆。
(1)調整97,97>49,不需要調整
(2)調整65,65>13,65>27,不需要調整
(3)調整38,38<97,38<76。需要調整,38和97交換,交換后38成為49的根節點,,繼續將38和49交換。
(4)調整49,49<97,49<65,所以49和較大者97交換,交換后,49<76,仍然不滿足大根堆,將49與76交換。
八、2路歸並排序
void mergeSort(int A[],int low,int high) { if(low<high) { int mid=(low+high)/2; mergeSort(A,low,mid); //歸並排序前半段 mergeSort(A,mid+1,high);//歸並排序后半段 merge(A,low,mid,high); //把數組中的low到mid 和 mid+1到high的兩段有序序列歸並成一段有序序列 } }

九、基數排序
"多關鍵字排序",(1)最高位優先(2)最低位優先。例如最高位優先:先按最高位排成若干子序列,再對每個子序列按次高位進行排序。
如下圖,低位優先的排序過程:每個桶相當於一個隊列,先進先出規則。
最后得到的結果:最高為有序,最高位相同的關鍵字次高位有序,次高位相同的關鍵字最低位有序,所以整個序列有序。