分治法求众数在C++中的实现

分治法求众数在C++中的实现

问题分析

该问题解决的是求解一串数组中的众数问题。
众数,即一串数组中出现次数最多的数,每个数字的出现次数为该数字的重数,所以重数最大的数即为众数。该问题要求能正确返回给定数组中的众数以及它的重数。

算法设计

该问题实际上已经将核心算法给限定了:分治法。

分治法将一个难以解决的大问题划分为一些规模较小的子问题,分别求解各个子问题,再合并子问题的解得到原问题的解。

实际上在我们学习分治法的例题的时候应该也发现,对于分治法的算法设计要尽量使子问题的规模完全相等,实现平衡子问题的启发式思想。举个例子:计算一个数n的8次方的时候,我们采用分治法将这个计算变为n4×n4,同理,n4还可以再分为n2×n2,n2分为n×n,虽然对于乘方问题,分治法并没有降低时间复杂度,仍然为O(n),但是这种平衡子问题的思路是可以学习引荐到其他问题中的。
那么对于这个求众数的问题,我们该如何以平衡子问题的启发式思想来进行算法设计呢?毕竟数组的长度是未知的,如果是个质数,比如说13个数,那么无论如何也无法实现子问题的规模完全相等了吧。
其实不然,对于求众数的问题其实并不是像刚刚的乘方问题一样,数字数量并不会影响求众数问题的子问题规模大小。说人话就是,即使每个子问题中数字数量不同,它们的规模也可以是相同的。
首先,我们需要对数组进行一次排序,不排序的话一堆数字很乱只能通过遍历去找一个数的重数,再迭代的时候每一遍都需要遍历整个数组,而且肯定不能通过条件判断直接减去不可能的情况了,算法效率会很低。排序的话C++里面插入#include<algorithm>头文件后可以直接用sort函数:sort(数组名, 数组名 + 数组大小, greater<int>());,这个排序会根据数据数量自动选择更优的排序方法,很好用。
接着对于整个数组分解成3个部分:中位数、中位数左边的数、中位数右边的数。这里的中位数指的并不是单单一个数,而是所有和它相等的,比如说在数组1 2 2 2 3 3中,这里分成的三个部分就是1 |2 2 2|3 3。
找到中位数后,我们对中位数求它的重数,这里可以一举两得,我们直接求出中位数的左右界,还是拿1 2 2 2 3 3这个例子来说,我们首先定位到第二个2,然后从这个位置往左往右分别寻找是否还有2,左右界一开始都设置为中位数的位置,左边每找到一个就left-1,右边每找到一个就right+1,因为是排过序的,只要发现一个不是2就可以停止这一边的搜素了。最后,中位数的重数就是right-left+1,同时还将数组成功按照我们刚刚的思路分成三部分了。
接下来就是印证我们每个部分数字数量不同,但是它们的规模却相同的时候了。
首先,得到了刚刚的中位数的重数后,我们用变量存储当前中位数即为众数,它的重数即为众数的重数,随后发现新的重数更大的数直接改变量的值就可以了。接着,对于中位数左边的部分进行一个判断,如果左边部分总数量还没有当前重数多,那直接不用判断了,因为即使整个部分全是同一个数,它也不可能是众数了。相反,如果存在可能性,就再将这个部分找中位数后分为3个部分。右边部分完全同理。
那么,现在来看这个问题是不是已经变得和刚刚的乘方问题完全一样,问题被一步步分到最小,用着同样的手段去解决每个部分的问题。
所以,最终我们设计一个递归即可实现整个代码。

源代码

#include<iostream>
#include<algorithm>
using namespace std;

void FindMiddle(int a[],int length,int &left,int &right,int &middle) {
	int half = length / 2;//中位数的位置
	middle = a[half];//中位数的值
	left = half, right = half;//中位数的左右边界(同时也是分治法的分组边界)
	for (int n = half - 1; n >= 0; n--) {
		if (a[n] == middle) {
			left--;//中位数的左边界
			continue;
		}
		break;
	}
	for (int n = half + 1; n < length; n++) {
		if (a[n] == middle) {
			right++;//中位数的右边界
			continue;
		}
		break;
	}
}

void FindMax(int a[],int length,int &count,int &max) {
	int left = 0, right = 0;
	FindMiddle(a, length, left, right, max);
	count = right - left + 1;
	if (left - 1 >= count) {
		int left1 = 0, right1 = 0, middle1 = 0;
		FindMiddle(a, left, left1, right1, middle1);
		int count1 = 0, max1 = 0;
		FindMax(a, left, count1, max1);
		if (count1 > count) { count = count1; max = max1; }
	}
	if (length - right - 1 >= count) {
		int b[100];
		for (int i = right + 1; i < length; i++) {
			b[i - right - 1] = a[i];
		}
		int left1 = 0, right1 = 0, middle1 = 0;
		FindMiddle(b, length - right - 1, left1, right1, middle1);
		int count1 = 0, max1 = 0;
		FindMax(b, length - right - 1, count1, max1);
		if (count1 > count) { count = count1; max = max1; }
	}
}

int main() {
	int numbers[100], length = 0;
	int n;
	cout << "请输入数字个数:" << endl;
	cin >> n;
	cout << "请输入数字序列,每两个数字之间用空格隔开:" << endl;
	for (int i = 0; i < n; i++) {
		int num;
		cin >> num;
		numbers[i] = num;
		length++;
	}
	sort(numbers, numbers + 100, greater<int>());
	int max = 0;//众数
	int count = 0;//众数的重数
	FindMax(numbers, length, count, max);
	cout << "这个序列的众数是:" << max << ",它的重数是:" << count << "。" << endl;
}

代码运行测试

首先用刚刚的例子打乱顺序测试一遍。
6
2 1 3 2 1 2
在这里插入图片描述
接着再测试一个复杂一点,需要多次递归的,首先定位中位数是3,众数也为3,随后从左部分找到重数更大的1,接着又在右部分找到重数更大的5。
15
6 8 5 2 1 3 7 5 1 2 1 5 5 3 4
在这里插入图片描述

总结

分治法虽然不能在每种情况都比蛮力法更高效,但是正确地使用分治法往往都比其他算法效率更高,它的逻辑性很强也更容易让人理解。进行分治法算法设计的时候,最重要的还是尽量保证子问题的规模相等,如果难以做到,那么对于当前问题可能不适用于分治法,可以尝试其他算法。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海重苹果

谢谢您对我技术的肯定!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值