C語(yǔ)言中的八大排序算法詳解
前言
所謂排序,就是使一串記錄,按照其中的某個(gè)或某些關(guān)鍵字的大小,遞增或遞減的排列起來(lái)的操作。
排序算法在很多領(lǐng)域得到相當(dāng)?shù)刂匾?,尤其是在大量?shù)據(jù)的處理方面,一個(gè)優(yōu)秀的算法可以節(jié)省大量的資源。
一、八大排序算法:
1.直接插入排序:
直接插入排序就是把待排序的元素逐個(gè)插入到一個(gè)已經(jīng)排好序的有序序列中,直到所有的記錄插入完為止,得到一個(gè)新的有序序列 。
實(shí)際中我們玩撲克牌時(shí),就用了插入排序的思想

動(dòng)圖演示:

那比如給我們一段序列,代碼如何實(shí)現(xiàn)呢? 我們可以把第一個(gè)元素看成有序序列(一個(gè)元素序列必然有序)進(jìn)行多次單趟插排
void InsertSort(int* arr, int size)//直接插入排序
{
for (int i = 0; i < size - 1; i++)
{
//單趟插入排序
//基本思想:[0,end]區(qū)間值為有序
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;//在這里break出去再去賦值tmp是為了防止最后一次end = -1進(jìn)不來(lái)賦值
}
}
arr[end + 1] = tmp;
}
}2.希爾排序:
希爾排序是對(duì)直接插入排序的優(yōu)化,它對(duì)序列先進(jìn)行多次預(yù)排序使之接近有序,因?yàn)樽詈蠼咏行蚴褂弥苯硬迦肱判蚍浅?臁?/p>

如圖所示:
- 當(dāng)gap越大,預(yù)排序越快,但是越不接近有序
- 當(dāng)gap越小,數(shù)據(jù)處理越慢,越接近有序
- 當(dāng)gap為1即直接插入排序
如下代碼所示:所以我們可以對(duì)gap進(jìn)行動(dòng)態(tài)改變
void ShellSort(int* arr, int size)//希爾排序
{
int gap = size;
//多次預(yù)排+最后一次直接插入排序
while (gap > 1)
{
gap = gap / 3 + 1;//控制最后一次進(jìn)來(lái)gap為1進(jìn)行直接插入排序
for (int i = 0; i < size - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}3.選擇排序:
選擇排序在未排序序列中找到最?。ù螅┰兀娣诺脚判蛐蛄械钠鹗嘉恢?,然后以此類(lèi)推,直到所有元素均排序完畢。
動(dòng)圖演示:

我們實(shí)際可以在一次遍歷中同時(shí)找到最小和最大的值:
void SelectSort(int* arr, int size)//優(yōu)化選擇排序
{
int begin = 0;
int end = size - 1;
while (begin < end)
{
int mini = begin, maxi = begin;
for (int i = begin + 1; i <= end; i++)
{
if (arr[i] < arr[mini])
{
mini = i;
}
if (arr[i] > arr[maxi])
{
maxi = i;
}
}
Swap(&arr[mini], &arr[begin]);
//如果maxi = begin,上一步交換了begin和mini的值,會(huì)影響maxi指向的值
if (maxi == begin)
{
maxi = mini;
}
Swap(&arr[maxi], &arr[end]);
begin++;
end--;
}
}4.堆排序:
堆排序可以看之前這篇:C語(yǔ)言中的二叉樹(shù)和堆詳解
5.冒泡排序:
冒泡排序也是通過(guò)遍歷比較左右值得大小,例如排升序即左值大于右值交換,最后最大值即排到最右邊。
動(dòng)圖演示:

void BubbleSort(int* arr, int size)//冒泡排序
{
for (int i = 1; i < size; i++)
{
int flag = 0;
for (int j = 0; j < size - i; j++)
{
if (arr[j] > arr[j + 1])
{
Swap(&arr[j], &arr[j + 1]);
flag++;
}
}
if (flag == 0)
{
break;
}
}
}6.快速排序:
這邊講解的是快速排序的前后指針?lè)ǎ?/p>
- 首先選擇一個(gè)keyi位置,一般為序列首。
- 創(chuàng)建兩個(gè)指針,prev指向keyi,cur指向prev+1
- cur往右找小于keyi位置的值,如果找到了prev往前找大于keyi位置的值,然后交換cur和prev位置的值(注意,這里既然cur找到arr[cur]>arr[keyi],那么cur和prev之間的值必然都會(huì)大于arr[keyi])
- 最后cur走完序列,再把keyi和prev位置值交換,這樣keyi左邊都會(huì)比他小,右邊都會(huì)比他大
- 再將區(qū)間分為[begin,keyi-1],[keyi+1,end]繼續(xù)遞歸直至有序
動(dòng)圖演示:

7.歸并排序:
歸并排序?qū)⒁延行虻淖有蛄泻喜ⅲ玫酵耆行虻男蛄?;即先使每個(gè)子序列有序,再使子序列段間有序。若將兩個(gè)有序表合并成一個(gè)有序表,稱(chēng)為二路歸并。
動(dòng)圖演示:


void _MergeSort(int* arr, int begin, int end, int* tmp)
{
if (begin >= end)
{
return;
}
//遞歸找有序區(qū)間
int mid = (end + begin) / 2;
//[begin, mid][mid+1,end]
_MergeSort(arr, begin, mid, tmp);
_MergeSort(arr,mid + 1, end, tmp);
//左右區(qū)間歸并有序
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] <= arr[begin2])
{
tmp[i++] = arr[begin1++];
}
else
{
tmp[i++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = arr[begin2++];
}
//輔助數(shù)組tmp中數(shù)據(jù)返回拷貝到原數(shù)組
memcpy(arr + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* arr, int size)//歸并排序
{
int* tmp = (int*)malloc(sizeof(int) * size);
if (tmp == NULL)
{
perror("malloc:fail");
exit(-1);
}
int begin = 0;
int end = size - 1;
_MergeSort(arr, begin, end, tmp);
}8.計(jì)數(shù)排序:
- 統(tǒng)計(jì)相同元素出現(xiàn)次數(shù)根據(jù)
- 統(tǒng)計(jì)的結(jié)果將序列回收到原來(lái)的序列中
- 計(jì)數(shù)排序只適用于范圍集中且重復(fù)數(shù)據(jù)較高的數(shù)據(jù)
動(dòng)圖演示:

//計(jì)數(shù)排序只適用于范圍集中且重復(fù)數(shù)據(jù)較高的數(shù)據(jù)
void CountSort(int* arr, int size)//計(jì)數(shù)排序
{
int min = arr[0];
int max = arr[0];
for (int i = 1; i < size; i++)
{
if (arr[i] < min)
{
min = arr[i];
}
if (arr[i] > max)
{
max = arr[i];
}
}
//計(jì)數(shù)數(shù)組count
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
perror("malloc:fail");
exit(-1);
}
memset(count, 0, sizeof(int) * range);
//開(kāi)始計(jì)數(shù)
for (int i = 0; i < size; i++)
{
count[arr[i] - min]++;
}
//回寫(xiě)排序
int j = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
{
arr[j++] = i + min;
}
}
}二、八大排序算法總結(jié):
| 排序算法 | 時(shí)間復(fù)雜度(平均) | 時(shí)間復(fù)雜度(最壞) | 時(shí)間復(fù)雜度(最好) | 空間復(fù)雜度 | 穩(wěn)定性 |
| 插入排序 | O(N^2) | O(N^2) | O(N) | O(1) | 穩(wěn)定 |
| 希爾排序 | O(N^1.3) | O(N^2) | O(N) | O(1) | 不穩(wěn)定 |
| 選擇排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不穩(wěn)定 |
| 堆排序 | O(N*log2(N)) | O(N*log2(N)) | O(N*log2(N)) | O(1) | 不穩(wěn)定 |
| 冒泡排序 | O(N^2) | O(N^2) | O(N) | O(1) | 穩(wěn)定 |
| 快速排序 | O(N*log2(N)) | O(N^2) | O(N*log2(N)) | O(N*log2(N)) | 不穩(wěn)定 |
| 歸并排序 | O(N*log2(N)) | O(N*log2(N)) | O(N*log2(N)) | O(N) | 穩(wěn)定 |
| 計(jì)數(shù)排序 | O(N+K) | O(N+K) | O(N+K) | O(N+K) | 穩(wěn)定 |
到此這篇關(guān)于C語(yǔ)言中的八大排序算法詳解的文章就介紹到這了,更多相關(guān)C語(yǔ)言排序算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
QT5中使用QRegularExpression代替QRegExp方法代碼
這篇文章主要給大家介紹了關(guān)于QT5中使用QRegularExpression代替QRegExp的相關(guān)資料,正則表達(dá)式(regep)是處理字符串和文本的強(qiáng)大工具,驗(yàn)證regexp可以測(cè)試子字符串是否滿(mǎn)足某些條件,例如是整數(shù)或不包含空格,需要的朋友可以參考下2024-04-04
C++實(shí)現(xiàn)LeetCode(41.首個(gè)缺失的正數(shù))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(41.首個(gè)缺失的正數(shù)),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
Qt實(shí)現(xiàn)將qsqlite數(shù)據(jù)庫(kù)中的數(shù)據(jù)導(dǎo)出為Excel表格
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)Qt實(shí)現(xiàn)將qsqlite數(shù)據(jù)庫(kù)中的數(shù)據(jù)導(dǎo)出為Excel表格,文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以了解一下2024-12-12
C++實(shí)現(xiàn)LeetCode(50.求x的n次方)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(50.求x的n次方),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

