目录
排序算法常见的有(7+1)种:

下面先介绍前七种:
算法一:插入排序(插入排序是一种简单直观的排序方法.)
算法思想:每次将一个需要排序的数据插入到已经排好序的有序数据序列中,从而得到一个新的、个数加一的有序数据序列.
插入排序的基本操作:
- 将待排序序列中第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)。
代码实现:
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 1; i < n; ++i)
{
//单个元素排序 第一个元素不需要排序
//找到已经排好序的最后一个元素的位置
int end = i-1;
//把end+1位置元素插入到合适的位置
int tmp = a[i];
while (end>=0 && a[end]>tmp)
{
a[end + 1] = a[end];
--end;
}
//找到合适的位置放
a[end + 1] = tmp;
}
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9,122, 4, 7 };
PrintArray(a, 10);
InsertSort(a, 10);
PrintArray(a, 10);
return 0;
}
简单过程图解说明:

小结:
- 时间复杂度 :O(N^2)
- 空间复杂度 :O(1)
- 稳定性 :稳定
- 适合场景 :接近有序序列,时间复杂度趋近于O(N)--> 对于有序序列,时间复杂度O(N)
算法二:希尔排序(缩小增量排序)
希尔排序是插入排序的一种又称“缩小增量排序”,是对直接插入排序算法的一种更高效的改进优化版本。
算法思想(过程):先取一个小于n的整数gap1作为第一个增量,把文件的全部记录分组。所有距离为gap的1倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量gap2<gap1重复上述的分组和排序,直至所取的增量gap =1,即所有记录放在同一组中进行直接插入排序为止。
代码实现:
void ShellSort(int* a, int n)
{
//gap>1 预排序
//gap=1 最终排序过程
int gap=n;
while (gap>1)
{
gap = gap / 3 + 1; //+1是保证最后一次gap为1插入排序
for (int i = gap; i<n; i++)
{
int end = i-gap;
int tmp = a[i];
while (end >= 0 && a[end]>tmp)
{
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
}
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
ShellSort(a, 10);
PrintArray(a, 10);
return 0;
}
简单过程图解说明:

小结:
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序
- 时间复杂度 :平均:O(1.3)--O(N^2)
- 空间复杂度 :O(1)
- 稳定性: 不稳定
算法三:选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的
数据元素排完 。
基本操作:
- 第一次从待排序的数据序列中选出最小(或最大)的一个数据,存放在序列的起始位置,
- 然后再从剩余的未排序数据序列中寻找到最小(大)数据,然后放到已排序的序列的末尾。
- 以此类推,直到全部待排序的数据的个数为零
代码实现(包含普通写法和优化写法):
//基本写法
//void SelectSort1(int* a, int n)
//{
// int max = 0;
// int i = 0;
// int j = 0;
// for (i = 0; i < n;i++)
// {
// max = a[0];
// for (j = 1; j < n - 1;j++)
// {
// if (a[j]>a[max])
// {
// max = j;
// }
// }
// if (max!=j-1)
// {
// Swap(&a[j - 1], &a[max]);
// }
// }
//
//}
//优化写法
void SelectSort2(int* a, int n)
{
int begain = 0;
int end = n - 1;
while (begain<end)
{
//每一次选一个最大的和最小的,放在相应的位置
int i;
int max;
int min;
max = min = begain;
for (i = begain; i <= end;i++)
{
if (a[i]<a[min])
min = i;
if (a[i]>=a[max])
max = i;
}
//min -->begain max--->end
Swap(&a[begain], &a[min]);
if (max==begain)
{
max = min;
}
Swap(&a[end], &a[max]);
begain++;
--end;
}
}
/*如果不进行if (max==begain) max = min;操作
可以使用测试用例1 10 8 9 3 2 4 5 7 6进行操作验证
*/
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
SelectSort1(a, 10);
SelectSort2(a, 10);
PrintArray(a, 10);
return 0;
}
简单过程图解说明:


小结:
- 时间复杂度 O(N^2)
- 空间复杂度 O(1)
- 稳定性: 可以变成稳定的
算法四:堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。(我们先了解下堆)
基本思想(操作):
- 将初始待排序关键字序列(R1,R2....Rn)构建成大(或小)顶堆(使用向下调整算法),此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整(使用向下调整算法)为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
代码实现:
void ShiftDown(int* a, int n, int root)
{
assert(a);
int parent = root;
int child = 2 * parent + 1;//定义左孩子
//当前节点是否有孩子
while (child <n)
{
//是否有右孩子 如果左孩子存在,选出孩子中最大的那个
if (child + 1 < n && a[child + 1] > a[child])//左孩子存在且左孩子大于右孩子
++child;//更新孩子索引
if (a[child]>a[parent])//判断孩子中最大的节点和父亲节点的关系 如果大于
{
//进行交换
Swap(&a[child], &a[parent]);//传址调用
//更新下一次调整位置
parent = child;
child = 2 * parent + 1;
}
//如果小于 结束此次调整
else
{
break;//以parent为根的子树已经是一个大堆了
}
}
}
void HeapSort(int*a, int n)
{
int i;
//建堆 从最后一颗子树开始: (n - 2)/2 i:表示当前子树的根
for (i = (n - 2) / 2; i >= 0; --i)
{
ShiftDown(a, n, i);
}
//排序: 1.swap 2. shiftdown
int end = n - 1;
while (end>=1)
{
Swap(&a[0], &a[end]);
ShiftDown(a, end, 0);
--end;
}
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
HeapSort(a, 10);
PrintArray(a, 10);
return 0;
}
简单过程图解说明:


小结:
- 时间复杂度: O(N*logN)
- 空间复杂度: O(1)
- 稳定性: 不稳定
算法五:冒泡排序
基本思想(操作):
1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3)针对所有的元素重复以上的步骤,除了最后一个。
4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码实现(包括基本版和优化版):
void BubbleSort1(int* a, int n)
{
for (int i = n; i > 0;i--)
{
for (int j = 1; j < i; j++)
{
if (a[j-1]>a[j])
{
Swap(&a[j], &a[j - 1]);
}
}
}
}
//优化版
void BubbleSort(int*a, int n)
{
for (int i = n-1; i > 0;i--)
{
int flag = 0;
for (int j = 0; j < i;j++)
{
if (a[j]>a[j+1])
{
flag = 1;//标记一次冒泡过程是否发生元素交换
Swap(&a[j], &a[j + 1]);
}
}
if (flag==0)
{
break;
}
}
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 12, 4, 7 };
PrintArray(a, 10);
//BubbleSort1(a, 10);
BubbleSort(a, 10);
PrintArray(a, 10);
return 0;
}
简单过程图解说明:

小结:
- 时间复杂度 :最坏:O(N^2) 最好:(有序)O(N)
- 空间复杂度 :O(1)
- 稳定性: 稳定
算法六:快速排序
基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
划分区间方法:将区间按照基准值划分为左右两半部分的常见方式有:
- hoare版本
- 挖坑法
- 前后指针版本
在这里我们进行优化快排算法还可以用到一种算法(三数取中法)
三数取中法实现:
//三数取中法:保证每一次划分均衡
int getMid(int* a, int left, int right)
{
int mid = left + (right - left) / 2;
if (a[mid] > a[left])
{
if (a[mid] < a[right])
{
return mid;
}
else
{
//mid>left, right
if (a[left] > a[right])
{
return left;
}
else
return right;
}
}
else
{
//mid <= left
if (a[left] < a[right])
{
return left;
}
else
{
//left >= right, mid
if (a[mid] > a[right])
{
return mid;
}
else
return right;
}
}
}
代码实现:
基本方法:
//快排基本方法
//快排的一次排序:确定基准值的位置
int PartSort(int* a, int left, int right)
{
int key = a[left];
int start = left;
while (left < right)
{
//先从右边找小于key的值
while (left < right && a[right] >= key)
{
--right;
}
//从左边找大于key的值
while (left < right && a[left] <= key)
{
++left;
}
Swap(&a[left], &a[right]);
}
//key位置确定: left ,right 相遇位置
Swap(&a[start], &a[left]);
return left;
}
//快排
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int mid = PartSort(a, left, right);
QuickSort(a, left, mid - 1);
QuickSort(a, mid + 1, right);
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
QuickSort(a, 0, 9);
PrintArray(a, 10);
return 0;
}
简单图解:

优化1:
//快排的一次排序:确定基准值的位置
int PartSort1(int* a, int left, int right)
{
//三数取中
int mid = getMid(a, left, right);
Swap(&a[mid], &a[left]);
int key = a[left];
int start = left;
while (left < right)
{
//先从右边找小于key的值
while (left<right && a[right] >= key)
{
--right;
}
//从左边找大于key的值
while (left<right && a[left] <= key)
{
++left;
}
Swap(&a[left], &a[right]);
}
//key位置确定: left ,right 相遇位置
Swap(&a[start], &a[left]);
return left;
}
//快排
void QuickSort1(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int mid = PartSort1(a, left, right);
QuickSort1(a, left, mid - 1);
QuickSort1(a, mid + 1, right);
/*
还可以进行小区间优化
if (left >= right)
{
return;
}
//小区间优化: 小区间不调用递归
else(right - left + 1 < 5)
{
InsertSort(a+left,right-left+1)
}
else
{
int mid = PartSort(a, left, right);
QuickSort(a, left, mid - 1);
QuickSort(a, mid + 1, right);
}
*/
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
QuickSort1(a, 0, 9);
PrintArray(a, 10);
return 0;
}
挖坑法:
//挖坑法: 快排的一次排序-->确定基准值的位置
int PartSort2(int* a, int left, int right)
{
int mid = getMid(a, left, right);
Swap(&a[mid], &a[left]);
int key = a[left];
while (left<right)
{
//从右边找小的
while (left < right && a[right] >= key)
--right;
//填坑
a[left] = a[right];
//从左边找大的
while (left < right && a[left] <= key)
++left;
a[right] = a[left];
}
//左右相遇 存放key 基准值
a[left] = key;
return left;
}
//快排
void QuickSort2(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int mid = PartSort2(a, left, right);
QuickSort2(a, left, mid - 1);
QuickSort2(a, mid + 1, right);
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
QuickSort2(a, 0, 9);
PrintArray(a, 10);
return 0;
}
前后指针法:
//快排3前后指针法 prev:最后一个小于key的位置 cur:下一个小于key的位置
int PartSort3(int* a, int left, int right)
{
int mid = getMid(a, left, right);
Swap(&a[mid], &a[left]);
int prev = left;
int cur = left + 1;
int key = a[left];
while (cur <= right)
{
if (a[cur] <key && (++prev)!=cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[left]);
return prev;
}
void QuickSort3(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int mid = PartSort3(a, left, right);
QuickSort3(a, left, mid-1);
QuickSort3(a, mid + 1, right);
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
QuickSort3(a, 0, 9);
PrintArray(a, 10);
return 0;
}
非递归版(利用栈)
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st) != 0)
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st);
int div = PartSort1(a, begin, end);
if (begin < div - 1)
{
StackPush(&st, begin);
StackPush(&st, div - 1);
}
if (div + 1 < end)
{
StackPush(&st, div + 1);
StackPush(&st, end);
}
}
}
小结:
- 时间复杂度:最好的时间复杂度:O(N*logN) 最坏的时间复杂度(原有序-->变逆序):O(N^2)
- 空间复杂度:空间可以复用,最大的递归调用链--->logN
- 稳定性 : 不稳定
算法七:归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法采用分治的思想.
基本步骤:
- 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针超出序列尾. 将另一序列剩下的所有元素直接复制到合并序列尾
代码实现:
//分解-->合并
void _MergeSort(int* a, int left, int right, int* tmp)
{
//区间只剩一个元素,不需要分解和归并
if (left >= right)
{
return;
}
//分解
int mid = left + (right - left) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
//归并
int begain1 = left;
int end1 = mid;
int begain2 = mid + 1;
int end2 = right;
int tmpindex = begain1;
while (begain1 <= end1 && begain2 <= end2)
{
if (a[begain1] <= a[begain2])
{
tmp[tmpindex++] = a[begain1++];
}
else
{
tmp[tmpindex++] = a[begain2++];
}
}
while (begain1 <= end1)
{
tmp[tmpindex++] = a[begain1++];
}
while (begain2 <= end2)
{
tmp[tmpindex++] = a[begain2++];
}
//拷贝到原有数组对应区间
memcpy(a + left, tmp + left, (right - left + 1)*sizeof(int));
}
void MergeSort(int* a, int n)
{
int* ret = (int*)malloc(n*sizeof(int));
_MergeSort(a, 0, n-1 , ret);
free(ret);
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
MergeSort(a, 10);
PrintArray(a, 10);
return 0;
}
简单图解:

小结:
- 时间复杂度:O(N*logN)
- 空间复杂度: O(N+logN)--O(N)
- 稳定性: 稳定
还要介绍一种特殊的排序方法-->计数排序
算法八:计数排序
计数排序一种非比较型整数排序算法,不同于前七种,它采用了类似于哈希表的思想方法
基本思想::计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
基本操作:
- 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
- 遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
- 对额外空间内数据进行计算,得出每一个元素的正确位置;
- 将待排序集合每一个元素移动到计算得出的正确位置上。
代码实现:
void CoutSort(int* a, int n)
{
int i = 0;
int min = a[0];
int max = a[0];
//获取数据的范围 范围: 最大值<--->最小值
for (i = 1; i < n;++i)
{
if (a[i]<min)
{
min = a[i];
}
if (a[i]>max)
{
max = a[i];
}
}
int range = max - min + 1;
int* CoutArr = (int*)malloc(sizeof(int)*range);
memset(CoutArr, 0, sizeof(int)*range);
//计数
for (int i = 0; i < n; ++i)
{
CoutArr[a[i] - min]++;
}
//排序
int index = 0;
for (i = 0; i < range; ++i)
{
while (CoutArr[i]--)
{
a[index++] = i + min;
}
}
}
int main()
{
int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
PrintArray(a, 10);
CoutSort(a, 10);
PrintArray(a, 10);
return 0;
}
简单图解:

小结:
- 时间复杂度: O(max(N,范围))
- 空间复杂度: O(范围)
- 稳定性: 稳定
补充几种排序算法中用到的函数实现:(打印函数,交换两个变量值函数)
void PrintArray(int* a, int n)
{
for (int i = 0; i < n;++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
void Swap(int*a, int*b)
{
int tmp=*a;
*a = *b;
*b = tmp;
}
排序算法复杂度及稳定性分析:

本文详细介绍了8种排序算法,包括插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序以及计数排序。每种算法都阐述了其基本思想、时间复杂度、空间复杂度和稳定性,并提供了简单的代码实现和图解说明。特别地,计数排序是一种非比较型整数排序算法,适用于特定场景。

2万+

被折叠的 条评论
为什么被折叠?



