一些比較常用的排序算法(C語言)
- 插入排序
- 選擇排序
- 歸併排序
- 堆排序
- 計數排序
- 快速排序
插入排序
- 算法分析
- 從第2個數開始,與之前第一個數比較,如果小於第一個數,那麼將第一個數後移,同時設置臨時變量保存第二個數,將第一個數設置爲第二個數
- 依次類推,當比較到arr[i]時,那麼將arr[i]與arr[i-1]個數相比較,如果第i個數小於第i-1個數, 那麼將第i-1個數後移,同時,與第i-2個數想比較…
- 如果第arr[i-1]個數>arr[i-2]個數,跳出循環,同時將保存的第i個數插入到i-2的位置
-
算法演示
-
算法實現(帶主函數)
void InsertSort(int numbers[],int length)
{
int p; //保存當前的位置
int tempValue; //保存當前變量
for (int i = 1;i < length ;i++)
{
tempValue = numbers[i]; //從第二個元素開始
p = i - 1; //標記上一個元素的位置
while (p >= 0 && tempValue < numbers[p]) {
//與上一個數比較,如果上一個數>當前要比較的數,將上一個數後移
numbers[p + 1] = numbers[p];
p --; //再與上一個數的上一個數進行比較
}
numbers[p+1] = tempValue; //如果上一個數<當前要比較的數,則將當前數插入到相應的位置
}
}
主函數的實現
/*
隨機生成一組數,測試使用,當然可以自己手動構建一組數進行測試
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void initARandom(int nums[],int length)
{
srand((unsigned)time(NULL));
for (int i = 0; i < length; ++i) {
nums[i] = rand()%60;
}
}
/*
打印數組中的值,方便測試
*/
void print_array(int A[],int length,char *print_string){
printf("%s\n", print_string);
for (int i = 0; i < length ; ++i){
printf("%d\t",A[i]);
}
printf("\n");
}
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
InsertSort(numbers, length);
print_array(numbers,length,"after sort:");
return 0;
}
輸出結果:
before sort:
38 15 54 18 35 31 51 34 46
after sort:
15 18 31 34 35 38 46 51 54
Program ended with exit code: 0
- 算法時間與空間複雜度:
最好情況:O(n)
最壞情況:O(n^2)
平均:O(n^2)
空間複雜度:O(1)
是一種穩定的排序算法
選擇排序
- 算法描述
- 查找每組數中的最大值,與最後一位數交換
- 依次遞減,再次查找最大值,與倒數第二個交換
- …直到交換到第一個數字,循環結束
- 算法演示
- 算法實現:
/*
查找最大值,返回index
*/
int findMax(int arr[],int length)
{
int max = arr[0];
int index = 0;
for (int i = 0; i < length; i++) {
if (max < arr[i])
{
max = arr[i];
index = i;
}
}
return index;
}
void selection_sort(int arr[],int length)
{
int maxPos;
while (length > 1)
{
maxPos = findMax(arr, length);
int temp = arr[length - 1];
arr[length - 1] = arr[maxPos];
arr[maxPos] = temp;
length--;
}
}
主函數中的實現
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
selection_sort(numbers, length);
print_array(numbers,length,"after sort:");
return 0;
}
測試結果
before sort:
25 16 10 52 51 21 7 59 49
after sort:
7 10 16 21 25 49 51 52 59
Program ended with exit code: 0
- 算法時間與空間複雜度:
最好情況:O(n^2)
最壞情況:O(n^2)
平均:O(n^2)
空間複雜度:O(1)
是不穩定排序算法
歸併排序
-
算法分析
主要分兩段:
第一段是合併算法
第二段是分治思想遞歸分解每一段,對每一小段進行排序
第一段合併算法:假設我們已經有了一個分爲兩段排好序的數組(例如:[6, 7, 8, 9,0,1,2,3,4])- 首先定義三個指針變量,
i
,j
k
,i指針指向第一段[6,7,8,9]中的第一個首元素,j指向第二段[0,1,2,3,4]中的0,k指針指向要輸出的數組首地址,[6,7,8,9,0,1,2,3,4]中的6 - 通過比較A[i] 與 B[j]的大小,如果A[i] < B[j],那麼,k指向A[i],i++,k++,反之,j++,k++,如果i 與 j 其中的一個已經達到了A 或者B的長度減一,那麼跳出循環,將另外一個數組中的元素輸出到k所指向的數組中,完成合並
第二段:產生有序的兩段數組,爲步驟1,步驟2做準備
- 產生一個分兩段排好序的數組,用遞歸操作,首先將數組分爲兩段,A數組和B數組,再對A數組分段,對B數組分段,直到分段數組中左右兩段元素個數都爲1個,跳出遞歸
- 調用上述合併算法,將左右兩段進行合併,完成排序
*算法演示
- 算法實現:
合併算法:
- 首先定義三個指針變量,
/*
leftStart:表示數組開始點
rightEnd:表示數組結束點
m:表示要將數組分割的分割點
*/
void merge(int array[],int leftStart,int m,int rightEnd)
{
int LEFT_SIZE = m - leftStart; //計算左邊數組的長度
int RIGHT_SIZE = rightEnd - m + 1; //計算右邊數組的長度
int leftArray[LEFT_SIZE]; //初始化分段左邊的數組
int rightArray[RIGHT_SIZE];//初始化分段右邊的數組
//1.將傳入的數組中前leftSize個元素分別輸出的leftArray數組中
for (int i = leftStart ; i < m; i++)
{
leftArray[i - leftStart] = array[i];
}
//2.將m - rightEnd之間的數據輸出到rightArray數組中
for (int i = m; i <= rightEnd; i++)
{
rightArray[i - m] = array[i];
}
int i = 0,j = 0,k = leftStart;
//將兩個數組內容合併,假如leftArray 或者 rightArray中某一個數組已經達到了它的長度,那麼跳出循環
while (i < LEFT_SIZE && j < RIGHT_SIZE)
{
if (leftArray[i] < rightArray[j])
{
array[k] = leftArray[i];
i++;
k++;
}else
{
array[k] = rightArray[j];
j++;
k++;
}
}
//如果是rightArray達到了它的長度,則將leftArray剩餘的數據輸出到輸出的數組
while (i < LEFT_SIZE)
{
array[k] = leftArray[i];
k++;
i++;
}
//如果是leftArray達到了它的長度,則將rightArray剩餘的數據輸出到輸出的數組
while (j < RIGHT_SIZE)
{
array[k] = rightArray[j];
k++;
j++;
}
}
分段算法:
//分治法思想,切分亂序數組
void mergeSort(int array[],int leftStart,int rightEnd)
{
if (leftStart == rightEnd) return; else {
int m = (leftStart + rightEnd) / 2;//找出中心位置
mergeSort(array, leftStart, m);//切分左邊數組
mergeSort(array, m + 1, rightEnd);//切分右邊數組
merge(array, leftStart, m + 1, rightEnd);//合併左右兩邊的數組
}
}
主函數中的實現:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
mergeSort(numbers, 0, length - 1);
print_array(numbers,length,"after sort:");
return 0;
}
實現結果:
before sort:
14 49 50 50 11 13 59 51 56
after sort:
11 13 14 49 50 50 51 56 59
Program ended with exit code: 0
- 算法時間複雜度與空間複雜度
最好情況:O(nlog2n)
最壞情況:O(nlog2n)
平均:O(nlog2n)
空間複雜度:O(n)
是一種穩定的排序算法
堆排序
-
算法分析:
首先要了解一下二叉樹的概念:- 每一個堆就是一個完全二叉樹,堆中的每一個根節點都小於兩個孩子子結點稱爲小頂堆,每一個根節點大於兩個孩子結點稱爲大頂堆
- 已知第i個孩子結點,那麼父結點爲
(i- 1)/2 向下取整
- 已知一個父親結點i:那麼左孩子結點爲:
2i + 1
,右孩子結點爲2i+2
- 最後一個葉子結點爲:
n - 1
,n爲結點個數 - 升序排序,要找大頂堆
- 降序排序,要找小頂堆
過程:
- 升序排序,創建大根堆
- 將大根堆的根節點與最後一個葉子結點進行交換
- 交換後將最後一個結點剔除(並不是物理剔除),重新構建大根堆(不包含最後一個最大的葉子結點),直到下次構建大根堆的時候當前結點已經大於n,循環結束,排序完成
- 算法演示
- 算法實現:
/*
交換數組中兩個元素之間的順序
*/
void swap(int arr[],int i, int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/*
除該節點外,子結點均爲堆時,可以直接調用此方法,此方法是堆排序的最小化單元
*/
void heapify(int tree[],int n,int i)
{
if (i >= n) //遞歸出口
{
return;
}
int left = 2 * i + 1; //第i個結點的左結點
int right = 2 * i + 2; //第i個結點的右結點
int max = i;
if (left < n && (tree[max] < tree[left]))
{ //當左結點不是葉子結點的時候(因爲葉子結點沒有子結點了)
max = left;
}
if (right < n && (tree[max] < tree[right]))
{
max = right;
}
//以上兩個判斷是找最大值,如果最大值不是當前結點,交換使得當前結點最大
if (max != i)
{
swap(tree, max, i);
//遞歸當前結點,當前結點達到跳出遞歸的條件,也就是到了葉子結點,跳出遞歸
heapify(tree, n, max);
}
}
以上heapify
算法是除了當前傳入的結點,子結點已經是堆了,纔會調用該方法,那麼如果是一個亂序的數組,則需要首先創建大根堆
void build_heap(int tree[],int n)
{
int last_node = n - 1;//找到最後一個葉子結點,自下而上的將所有父親結點都變爲大根堆
int parent = (last_node - 1) / 2;//找到父親結點
for (int i = parent; i >= 0; i--)
{
heapify(tree, n, i);將每一個結點都變爲大根堆
}
}
排序實現算法
void heap_short(int tree[],int n)
{
build_heap(tree,n); //首先要創建一個大根堆
int last_node = n - 1; //找到最後一個葉子結點
for (int i = last_node;i >= 0; i--)
{
swap(tree, i, 0);//交換根節點與葉子結點,交換後,根節點再是大根堆結點,但是除了最後一個結點,其他結點的子結點均爲大根堆結點,所以需要對根節點重新做一次heapify
heapify(tree, i, 0);
}
}
主函數中的實現:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
heap_short(numbers, length);
print_array(numbers,length,"after sort:");
return 0;
}
before sort:
10 47 40 30 51 37 13 25 23
after sort:
10 13 23 25 30 37 40 47 51
Program ended with exit code: 0
- 算法時間與空間複雜度:
最好情況:O(nlog2n)
最壞情況: O(nlog2n)
平均:O(nlog2n)
空間複雜度:O(1)
計數排序
-
算法分析:
- 首先,需要一個輔助數組cArray,用於存放計數的數目,還需要一個bArray,用於存放排好序的數據
- 獲取原數據數組中的最大值,已確定輔助數組cArray的長度,長度爲最大值+1
- 將原數組中第i個元素出現的次數放入到輔助數組cArray對應的下標,例如 原數組中的數據爲3,那麼就將1放入cArray下標爲3的位置cArray[3]=1
- 如果有重複,那麼就將該位置計數器加1,重複幾次,就加幾,比如3出現了4次,那麼cArray[3]=4
- 將原數組中的元素逐個輸出到bArray中,沒輸出一次,cArray對應的數據就要減1,比如已經輸出了3,那麼 cArray[3] = 4-1
-
算法演示:
-
算法實現:
//首先要獲取數組的最大值
int getMax(int nums[],int length)
{
int max = nums[0];
for (int i = 1; i < length; i++) {
if (nums[i] > max)
{
max = nums[i];
}
}
return max;
}
//計數方法
void counting(int numbers[],int bArray[],int k,int length)
{
//找出最大值,確認在計數排序數組中的位置
int cArray[k + 1];
for (int i = 0; i < k + 1; i++)
{
cArray[i] = 0;
}
//計數排序核心,記錄每個數據出現的次數,在cArray中對應的位置做+1操作
for (int i = 0; i < length; i++) {
cArray[numbers[i]] += 1;
}
//將cArray中每一項與前一項做加法,得到i(這裏的i其實是原數組中的元素) 元素在即將要輸出數組中的位置
for (int i = 1 ;i < k + 1; i++)
{
cArray[i] += cArray[i - 1];
}
//輸出到B數組
for (int i = 0; i < length ; i++)
{
//輸出一次,那麼久相應的要在c數組中對應的位置減一
cArray[numbers[i]] --;
bArray[cArray[numbers[i]]] = numbers[i];
}
numbers
}
//排序的實現
void counting_sort(int array[],int bArray[],int length)
{
int max = getMax(array, length);
counting(array, bArray, max, length);
}
在主函數中的實現:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
int bArray[length];
counting_sort(numbers,bArray, length);
print_array(bArray,length,"after sort:");
return 0;
- 輸出結果
before sort:
6 21 14 32 2 21 33 34 25
after sort:
2 6 14 21 21 25 32 33 34
Program ended with exit code: 0
- 算法時間與空間複雜度分析:
最好情況:O(n + k)
最壞情況:O(n + k)
平均:O(n + k)
空間複雜度:O(n+k)
計數排序是一個穩定的排序
快速排序
- 算法分析:
在快排中,還是用用到分治的思想,將問題最小化到一個子問題,然後通過遞歸到全部問題。- 首先,我們要找一個basicKey,也叫pivotKey,作爲左右指針比較的基準值,從最右邊的指針開始,依次向左移動,直到出現一個比pivotKey小的值,將a[right]賦值給a[left],然後移動左指針left,找到一個比pivokey大的值,將a[left]賦值給a[right],循環直到left=right,此時返回left與right所處的值爲整個數組的中心點
- 將pivokey賦值給a[left]
- 以返回的樞值爲中心,分別對左右兩個子數組進行步驟一,直到數組中的元素爲1,此時,跳出遞歸
- 整個循環結束,排序完成
- 算法演示圖:
- 算法實現過程:
//返回樞值(中心值,也就是left與right指針重合的地方)
int partition(int array[],int left,int right)
{
int key = array[left];// 將第1個作爲關鍵字
while (left < right) {// 兩端交替向中間掃描
// 移動最右邊的指針,找到比pivotKey值小的值
while (left < right && array[right] >= key) --right;
array[left] = array[right];// 將比關鍵字小的移動到左邊
// 移動左指針,找到比pivotKey大的值
while (left < right && array[left] <= key) ++left;
array[right] = array[left];// 將比關鍵字大的移動到右邊
}
array[left] = key;// 此時left就是一趟快速排序後的關鍵字所在的位置
return left;
}
//排序算法
void quickShort(int array[],int left,int right)
{
if (left < right)
{
int mid = partition(array,left,right);
quickShort(array, left, mid - 1); //遞歸對左子數組排序
quickShort(array, mid + 1, right);//遞歸對右子數組排序
}
}
主函數中的實現
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
quickShort(numbers, 0, length - 1);
print_array(numbers,length,"after sort:");
return 0;
}
- 輸出結果:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
quickShort(numbers, 0, length - 1);
print_array(numbers,length,"after sort:");
return 0;
}
- 時間複雜度與空間複雜度:
最好:O(nlog2n)
最壞:O(n^2)
平均:O(nlog2n)
空間複雜度:O(nlog2n)
快速排序是不穩定排序