👀个人简介:++一个正在努力奋斗逆天改命的二本觉悟生++
前言:今天这篇博客就给大家将一个计数排序,然乎就给大家总结一下所有的排序算法的时间复杂度,空间复杂度,稳定性进行一个归纳总结。
目录
一、计数排序
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
思路:
- 统计相同元素出现次数
- 根据统计的结果将序列回收到原来的序列中
核心步骤:
1. 确定数据范围
遍历数组,找到最大值和最小值,然后计算数据范围range=max-min+1确定数组的空间(避免空间浪费)


2. 统计元素出现次数
创建一个计数数组count,空间大小为range,并且要给count初始化(calloc或者memset),遍历原数组,将每个元素 arr[i] 映射到 count[arr[i] - min](减去 min 是为了处理包含负数的情况,一定要用arr[i]-min),统计每个值的出现次数。

3. 将count数组中的数据排序还原到原数组中
再定义一个index变量,作为原数组的下标,遍历count数组,根据count[i]统计到的个数进行映射i+min就是原数组的值,循环次数等于该值出现的次数,将数组的原始数据值放入arr原始数组中(对应原始值一定是i+min)

代码实现:
cpp
//非比较排序--计数排序
void CountSort(int* arr, int n)
{
int min = arr[0], max = arr[0];
for (int i = 0; i < n; i++)
{
if (arr[i] < min)
{
min = arr[i];
}
if (arr[i] > max)
{
max = arr[i];
}
}
//确定count数组大小
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
perror("malloc fail!");
exit(1);
}
//对count初始化
memset(count, 0, sizeof(int) * range);
for (int i = 0; i < n; i++)
{
count[arr[i] - min]++;
}
//将count数组映射到arr数组中
int index = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
{
arr[index++] = i + min ;
}
}
}
test.c:
cpp
#include"Sort.h"
void printArr(int* arr, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test01()
{
int a[] = { 5,3,9,6,2,4,7,1,8 };
//int a[] = { 6,1,2,7,9,3 };
int n = sizeof(a) / sizeof(a[0]);
printf("排序之前:");
printArr(a, n);
//InsertSort(a, n);
//ShellSort(a, n);
//SelectSort(a, n);
//HeapSort(a, n);
//BubbleSort(a, n);
//QuickSort(a, 0, n - 1);
//QuickSortNorR(a, 0, n - 1);
//MergeSort(a, n);
CountSort(a, n);
//MergeSortNonR(a, n);
printf("排序之后:");
printArr(a, n);
}
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
int* a5 = (int*)malloc(sizeof(int) * N);
int* a6 = (int*)malloc(sizeof(int) * N);
int* a7 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
a7[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
printf("BubbleSort:%d\n", end7 - begin7);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
int main()
{
test01();
//TestOP();
return 0;
}
测试结果:

测试完成,打印没有问题,升序排序正确,退出码为0
计数排序的特性:
- 计数排序在数据范围集中时,效率很高,但是适用范围以及场景有限
- 时间复杂度:O(n+range)
- 空间复杂度:O(range)
- 稳定性:稳定
二.排序算法复杂度及稳定性分析
**稳定性:**假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
各排序算法对比表:

其中冒泡排序,直接插入排序,归并排序是稳定的,这里就不过多介绍了,我们主要通过一些特例来看下那些不稳定的排序算法。至于时间复杂度和空间复杂度,博主大部分都在前面的博客中分享过了。
1.直接选择排序:
2.希尔排序:
3.堆排序:
4.快速排序:
代码展现:
Sort.c:
cpp
#include"Sort.h"
#include"stack.h"
//1)直接插入排序
void InsertSort(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
//2)希尔排序
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end-=gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//向下调整
void AdjustDown(int* arr, int parent, int n)
{
int child = parent * 2 + 1;
while (child < n)
{
//找大的孩子
//建大堆 <
//建小堆 >
if (child + 1 < n && arr[child] < arr[child + 1])
{
child++;
}
//孩子和父亲比较
//建大堆 <
//建小堆 >
if (arr[parent] < arr[child])
{
Swap(&arr[parent], &arr[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* arr, int n)
{
//向下调整算法--建堆 时间复杂度O(n)
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, i, n);//因为这里建的是小堆,所以向下调整,就成了降序
}
//向上调整算法--建堆 时间复杂度O(n*logn)
/*for (int i = 0; i < n; i++)
{
AdjustUp(arr, i);
}*/
//n* logn
int end = n - 1;
while (end > 0)//循环取最后一个元素与顶交换,再向下调整
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
//冒泡排序
void BubbleSort(int* arr, int n)
{
int change = 1;
for (int i = 0; i < n-1; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
Swap(&arr[j], &arr[j + 1]);
change = 0;
}
}
if (change == 1)
{
break;
}
}
}
//1)直接选择排序
//void SelectSort(int* arr, int n)
//{
// for (int i = 0; i < n; i++)
// {
// int mini = i;
// for (int j = i + 1; j < n; j++)
// {
// if (arr[j] < arr[mini])
// {
// mini = j;
// }
// }
// Swap(&arr[mini], &arr[i]);
// }
//}
//直接选择排序
void SelectSort(int* arr, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int mini = begin;
int maxi = begin;
for (int i = begin+1; i <= end; i++)
{
if (arr[i] < arr[mini])
{
mini = i;
}
if (arr[i] > arr[maxi])
{
maxi = i;
}
}
//交换
if (begin == maxi)
{
maxi = mini;
}
Swap(&arr[begin], &arr[mini]);
Swap(&arr[end], &arr[maxi]);
begin++;
end--;
}
}
//找基准值--hoare版本
int _QuickSort1(int* arr, int left, int right)
{
int keyi = left;
left++;
while (left <= right)
{
//right从右往左找比基准值小的,如果大于基准值就--
while (left <= right && arr[right] > arr[keyi])
{
--right;
}
//left从左往右找比基准值大的,如果小于基准值就++
while (left <= right && arr[left] < arr[keyi])
{
++left;
}
//left和right交换
if(left<=right)
Swap(&arr[left++], &arr[right--]);
}
//right位置就是基准值的位置
Swap(&arr[right], &arr[keyi]);
return right;
}
//找基准值--挖坑法
int _QuickSort(int* arr, int left, int right)
{
int hole = left;
int key = arr[hole];
while (left < right)
{
while (left<right && arr[right] > key)
{
right--;
}
arr[hole] = arr[right];
hole = right;
while (left < right && arr[left] < key)
{
++left;
}
arr[hole] = arr[left];
hole = left;
}
arr[hole] = key;
return hole;
}
//找基准值--lumoto双指针法
int _QuickSort2(int* arr, int left, int right)
{
int prev = left, cur = prev + 1;
int keyi = left;
while (cur < right)
{
if (arr[cur] < arr[keyi] && ++prev != cur)
{
Swap(&arr[cur], &arr[prev]);
}
++cur;
}
Swap(&arr[prev], &arr[keyi]);
return prev;
}
//快速排序
void QuickSort(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
//找基准值keyi
int keyi = _QuickSort(arr, left, right);
//左序列[left,keyi-1]右序列[keyi+1,right]
QuickSort(arr, left, keyi - 1);
QuickSort(arr, keyi + 1, right);
}
//找基准值非递归版--栈
void QuickSortNorR(int* arr, int left, int right)
{
ST st;
STInit(&st);
STPush(&st, right);
STPush(&st, left);
while (!STEmpty(&st))
{
//取栈顶两次
int begin = STTop(&st);
STPop(&st);
int end = STTop(&st);
STPop(&st);
//[begin,end]--找基准值
int keyi = begin;
int prev = begin, cur = prev + 1;
while (cur <= end)
{
if (arr[cur] < arr[keyi] && ++prev != cur)
{
Swap(&arr[cur], &arr[prev]);
}
++cur;
}
Swap(&arr[prev], &arr[keyi]);
keyi = prev;
if (keyi + 1 < end)
{
STPush(&st, end);
STPush(&st, keyi+1);
}
if (begin < keyi - 1)
{
STPush(&st, keyi - 1);
STPush(&st, begin);
}
}
STDesTroy(&st);
}
void _MergeSort(int* arr, int left, int right,int*tmp)
{
//分解
if (left >= right)
{
return;
}
//根据mid将[left,right]划分为左右两个序列[left,mid] [mid+1,right]
int mid = left + (right - left) / 2;
_MergeSort(arr, left, mid,tmp);
_MergeSort(arr, mid+1, right,tmp);
//合并[left,mid] [mid+1,right]
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int index = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
tmp[index++] = arr[begin1++];
}
else
{
tmp[index++] = arr[begin2++];
}
}
//要么begin1越界
//要么begin2越界
while (begin1 <= end1)
{
tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = arr[begin2++];
}
for (int i = left; i <= right; i++)
{
arr[i] = tmp[i];
}
}
//归并排序
void MergeSort(int* arr, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
//[0,n-1]
_MergeSort(arr, 0, n - 1,tmp);
free(tmp);
tmp = NULL;
}
//非比较排序--计数排序
void CountSort(int* arr, int n)
{
int min = arr[0], max = arr[0];
for (int i = 0; i < n; i++)
{
if (arr[i] < min)
{
min = arr[i];
}
if (arr[i] > max)
{
max = arr[i];
}
}
//确定count数组大小
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
perror("malloc fail!");
exit(1);
}
//对count初始化
memset(count, 0, sizeof(int) * range);
for (int i = 0; i < n; i++)
{
count[arr[i] - min]++;
}
//将count数组映射到arr数组中
int index = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
{
arr[index++] = i + min ;
}
}
}
//归并排序--非递归版本
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail!\n");
exit(1);
}
int gap = 1;
while (gap < n)
{
//根据gap划分组,两两合并
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
int index = begin1;
//处理奇数个数据
if (begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
//合并
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
//tmp导入到a数组中
memcpy(a + i, tmp+i, sizeof(int)*(end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
Sort.h:
cpp
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<time.h>
//插入排序
//1)直接插入排序
void InsertSort(int* arr, int n);
//2)希尔排序
void ShellSort(int* arr, int n);
//选择排序
//1)直接选择排序
void SelectSort(int* arr, int n);
//2)堆排序
void HeapSort(int* arr, int n);
//冒泡排序
void BubbleSort(int* arr, int n);
//快速排序
void QuickSort(int* arr, int left, int right);
//找基准值非递归版--栈
void QuickSortNorR(int* arr, int left, int right);
//归并排序--递归版本
void MergeSort(int* arr, int n);
//非比较排序--计数排序
void CountSort(int* arr, int n);
//归并排序--非递归版本
void MergeSortNonR(int* a, int n);
test.c:
cpp
#include"Sort.h"
void printArr(int* arr, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test01()
{
int a[] = { 5,3,9,6,2,4,7,1,8 };
//int a[] = { 6,1,2,7,9,3 };
int n = sizeof(a) / sizeof(a[0]);
printf("排序之前:");
printArr(a, n);
//InsertSort(a, n);
//ShellSort(a, n);
//SelectSort(a, n);
//HeapSort(a, n);
//BubbleSort(a, n);
//QuickSort(a, 0, n - 1);
//QuickSortNorR(a, 0, n - 1);
//MergeSort(a, n);
CountSort(a, n);
//MergeSortNonR(a, n);
printf("排序之后:");
printArr(a, n);
}
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
int* a5 = (int*)malloc(sizeof(int) * N);
int* a6 = (int*)malloc(sizeof(int) * N);
int* a7 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
a7[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
printf("BubbleSort:%d\n", end7 - begin7);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
int main()
{
test01();
//TestOP();
return 0;
}
往期回顾:
总结:这篇博客到此为止,排序的数据结构就已经全部写完了,数据结构初阶也就结束了,后续我还会写一些某些排序的进阶,然后就正式进入C++的学习了。我们数据结构初阶讲这些数据结构都是用C语言实现的,还有些比较难的数据结构在后续C++的学习中我们也会接触到,但是利用C++来实现就方便很多了,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。