【数据结构初阶】--排序(五):计数排序,排序算法复杂度对比和稳定性分析

😘个人主页@Cx330❀

👀个人简介:++一个正在努力奋斗逆天改命的二本觉悟生++

📖个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》

前言:今天这篇博客就给大家将一个计数排序,然乎就给大家总结一下所有的排序算法的时间复杂度,空间复杂度,稳定性进行一个归纳总结。


目录

一、计数排序

核心步骤:

代码实现:

测试结果:

计数排序的特性:

二.排序算法复杂度及稳定性分析

各排序算法对比表:

代码展现:

Sort.c:

Sort.h:

test.c:


一、计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
思路:

  • 统计相同元素出现次数
  • 根据统计的结果将序列回收到原来的序列中

核心步骤:

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++来实现就方便很多了,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

相关推荐
_Kayo_2 小时前
node.js 学习笔记3 HTTP
笔记·学习
PAK向日葵3 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
地平线开发者5 小时前
ReID/OSNet 算法模型量化转换实践
算法·自动驾驶
地平线开发者5 小时前
开发者说|EmbodiedGen:为具身智能打造可交互3D世界生成引擎
算法·自动驾驶
星星火柴9366 小时前
关于“双指针法“的总结
数据结构·c++·笔记·学习·算法
艾莉丝努力练剑7 小时前
【洛谷刷题】用C语言和C++做一些入门题,练习洛谷IDE模式:分支机构(一)
c语言·开发语言·数据结构·c++·学习·算法
闪电麦坤958 小时前
数据结构:迭代方法(Iteration)实现树的遍历
数据结构·二叉树·
C++、Java和Python的菜鸟9 小时前
第六章 统计初步
算法·机器学习·概率论
散1129 小时前
01数据结构-Prim算法
数据结构·算法·图论