归并排序 与 计数排序

目录

1.归并排序

[1.1 递归实现归并排序:](#1.1 递归实现归并排序:)

[1.2 非递归实现归并排序](#1.2 非递归实现归并排序)

[1.3 归并排序的特性总结:](#1.3 归并排序的特性总结:)

[1.4 外部排序](#1.4 外部排序)

2.计数排序

[2.1 操作步骤:](#2.1 操作步骤:)

[2.2 计数排序的特性总结:](#2.2 计数排序的特性总结:)

[3. 7种常见比较排序比较](#3. 7种常见比较排序比较)


1.归并排序

基本思想:

归并排序 (MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并 ,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序核心步骤:

动图演示:

1.1 递归实现归并排序:

归并排序类似于二叉树中的后序遍历,先让整个数组分为两个子序列,归并这两部份子序列,但是**归并需要两部份子序列有序,然后取小的尾插到一个新开辟的数组中,归并完成后后再拷贝回原数组,**如何让子序列有序,还要再次将每个子序列分为两部分,直到每个子序列只有一个值,这时已经递归到最深处,然会递归向回归并。

递归代码实现:

cpp 复制代码
//归并排序
//开辟好空间后由下面元素调用此函数
void _MergeSort(int* arr, int* tmp, int begin, int end)
{
	if (begin == end)
	{
		return;
	}

	int midi = (begin + end) / 2;
	_MergeSort(arr, tmp, begin, midi);
	_MergeSort(arr, tmp, midi+1, end);

	int begin1 = begin;
	int end1 = midi;
	int begin2 = midi + 1;
	int end2 = end;

	int i = begin;
	//归并  取小的尾插到开辟的空间
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] <= arr[begin2])
		{
			tmp[i++] = arr[begin1++];
		}
		else
		{
			tmp[i++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
    //将归并好的两组数据拷贝会原数组
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));

}

void MergeSort(int* arr, int n)
{
    //开辟空间
	int* tmp = (int*)malloc(sizeof(int) * n);

	_MergeSort(arr, tmp, 0, n - 1);
}

小区间优化

cpp 复制代码
//小区间优化
if (end - begin +1<10)
{
    //使用插入排序
	InsertSort(arr + begin, end - begin + 1);
	return;
}

优化的本质是减小递归调用的次数, 由于二叉树的性质。我们可以得出满二叉树后三层大约占总个数的85%。为了减小递归开销,我们可以将小区间的递归调用改为直接插入排序可以提高一点排序的性能,但也不会提高很多。快排也可以使用这种方式优化。

1.2 非递归实现归并排序

我们可以先让每组gap=1个数据,每次归并两组,然后在让gap*=2,再次归并,直到gap>n。

代码实现:

cpp 复制代码
//非递归实现归并排序
void MergeSortNonR1(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	//每组有gap个数据,归并两组
	int gap = 1;

	while (gap < n)
	{
		int j = 0;
		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;


			if (end1 >= n || begin2 >= n)//不需要归并
			{
				break;
			}
			//修正
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			//归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] <= arr[begin2])
				{
					tmp[j++] = arr[begin1++];
				}
				else
				{
					tmp[j++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = arr[begin2++];
			}
			//将归并后的两组数据 拷贝回原数组 
			memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}

		gap *= 2;
	}
}

边界越界问题:

cpp 复制代码
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;

begin1不会越界,因为begin1 = i,i 复合循环条件 。

  1. end1,begin2,end2都越界

  2. begin2,end2越界

  3. end2越界

  4. end1,begin2,end2都越界

此时不需要归并直接跳出循环。

  1. begin2,end2越界

此时也不需要归并直接跳出循环。

  1. end2越界

此时需要归并,但是我们要修改end2,将end2改为n-1。

代码:

cpp 复制代码
if (end1 >= n || begin2 >= n)//不需要归并
	{
		break;
	}
	//修正
	if (end2 >= n)
	{
		end2 = n - 1;
	}

1.3 归并排序的特性总结:

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

1.4 外部排序

概念:当数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

在我们所学的排序算法中**,只有非递归归并排序的思想可以用于外部排序**。其他排序算法都只适用于内部排序,因为他们都使用了下标来进行随机存取,而非递归归并排序不需要,是顺序存取,这里举个例子:

假如我们由100亿个整数要排序,也就是大约40G,而我们的内存中只有1G,步骤:

  1. 把40G的文件分为40份。
  2. 让每份文件依次放到内部中排序,让40份文件内部有序。
  3. 两两归并,分别从两个文件中读一个数据,然后选小的写文件,这时就与非递归归并排序相同了。

2.计数排序

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

2.1 操作步骤:

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

代码实现:

cpp 复制代码
// 计数排序
void CountSort(int* arr, int n)
{
	//遍历 确定最大值与最小值
	int max = arr[0];
	int min = arr[0];
	for (int i = 0; i < n; i++)
	{
		if (arr[i] < min)
		{
			min = arr[i];
		}
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}

	//遍历计数
	int range = max - min + 1;
	int* CountA = (int*)malloc(sizeof(int) * range);
	memset(CountA, 0, sizeof(int) * range);
	for (int i = 0; i < n; i++)
	{
		CountA[arr[i] - min]++;
	}
	//回收到原数组
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (CountA[i]--)
		{
			arr[j++] = i + min;
		}
	}
}

2.2 计数排序的特性总结:

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

3. 7种常见比较排序比较

排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(N^2) O(N) O(N^2) O(1) 稳定
简单选择排序 O(N^2) O(N^2) O(N^2) O(1) 不稳定
直接插入排序 O(N^2) O(N) O(N^2) O(1) 稳定
希尔排序 O(NlogN)~O(N^2) O(N^1.3) O(N^2) O(1) 不稳定
堆排序 O(NlogN) O(NlogN) O(N*logN) O(1) 不稳定
归并排序 O(NlogN) O(NlogN) O(N*logN) O(n) 稳定
快速排序 O(NlogN) O(NlogN) O(N^2) O(logn)~O(n) 不稳定

本篇结束!

相关推荐
lb363636363628 分钟前
分享一下arr的意义(c基础)(必看)(牢记)
c语言·知识点
南东山人2 小时前
一文说清:C和C++混合编程
c语言·c++
stm 学习ing3 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
LNTON羚通3 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
哭泣的眼泪4085 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
Microsoft Word6 小时前
c++基础语法
开发语言·c++·算法
天才在此6 小时前
汽车加油行驶问题-动态规划算法(已在洛谷AC)
算法·动态规划
莫叫石榴姐7 小时前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
茶猫_8 小时前
力扣面试题 - 25 二进制数转字符串
c语言·算法·leetcode·职场和发展
ö Constancy8 小时前
Linux 使用gdb调试core文件
linux·c语言·vim