排序
-
插入類排序
1
、直接插入排序
O(n2) O(n) O(n2)
每次將一個待排序元素按照關鍵字大小插入到已經排序的序列中去。
void insert(int a[],int n)
{
int i,j;
int temp;
for(i=1;i<=n;i++) //從第二個元素開始,由於第一個元素確定是有序的
{
temp=a[i]; //temp存儲a[i]防丟失
j=i-1;
while(j>=0 && temp<a[j]) //在i以前的元素已是有序的
{
a[j+1] = a[j];
j--; //一個個日後移
}
a[j+1]=temp;
}
}
-
折半插入排序
O(n2) O(n) O(n2)
折半查找法尋找元素插入位置。與
1
最大不一樣在於,
1
用的是順序查找法。
-
希爾排序
縮小增量排序。實質就是分組插入排序。
把記錄按步長分組,對每組記錄採用直接插入排序方法進行排序。
隨着步長逐漸減少,所分紅的組包含的記錄愈來愈多,當步長的值減少到 1 時,完成排序。
一個好的增量序列有如下特徵:最後一個增量必須爲1;儘可能避免序列中的值,尤爲是相鄰的值,互爲倍數。
-
交換類排序
1
、冒泡排序
O(n2) O(n) O(n2)
結束條件是一趟排序中未發生元素交換。
基本原理:依次比較兩個相鄰的數,
如果按從大到小排序,將大數放前,小數放後,直到比較到最後兩位數。重複上述步驟,直到一趟排序中未發生元素交換爲止。
//
普通冒泡
void bubble_sort(int a[],int n)//n爲數組a的元素個數
{
//必定進行N-1輪比較
for(int i=0; i<n-1; i++)
{
//每一輪比較前n-1-i個,即已排序好的最後i個不用比較
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
}
}
//
優化實現
void bubble_sort_better(int a[],int n)//n爲數組a的元素個數
{
//最多進行N-1輪比較
for(int i=0; i<n-1; i++)
{
bool isSorted = true;
//每一輪比較前n-1-i個,即已排序好的最後i個不用比較
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
isSorted = false;
int temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
if(isSorted) break; //若是沒有發生交換,說明數組已經排序好了
}
}
2
、快速排序
O(nlog2n) O(nlog2n) O(n2)
分治的思想。分治法一般有3步:Divide(分解子問題的步驟)、Conquer(遞歸解決子問題的步驟)、Combine(子問題解求出來後合併成原問題解的步驟)。而求解遞歸式的三種方法有:算法
(1)替換法:主要用於驗證遞歸式的複雜度。數組
(2)遞歸樹:可以大體估算遞歸式的複雜度,估算完後能夠用替換法驗證。ide
(3)主定理:用於解一些常見的遞歸式。優化
快排中,基準的左邊全是比它小的,右邊都是大的。三種取基準的方法:第一個或最後一個元素,中間元素,隨機元素。
待排序列越接近有序算法效率越低。當每次劃分時,若都能分紅兩個等長的子序列時,效率會達到最大。最理想的方法是,選擇的基準剛好能把待排序序列分紅兩個等長的子序列。
遞歸進行的,遞歸須要棧的輔助,所以輔助空間爲
O(log2n)
。
void quiksort(int a[],int start,int end)
{
int low = start;
int high= end;
int key = a[low];
if( start < end)
{
while(low <high && key<=a[high])
high--; //若是high所在的值不比key小,就往左移動
a[low] = a[high]; //遇到比key小的high值,就把a[high]值給a[low],a[low]本來的值已經在key裏面了
while(low < high && a[low] <= key)
low++;
a[high]= a[low];
a[low]=key; //此時的low的左邊全是比key小的,右邊全是比key大的,把key值放大當前low位置
quiksort(a,start,low-1);//左邊重複
quiksort(a,low+1,end);//右邊重複
}
else
return;
}
-
選擇類排序
-
-
簡單選擇排序
從頭到尾順序掃描未排序序列,選最小的元素與第一個進行交換。
-
堆排序
O(nlog2n) O(nlog2n) O(nlog2n)
從無序序列所肯定的徹底二叉樹的第一個非葉子節點開始,從右向左,從下往上,對每一個節點進行調整(大頂堆,小頂堆),直到無序序列中只剩下一個元素。
優勢:
a
)最壞狀況下時間複雜度也是
O(nlog2n)
,這是它相對快排最大的優勢。
b
)空間複雜度爲
O(1)
,這是在全部時間複雜度爲
O(nlog2n)
中最小的。
適用於元素不少的場合,好比
100
萬個元素中選前
10
個最大的。
堆排序相對快速排序:
1
、最好最壞狀況下時間複雜度都爲
O(nlog2n)
,不會出現快排最壞的
O(n2) 2
、堆排序所需的輔助空間少,是
O(1)
,而快排是
O(log2n)
4. 歸併
排序
O(nlog2n) O(nlog2n) O(nlog2n)
採用分治法的思想。將n個元素的序列劃分爲兩個序列,再將兩個序列劃分爲4個序列,直到每一個序列只有一個元素,最後,再有序序列兩兩歸併成一個有序的序列。
歸併排序時間複雜度與初始序列無關。都是
O(nlog2n)
。空間複雜度爲
O(n)
內存空間不足的時候,可以並行計算的時候使用歸併排序。
//歸併,將有二個有序數列a[start…mid]和a[mid+1…end]合併。把結果放到temp裏面
void Merge(int a[],int temp[], int start, int mid, int end)
{
int i = start, j=mid+1, k = start;
while(i!=mid+1 && j!=end+1)
{
if(a[i] >a[j])
temp[k++] = a[j++]; //從小到大排序,a[j]放入temp[k],而後j,k都後移一位
else
temp[k++] = a[i++];
}
while(i != mid+1)
temp[k++] = a[i++];
while(j != end+1)
temp[k++] = a[j++];
for(i=start; i<=end; i++)
a[i] = temp[i];
}
//內部使用遞歸
void MergeSort(int a[], int tempArr[], int start, int end)
{
int mid;
if(start < end)
{
mid = (start+ end) / 2;
MergeSort(a, temp, start, mid);
MergeSort(a, temp, mid+1, end);
Merge(a, temp, start, mid, end);
}
}
-
基數排序
多關鍵字排序。兩種,最高位優先、最低位優先。
適用場景:序列中元素個數不少,可是組成元素的關鍵字的取值範圍比較小。好比取值範圍
0~9.
總結:
1
、平均時間複雜度爲
O(nlog2n)
快些歸隊
快速
希爾
歸併
堆排序
2
、不穩定
快些選
快速
希爾
選擇類排序(簡單選擇、堆排序)
3
、特殊的空間複雜度
其餘都是
O(1)
快速
O(log2n)
歸併
O(n)
4
、一趟保證一個元素到底最終位置
交換類
選擇類
5
、比較次數與初始序列無關
簡單選擇
折半插入
6
、排序趟數與初始序列有關
交換類排序
7
、元素基本有序
(
正序
)
直接插入
冒泡
8
、當數據規模
n
較小
直接插入排序
簡單選擇排序
9
、當數據規模
n
較大,採用時間複雜度爲
O(nlog2n)
的排序方法(快些歸隊)