算法设计:回溯例题--OJ代码

实验题目
符号三角形的 第1行有n个由“+”和“-”组成的符号 ,以后每行符号比上行少1个,2个同号下面是“+”,2个异号下面是“-” 。计算有多少个不同的符号三角形,使其所含“+” 和“-” 的个数相同。

OJ代码——Accepted:

#include <iostream>  
using namespace std;

class Triangle
{
private:
	int n; //第一行的符号数量  
	int **a;//存储+ -号,0表示+,1表示-  
	int coun;//记录+号数量,根据+号数量,可以用i*(i+1)/2-coun求出减号数量  
	int num;//记录+与-数量相等且等于num*(num+1)/4的情况  
	const int half;//加号=减号=总符号数/2  
public:
	Triangle(int n) :half(n*(n + 1) / 4)	//构造函数 
	{
		this->n = n;
		coun = 0;
		num = 0;

		a = new int*[n + 1];
		for (int i = 0; i <= n; i++)
			a[i] = new int[n + 1];//开辟一个n*n的矩阵,用于在回溯过程中记录三角形符号分布  
	}
	void triangleSolve()//调用回溯算法,并且先判断n*(n+1)/2是否为奇数  
	{
		if (n*(n + 1) / 2 % 2 == 0)
		{
			find(1);  //调用回溯算法
			display();
		}
		else
		{
			cout << 0 << endl;
			return;
		}
	}
	void find(int m)//回溯核心算法  
	{
		if (m > n)
		{
			num++;   //找到的解法++
			return;
		}
		for (int i = 0; i < 2; i++)
		{
			a[1][m] = i;  //第一行第m个符号
			coun += i;  //符号统计
			for (int j = 2; j <= m; j++)
			{
				a[j][m - j + 1] = a[j - 1][m - j + 1] ^ a[j - 1][m - j + 2];  //异或运算符查看关系
				coun += a[j][m - j + 1];  //记录1 即‘-’的个数
			}
			if ((coun <= half) && (m*(m + 1) / 2 - coun <= half)) //约束函数
				find(m + 1);  //符合条件继续寻找
			for (int j = 2; j <= m; j++)  //恢复现场
				coun -= a[j][m - j + 1];  //减去新加入的右侧边的+数量
			coun -= i;   //减去m位置的+的数量
		}
	}
	void display() 
	{
		cout << num << endl;
	}
};

int main()
{
	int n;
	cin >> n;
	Triangle test(n);
	test.triangleSolve();//解决问题  
	system("pause");
	return 0;
}

实验截图:
在这里插入图片描述

解题思路:
1、 因为计算含有“+”和“-”相同的符号三角形的数量与第一行符号有关,不断改变第一行每个符号,并搜索符合条件的解可以使用回溯算法。
2、 为了便于计算,我们用0和1分别代替“+”和“-”,此时可以直接运用异或运算符来判断符号三角形的关系,即:+ +为 + :“1^1=0”,- - 为+ :“0^0=0”,+ - 为- :“1^0=1”,- + 为- :“0^1=1”,在代码中体现为:
a[j][m - j + 1] = a[j - 1][m - j + 1] ^ a[j - 1][m - j + 2]
3、 不进行回溯的条件:1)所有符号总数为奇数,不存在“+”和“-”数量相等的情况;2)“+”或“-”的数量超过总数的一半

算法分析:
想要求得整个三角形中“+”和“-”数量相等的三角形数量只与三角形的第一行的符号有关,故而可以逐次安排第一行的每一个符号,由于每次安排一个符号,就会在原定的三角形中最右侧加一条三角形边,故而采用回溯法,逐次对第一行每个位置的符号进行选择,并且扩充新的三角形边。
为了在回溯过程中及时的计算出三角形的最新状况,采用二维数组存储三角形,根据图形的关系,可以求出三角形后一行与前一行的关系,并且加入到二维数组中。注意:在每次回溯完成之后都要进行恢复现场处理,即:coun -= a[j][m - j + 1];。
为了更好地实现回溯算法,必须存在剪枝语句,这里使用“+”或者“-”都小于总数的一半作为约束,即:(coun <= half) && (m*(m + 1) / 2 - coun <= half),只要函数可以递归到n+1个字符,说明前n个字符的安排均满足以上约束条件,即“+”和“-”数量相等,找到的三角形数量加一。

法二:
不采用定义类的方式,并且输出每个符合条件的符号三角形。
实验代码:

#include"iostream"  
using namespace std;
char cc[2] = { '+','-' };   //便于输出  
int n, half;               //全部符号总数一半  
int counter;            //1计数,即“-”号计数  
char **p;              //符号存储空间      
long sum;               //符合条件的三角形计数  
void Backtrace(int t)  //t,第一行第t个符号  
{
	int i, j;
	if (t > n)
	{//符号填充完毕  
		sum++;	
		cout << "第" << sum << "个:" << endl;
		for (i = 1; i <= n; ++i)   //打印符号  
		{
			for (j = 1; j < i; ++j)
				cout << " ";

			for (j = 1; j <= n - i + 1; ++j)
				cout << cc[p[i][j]] << " ";
			cout << endl;
		}
	}
	else
	{
		for (i = 0; i < 2; ++i)
		{
			p[1][t] = i;        //第一行第t个符号  
			counter += i;       //“-”号统计  
			for (j = 2; j <= t; ++j)  //当第一行符号>=2时,可以运算出下面行的某些符号  
			{
				p[j][t - j + 1] = p[j - 1][t - j + 1] ^ p[j - 1][t - j + 2];//异或运算
				counter += p[j][t - j + 1];
			}

			if ((counter <= half) && (t*(t + 1) / 2 - counter <= half))  //约束条件
				Backtrace(t + 1);         //在第一行增加下一个符号  			 
			for (j = 2; j <= t; ++j)    //恢复现场
				counter -= p[j][t - j + 1];

			counter -= i;
		}
	}
}

int main()
{
	cin >> n;
	counter = 0;
	sum = 0;
	half = n * (n + 1) / 2;
	int i = 0;
	if (half % 2 == 0)//总数须为偶数,若为奇数则无解  
	{
		half /= 2;   //+ -相等时的数量
		p = new char *[n + 1];

		for (i = 0; i <= n; ++i)
		{
			p[i] = new char[n + 1];
			memset(p[i], 0, sizeof(char)*(n + 1));
		}
		Backtrace(1);
	}
	cout <<  sum << endl;
	system("pause");
	return 0;
}

实验截图:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值