文章目錄
八大排序
將所需要使用到的交換函數,放置在最前面
交換函數
void Swap(int* array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
一:插入排序
1. 直接插入排序
在已經有序的數據中給待插入的數據找到一個合適的位置
排序開始之前:假設第一個數據是有序的
排序的過程:將待插入的數據從後向前依次和有序的數據進行比較
代碼段
/*
時間複雜度:最壞O(n^2) 平均O(n^2) 最好O(n)
空間複雜度:O(1)
穩定性:穩定
數據敏感:敏感
*/
void insertSort(int* array, int n) {
//進行遍歷
for (int i = 0; i < n - 1; i++) {
int end = i;
//有序序列的最後一個位置
int key = array[end + 1];
//待插入得一個數據
while (end >= 0 && array[end] > key) {
//找到第一個小於等於key的位置
array[end + 1] = array[end];
//將當前數據向後移動
--end;
}
array[end + 1] = key;
//將key數據存放在當前位置
}
}
2. 希爾排序
先選定一個整數,把待排序的文件中所有記錄分成多個組,所有距離爲gap的記爲一組,並對每一組內的數據進行排序,然後重複進行,直到gap達到1時,所有記錄的數據都排好序了。
希爾排序:進行多輪的預排序,最後再執行普通的插入排序
預排序:組內元素進行插入排序
代碼段:
/*
時間複雜度:最壞O(n^1.3) 平均O(n^1.3) 最好O(n)
空間複雜度:O(1)
穩定性:不穩定 ---> 分組時相同值的元素不一定可以分到同一組,預排序是可能導致相對位置發生變化
數據敏感:敏感
*/
void shellSort(int* array, int n) {
int gap = n;
//步長
while (gap > 1) {
gap = gap / 3 + 1;
//步長縮減 通過不同的gap來進行邏輯分組
//在組內進行插入排序 不同組的元素交替進行排序
for (int i = 0; i < n - gap; i++) {
//一輪插入排序
int end = i;
//排序的最後一個位置
int key = array[end + gap];
//同分組之中第一個要插入的數據
while (end >= 0 && array[end] > key) {
//找到第一個小於等於的數據
array[end + gap] = array[end];
//將數據向後瞬移
end -= gap;
//end重新賦值
}
array[end + gap] = key;
//將所需要插入的數據插入到新的位置
}
}
}
二:選擇排序
1. 兩種選擇排序
每一次從待排序的數據元素之中選擇出最小(最大)的一個元素,存放在序列的起始位置,知道全部待排序的數據排列完畢。
將所取出的最大的元素,放置在最末尾位置:
代碼段:
/*
時間複雜度:最壞O(n^2) 平均O(n^2) 最好O(n^2)
空間複雜度:O(1)
穩定性:穩定
數據敏感:不敏感
*/
void selectSort(int* array, int n)
{
for (int i = 0; i < n; ++i)
{
//start:未排序數據的最左邊
int start = i;
//min: 最小值的位置
int min = start;
//從未排序的數據中找最小值
for (int j = start + 1; j < n; ++j)
{
if (array[j] < array[min]) {
min = j;
//將j賦值給min位置
}
}
Swap(array, start, min);
}
}
- 第二種選擇排序
/*
時間複雜度:最壞O(n^2) 平均O(n^2) 最好O(n^2)
空間複雜度:O(1)
穩定性:穩定
數據敏感:不敏感
*/
//第二種選擇排序,從兩邊開始進行選擇排序
void selectSort(int* array, int n) {
int begin = 0;
//最左邊的位置
int end = n - 1;
//最右邊的位置
while (begin < end) {
//左邊小於右邊
int min = begin, max = begin;
//初始化min和max位置全部指向最開始
for (int i = begin + 1; i < end; i++) {
if (array[i] >= array[max]) {
//找到最大的位置
max = i;
}
if (array[i] <array[min]) {
//找到最小的位置
min = i;
}
}
Swap(array, begin, min);
//將最小的位置和最左邊的數據交換
if (max == begin) {
//若最大的等於左邊的數據時,則表示數據發生了變化
max = min;
//將min位置賦值給max
}
Swap(array, end, max);
//進行最大值的交換
begin++;
end--;
}
}
2. 堆排序
利用堆積樹這種數據結構來實現的一種算法,需要注意的時升序建大堆,降序建小堆。
/*
時間複雜度:O(nlogn)
空間複雜度:O(1)
穩定性:不穩定 --> 調整的過程中相對位置可能會發生變化
數據敏感:不敏感
*/
//建大堆 升序排序
void ShiftDown(int* array, int n, int parent) {
int child = 2 * parent + 1;
//尋找孩子位置
while (child < n) {
if (child + 1 < n && array[child + 1] > array[child]) {
//判斷在滿足條件的情況下,左右孩子兩者的較大值
child++;
//若右邊孩子比左邊孩子大,則將child位置加1
}
if (array[child] > array[parent]) {
//若孩子比父親的值大,則交換父親和孩子的位置
Swap(array, child, parent);
parent = child;
//交換後重新將孩子位置賦予給父親
child = 2 * parent + 1;
//重新計算新的孩子位置
}
else {
break;
//如果孩子值小於父親,則跳出
}
}
}
void SortHeap(int* array, int n) {
//建堆的過程
for (int i = (n - 2) / 2; i >= 0; i--) {
ShiftDown(array, n, i);
//從下最後一個父節點開始進行建堆
}
//循環刪除
while (n) {
Swap(array, 0, n - 1);
n--;
ShiftDown(array, n, 0);
}
}
三:交換排序
1. 冒泡排序
相鄰元素之間進行比較,大的開始向後移動
/*
時間複雜度:最壞O(n^2) 平均O(n^2) 最好O(n)
空間複雜度:O(1)
穩定性:穩定
數據敏感:敏感
*/
void bubbleSort(int* array, int n) {
while (n) {
//循環n次
int flag = 1;
//設置標識符,若標識符改變,則意味着此數組已經是有序序列
int end = n;
//最後的位置
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
//若前一項大於後一項,則進行交換
Swap(array, i, i - 1);
//之後將標識符置零
flag = 0;
}
}
if (flag) {
break;
}
n--;
}
}
- C語言課本上所書寫的冒泡
/*
時間複雜度:最壞O(n^2) 平均O(n^2) 最好O(n^2)
空間複雜度:O(1)
穩定性:穩定
數據敏感:敏感
*/
void BubbleSort(int* array,int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (array[i] > array[j]) {
Swap(array, i, j);
}
}
printf("%d ", array[i]);
}
system("pause");
}
2. 快速排序
2.1 hora劃分法
- 從帶劃分的區間中選取一個基準值
- 從待劃分區間的末尾開始向前查找第一個小於基準值的數據
- 從帶劃分區間的開頭開始向後查找第一個大於基準值的數據
- 交換2,3兩步所找出的數據,循環執行2,3,4步
- 查找到相遇的位置時,則把基準值和相遇位置的數據進行交換,完成一輪劃分
對於hora法來說,先從前往後找第一個大的是否可行?
代碼段:
//Hore分割法
int PartionHora(int* array, int begin, int end) {
//選擇基準值
int key = array[begin];
int start = begin;
//開始劃分
while (begin < end) {
//從後向前,找第一個小於key的值
while (begin < end && array[end] >= key) {
end--;
}
//從前向後,找第一個大於key的值
while (begin < end && array[begin] <= key) {
begin++;
}
//交換找到的兩者值得位置
Swap(array, begin, end);
}
//最後將基準值和相遇位置得值進行交換
Swap(array, start, begin);
//返回基準值交換後的位置
return begin;
}
2.2 挖坑法
- 挖取基準值,一般情況下,第一個坑位基準值的位置
- 從後向前找第一個小於基準值的數據,去填上上一次的坑
- 從前向後找第一個大於基準值的數據,去填上上一次的坑
- 循環執行2,3兩步
- 找到相遇的位置,用基準值進行填坑
代碼段:
//挖坑分割法
int PartionHole(int* array, int begin, int end) {
//基準值取出來,空出這個坑
int key = array[begin];
//開始劃分
while (begin < end) {
//從後找第一個小於key的位置
while (begin < end && array[end] >= key) {
end--;
}
//將找到的此位置值填到基準值的坑裏,空出一個新的坑
array[begin] = array[end];
//從前向後找到第一個大於key的位置
while (begin < end && array[begin] >= key) {
begin++;
}
//將找到的位置的值取出,填到之前的坑之中,又空出另一個新坑
array[end] = array[begin];
}
//直到相遇之後,將最早的基準值放在最後空出來的坑之中
array[begin] = key;
//返回基準值此時的位置
return begin;
}
2.3 前後指針法
前面的指針和後面的指針中間是否有大於基準值的數據:
如果前後指針連續,則表示中間沒有大於基準值的數據
前後指針若不連續,則表示中間有大於基準值的數據
大數據和新發現的小數據進行交換,最終實現基準值的左邊都是小於基準值的,右邊都是大於基準值的。
代碼段:
//前後指針分割法
int PartionHand(int* array, int begin, int end) {
//最後一個小於基準值的位置
int last = begin;
//新發現的下一個小於基準值的位置
int next = last + 1;
//基準值
int key = array[begin];
//開始劃分
while (next <= end) {
//若next所指的數據小於基準值,但last和next之間還存在其他數據
//則代表兩者之間還包含着大於基準值的數據存在,進行交換
//大的數據向後移動,小的數據向前移動
if (array[next] < key && ++last != next) {
Swap(array, last, next);
next++;
}
}
//最後當next走完之後,將last的位置和最開始的基準值位置進行交換
Swap(array, begin, last);
return last;
//返回基準值的位置
}
快速排序代碼
void QuichSort(int* array, int begin, int end) {
if (begin >= end) {
//若begin和end之間沒有其他元素,則表示兩者是有序的
return;
}
int keyPos = PartionHora(array, begin, end);
//以此基準值,來劃分當前區域
QuichSort(array, begin, keyPos - 1);
//遞歸法劃分左邊子區域
QuichSort(array, keyPos + 1, end);
//遞歸法劃分右邊子區域
}
3. 優化版快速排序
因爲對於有序的數列,再次進行快速排序的話,會產生一個深度較大的遞歸,容易造成棧溢出,因此需要對其進行優化。
幾個數中取中間值作爲基準值,三數取中法:可以把最壞的情況變成最理想的情況,均衡劃分;
對於小區間的優化,也能夠減少底層遞歸調用的次數
//三數取中法則
int GetMid(int* array, int begin, int end) {
int mid = begin + (end - begin) / 2;
//選擇begin,和end的中間位置
if (array[begin] < array[mid]) {
//若起始位置的值小於中間位置
if (array[mid] < array[end]) {
//且中間位置的值小於尾部位置的值
return mid;
//則返回中間的值
}
else {
if (array[begin] > array[end]) {
//否則,若起始位置的值也大於尾部位置的值
return begin;
//則返回開始位置
}
else {
return end;
//否則返回尾部位置
}
}
}
else {
//若中間位置的值不大於起始位置的值
if (array[begin] < array[end]) {
//開始位置小於尾部位置的值
return mid;
//則返回中間位置
}
else {
if (array[begin] < array[end]) {
//在中間位置不大於起始位置,且開始位置不小於尾部位置的值
return begin;
//則返回開始位置
}
else {
return end;
//否則返回尾部位置
}
}
}
}
//hora法
int PartionOne(int* array, int begin, int end) {
int mid = GetMid(array, begin, end);
//三數取中 找到mid值
Swap(array, begin, mid);
//進行交換
//確定基準值
int key = array[begin];
//開始位置
int start = begin;
while (begin < end) {
//從後向前,找第一個小於基準值的位置
while (begin < end && array[end] >= key) {
end--;
}
//從前向後,找第一個大於基準值的位置
while (begin < end && array[begin] <= key) {
begin++;
}
//兩者進行交換
Swap(array, begin, end);
}
//最後將相遇的位置和初始位置進行交換
Swap(array, start, begin);
//返回基準值位置
return begin;
}
//挖坑法
int PartionTwo(int* array, int begin, int end) {
int mid = GetMid(array, begin, end);
//三數取中 找到mid值
Swap(array, begin, mid);
//進行交換
//確定基準值
int key = array[begin];
while (begin < end) {
//從後向前找第一個小於基準值的位置
while (begin < end && array[end] >= key) {
end--;
}
//將此位置的值,填入一開始的基準值坑之中
array[begin] = array[end];
//從前向後找第一個大於基準值的位置
while (begin < end && array[begin] <= key) {
begin++;
}
//將此位置的值填入之前找到的小於基準值的位置
array[end] = array[begin];
}
//最後將key的值填入相遇的位置
array[begin] = key;
//返回基準值位置
return begin;
}
//前後指針法
int PartionThree(int* array, int begin, int end) {
int mid = GetMid(array, begin, end);
//三數取中 找到mid值
Swap(array, begin, mid);
//進行交換
//最後一個小於基準值的位置
int prev = begin;
//下一個小於基準值的位置
int cur = prev + 1;
//基準值
int key = array[begin];
while (cur <= end) {
//若下一個基準值位置和最後一個基準值位置之間不連續
if (array[cur] < key && ++prev != cur) {
//則進行交換
Swap(array, prev, cur);
}
cur++;
}
//交換最後基準值和基準值的位置
Swap(array, begin, prev);
return prev;
}
/*
時間複雜度:最壞:O(n^2)-->不會出現 最好:O(nlogn) 平均:O(nlogn)
空間複雜度:O(logn) 函數調用棧, 極端情況: O(n)-->不會出現
穩定性:不穩定
數據敏感:敏感
*/
void SortQucik(int* array, int begin, int end) {
if (begin >= end) {
//下h只剩下最後一個元素時,則直接返回
return;
}
int keyPos = PartionOne(array, begin, end);
//尋找第一個基準值
SortQucik(array, begin, keyPos);
//左邊分
SortQucik(array, keyPos+1,end);
//右邊分
}
4. 非遞歸版快速排序
4.1 用棧實現快速排序
代碼段:
void SortQuickStack(int* array, int n) {
//創建棧
Stack st;
//初始化棧
StackInit(&st, 10);
//入棧操作
if (n > 1) {
//先入最後
StackPush(&st, n - 1);
//再入開始
StackPush(&st, 0);
}
while (StackEmpty(&st) != 1) {
//獲取棧頂元素,則是後入的
int begin = StackTop(&st);
//刪除棧頂元素
StackPop(&st);
//再次獲取棧頂元素
int end = StackTop(&st);
//刪除棧頂元素
StackPop(&st);
//返回的基準值位置
int keyPos = PartionThree(array, begin, end);
if (keyPos + 1 < end) {
//後半部若還有其他未排序元素
//入棧
StackPush(&st, end);
StackPush(&st, keyPos+1);
}
if (begin < keyPos - 1) {
//前半部分若還有其他未排序元素
//入棧
StackPush(&st, keyPos - 1);
StackPush(&st, begin);
}
}
}
4.2 用隊列實現快速排序
對於隊列,則是先將隊列之中的區間進行劃分結束之後,再劃分之後進入隊列之中的區間,顧名思義則是一層一層去進行劃分。
代碼段:
void SortQuickQueue(int* array, int n) {
//創建隊列
Queue q;
//隊列初始化
QueueInit(&q);
if (n > 1) {
//入隊操作
QueuePush(&q, 0);
QueuePush(&q, n - 1);
}
while (QueueEmpty(&q) != 1) {
//獲取隊頭元素
int begin = QueueFront(&q);
//刪除隊頭元素
QueuePop(&q);
//獲取隊頭元素
int end = QueueFront(&q);
//刪除對頭元素
QueuePop(&q);
//找到基準值位置
int keyPos = PartionThree(array, begin, end);
if (begin < keyPos - 1) {
//左半部分
QueuePush(&q, begin);
QueuePush(&q, keyPos-1);
}
if (keyPos + 1 < end) {
//右半部分
QueuePush(&q, keyPos+1);
QueuePush(&q, end);
}
}
}
四:歸併排序
1. 歸併排序
合併有序的子序列,使其變成一個更大的有序的組序列。
歸併合併的前提時:子序列本身必須是有序的(有序的子序列只包含一個元素的子序列本身有序)
合併過程:相鄰的有序子序列進行相互合併
不能夠進行原地合併,需要重新申請輔助空間,否則會造成覆蓋元素。
1.1 遞歸版歸併排序
//合併函數 前提是需要知道兩個有序子序列的區間 [begin ,mid ][mid+1 end]
void Merge(int* array, int begin, int mid, int end, int* tmp) {
int begin1 = begin, end1 = end, begin2 = mid + 1, end2 = end;
int idx = begin;
//兩個有序序列進行排序
while (begin1 <= end1 && begin2 <= end2) {
if (array[begin1] <= array[begin2]) {
//將兩個序列之中較小的值放入到tmp之中
tmp[idx++] = array[begin1++];
}
else {
tmp[idx++] = array[begin2++];
}
}
//將剩餘的數據直接拷貝到當前的空間之中
if (begin1 < end1) {
memcpy(tmp + idx, array + begin1, sizeof(int) * (end1 - begin1 + 1));
}
if (begin2 < end2) {
memcpy(tmp + idx, array + begin2, sizeof(int) * (end2 - begin2 + 1));
}
//之後將tem之中的數據拷貝到原始空間之中
memcpy(array + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void SortMergeR(int* array, int begin, int end, int* tmp) {
if (begin >= end) {
//若Begin和end之間沒有任何數據,則直接返回
return;
}
//查找中間位置
int mid = begin + (end - begin) / 2;
//先進行子區間的排序
SortMergeR(array, begin, mid, tmp);
SortMergeR(array, mid + 1, end, tmp);
//最後合併有序的子區間
Merge(array, begin, mid, end, tmp);
}
1.2 非遞歸版歸併排序
//合併函數 前提是需要知道兩個有序子序列的區間 [begin ,mid ][mid+1 end]
void Merge(int* array, int begin, int mid, int end, int* tmp) {
int begin1 = begin, end1 = end, begin2 = mid + 1, end2 = end;
int idx = begin;
//兩個有序序列進行排序
while (begin1 <= end1 && begin2 <= end2) {
if (array[begin1] <= array[begin2]) {
//將兩個序列之中較小的值放入到tmp之中
tmp[idx++] = array[begin1++];
}
else {
tmp[idx++] = array[begin2++];
}
}
//將剩餘的數據直接拷貝到當前的空間之中
if (begin1 < end1) {
memcpy(tmp + idx, array + begin1, sizeof(int) * (end1 - begin1 + 1));
}
if (begin2 < end2) {
memcpy(tmp + idx, array + begin2, sizeof(int) * (end2 - begin2 + 1));
}
//之後將tem之中的數據拷貝到原始空間之中
memcpy(array + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void SortMergeNoR(int* array, int n) {
//申請新的空間
int* tmp = (int*)malloc(sizeof(int) * n);
//設置跨度
int k = 1;
//開始多輪的合併
while (k < n) {
//一輪合併
for (int i = 0; i < n; i += 2 * k) {
int begin = i;
int mid = i + k - 1;
//若中間值大於最大位置則繼續進行
if (mid >= n - 1) {
continue;
}
int end = i + 2 * k - 1;
//若末尾值大於最大位置時,則將末尾值置到最後一個位置
if (end >= n) {
end = n - 1;
}
//合併
Merge(array, begin, mid, end, tmp);
}
//跨度擴大2倍
k *= 2;
}
}
歸併排序代碼
/*
時間複雜度:O(nlogn)
空間複雜度:O(n)
穩定:穩定
數據敏感:不敏感
*/
void SortMerge(int* array, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
SortMergeR(array, 0, n - 1, tmp);
free(tmp);
}
0. 外排序思想
由歸併排序講解的 外排序:
五:非比較排序
1. 計數排序
計數排序又被稱之爲鴿巢原理,是對哈希直接定址法的變形應用
- 統計相同元素出現的次數根據統計的結果將序列回收到原來的序列之中
兩種表示方式:
代碼段:
/*
時間複雜度:O(max(n,範圍))
空間複雜度:O(範圍)
穩定:一般教材認爲是穩定的(欠妥)
數據敏感:不敏感
計數排序: 只適合小範圍數據, 如果範圍大,空間複雜度較高
*/
void SortCount(int* array, int n) {
//統計範圍
int min = array[0], max = array[0];
//找到最大值和最小值
for (int i = 1; i < n; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
//確定準確的範圍
int range = max - min + 1;
//開放輔助空間,進行計數
int* countArr = (int*)malloc(sizeof(int*)range);
//將申請的輔助空間全部初始化爲零
memset(countArr, 0, sizeof(int) * range);
//開始統計數據出現的個數
for (int i = 0; i < n; i++) {
//出現一次則加一次
countArr[array[i] - min]++;
}
//設置中間量
int idx = 0;
//恢復數據,遍歷計數數組
for (int i = 0; i < range; i++) {
while (countArr[i]--) {
array[idx++] = i + min;
}
}
//釋放輔助空間
free(countArr);
}
各種排序的總結樹狀圖
溫故而知新,幫助自己,也是幫助別人,有則改之無則加勉(歡迎討論)!