【数据结构】排序之归并排序与计数排序

个人主页 : zxctsclrjjjcph
文章封面来自:艺术家–贤海林
如有转载请先通知

目录

  • 1. 前言
  • 2. 归并排序
    • 2.1 递归实现
      • 2.1.1 分析
      • 2.1.2 代码实现
    • 2.2 非递归实现
      • 2.2.1 分析
      • 2.2.2 代码实现
  • 3. 计数排序
    • 3.1 分析
    • 3.2 代码实现
  • 4. 附代码
    • 4.1 Sort.h
    • 4.2 Sort.c
    • 4.3 Test.c

1. 前言

在前面的文章中介绍了 插入排序和交换排序,今天来分享的是归并排序和计数排序。
话不多说,正文开始。

2. 归并排序

归并排序既是内排序也是外排序。

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
在这里插入图片描述

在这里插入图片描述
归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

2.1 递归实现

2.1.1 分析

左边和右边都无序,先分割,8个分为4个,4个分为两个,两个分为1个,一个可以认为它有序了。
在这里插入图片描述
一个和一个归为2个有序,2个和2个归为4个有序,4个4个归为有序。这里用的就是后序递归
用一个临时数组tmp来进行排序后再拷贝回原数组,不可能每次调用数组自己就再开辟一次空间。
在递归的时候必须是一段区间,所以这里重新写一个子函数_MergeSort()来实现递归。
直接分割区间mid = (begin + end) / 2,然后分割左区间再分割右区间,当只有一个值时,已经有序了。
在这里插入图片描述
归并时,将左右区间里面的值进行比较,取小的尾插在tmp临时数组中。一个一个插入,最后肯定还剩下一组,如果剩下第一个区间就直接尾插tmp[i++] = a[begin1++];同样剩下第二个区间也直接尾插tmp[i++] = a[begin2++],最后拷贝回原数组memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1))

2.1.2 代码实现

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	// [begin, mid][mid+1, end]
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	// [begin, mid][mid+1, end]归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

来个例子测试一下。
在这里插入图片描述

2.2 非递归实现

如果用栈模拟实现,是不合适的,栈适合前序遍历,而归并排序是后序遍历。可以在栈里面对区间进行分割,但是栈空了,已经没有区间了,实现不了归并。

2.2.1 分析

归并分割是为了实现有序,直接到过来,一个和一个归并就实现有序。
在这里插入图片描述
同样要先开一个临时数组tmp,先归第一组区间[begin1, end1][begin2, end2]实现归并,谁小谁尾插,归并逻辑和上面递归是一样的。
gap为每一组数据个数,第一个区间就是int begin1 = i, end1 = i + gap - 1;
第二个区间就是int begin2 = i + gap, end2 = i + 2 * gap - 1。(这里算的是下标,所以end得减1)。
在这里插入图片描述
这里实现完一组gap,要实现下一个gap,用一个for 循环实现for(size_t i = 0; i < n; i += 2 * gap)。那么结束条件就是gap > n。
在这里插入图片描述

代码写出来就是

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		printf("gap:%2d->", gap);
		for (size_t i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			// [begin1, end1][begin2, end2] 归并
			printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			memcpy(a + i, tmp + i, sizeof(int) * 2 * gap);
		}
		printf("\n");
		gap *= 2;
	}
	free(tmp);
}

举个例子实现代码,发现结果出不来。为什么呢?
在这里插入图片描述
说明区间越界了,得对区间进行处理。
区间[begin1, end1][begin2, end2]中begin1不存在越界,i是一直小于n。
end1,begin2, end2都会存在越界情况。
对end1如果它大于n,不需要归并了,就直接break;
对begin2如果它大于n,说明第二个区间越界了,也不需要归并,就直接break;
对end2如果它大于n,这里的第二个区间还存在一些值,将区间修改为n-1(end2 = n - 1)。

            if (end1 >= n || begin2 >= n)
			{
				break;
			}

			if (end2 >= n)
			{
				end2 = n - 1;
			}

在这里插入图片描述
这里得注意拷贝,在使用memcpy时,归一组就拷贝一组,如果全部归并之后再拷贝,就会出现随机值。
在这里插入图片描述
放在外面,如果后面区间出现越界,直接break,就没有就行归并,它本身就是有序的,会把之前有序的数据覆盖。

2.2.2 代码实现

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		printf("gap:%2d->", gap);
		for (size_t i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			// [begin1, end1][begin2, end2] 归并
			// 边界的处理
			if (end1 >= n || begin2 >= n)
			{
				break;
			}

			if (end2 >= n)
			{
				end2 = n - 1;
			}

			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}

		printf("\n");

		gap *= 2;
	}
	free(tmp);
}

举个例子:
在这里插入图片描述

3. 计数排序

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

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

在这里插入图片描述
计数排序的特性总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(countN范围)
  4. 稳定性:稳定

局限性:

  1. 不适合分散的数据,更适合集中数据;
  2. 不适合浮点数、字符串、结构体数据排序,只适合整数。

3.1 分析

在这里插入图片描述

代码核心就是:
在这里插入图片描述
a[i]是多少就对多少进行计数,出现几次就加几次。
在这里插入图片描述
1这个位置出现3次就在原数组中写3个1,2的位置出现一次就在原数组中写一个2。
在这里插入图片描述
这里不可能每一次都从0开始进行排序,每一次都是几对于几
在这里插入图片描述

如果是这样,那么就浪费了1000个空间。
这里使用相对映射而不是绝对映射。
在这里插入图片描述
找最小值1000,最大值1999。
然后用calloc开一个计数数组,因为calloc会初始化为0。
这里1000就在0的位置,1999就在999的位置。
统计次数:对相对映射位置进计数。

    for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

这里怎么还原呢?
加回去就行j + min
后置减减,返回的减减之前的值,往回写。

    for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}

这里负数也能使用计数排序。
在这里插入图片描述

3.2 代码实现

void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int 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* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		printf("calloc fail\n");
		return;
	}

	// 统计次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	// 排序
	int i = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}
}

4. 附代码

4.1 Sort.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>

void PrintArray(int* a, int n);

void MergeSort(int* a, int n);
void MergeSortNonR(int* a, int n);
void CountSort(int* a, int n);

4.2 Sort.c

#include"Sort.h"

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	// [begin, mid][mid+1, end]
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	// [begin, mid][mid+1, end]归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		/*printf("gap:%2d->", gap);*/
		for (size_t i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			// [begin1, end1][begin2, end2] 归并
			/*printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);*/

			// 边界的处理
			if (end1 >= n || begin2 >= n)
			{
				break;
			}

			if (end2 >= n)
			{
				end2 = n - 1;
			}

			/*printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);*/

			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}

		/*printf("\n");*/

		gap *= 2;
	}


	free(tmp);
}

// 基数排序/桶排序

// 计数排序
// 时间:O(N+range)
// 空间:O(range)
void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int 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* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		printf("calloc fail\n");
		return;
	}

	// 统计次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	// 排序
	int i = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}
}

4.3 Test.c

#include"Sort.h"

void TestMergeSort()
{
	
	int a[] = {10,8,7,1,3,9,4,2,9,10 };
	PrintArray(a, sizeof(a) / sizeof(int));
	/*MergeSort(a, sizeof(a) / sizeof(int));*/
	MergeSortNonR(a, sizeof(a) / sizeof(int));
    PrintArray(a, sizeof(a) / sizeof(int));
}

void TestCountSort()
{
	int a[] = { 1,3,9,1,5,1,2,3,-5,-5,-2 };
	PrintArray(a, sizeof(a) / sizeof(int));

	CountSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}



int main()
{
	
	/*TestMergeSort();*/
	TestCountSort();

	return 0;
}

有问题请指出,大家一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/323246.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Intel校企实践】猫狗大战

作业简介&#xff1a; 问题描述&#xff1a; ​ 在这个问题中&#xff0c;你将面临一个经典的机器学习分类挑战——猫狗大战。你的任务是建立一个分类模型&#xff0c;能够准确地区分图像中是猫还是狗。 预期解决方案&#xff1a; ​ 你的目标是通过训练一个机器学习模型&a…

【深蓝学院】移动机器人运动规划--第1章 运动规划介绍与地图构建--笔记

文章目录 1. Course introduction2. Course Outline2.1 课程概览2.2 课程算法概览2.2.1 基于搜索的前端2.2.2 基于采样的前端2.2.3 满足动力学约束的路径搜索2.2.4 后端轨迹优化 3. 地图表示3.1 Occupancy grid map占用栅格地图3.2 八叉树地图3.3 Voxel hashing&#xff08;体素…

虾皮开通:如何在虾皮(Shopee)平台上开通店铺详细步骤

在全球电商市场的竞争中&#xff0c;越来越多的卖家选择在虾皮&#xff08;Shopee&#xff09;平台上开设店铺。作为东南亚地区最大的电子商务平台之一&#xff0c;虾皮提供了一个便捷的销售渠道&#xff0c;吸引了数百万的买家和卖家。如果您想在虾皮上开设自己的店铺&#xf…

《动手学深度学习》学习笔记 第10章 注意力机制

本系列为《动手学深度学习》学习笔记 书籍链接&#xff1a;动手学深度学习 笔记是从第四章开始&#xff0c;前面三章为基础知识&#xff0c;有需要的可以自己去看看 关于本系列笔记&#xff1a; 书里为了让读者更好的理解&#xff0c;有大篇幅的描述性的文字&#xff0c;内容很…

利用c 原生头文件完成JPEG全流程编码

骄傲一下&#xff0c;经过一个多月的努力&#xff0c;终于完成jpeg的全套编码。经验证此程序可以把摄像头yuv信号转为JPG图片。现在的程序还不完美&#xff0c;只能对长和宽尺寸是16倍数的信号转码。而且转码速度太慢&#xff0c;一帧1280720的图片要2秒多。此程序只能对yuv420…

静态路由高级特性(HCIA)

目录 一、静态路由高级特性 1、路由条目六要素 2、路由分类 3、静态路由配置命令 &#xff08;1&#xff09;静态路由中下一跳MA和P2P区别 4、静态路由加路由表条件 5、permanent特性 二、路由冗余和负载 1、控制层面control plane 2、数据层面data plane 路由操控精髓&#xf…

测试用例的设计(超详细总结)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料 1. 测试用例的概念 软件测试人员向被测试系统提供的一组数据的集合&#xff0c;包括 测试环境、测试步骤、…

深入探究Python Collections模块:高效数据结构解决方案

前言 这几天刷leetcode题时&#xff0c;看到题解中有这样一行代码collections.defaultdict(list)&#xff0c;不明白是啥意思&#xff0c;平时开发的脚本中未遇到&#xff0c;借着这个机会&#xff0c;学习一下collections模块的用法。 collections 这个模块实现了一些专门化…

Overleaf Docker编译复现计划

Overleaf Docker编译复现计划 Overleaf Pro可以支持不同年份的Latex镜像自由选择编译&#xff0c;这实在是一个让人看了心痒痒的功能。但是很抱歉&#xff0c;这属于Pro付费功能。但是我研究了一下&#xff0c;发现其实和Docker编译相关的代码&#xff0c;社区版的很多代码都没…

使用Dockerfile构建镜像的详细指南

目录 前言 一、什么是 Dockerfile 二、使用 Dockerfile 定制镜像 开始构建镜像 上下文路径 三、指令详解 四、构建阿里云仓库 前言 Docker是一种流行的容器化平台&#xff0c;可以帮助开发人员和运维团队更轻松地构建、发布和运行应用程序。在Docker中&#xff0c;镜像是…

捷捷微电突发涨价函,Trench MOS产品线上调5%-10% | 百能云芯

近日&#xff0c;国产功率半导体厂商捷捷微电发布了一份《价格调整函》&#xff0c;宣布自2024年1月15日起&#xff0c;将对公司Trench MOS产品线的单价进行上调&#xff0c;上调幅度为5%-10%。 据悉&#xff0c;调整前已下的订单将继续按照原有单价和数量履行&#xff0c;而新…

java实现AES256对称加解密工具类

一、引入依赖包 引入相关依赖包 <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version> </dependency> <!--lombok用于简化实体类开发--> <dependency&g…

Unity寻路A星算法

文章目录 实现步骤概览&#xff1a; 计算移动成本1. **定义移动成本函数**&#xff1a;2. **考虑不同类型的格子**&#xff1a;3. **动态调整成本**&#xff1a;4. **实际应用**&#xff1a; 优先级队列1. **初始化**&#xff1a;2. **节点评估**&#xff1a;3. **更新节点状态…

spring boot学习第八篇:通过spring boot、jedis实现秒单

参考&#xff1a;Redis实现分布式锁的7种方案 - 知乎 1、 准备数据库表&#xff0c;如下SQL表示库存表&#xff0c;有主键ID和库存数量字段 CREATE TABLE t_stock (id bigint(20) NOT NULL AUTO_INCREMENT,quantity bigint(20) NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEF…

未来气膜体育馆的发展趋势是什么?

未来气膜体育馆的发展趋势是多方面的&#xff0c;以下是其中几个方面的趋势。 起初&#xff0c;随着人们对体育运动的需求不断增加&#xff0c;气膜体育馆的建设和使用将成为一种趋势。气膜体育馆具有灵活性和可移动性的特点&#xff0c;可以快速搭建和拆除&#xff0c;能够适…

TOP 10 屏幕录制软件工具,可帮您轻松录制视频!

随着越来越多的人远程工作和学习&#xff0c;对可靠、高效的屏幕录制工具的需求变得越来越重要。屏幕录制已成为电子学习、游戏和视频创作的重要组成部分。然而&#xff0c;有这么多可用的屏幕录制工具&#xff0c;选择合适的工具可能具有挑战性。为了帮助您节省搜索时间和精力…

案例127:基于微信小程序的预约挂号系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

如何去开发直播电商系统小程序

明确你的直播电商系统的功能和特性&#xff0c;包括用户注册、商品展示、购物车、支付结算、直播功能、评论互动等。根据需求确定系统的基本架构和主要模块。 技术选型&#xff1a;选择适合你的直播电商系统的技术栈。考虑前端框架&#xff08;如React、Vue.js&#xff09;、后…

基于等效消耗最小(ECMS)的电氢综合能源系统能量管理策略Simulink模型

0. 前言 常见的EMS控制策略为基于状态机&#xff08;State Machine Control&#xff09;、基于等效消耗最小&#xff08;Equivalent Consumption Minimization Strategy&#xff0c;ECMS&#xff09;及调度控制模式。本文着重介绍前两种&#xff0c;针对第一种控制策略可参考模…

Unity Urp 渲染管线 创建透明材质球

按照以上方式设置后就可以得到一个透明的材质球 Tips&#xff1a;Blending mode &#xff1a; alpha 和 Blending mode &#xff1a; additive都是完全透明效果具体差异暂时不知道