归并排序 与 计数排序

目录

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) 不稳定

本篇结束!

相关推荐
帅逼码农1 小时前
有限域、伽罗瓦域、扩域、素域、代数扩张、分裂域概念解释
算法·有限域·伽罗瓦域
Jayen H1 小时前
【优选算法】盛最多水的容器
算法
机跃1 小时前
递归算法常见问题(Java)
java·开发语言·算法
<但凡.1 小时前
题海拾贝:蓝桥杯 2020 省AB 乘法表
c++·算法·蓝桥杯
pzx_0012 小时前
【LeetCode】94.二叉树的中序遍历
算法·leetcode·职场和发展
DogDaoDao2 小时前
leetcode 面试经典 150 题:矩阵置零
数据结构·c++·leetcode·面试·矩阵·二维数组·矩阵置零
我曾经是个程序员2 小时前
使用C#生成一张1G大小的空白图片
java·算法·c#
芒果de香蕉皮2 小时前
mavlink移植到单片机stm32f103c8t6,实现接收和发送数据
stm32·单片机·嵌入式硬件·算法·无人机
徐子童2 小时前
二分查找算法专题
数据结构·算法
小王子10242 小时前
数据结构与算法Python版 二叉查找树
数据结构·python·算法·二叉查找树