c语言讨论寻找多数元素(主元素问题)

摘要

c语言讨论寻找多数元素(主元素问题),这个问题最初由投票问题引出,比如在投票系统中如果有一个人的票数超过50%,那么这个人立即当选。

下面我们将问题抽象出来。

一.问题描述

令A[1…n]是一个整数序列,A中的整数a如果在A中出现的次数多于[n/2]次,那么a就称为多数元素。

比如现在有一个序列,已知其中有一个数出现的次数超过了50%,请你找出这个数。如3,3 ,1 ,1,3 ,2,3中,出现次数超过50%的数是3。

小伙伴们,你们有什么方法吗?

二.方法思路

我当时就想啊,第一个方法,两两比较呗,分别记录每个数字的出现的次数,2个for循环就解决了,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	int n;
	int a[101];
	int count, max=0;
	printf("数据个数:\n");
	scanf_s("%d", &n);
	int i, j, k = 0;
	for (i = 1; i <= n; ++i)           //循环存入数据
	{
		scanf_s("%d", &a[i]);
	}
	for (i = 1; i <= n; ++i)           //遍历每一个数据
	{
		count = 0;
		for (j = 1; j <= n; ++j)
		{
			if (a[i] == a[j] && i != j)       //遇到相同的数据,计数器加1
			{
				count++;
			}
		}
		if (count > max)                //寻找有相同数据最多的数
		{
			max = count;
			k = a[i];
		}
	}
	printf("%d\n",k);
	return 0;
}

这种方法显然时间复杂度为O(n^ 2), 即n的平方。那么我有思考了,这样的处理还能更好吗?当然有,我脑中一机灵(哈哈我觉得可能是彼得一机灵,天哪,我为什么会想到这个),想到了排序,排完序后所有相同的数会就会在一起。然后再从头到尾扫一遍,用一个变量count来统计,并再用一个max来更新count的最大值。这段代码之所以比刚才那2个for循环的方法要好,是因为我们可以使用堆排或快排,这样时间复杂度降为O(n*log n),比刚才的O(n^ 2)要快很多。如果不用堆排或快排,则时间复杂度仍为O(n^2),代码如下:

快排:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int a[101], n;         //定义全局变量,因为这两个变量在子函数中使用


void quicksort(int left, int right)
{
	int i, j, t, temp;
	if (left > right)
	{
		return;
	}
	temp = a[left];
	i = left;
	j = right;
	while (i != j)
	{
		//顺序很重要,要先从右往左找
		while (a[j] >= temp && i < j)
		{
			j--;
		}
		//再从左往右找
		while (a[i] <= temp && i < j)
		{
			i++;
		}
		//交换两个数在数组中的位置
		if (i < j)//当i与j没有相遇时
		{
			t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
	}
	//最后将基准数归位
	a[left] = a[i];
	a[i] = temp;

	quicksort(left, i - 1);
	quicksort(i + 1, right);
	return;
}


int main(void)
{
	int count = 1, max = 0;
	printf("数据个数:\n");
	scanf_s("%d", &n);
	int i, k = 0;
	for (i = 1; i <= n; ++i)           //循环存入数据
	{
		scanf_s("%d", &a[i]);
	}
	quicksort(1,n);                    //快速排序
	for (i = 1; i <= n; ++i)           //遍历每一个数据
	{
		if (a[i] == a[i + 1])
		{
			count++;
		}
		else
		{
			if (count > max)                //寻找有相同数据最多的数
			{
				max = count;
				k = a[i];
			}
			count = 1;
		}
		
	}
	printf("%d\n",k);
	return 0;
}

堆排:

/*对于堆,作向上调整,对内部变量h[101]作调整*/
int* siftup(int n, int h[101])
{
	int i = n, flag = 0, j;
	while (i >= 1 && flag == 0)
	{
		if (h[i] < h[i / 2] && i >= 1)
		{
			j = i / 2;
			int t;
			t = a[i];
			a[i] = a[j];
			a[j] = t;
			i = j;
		}
		else
		{
			flag = 1;
		}
	}
	return h;
}

上面的代码用于堆排序一个数组,排好后在主函数中给出第一个数,与第二个数比较,如相等count加1,如不相等,与max比较,更新max,最终找出多数元素。

/*对于堆,作向下调整,对内部变量h[101]作调整*/
int* siftdown2(int x, int num, int h[101])
{
	int n = num, flag = 0;
	int i, j;
	j = x;
	while (j <= n && flag == 0)
	{
		if (h[j] > h[2 * j] && 2 * j <= n)
		{
			i = 2 * j;
		}
		else
		{
			i = j;
		}
		if (h[i] > h[2 * j + 1] && 2 * j + 1 <= n)
		{
			i = 2 * j + 1;
		}
		if (i != j)
		{
			int t;
			t = a[i];
			a[i] = a[j];
			a[j] = t;
			j = i;
		}
		else
		{
			flag = 1;
		}
	}
	return h;
}


int main(void)
{
	int count = 1, max = 0;
	printf("数据个数:\n");
	scanf_s("%d", &n);
	int i, k = 0;
	for (i = 1; i <= n; ++i)           //循环存入数据
	{
		scanf_s("%d", &a[i]);
	}
	siftup(n, a);                        //堆排序
	int c = a[1]; 
	int num = n;
	for (i = 1; i <= n; ++i)           //遍历每一个数据
	{
		a[1] = a[num];
		--num;
		siftdown2(1, num, a);          //对于堆,作向下调整,对内部变量h[101]作调整
		if (a[1] ==c)                  //找出下一个较小的数,与前一个比较是否相等
		{
			count++;
		}
		else
		{
			if (count > max)                //寻找有相同数据最多的数
			{
				max = count;
				k = a[i];
			}
			count = 1;
		}
		c = a[1];
	}
	printf("%d\n",k);
	return 0;
}

然后想到这,我再一此灵机一动,如果这个数出现的次数大于50%的话,排序之后应该就是位于n/2的位置的那个数,故如果堆排或快排,直接排完序输出n/2位置的那个数就是这串序列的多数元素,时间复杂度变为O(log n)。

三.深入思考

刚才我们的问题是已知这个序列存在一个数出现的次数大于50%,那如果没有一个数出现的次数大于50%,那我们找到的数就一定是错的,那有人就说了,将找到的数在与序列对比一遍,查看它出现的次数是否超过50%即可,当然可以,那么我们上面所有的代码时间复杂度都要再乘以n,时间过于复杂,那么在一个数列中不知道是否存在一个数,它出现的次数大于50%的前提下,如何找到这个数,或验证它不存在呢?

哈哈,新的问题诞生了!!!

我又开始了一大波思考,,,
这里提供一个思路,我开始是这样想的,可以用一个数组来记录每个数出现的次数,就是桶排序那样,数组中的最大值的下标就是出现次数超过50%的数。时间空间复杂度是O(n+m) ,但是如果数的大小分布跨度很大怎么办,比如1,10000,10000000·····这样就很浪费空间。嗯~,补充一下吧,可以将数据离散化一下,再开一个数组一一对应,让1对应1,2对应10000,3对应10000000·····。

听到这你一定晕了吧,哈哈,这只是一个思路,下面我们来点硬货,小编查阅了大量资料(显得我好爱学习呀!!你们可以当我不存在,啊,我飘了),发现这是著名的“多数元素问题”,也叫“主元素问题”。

四. 终结问题(探索主元素问题)

如果一个序列存在一个数,且它出现的次数大于50%,则这个序列有一个特性:在原序列中去除两个不同的元素后,那么在原序列中出现次数超过了50%的数,在新序列中的出现次数也一定会超过50%。

知道了这个特性问题应该很好解决了吧!!

不!我在探索的过程中发现了另一个问题,我们来看看吧。

例:
序列 1,4,4,7,4,1这个序列显然4是多数元素。
去除1, 4 仍然4是多数元素。
去除1, 7更是。

序列1 ,4,4, 7, 4 ,7,7这个序列显然没有多数元素。
但是如去除1, 4,则序列变为4, 7, 4, 7 ,7,这时 7为多数元素。

故产生了一个新问题,在最终序列遍历完后找到的多数元素不一定是多数元素,但如果序列有多数元素存在,则在最终序列遍历完后找到的多数元素一定为该序列的多数元素,故我们需要一个函数来找序列中是否有多数元素

话不多说上代码,先找这个多数元素:

/*找出多数元素*/
int Candidate(int m)
{
	int c=a[m]; //将新序列第一个数开始向后遍历
	int count = 1;//定义计数器
	int j;
	for ( j= m + 1; count > 0 && j < n; ++j) //如果不相等的数等于了相等的数,去除该段已遍历的序列
	{
		if (a[j] == c)//如果与第一个数相等,计数器加1
		{
			count++;
		}
		else
		{
			count--;       //不相等减1
		}
	}
	if (j == n)           //序列遍历完
	{
		return c;
	}
	else                 //如没有,则从接下来新的数开始再往后遍历
	{
		return Candidate(j);
	}
}

就上面所说我们找到这个元素后,要判断这个数是否出现次数大于50%,故我们需要一个函数来判断序列中是否有多数元素,代码如下:

/*判断是否有多数元素*/
int Majority()
{
	int c = Candidate(1);
	int i, count = 0;
	for (i = 1; i <= n; ++i)
	{
		if (a[i] == c)
		{
			count++;
		}
	}
	if (count >= n / 2)              //如超过一半,则存在多数元素,且为c这个变量中所存的数
	{
		return c;
	}
	else
	{
		return 0;
	}
}

这个算法将所有数读了一遍,故时间复杂度最优为O(n) 。下面展示完整代码:

#include <stdio.h>                    /*寻找多数元素(主元素)问题*/
#include <stdlib.h>                   /*此代码为最简代码(时间复杂度最简)*/


int a[101], n;                        //定义全局变量,因为这两个变量在子函数中使用



/*找出多数元素*/
int Candidate(int m)
{
	int c=a[m]; //将新序列第一个数开始向后遍历
	int count = 1;//定义计数器
	int j;
	for ( j= m + 1; count > 0 && j < n; ++j)   //如果不相等的数等于了相等的数,去除该段已遍历的序列
	{
		if (a[j] == c)//如果与第一个数相等,计数器加1
		{
			count++;
		}
		else
		{
			count--;      //不相等减1
		}
	}
	if (j == n)           //序列遍历完
	{
		return c;
	}
	else                  //如没有,则从接下来新的数开始再往后遍历
	{
		return Candidate(j);
	}
}


/*判断是否有多数元素*/
int Majority()
{
	int c = Candidate(1);
	int i, count = 0;
	for (i = 1; i <= n; ++i)
	{
		if (a[i] == c)
		{
			count++;
		}
	}
	if (count > n / 2)          //如超过一半,则存在多数元素,且为c这个变量中所存的数
	{
		return c;           
	}
	else
	{
		return 0;
	}
}



int main(void)
{
	printf("数据个数:\n");
	scanf_s("%d",&n);
	int i;
	for (i = 1; i <= n; ++i)
	{
		scanf_s("%d",&a[i]);
	}
	if (i = Majority() != 0)
	{
		printf("%d", i);
	}
	else
	{
		printf("不存在多数元素\n");
	}
	return 0;
}

好了,这就是本次小编为大家带来的关于c语言讨论寻找多数元素(主元素问题)的全部内容啦!感谢您的阅读,分享知识,这个世界还能更好!

参考资料

  1. 《C语言程序设计(第5版)》 谭浩强 著 清华大学出版社

  2. 《C语言程序设计教程 》 王曙燕 主编 人民邮电出版社

  3. 《啊哈!算法》 啊哈磊 著 人民邮电出版社

  4. 《数据结构与算法分析C语言描述》 [美] 马克·艾伦·维斯 著 机械工业出版社

  • 18
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值