电路板排列0032算法笔记——电路板排列问题和连续邮资问题回溯法求解

每日一贴,今天的内容关键字为电路板排列

     1、电路板排列问题

        问题描述

         将n块电路板以佳最排列式方入插带有n个插槽的机箱中。n块电路板的不同排列式方对应于不同的电路板入插案方。设B={1, 2, …, n}是n块电路板的集合,L={N1, N2, …, Nm}是接连这n块电路板中多少电路板的m个接连块。Ni是B的一个集子,且Ni中的电路板用统一条导线接连在一起。设x表现n块电路板的一个排列,即在机箱的第i个插槽中入插的电路板编号是x[i]。x所肯定的电路板排列Density (x)度密定义为越跨相邻电路板插槽的最大连线数。

        例:如图,设n=8, m=5,给定n块电路板及其m个接连块:B={1, 2, 3, 4, 5, 6, 7, 8},N1={4, 5, 6},N2={2, 3},N3={1, 3},N4={3, 6},N5={7, 8};其中两个可能的排列如图所示,则该电路板排列的度密分别是2,3。

    电路板和排列       电路板和排列

         左上图中,越跨插槽2和3,4和5,以及插槽5和6的连线数均为2。插槽6和7之间无越跨连线。其余插槽之间只有1条越跨连线。在计划机箱时,插槽一侧的线布间隙由电路板的排列的度密肯定因此,电路板排列问题求要对于给定的电路板接连条件(接连块),肯定电路板的佳最排列,使其有具小最度密

         问题分析

         电路板排列问题是NP难问题,因此不大可能找到解此问题的多项式时光法算。虑考用采溯回法系统的索搜问题解间空的排列树,找出电路板的佳最排列。设用数组B表现入输。B[i][j]的值为1当且仅当电路板i在接连块Nj中。设total[j]是接连块Nj中的电路板数。对于电路板的部份排列x[1:i],设now[j]是x[1:i]中所含包的Nj中的电路板数。由此可知,接连块Nj的连线越跨插槽i和i+1当且仅当now[j]>0且now[j]!=total[j]。用这个条件来算计插槽i和i+1间的连线度密。

        法算体具实现如下:

//电路板排列问题 溯回法求解
#include "stdafx.h"
#include <iostream>
#include <fstream> 
using namespace std;

ifstream fin("5d11.txt"); 

class Board
{
	friend int Arrangement(int **B, int n, int m, int bestx[]);
	private:
		void Backtrack(int i,int cd);
		int n,		//电路板数
			m,		//接连板数
			*x,		//以后解
			*bestx,//以后最优解
			bestd,  //以后最优度密
			*total, //total[j]=接连块j的电路板数
			*now,   //now[j]=以后解中所含接连块j的电路板数
			**B;    //接连块数组
};

template <class Type>
inline void Swap(Type &a, Type &b);

int Arrangement(int **B, int n, int m, int bestx[]);

int main()
{
	int m = 5,n = 8;
	int bestx[9];

	//B={1,2,3,4,5,6,7,8}
	//N1={4,5,6},N2={2,3},N3={1,3},N4={3,6},N5={7,8}

	cout<<"m="<<m<<",n="<<n<<endl;
	cout<<"N1={4,5,6},N2={2,3},N3={1,3},N4={3,6},N5={7,8}"<<endl;
	cout<<"二维数组B如下:"<<endl;

	//结构B
	int **B = new int*[n+1];
	for(int i=1; i<=n; i++)
	{
		B[i] = new int[m+1];
	}

	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=m ;j++)
		{
			fin>>B[i][j];
			cout<<B[i][j]<<" ";
		}
		cout<<endl;
	}

	cout<<"以后最优度密为:"<<Arrangement(B,n,m,bestx)<<endl;
	cout<<"最优排列为:"<<endl;
	for(int i=1; i<=n; i++)
	{
		cout<<bestx[i]<<" ";
	}
	cout<<endl;

	for(int i=1; i<=n; i++)
	{
		delete[] B[i];
	}
	delete[] B;

	return 0;
}

void Board::Backtrack(int i,int cd)//溯回法索搜排列树
{
	if(i == n)
	{
		for(int j=1; j<=n; j++)
		{
			bestx[j] = x[j];
		}
		bestd = cd;
	}
	else
	{
		for(int j=1; j<=n; j++)
		{
			//选择x[j]为下一块电路板
			int ld = 0;
			for(int k=1; k<=m; k++)
			{
				now[k] += B[x[j]][k];
				if(now[k]>0 && total[k]!=now[k])
				{
					ld ++;
				}
			}

			//更新ld
			if(cd>ld)
			{
				ld = cd;
			}

			if(ld<bestd)//索搜子树
			{
				Swap(x[i],x[j]);
				Backtrack(i+1,ld);
				Swap(x[i],x[j]);

				//恢复状态
				for(int k=1; k<=m; k++)
				{
					now[k] -= B[x[j]][k];
				}
			}
		}
	}	
}

int Arrangement(int **B, int n, int m, int bestx[])
{
	Board X;

	//初始化X
	X.x = new int[n+1];
	X.total = new int[m+1];
	X.now = new int[m+1];
	X.B = B;
	X.n = n;
	X.m = m;
	X.bestx = bestx;
	X.bestd = m+1;

	//初始化total和now
	for(int i=1; i<=m; i++)
	{
		X.total[i] = 0;
		X.now[i] = 0;
	}


	//初始化x为单位排列并算计total
	for(int i=1; i<=n; i++)
	{
		X.x[i] = i;
		for(int j=1; j<=m; j++)
		{
			X.total[j] += B[i][j];
		}
	}

	//溯回索搜
	X.Backtrack(1,0);
	delete []X.x;
	delete []X.total;
	delete []X.now;
	return X.bestd;
}

template <class Type>
inline void Swap(Type &a, Type &b)
{  
	Type temp=a; 
	a=b; 
	b=temp;
}

     

    法算率效

         在解间空排列树的个每节点处,法算Backtrack费花O(m)算计时光为个每儿子节点算计度密。因此算计度密所消费的总算计时光为O(mn!)。另外,成生排列树要需O(n!)时光。每次更新以后最优解少至使bestd增加1,而法算运行结束时bestd>=0。因此最优解被更新的额次数为O(m)。更新最优解要需O(mn)时光。综上,解电路板排列问题的溯回法算Backtrack所要需的算计时光为O(mn!)。

         程序运行结果为:

    电路板和排列

         2、连续邮资问题

         问题描述

    每日一道理
共和国迎来了她五十诞辰。五十年像一条长河,有急流也有缓流;五十年像一幅长卷,有冷色也有暖色;五十年像一首乐曲,有低音也有高音;五十年像一部史诗,有痛苦也有欢乐。长河永远奔流,画卷刚刚展开,乐曲渐趋高潮,史诗还在续写。我们的共和国正迈着坚定的步伐,跨入新时代。

         设假国度发行了n种不同面值的邮票,并且划定每张信封上最多只允许贴m张邮票。连续邮资问题求要对于给定的n和m的值,给出邮票面值的佳最计划,在1张信封上可贴出从邮资1开始,增量为1的最大连续邮资区间。例如,当n=5和m=4时,面值为(1,3,11,15,32)的5种邮票可以贴出邮资的最大连续邮资区间是1到70。

         问题分析

        解向量:用n元组x[1:n]表现n种不同的邮票面值,并约定它们从小到大排列。x[1]=1是一唯的选择。
    可行性约束函数:已选定x[1:i-1],最大连续邮资区间是[1:r],接下来x[i]的可取值范围是[x[i-1]+1:r+1]

         算计X[1:i]的最大连续邮资区间在本法算中被频仍应用到,因此势须要找到一个高效的方法。直接递归的求解复杂度太高,我们不妨实验算计用不超越m张面值为x[1:i]的邮票贴出邮资k所需的起码邮票数y[k]。通过y[k]可以很快推出r的值。如果y[r]的值在上述动态规划算运程过中已赋值,则y[r]<maxint。句语while(y[r]<maxint) r++可以很快的算计出r值。关键是如何算计数组y,分析程过如下:

         r表现由x[1…i]能贴出的最大连续区间,当初,要想把第i层的结点往下展扩,有两个问题要需处理:一,哪些数有可能成为下一个的邮票面值,即x[i+1]的取值范围是什么;二,对于一个肯定的x[i+1],如何更新r的值让它表现x[1…i+1]能表现的最大连续邮资区间。
第一个问题很简单,x[i+1]的取值要和面前i个数各不相同,小最应该是x[i] + 1,最大就是r+1,否则r+1没有办法表现。我们当初注专第二个问题。
第二个问题自己有两种思绪:一,算计出全部应用不超越m张x[1…i+1]中的面值可以贴出的邮资,然后从r+1开始个逐查检否是被算计出来。二,从r+1开始,个逐讯问它是不是可以用不超越m张x[1…i+1]中的面值贴出来。
     两种思绪直接算计其算计量都是大巨的,要需借助动态规划的方法。模拟0-1背包问题,设假S(i)表现x[1…i]中不超越m张邮票的贴法的集合,这个集合中的元素数目是大巨的,例如,只应用1张邮票的贴法有C(i+1-1,1)=C(i,1)=i种,应用2张邮票的贴法有C(i+2-1,2)=C(i+1,2)=i*(i+1)/2种,……,应用m张邮票的贴法有C(i+m-1, m)种,其中C(n,r)表现n个元素中取r个元素的组合数。于是,S(i)中的元素的数目总共有C(i+1-1, 1) + C(i+2-1,2)+ … + C(i+m-1,m)个。S(i)中的个每元素就是一种正当的贴法,对应一个邮资。以后最大连续邮资区间为1到r,那么S(i)中个每元素的邮资是不是也在1到r之间呢?不一定,比如{1,2,4},当m=2时,它能贴出来8,但不能贴出来7。总之,在索搜时,一定要坚持状态的一致性,即当深度索搜到第i层时,一定要确保用来存保结点状态的变量中存保的一定是第i层的这个结点的状态。定义S(i)中元素的值就是它所表现的贴法贴出来的邮资,于是,可以把S(i)中的元素按照它们的值的等相关系成分k类。第j类表现贴出邮资为j的全部的贴法集合,用T(j)表现,T(j)有是能可空集,例如对于{1,2,4},T(7)为空集,T(8)={{4,4}}。此时有:S(i) = T(1) U T(2) U T(3) U … U T(k),U表现两个集合的并。
     当初虑考x[i+1]参加后对以后状态S(i)的影响。设假s是S(i)中的一个元素,即s表现一种正当的贴法,x[i+1]对s能贴出的邮资的影响就是x[i+1]的多次重复增加了s能贴出的邮资。x[i+1]对s的影响就是,如果s中贴的邮票不满m张,那就直一贴x[i+1],直到s中有m张邮票,这个程过会生产出很多不同的邮资,它们都应该被参加到S(i+1)中。因为s属于S。

         综上分析,虑考如果应用动态规划方法算计数组y的值,状态转移程过:将x[i-1]参加等价类集S中,将会起引数组x能贴出的邮资范围变大,对S的影响是如果S中的邮票不满m张,那就直一贴x[i-1],直到S中有m张邮票,这个程过会生产很多不同的邮资,取能生产最多不同邮资的用邮票起码的那个元素。

          例如:如下图所示,设m=4,n=5。当x[1]=1时,2张{1,1}可以贴出邮资2。这时,设x[2]=3。将3往{1,1}中添加,生产新的邮资贴法:5:{3,1,1},8:{3,3,1,1}。这时,程序要需更新数组y的值。如果新的贴法比y[5],y[8]已有的贴法所用的张数更少,则更新之

    电路板和排列

        法算体具实现如下:

    

//连续邮资问题 溯回法求解
#include "stdafx.h"
#include <iostream>
using namespace std;

class Stamp
{
	friend int MaxStamp(int  ,int  ,int []);
	private:
		int Bound(int i);
		void Backtrack(int i,int r);
		int n;//邮票面值数
		int m;//每张信封最多允许贴的邮票数
		int maxvalue;//以后最优值
		int maxint;//大整数
		int maxl;//邮资上界
		int *x;//以后解
		int *y;//贴出各种邮资所需起码邮票数
		int *bestx;//以后最优解
};

int MaxStamp(int n,int m,int bestx[]);

int main()
{
	int *bestx;
	int n = 5;
	int m = 4;
	cout<<"邮票面值数:"<<n<<endl;
	cout<<"每张信封最多允许贴的邮票数:"<<m<<endl;

	bestx=new int[n+1];
	for(int i=1;i<=n;i++)
	{
		bestx[i]=0;
	}
	
	cout<<"最大邮资:"<<MaxStamp(n,m,bestx)<<endl;

	cout<<"以后最优解:";
	for(int i=1;i<=n;i++)
	{
		cout<<bestx[i]<<"  ";
	}
	cout<<endl;
	
	return 0;
}

void Stamp::Backtrack(int i,int r)
{
	/*
	 *动态规划方法算计数组y的值。状态转移程过:
	 *虑考将x[i-1]参加等价类集S中,将会起引数组x
	 *能贴出的邮资范围变大,对S的影响是如果S中的
	 *邮票不满m张,那就直一贴x[i-1],直到S中有m张
	 *邮票,这个程过会生产很多不同的邮资,取能生产
	 *最多不同邮资的用邮票起码的那个元素
	 */
	for(int j=0;j<=x[i-2]*(m-1);j++)
	{
		if(y[j]<m)
		{
			for(int k=1;k<=m-y[j];k++)//k x[i-1]的重复次数
			{
				if(y[j]+k<y[j+x[i-1]*k])
				{
					y[j+x[i-1]*k]=y[j]+k;
				}
			}
		}
	}

	//如果y[r]的值在上述动态规划算运程过中已赋值,则y[r]<maxint
	while(y[r]<maxint)
	{
		r++;
	}

	if(i>n)
	{
		if(r-1>maxvalue)
		{
			maxvalue=r-1;
			for(int j=1;j<=n;j++)
			{
				bestx[j]=x[j];
			}
		}
		return;
	}

	int *z=new int[maxl+1];

	for(int k=1;k<=maxl;k++)
	{
		z[k]=y[k];
	}

	for(int j=x[i-1]+1;j<=r;j++)
	{
		x[i]=j;
		Backtrack(i+1,r);
		for(int k=1;k<=maxl;k++)
		{
			y[k]=z[k];
		}
	}
	delete[] z;
}

int MaxStamp(int n,int m,int bestx[])
{
	Stamp X;
	int maxint=32767;
	int maxl=1500;
	
	X.n=n;
	X.m=m;
	X.maxvalue=0;

	X.maxint=maxint;
	X.maxl=maxl;
	X.bestx=bestx;

	X.x=new int [n+1];
	X.y=new int [maxl+1];
	
	for(int i=0;i<=n;i++)
	{
		X.x[i]=0;
	}

	for(int i=1;i<=maxl;i++)
	{
		X.y[i]=maxint;
	}

	X.x[1]=1;
	X.y[0]=0;

	X.Backtrack(2,1);

	delete[] X.x;
	delete [] X.y;
	return X.maxvalue;
}

     程序运行结果如图:

    电路板和排列
 

文章结束给大家分享下程序员的一些笑话语录: 据说有一位软件工程师,一位硬件工程师和一位项目经理同坐车参加研讨会。不幸在从盘山公路下山时坏在半路上了。于是两位工程师和一位经理就如何修车的问题展开了讨论。
硬件工程师说:“我可以用随身携带的瑞士军刀把车坏的部分拆下来,找出原因,排除故障。”
项目经理说:“根据经营管理学,应该召开会议,根据问题现状写出需求报告,制订计划,编写日程安排,逐步逼近,alpha测试,beta1测试和beta2测试解决问题。”
软件工程说:“咱们还是应该把车推回山顶再开下来,看看问题是否重复发生。”

posted @ 2013-05-08 19:38  坚固66  阅读(1890)  评论(0编辑  收藏  举报