IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 排序算法之非比较排序(基数排序和计数排序) -> 正文阅读

[数据结构与算法]排序算法之非比较排序(基数排序和计数排序)

??个人主页:欢迎大家光临——>沙漠下的胡杨

? 各位大帅哥,大漂亮

?如果觉得文章对自己有帮助

?可以一键三连支持博主

?你的每一分关心都是我坚持的动力

?

??: 本期重点:

??希望大家每天都心情愉悦的学习工作。?


目录

??: 本期重点:非比较排序(计数排序和基数排序)

计数排序:

计数排序图示分析:

代码实现:

基数排序:

基数排序图示:

代码实现(用队列辅助实现):

代码实现(数组实现):


在我们之前的讲解中,我们学到了大多数的比较排序,今天我们主要学一下非比较排序。

计数排序:

? ? ? ? 计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。?当然这是一种牺牲空间换取时间的做法,而且O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)

排序思想:

1. 确定开辟的空间大小(一般为数据中的最大值 - 最小值的差值)。、

2.?确定对应关系。

3. 把数据对应关系的数组下标自增。

4. 根据队形关系拷贝数据。

计数排序图示分析:

首先我们有原数组,还要开辟一个辅助数组。并且辅助数组的大小是根据原数组中的数据的最大值—最小值决定的。下面数组中的值都在 1 到 8之间,所以我们辅助数组开辟 8?个大小的空间,其中 原数组中 1?对应辅助数组中下标 0 的位置,原数组中 8 数据,对应辅助数组下标为 7 的位置,刚开始时我们把辅助数据都为内存设置为0。

下面我们把数据根据对应关系写入辅助数组中去,1对应位置就是数组下标为0的位置,然后把辅助数组内的数据自增。8 对应下标为 7 的位置,然后把辅助数组内的数组自增。

写入完成后,我们根据辅助数组和原数组的对应关系,一个个的在写入原数组,其中辅助数组 0 下标对应的是 1 ,然后 0 下标的数据 2 表示写入两次?。同理,下标为辅助数组 7 下标对相应的是 8 ,辅助数组下标为 7 的数据为 2 ,表示写入 2 次 8。

? ?

代码实现:

void CountSort(int* a, int n)//计数排序
{
	//获取最大值和最小值
	int max = a[0];
	int min = a[0];

	for (int i = 0; i < n; i++)
	{
		if (max < a[i])
		{
			max = a[i];
		}

		if (min > a[i])
		{
			min = a[i];
		}
	}

	//获取开辟的区间大小
	int rang = max - min + 1;
	
	//开辟空间
	int* count = (int*)malloc(sizeof(int)*rang);
	if (count == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//把辅助数组的区间设置为0。
	memset(count, 0, sizeof(int)*rang);

	//根据对应关系写入辅助数组
	for (int i = 0; i < n; i++)
	{
		//写入时,根据对应关系一定是下标原数组数据减去最小值。
		count[a[i] - min]++;
	}

	//根据对应关系写回原数组
	int j = 0;

	//判断区间为是否结束
	for (int i = 0; i < rang; i++)
	{
		//此时判断的是辅助数组的数据是否为0
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}

	//释放空间
	free(count);
	count = NULL;
}

基数排序:

基数排序算法思想:

? ? 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,将要排序的元素分配至某些“桶”中,以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

奇数排序实现流程:

1. 我们首先明确基数,一般是0~9这10个数字为基数。

2. 选取最大数的位数作为排序的次数。

3.然后根据个位上的数据排序,拷贝回原数组,再根据十位上的数据排序,拷贝回原数组(不足的位数补0,比如 2 ,2不到10,所以十位为 0?)

4.一直拷贝到最大数的最高位数停止,此时数据已经有序。

基数排序图示:

下面是一些随机数,然后我们排序,首先我们确定基数是 0~9 。然后我们确定最大数的最高位次是3,表示排序 3 次?。

下面我们按照个位上的数进行分配数据。?

?然后在进行回收数据,先放的数据先取出(先进先出):

根据十位上的数据分发数据:

回收十位上排序好的数据:

?根据百位上的数据分发:

在回收百位上排序好的数据:

因为最大的数只有百位,所以此时排序完成。

??

代码实现(用队列辅助实现):

//最大数据的位数,在数据量较小时,我们可以看出所以直接定义了最大的位数
#define K 3

//定义全局的10个队列,对应基数为0 ~ 9
Queue q0,q1, q2, q3, q4, q5, q6, q7, q8, q9;

//获取该数据第几位的数字
int GetKey(int val, int k)
{
	int keyi = 0;
	while (k >= 0)
	{
		keyi = val % 10;
		val /= 10;
		k--;
	}

	return keyi;
}

//分发数据
void Distribute(int* a, int left, int right, int k)
{
	for (int i = left; i < right; i++)
	{
        //获取当前要排序的位上的数据。
		int key = GetKey(a[i], k);

		//按照数据的每个位上的数字,分别进入对应的队列
		switch (key)
		{
		case 0:
			QueuePush(&q0, a[i]);
			break;
		case 1:
			QueuePush(&q1, a[i]);
			break;
		case 2:
			QueuePush(&q2, a[i]);
			break;
		case 3:
			QueuePush(&q3, a[i]);
			break;
		case 4:
			QueuePush(&q4, a[i]);
			break;
		case 5:
			QueuePush(&q5, a[i]);
			break;
		case 6:
			QueuePush(&q6, a[i]);
			break;
		case 7:
			QueuePush(&q7, a[i]);
			break;
		case 8:
			QueuePush(&q8, a[i]);
			break;
		case 9:
			QueuePush(&q9, a[i]);
			break;
		default:
			break;
		}
	}
}

//回收数据
void Collect(int* a)
{
	//下标值
	int i = 0;

	//依次取出0到9队列中的数据
	while (!QueueEmpty(&q0))
	{
		a[i++] = QueueFront(&q0);
		QueuePop(&q0);
	}
	while (!QueueEmpty(&q1))
	{
		a[i++] = QueueFront(&q1);
		QueuePop(&q1);
	}
	while (!QueueEmpty(&q2))
	{
		a[i++] = QueueFront(&q2);
		QueuePop(&q2);
	}
	while (!QueueEmpty(&q3))
	{
		a[i++] = QueueFront(&q3);
		QueuePop(&q3);
	}
	while (!QueueEmpty(&q4))
	{
		a[i++] = QueueFront(&q4);
		QueuePop(&q4);
	}
	while (!QueueEmpty(&q5))
	{
		a[i++] = QueueFront(&q5);
		QueuePop(&q5);
	}
	while (!QueueEmpty(&q6))
	{
		a[i++] = QueueFront(&q6);
		QueuePop(&q6);
	}
	while (!QueueEmpty(&q7))
	{
		a[i++] = QueueFront(&q7);
		QueuePop(&q7);
	}
	while (!QueueEmpty(&q8))
	{
		a[i++] = QueueFront(&q8);
		QueuePop(&q8);
	}
	while (!QueueEmpty(&q9))
	{
		a[i++] = QueueFront(&q9);
		QueuePop(&q9);
	}
	
}


void RadixSort(int* a, int left, int right)
{
	//初始化队列
	QueueInit(&q0);
	QueueInit(&q1);
	QueueInit(&q2);
	QueueInit(&q3);
	QueueInit(&q4);
	QueueInit(&q5);
	QueueInit(&q6);
	QueueInit(&q7);
	QueueInit(&q8);
	QueueInit(&q9);

	for (int i = 0; i < K; i++)
	{
		//分发数据
		Distribute(a, left, right, i);
		
		//回收数据
		Collect(a);
	}

	//销毁队列
	QueueDestroy(&q0);
	QueueDestroy(&q1);
	QueueDestroy(&q2);
	QueueDestroy(&q3);
	QueueDestroy(&q4);
	QueueDestroy(&q5);
	QueueDestroy(&q6);
	QueueDestroy(&q7);
	QueueDestroy(&q8);
	QueueDestroy(&q9);

}

因为我们使用纯C来写的,所以在一些代码上比较麻烦,需要注意一下几点:

1 . 队列要定义为全局的,方便我们直接使用,否则作为参数传递太过麻烦。

2. 我们可以直接写入要排的数据的最大位数,也可以写入的位数大一点,比如最大值只有百位的数据,我们可以写入千位,也就是比预定的要多排序一次,只会影响效率,不会影响结果。

3.使用队列的好处是我们不用考虑数据先进先出这点啦,因为这是队列的性质。

4.我们需要先写好一个队列才能使用上面的方法。

??

上述的写法十分麻烦,下面我们使用数组方式实现。

代码实现(数组实现):

//求数据的最大值
int GetMaxBitCount(int* array, int size)
{
	//设定位数初始值
	int max_count = 1;
	
	//设定初始边界值
	int bound = 10;
	
	// 针对数组中的每一个元素, 都使用边界值来进行试探.
	// 看看边界值是否能够涵盖数组中的当前值.
	int i = 0;
	for (; i < size; ++i) 
	{
		while (array[i] >= bound) 
		{
			++max_count;
			bound *= 10;
		}
	}

	//返回数据中最大的位数
	return max_count;
}

void RadixSort1(int* array, int size) {
	// 1. 求出最大的数字是多少位
	int max_bit_count = GetMaxBitCount(array, size);
    //开辟临时数组
	int* bucket = (int*)malloc(sizeof(int)* size);

	// 2. 最大的数字有多少位, 就需要进行多少次的排序
	int radix = 1;  // 表示当前要取个位, 十位, 还是百位...
	int count = 0;
	for (; count < max_bit_count; ++count, radix *= 10) 
	{
		// 统计每个桶中的元素个数(基数桶)
		int bucket_count[10] = { 0 };
		int i = 0;
		for (; i < size; ++i) 
		{
			++bucket_count[array[i] / radix % 10];
		}
		// 根据每个桶的元素的个数, 分配起始位置.(辅助数组)
        // 一维数组表示二维数组
		int start_addr[10] = { 0 };
		for (i = 1; i < 10; ++i) 
		{
			start_addr[i] = start_addr[i - 1] + bucket_count[i - 1];
		}
		// 代码运行到这里, 就已经把地盘瓜分好了.
		// 接下来将所有的元素放到桶中
		for (i = 0; i < size; ++i) 
		{
			int bucket_no = array[i] / radix % 10;
			// 这里的 start_addr[bucket_no]++ 相当于线性表的尾插.
			// 通过 start_addr[bucket_no] 记录每个桶当前写到了哪个位置.
			bucket[start_addr[bucket_no]++] = array[i];
		}
		memcpy(array, bucket, sizeof(int)* size);
	}
}

使用数组方式更为简洁,但是理解上更为麻烦些:

1.我们先试探边界求出最大数的位数。

2.在根据每次个位或者十位等等,这些位上的数据放入一个基数桶中。

3.最后我们要按照0~9这样的顺序插入到临时数组中

4.在插入时我们要根据辅助数组的顺序依次插入,这个类似一维数组表示二维数组的形式。

5.最后拷贝回原数组,释放空间。

今天到这里就结束啦!?

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:48:40  更:2022-07-17 16:54:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:21:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码