排序算法
目录
前言
今天为我们来通过C语言来实现常见排序算法:直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、堆排序,了解算法的具体实现和算法的性能。
一、排序的概念
1.1排序的概念
1.2 常见的排序算法
![](https://img-blog.csdnimg.cn/direct/5b07f06857d24ce3b9f732c26cb3865e.png)
二、常见排序算法的实现
2.1 插入排序
动画图解如下:
![](https://img-blog.csdnimg.cn/20210223174254141.gif#pic_center)
//插入排序
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end=i;
int temp = a[end + 1];
while (end >= 0)
{
//比较与end位置元素大小
if (temp < a[end])
{
a[end + 1] = a[end];
end--;
}
//大于end位置元素,跳出
else
{
break;
}
}
//1.end大于零,temp放在end元素后一个
//2.end小于零,temp也是放在end元素后,只不过此时为第一个元素
a[end + 1] = temp;
}
}
直接插入排序
时间复杂度:O(N^2)空间复杂度:O(1)
稳定性:稳定
最坏情况:逆序
最好情况是多少:顺序有序或者接近有序 O(N)
2.2 希尔排序
,先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序,重复上述步骤。然后排序就变得简单了。
![](https://img-blog.csdnimg.cn/direct/687de9cc59c64b588115fb7363c83e3d.png)
![](https://img-blog.csdnimg.cn/20210509190237603.gif#pic_center)
//希尔排序
void ShellSort(int* a, int n) {
int gap=n;
while (gap > 1)
{
gap/=2;
//循环到n-gap-1元素
for (int i = 0; i < n-gap; i++)
{
int end = i;
int temp = a[end + gap];
while (end >= 0)
{
if (temp < a[end])
{
a[end + gap] = a[end];
//end移动距离为gap
end -= gap;
}
else
{
break;
}
}
//各自插入到每组元素
a[end + gap] = temp;
}
}
}
希尔排序时间复杂度:O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定
2.3 选择排序
![](https://img-blog.csdnimg.cn/20210509190545640.gif#pic_center)
//选择排序
void SelectSort(int* a, int n) {
int begin = 0, end = n - 1;
while (begin < end)
{
int maxi = begin, mini = begin;
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[begin], &a[mini]);
//避免二次交换
if (begin == maxi)
maxi = mini;
Swap(&a[end], &a[maxi]);
//缩小范围
begin++;
end--;
}
}
直接选择排序时间复杂度: O(N^2)空间复杂度: O(1)稳定性:不稳定
2.4 堆排序
![](https://img-blog.csdnimg.cn/direct/2723e61f5d49485fbabf31e66dc4cac6.png)
//堆排序
//向下调整
void AdjustDown(int* a, int n, int parent) {
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
if (a[child] > a[parent])
{
Swap(&a[parent], &a[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n) {
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
堆排序时间复杂度: O(N*logN)空间复杂度: O(1)稳定性:不稳定
2.5 冒泡排序
![](https://img-blog.csdnimg.cn/20210509190446264.gif#pic_center)
//冒泡排序
void BubbleSort(int* a, int n) {
for (int i = 0; i < n - 1; i++)
{
int exchang = 0;
for (int j = 1; j < n - i; j++)
{
//前一个比后一个大就交换
if (a[j] < a[j - 1])
{
Swap(&a[j], &a[j - 1]);
exchang = 1;
}
}
if (exchang == 0)
break;
}
}
冒泡排序
时间复杂度: O(N^2)空间复杂度: O(1)稳定性:稳定
2.6 快速排序
思路:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
选择基准值有多种方法:
1.直接选择头或者尾为基准值(但是序列为有序或者接近有序时间复杂度大)
2.随机数取基准值
//随机取基准值
int randi = rand() % (right - left + 1);
randi += left;
Swap(&a[left], &a[randi]);//基准值放到序列头
3.三位数取中
取序列头尾和中间三个数,让不是最大的,也不是最小的作为基准值。
//三位数取中
int midi = GetMidi(a, left, right);//取中间值函数
Swap(&a[midi], &a[left]);//基准值放到序列头
将区间按照基准值划分为左右两半部分的常见方式有:
2.6.1 hoare版本
思路:选择key(基准值),从尾向头开始找比key小的,然后再从头找比key大的,交换两者,然后继续整个步骤,直到它们两个相遇,交换key位置到相遇位置处,这样key前面就全为比key小的,后面全为比key大的,此时key为正确位置,然后分割序列,分为key前面的和key后面,重复上面所有步骤。
我们这直接选择最左边为key
步骤:1.最左边为key,定义left,right代表key下一个位置,序列尾
2.让right向前走,找到比key小的,找到了停止
3.让left向后走,找到比key大的,找到了停止
4.交换此时righ和left的位置上的元素,重复2,3步骤
5.直到相遇,交换此时相遇位置和key的。
6.分割序列,分为keyi前面的序列和keyi后面的位置,重复以上所有步骤。
动画图解如下:
//快速排序 hoare版本
void QuickSort1(int* a, int left, int right) {
//当只有一个元素和为空序列返回空
if (left >= right)
return;
int begin = left, end = right;
//取序列开头为key
int keyi = left;
while (left < right)
{
//找比key小的
while (left < right && a[keyi] <= a[right])
{
right--;
}
//找比key大的
while (left < right && a[keyi] >= a[left])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
//递归[begin,keyi-1] 和 [keyi+1,end]
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi+1, end);
}
2.6.2 前后指针版本
思路:选择最左或者最右为key,定义两个指针prev和cur分别指向序列开头,prev下一个位置。然后向后走,如果遇到比key小的cur走,prev也向后走。直到遇到比key大的,只有cur走,prev不走,当再次遇到比key小的,prev先走,然后交换此时cur和prev的值,cur再走。重复上面步骤,直到cur走到序列尾,然后交换prev和key的值。通过上面操作我们可以让cur和prev中间的值为比key大的,此方法就是把比key大的往后推,小的往前移。
步骤:1.选择最左key,prev和cur分别指向序列开头,开头下一个
2.cur先走,prev后走
3.遇到比key大的,只有cur走
4.再遇到key小的,prev先走,然后交换此时prev和cur的值,cur在走
5.cur走到序列尾,交换prev和key的值
6..分割序列,分为keyi前面的序列和keyi后面的位置,重复以上所有步骤。
动画图解如下:
//快速排序
//前后指针法
void QuickSort2(int* a, int left, int right) {
if (left >= right)
return;
int prev = left, cur = left+1;
int keyi = left;
while (cur <= right)
{
//如果遇到比key小的才交换
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
//分割[left,keyi-1] [keyi+1,right]
QuickSort2(a, left, keyi - 1);
QuickSort2(a, keyi+1, right);
}
2.6.3 非递归版本
思路:之前我们都是通过递归来解决的,我们需要的是排序的左右子区间,所以我们可以通过一个栈来存储排序的左右子空间,根据栈先进后出的特性。左右子区间依次进栈出栈从而达到递归的效果。
void QuickSortNonR(int* a, int left, int right) {
ST st;
StackInit(&st);
//左右下标入栈
//右边先进 左边后进
StackPush(&st,right);
StackPush(&st,left);
while (!StackEmpty(&st))
{
left = StackTop(&st);
StackPop(&st);
right = StackTop(&st);
StackPop(&st);
//单趟
int prev = left, cur = left + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
//[left,key-1] && [key+1,right]
//进左右子区间
if (keyi + 1 < right)
{
StackPush(&st, right);
StackPush(&st, keyi + 1);
}
if (keyi - 1 > left)
{
StackPush(&st, keyi - 1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
快速排序时间复杂度: O(N*logN)空间复杂度: O(logN)稳定性:不稳定
2.7 归并排序
思路:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
![](https://img-blog.csdnimg.cn/direct/6ee3dd89bab04867a5579c4857777b6e.png)
![](https://img-blog.csdnimg.cn/f4465015ecb2490492d93ab8c575d0ea.gif)
//归并排序
void _MergeSort(int* a, int begin, int end, int* temp) {
//只有一个元素就返回
if (begin == end)
return;
//从中间开始分割
int mini = (begin + end) / 2;
//指向两个序列起始位置
int begin1 = begin, end1 = mini;
int begin2 = mini + 1, end2 = end;
_MergeSort(a, begin1, end1, temp);
_MergeSort(a, begin2, end2, temp);
//单趟
//选下的插入到新的序列中
int i = begin;//插入位置要保持相对位置
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
temp[i++] = a[begin1++];
}
else
{
temp[i++] = a[begin2++];
}
}
//插入没有插入完的
while (begin1 <= end1)
{
temp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = a[begin2++];
}
//拷贝位置也要保持相对位置
memcpy(a + begin, temp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n) {
//开辟新的序列
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc fail");
return;
}
_MergeSort(a, 0, n-1, temp);
free(temp);
temp = NULL;
}
非递归版本
我们之前是用递归先进行单个排序,在到组排。我们也可以不通过递归,通过回溯的思想,先定义一个gap,gap从1开始,即先单个为一组,然后两两排序,然后进行循环gap×2来进行gap数量为一组在进行归并。
void MergeSortNonR(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc fail");
return;
}
int gap = 1;
while (gap < n)
{
for (int j = 0; j < n; j += gap * 2)
{
//两个区间的起始位置
int begin1 = j, end1 = j + gap - 1;
int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
//判断是否有越界
if (end1 >= n || begin2 >= n)
break;
if (end2 >= n)
end2 = n - 1;
int i = j;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
temp[i++] = a[begin1++];
}
else
{
temp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = a[begin2++];
}
//相对位置
memcpy(a + j, temp + j, sizeof(int) * (end2 - j + 1));
}
gap *= 2;
}
free(temp);
temp = NULL;
}
归并排序
时间复杂度:O(N*long N)
空间复杂度:O(N)
稳定性:稳定
2.8 计数排序
//计数排序
void CountSort(int* a, int n) {
int max = a[0], min = a[0];
for (int i = 0; i < n; i++)
{
if (a[i] > max)
max = a[i];
if (a[i] < min)
min = a[i];
}
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
perror("malloc fail");
return;
}
memset(count, 0, sizeof(int) * range);
//计数
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
//排序
int j = 0;
for (int i = 0; i < n; i++)
{
while (count[i]--)
{
a[i] = i + min;
}
}
}
计数排序
时间复杂度:O(N+range)
空间复杂度:O(range)
三、算法性能
![](https://img-blog.csdnimg.cn/direct/bbddb2e62c574b678dcd9dc04af2ebc6.png)
总结
上述文章,我们介绍了各种排序算法,希望对你有所帮助。