求数组中出现多于一半的数字

本文探讨了在长度为n的数组中找到出现次数超过n/2的元素的问题,提出了两种解决方案:一是利用分治策略,时间复杂度为nlgn;二是采用何海涛提出的优化算法,时间复杂度为O(n),并提供了C语言实现。

问题:给定一个数组,长度为 n,求这个数组里面出现多于 n/ 2 的数字。

可以用分冶的方法解决。如下图。


我们不断地将问题缩减到两个一半(left 和 right)的规模,根据 left 和 right 求出的多于一半的元素,分别为 lResult  和 rResult,如果这两个值相等,那毫无疑问这个元素是所求。如果不相等,则需要在 left 中求 rResult 出现的次数,在 right 中求 lResult 出现的次数,与递归中求得的 lResult 和 rResult 分别的次数相加,看是不是出现了一半以上。

这个算法的复杂度的递推式为:T(n) = 2 * T(n/2) + n。后面加上的一个 n 是最坏的情况下,lResult 和 rResult 不相等,此时,需要在两边独立地求它们出现的次数。即两个 n/2。算出来时间复杂度为 nlgn。因此这个算法效率不高,我们试想,直接使用排序,就可以达到  nlgn 的时间复杂度,再直接取排序后的数组的中间元素,需要验证它是出现次数大于一半的元素(如果有出现次数多于一半的元素,那么中间元素必定是所求)我们需要验证这个结果是不是真的出现了多于一半。因此,我们不需要使用分冶的办法,而直接使用排序算法就可以达到本算法的时间复杂度。但是,因为排序带入了我们不需要的信息,如元素之间大小排序关系,因此, 本问题应该有更简单的解法。

我们在下面给出两种解法,前面一种是本文的算法,后面一种是何海涛的解法(http://zhedahht.blog.163.com/blog/static/25411174201085114733349/),何海涛的实现比较巧妙,算法充分利用了“有个数字出现的次数比其他所有数字出现次数的和还要多”。最终时间复杂度为 O(n)。

给出实现的源代码如下。

#include 
#include 
#include 

#define NOT_FOUND 9999999

//fnum 在长度为 nlength 的数组 nums 中出现了多少次
int appear(int fnum,int *nums,int nlength)
{
	int i,ncount = 0;
	for (i = 0;i < nlength;++ i)
	{
		if(fnum == *(nums + i))
		{
			++ ncount;
		}
	}
	return ncount;
}

//返回长度为 nlength 的数组 nums 出现次数超过一半的元素,其出现的次数放在 appearCounts 中
int findNumMoreThanHalf(int *nums,int nlength,int *appearCounts)
{
	if(0 == nlength)
	{
		return NOT_FOUND;
	}
	else if(1 == nlength)
	{
		*appearCounts = 1;
		return *nums;
	}
	else
	{
		int mid = nlength / 2;
		int leftFind,rightFind;
		int *lnums = nums,*rnums = nums + mid;
		int llength = mid,rlength = mid + (nlength & 1);
		int lcounts,rcounts;
		leftFind = findNumMoreThanHalf(lnums,llength,&lcounts);
		rightFind = findNumMoreThanHalf(rnums,rlength,&rcounts);
		if(NOT_FOUND != leftFind && NOT_FOUND != rightFind)
		{
			if(leftFind != rightFind)
			{
				int appearInRigth = appear(leftFind,rnums,rlength);
				int appearInLeft  = appear(rightFind,lnums,llength);
				
				if(appearInRigth + lcounts > nlength / 2)
				{
					*appearCounts = appearInRigth + lcounts;
					return leftFind;
				}
				else if(appearInLeft + rcounts > nlength / 2)
				{
					*appearCounts = appearInLeft + rcounts;
					return rightFind;
				}
				else
				{
					return NOT_FOUND;
				}
			}
			else
			{
				*appearCounts = lcounts + rcounts;
				return leftFind;
			}
		}
		else if(NOT_FOUND == leftFind && NOT_FOUND == rightFind)
		{
			return NOT_FOUND;
		}
		else
		{
			if(NOT_FOUND != leftFind)
			{
				int appearInRigth = appear(leftFind,rnums,rlength);
				if(appearInRigth + lcounts > nlength / 2)
				{
					*appearCounts = appearInRigth + lcounts;
					return leftFind;
				}
				else
				{
					return NOT_FOUND;
				}
			}
			else
			{
				int appearInLeft = appear(rightFind,lnums,llength);
				if(appearInLeft + rcounts > nlength / 2)
				{
					*appearCounts = appearInLeft + rcounts;
					return rightFind;
				}
				else
				{
					return NOT_FOUND;
				}
			}
		}
	}
}

//生成长度为 nlength 的整数数组,每一个元素随机生成,上界为 upperBound(exclusive)
int *randomNums(int nlength,int upperBound)
{
	int i ;
	int *nums = (int*)malloc(sizeof(int) * nlength);
	srand((int)time(NULL));
	for (i = 0;i < nlength;++ i)
	{
		*(nums + i) = rand() % upperBound;
	}
	return nums;
}




//何海涛的方法http://zhedahht.blog.163.com/blog/static/25411174201085114733349/ - 
//返回长度为 nlength 的数组 nums 出现次数超过一半的元素,其出现的次数放在 appearCounts 中
int findNumMoreThanHalf_haitao(int *nums,int nlength,int *appearCounts)
{
	int last = NOT_FOUND,counts = 0;
	int i;
	int verifyCounts = 0;
	for (i = 0;i < nlength;++ i)
	{
		if(last != *(nums + i))
		{
			if(0 == counts)
			{
				counts = 1;
				last = *(nums + i);
			}
			else
			{
				--counts;
			}
		}
		else
		{
			++ counts;
		}
	}
	
	
	if(0 == counts)
	{
		return NOT_FOUND;
	}
	//verify
	for (i = 0;i < nlength;++ i)
	{
		if(last == *(nums + i))
		{
			++ verifyCounts;
		}
	}
	if(verifyCounts > nlength / 2)
	{
		*appearCounts = verifyCounts;
		return last;
	}
	else
	{
		return NOT_FOUND;
	}
}


//取得当前毫秒数
#include 
long long getCurrentMillis()    
{
	SYSTEMTIME lpsystime;  
	GetLocalTime(&lpsystime);
	return ((lpsystime.wHour * 60 + lpsystime.wMinute) * 60 + lpsystime.wSecond) * 1000 +  lpsystime.wMilliseconds;
}

void main()
{
	int nlength = 10000000;
	int *nums = randomNums(nlength,NOT_FOUND);
//	const static int nlength = 10;
//	int nums[10] = {3,2,4,5,7,4,4,4,4,4};
	int appearCounts;
	long long startTime = getCurrentMillis();
	int moreThanHalf = findNumMoreThanHalf(nums,nlength,&appearCounts);
	long long endTime = getCurrentMillis();
	if(NOT_FOUND == moreThanHalf)
	{
		printf("not found.spend %lld millseconds\r\n",endTime - startTime);
	}
	else
	{
		printf("%d appears %d,more than half.spend %lld millseconds\r\n",moreThanHalf,appearCounts,endTime - startTime);
	}

	startTime = getCurrentMillis();
	moreThanHalf = findNumMoreThanHalf_haitao(nums,nlength,&appearCounts);
	endTime = getCurrentMillis();
	if(NOT_FOUND == moreThanHalf)
	{
		printf("not found.spend %lld millseconds\r\n",endTime - startTime);
	}
	else
	{
		printf("%d appears %d,more than half.spend %lld millseconds\r\n",moreThanHalf,appearCounts,endTime - startTime);
	}
	
	system("PAUSE");
}

何海涛的算法要优于我的算法,在理论上和上面的实现中的测试都说明了这一点。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值