用分治算法求数组最小的两个元素的算法错误分析与改正

以下代码是书上151页例17求数组最小的两个元素的算法

总体思路是用分治算法,把数组分成两个子集,在两个子集中选择最小的两个元素,然后再把两个集合中,选出来的四个数选出较小的两个数.这样就把大问题分解成两个容易解决的小问题了.直到回溯结束就可以得到时原问题的解.

书上的算法如下:

#include <stdio.h>
#include <algorithm>
int a[100];
int second(int n)
{
	void two(int ,int,int&,int&);
	int min2=0,min1=0;
	two(0,n-1,min2,min1);
	return min2;
}
void two(int i,int j,int &fmin2,int &fmin1)
{
	int lmin2,lmin1,rmin2,rmin1;
	int mid;
	if (i == j)
	{
		fmin2 = fmin1 = a[i];
	}
 	else if (i == j-1)
	{
		if (a[i]<a[j])
		{
			fmin2 = a[j];
			fmin1 = a[i];
		}
		else
		{
			fmin2 = a[i];
			fmin1 = a[j];
		}
	}
	else
	{
		mid = (i+j)/2;
		two(i,mid,lmin2,lmin1);
		two(mid+1,j,rmin2,rmin1);
		if (lmin1 < rmin1)
		{
			if (lmin2<rmin1)
			{
				fmin1 = lmin1;
				fmin2 = lmin2;
			}
			else
			{
				fmin1 = lmin1;
				fmin2 = rmin1;
			}
		}
		else
		{
			if (rmin2<lmin1)
			{
				fmin1 = rmin1;
				fmin2 = rmin2;
			}
			else
			{
				fmin1 = rmin1;
				fmin2 = lmin2;
			}
		}
	}
}
void main()
{
	for (int i=0;i<10;i++)
	{
		a[i]=10-i;
	}
	for (i=0;i<10;i++)
	{
		std::random_shuffle(a,a+10);
		for (int j=0;j<10;j++)
		{
			printf("%d ",a[j]);
		}
		printf("------第二小的数是%d\n",second(10));
 	}	
}

main函数中用不通的数据测试时,发现算法出错了.十次运算中,有五次结果是错的.出了几次4,出现了几次1.

通过检查,没有什么书写错误.

然后就开始思考是不是逻辑错误.

观察递归的结束条件,i==j和i==j-1;就是说最把问题分解成最小的问题是要处里的数组中只一个或者两个元素.

当只有一个元素时,最个两个数的返回值被赋予一样的值.

然在在回溯到时上一层时,本来只有一个元素的返回出了两个元素.

由于有一个元素返回两个元素的值,把问题的解合并时,看起来是从四个元素中选出两个最小的,实际上只是从三个元素中选出两个最小的,或都从丙个元素中选出两个最小的.

这样就会出现错误的结果

解题的分治算法思路还是正确的,只是算法的最小可解问题选择错误了.

我对算法进行了改写,当数组中只有两个元素时或都有三个元素时进行递归的回溯.

这样就不会出现上面的问题.

只中会出现,求解两个元素的最小两个数的过程,

这个不像从四个数中选择出两个最小的.

那四个数分两组,每组中是有大小之分.所以只有四种情况.

而从三个数中找出最小的两个数,是没有顺序的,所以穷举有六种情况.

然后解决了最小的元问题.回溯,就可以得到结果.

改写后的代码如下:

#include <stdio.h>
#include <algorithm>
int a[20];
void min3(int &x,int &y,int a,int b,int c);
void two(int i,int j,int &fmin2,int &fmin1)
{
	int lmin2,lmin1,rmin2,rmin1;
	int mid;
	if ((j-i) ==1)
	{
		if (a[i]>a[j])
		{
			fmin1 = a[j];
			fmin2 = a[i];
		}
		else
		{
			fmin2 = a[j];
			fmin1 = a[i];
		}
	}
 	else if ((j-i)==2)
	{
		min3(fmin2,fmin1,a[i],a[i+1],a[j]);
 	}
	else
	{
		mid = (i+j)/2;
		two(i,mid,lmin2,lmin1);
		two(mid+1,j,rmin2,rmin1);
		if (lmin1 > rmin2)
		{ 
			fmin1 = rmin1;
			fmin2 = rmin2;
		}
		else if (rmin1 > lmin2)
		{
			fmin1 = lmin1;
			fmin2 = lmin2;
		}
		else if (lmin1 > rmin1)
		{
			fmin1 = rmin1;
			fmin2 = lmin1;
		}
		else
		{
			fmin1 = lmin1;
			fmin2 = rmin1;
		}
	}
}
void main()
{	
	int n=20;
	for (int i=0;i< n;i++)
	{
		a[i]=n-i;
	}

	int c,b;
	//测试数据,循环10每次数组数据不同
	for (i=0;i<10;i++)
	{
		std::random_shuffle(a,a+n);
		for (int j=0;j<n;j++)
		{
			printf("%d ",a[j]);
		}
		two(0,n-1,c,b);
		printf("--------%d,%d\n",c,b);
  	}	
}
void min3(int &x,int &y,int a,int b,int c)//求三个数中最小的两个。
{
	if (a>b && a>c)
	{
		if (b>c)
		{
			y=c;
			x=b;
		}
		else
		{
			y=b;
			x=c;
		}
	}
	else if (b >a && b>c)
	{
		if (a>c)
		{
			y=c;
			x=a;
		}
		else
		{
			y=c;
			x=a;
		}

	}
	else
	{
		if (a>b)
		{
			y=b;
			x=a;
		}
		else
		{
			y=a;
			x=b;
		}
	}
}

写写总结吧,从发现问题,找到问题,解决问题真花了不少时间.

遇到这种的问题,一定要理清思路,想清程序是怎么运行的,才能在其中发现漏洞.

从分治的思想上,先是分解问题,分解得的没有问题,然后再看分解的原子问题的解有没有问题,各个原子问题之间有没有联系.

然后再看把原子问题的解合并成原问题的解,合并过程中有没有出问题.


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值