回溯算法

轉載自:https://blog.csdn.net/qq_32400847/article/details/51474105

目錄

1.八皇后問題

2.圖的着色問題

3.裝載問題

4.批處理作業調度問題

5.再論揹包問題

6.最大團問題

7.圓排列問題

8.連續郵資問題

9.符號三角形問題

10.集合劃分問題


本文在寫作過程中參考了大量資料,不能一一列舉,還請見諒。
回溯算法的定義:回溯算法也叫試探法,它是一種系統地搜索問題的解的方法。回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。
解題的一般步驟是:
1.定義一個解空間,它包含問題的解;
2.利用適於搜索的方法組織解空間;
3.利用深度優先法搜索解空間;
4.利用限界函數避免移動到不可能產生解的子空間。
問題的解空間通常是在搜索問題的解的過程中動態產生的,這是回溯算法的一個重要特性。
話不多說,我們來看幾個具體的例子慢慢理解它:


1.八皇后問題


該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 下面的解法參考了《算法競賽入門經典》。如果我們逐行放置皇后則肯定沒有任意兩個皇后位於同一行,只需要判斷列和對角線即可。使用一個二維數組vis[3][],其中vis[0][i]表示列,vis[1][i]和vis[2][i]表示對角線。因爲(x,y)的y-x值標識了主對角線,x+y值標識了副對角線。由於y-x可能爲負,所以存取時要加上n。

#include<cstring> 
#include<iostream>
using namespace std;  
int vis[3][15],tot;  
 
void search(int cur)  
{  
    int i,j;  
    if(cur==8) tot++;
    else  
    {  
    	for(i=0;i<8;i++)  
        {  
            if(!vis[0][i]&&!vis[1][cur-i+8]&&!vis[2][cur+i])  
            {  
                vis[0][i]=1;  
                vis[1][cur-i+8]=1;  
                vis[2][cur+i]=1;    
                search(cur+1);  
                //改回輔助的全局變量 
                vis[0][i]=0;       
                vis[1][cur-i+8]=0;  
                vis[2][cur+i]=0;  
            }  
        }  
    }  
}  
 
int main()  
{  
    search(0);   
    cout<<tot<<endl;
}

2.圖的着色問題


給定無向連通圖G=(V,E)和m種不同的顏色,用這些顏色爲圖G的各頂點着色,每個頂點着一種顏色。如果一個圖最少需要m種顏色才能使圖中每條邊連接的2個頂點着不同顏色,則稱m爲該圖的色數。地圖着色問題可轉換爲圖的着色問題:以地圖中的區域作爲圖中頂點,2個區域如果鄰接,則這2個區域對應的頂點間有一條邊,即邊表示了區域間的鄰接關係。著名的四色定理就是指每個平面地圖都可以只用四種顏色來染色,而且沒有兩個鄰接的區域顏色相同。

給定圖和顏色的數目求出着色方法的數目,可以使用回溯法。

#define N 100
#include<iostream>
using namespace std;
int v,e,c,graph[N][N],color[N];
//頂點數,邊數,顏色數 
int sum;
 
bool ok(int k)
{
	for(int j=1;j<=v;j++)
	{
		if(graph[k][j]&&(color[j]==color[k])) return false;
	}
	return true;
}
 
void backtrack(int t)
{
	if(t>v) sum++;
 	else
 	{
    	for(int i=1;i<=c;i++)
		{
			color[t]=i;
   			if(ok(t)) backtrack(t+1);
   			//改回輔助的全局變量 
   			color[t]=0;
		}
 	}
}
 
int main()
{
    int i,j;
    cin>>v>>e>>c;                
	for(i=1;i<=v;i++)
	{
		for(j=1;j<=v;j++)
		{
			graph[i][j]=0; 
		}
	}           
	for(int k=1;k<=e;k++)      
	{
		cin>>i>>j;
		graph[i][j]=1;
		graph[j][i]=1;
	}
	for(i=0;i<=v;i++) color[i]=0;
 	backtrack(1);
  	cout<<sum<<endl;
}

3.裝載問題

有一批共n個集裝箱要裝上2艘載重量分別爲c1和c2的船,其中集裝箱i的重量爲wi,且。裝載問題要求確定是否有一個合理的裝載方案可將這些集裝箱裝上這2艘船。如果有,找出一種裝載方案。例如當n=3,c1=c2=50且w=[10,40,40]時,則可以將集裝箱1和2裝到第一艘輪船上,而將集裝箱3裝到第二艘輪船上;如果w=[20,40,40],則無法將這3個集裝箱都裝上輪船。容易證明,如果一個給定裝載問題有解,則首先將第一艘船儘可能裝滿再將剩餘的集裝箱裝上第二艘船可得到最優裝載方案。將第一艘船儘可能裝滿等價於選取全體集裝箱的一個子集,使該子集中集裝箱重量之和最接近c1。用回溯法解裝載問題,  時間複雜度O(2^n),在某些情況下優於動態規劃算法。剪枝方案是如果當前已經選擇的全部物品載重量cw+剩餘集裝箱的重量r<=當前已知的最優載重量bestw,則刪去該分支。

#include<iostream>
using namespace std;  
int n;//集裝箱數  
int w[40];//集裝箱重量
int c1,c2;//兩艘船的載重量  
int ans;//當前載重量  
int bestans;//當前最優載重量  
int r;//剩餘集裝箱重量  
  
void backtrack(int i)  
{        
    if(i>n)    
    {  
		if(ans>bestans) bestans=ans;  
		return;  
    }  
    r-=w[i];  
    if(ans+w[i]<=c1)  
    {   
      ans+=w[i];  
      backtrack(i+1);  
      //改回輔助的全局變量 
      ans-=w[i];  
    }  
    if(ans+r>bestans) backtrack(i+1);    
    //改回輔助的全局變量 
    r+=w[i];  
}    
  
int maxloading()  
{  
	ans=0;  
	bestans=0;  
    backtrack(1);   
    return bestans;  
}
 
int main()  
{    
	cin>>n>>c1>>c2;  
 	int i=1;  
 	int sum=0;  
 	//集裝箱總重量 
 	while(i<=n)  
	{  
		cin>>w[i];  
		r+=w[i];  
		sum+=w[i];  
 		i++;  
 	}    
	maxloading();  
	if(bestans>0&&((sum-bestans)<=c2)) cout<<bestans<<endl;  
 	else if(sum<=c2) cout<<bestans<<endl;  
  	else cout<<"No"<<endl;  
}

4.批處理作業調度問題


給定n個作業的集合{J1,J2,…,Jn}。每個作業必須先由機器1處理,然後由機器2處理。作業Ji需(1≤i≤n)要機器j(1≤j≤2)的處理時間爲tji。對於一個確定的作業調度,設Fji是作業i在機器j上完成處理的時間。所有作業在機器2上完成處理的時間和稱爲該作業調度的完成時間和:。要求對於給定的n個作業,制定最佳作業調度方案,使其完成時間和達到最小。

tji 機器1 機器2
作業1 2 1
作業2 3 1
作業3 2 3


例如,對於這張表格所示的情況,3個作業有3!=6種可能調度方案,很顯然最壞複雜度即爲O(n!)。如果按照2,3,1的順序,則作業2的完成時間爲4,作業3的完成時間爲8,作業1的完成時間爲9,完成時間和爲21。最優的作業調度順序爲最佳調度方案是1,3,2,其完成時間和爲18。

#define MAX 200
#include<iostream>
using namespace std;
int* x1;//作業Ji在機器1上的工作時間
int* x2;//作業Ji在機器2上的工作時間
int number=0;//作業的數目
int* xorder;//作業順序
int* bestorder;//最優的作業順序
int bestvalue=MAX;//最優的時間
int xvalue=0;//當前完成用的時間
int f1=0;//機器1完成的時間
int* f2;//機器2完成的時間
 
void backtrack(int k)
{
	if(k>number)
	{
		for(int i=1;i<=number;i++) bestorder[i]=xorder[i];
  		bestvalue=xvalue;
	}
	else
	{
		for(int i=k;i<=number;i++)
  		{
           f1+=x1[xorder[i]];
           f2[k]=(f2[k-1]>f1?f2[k-1]:f1)+x2[xorder[i]];
           xvalue+=f2[k];
           swap(xorder[i],xorder[k]);
           if(xvalue<bestvalue) backtrack(k+1);
           swap(xorder[i],xorder[k]);
           xvalue-=f2[k];
           f1-=x1[xorder[i]];
		}
	}
}
 
int main()
{
	cout<<"請輸入作業數目:";
 	cin>>number;
	x1=new int[number+1];
 	x2=new int[number+1];
  	xorder=new int[number+1];
   	bestorder=new int[number+1];
   	f2=new int[number+1];
   	x1[0]=0;
   	x2[0]=0;
   	xorder[0]=0;
   	bestorder[0]=0;
    f2[0]=0;
    cout<<"請輸入每個作業在機器1上所用的時間:"<<endl;
    int i;
    for(i=1;i<=number;i++)
    {
		cout<<"第"<<i<<"個作業=";
		cin>>x1[i];
  	}
	cout<<"請輸入每個作業在機器2上所用的時間:"<<endl;
 	for(i=1;i<=number;i++)
  	{
   		cout<<"第"<<i<<"個作業=";
     	cin>>x2[i];
  	}
   	for(i=1;i<=number;i++) xorder[i]=i;
    backtrack(1);
    cout<<"最節省的時間爲:"<<bestvalue<<endl;
    cout<<"對應的方案爲:";
    for(i=1;i<=number;i++) cout<<bestorder[i]<<"  ";
    cout<<endl;
}

5.再論揹包問題


從零開始學動態規劃從零開始學貪心算法中我們已經討論過了揹包問題,這裏我們再次用回溯法求解經典的零一揹包問題。(原文代碼有誤,此處已修正)

代碼出處:https://blog.csdn.net/qian2213762498/article/details/79420269

// https://blog.csdn.net/qq_29856169/article/details/89786724

#include <iostream>
#include <stdio.h>
//#include <conio.h>
using namespace std;

int n;//物品數量
double c;//揹包容量
double v[100];//各個物品的價值 value
double w[100];//各個物品的重量 weight
double cw = 0.0;//當前揹包重量 current weight
double cp = 0.0;//當前揹包中物品總價值 current value
double bestp = 0.0;//當前最優價值best price
double perp[100];//單位物品價值(排序後) per price
int order[100];//物品編號
int put[100];//設置是否裝入,爲1的時候表示選擇該組數據裝入,爲0的表示不選擇該組數據

//按單位價值排序
void knapsack()
{
    int i,j;
    int temporder = 0;
    double temp = 0.0;

    for(i=1;i<=n;i++)
        perp[i]=v[i]/w[i]; //計算單位價值(單位重量的物品價值)

    for(i=1;i<=n-1;i++)
    {
        for(j=i+1;j<=n;j++)
            if(perp[i]<perp[j])//冒泡排序perp[],order[],sortv[],sortw[]
	        {
	            temp = perp[i];  //冒泡對perp[]排序
	            perp[i]=perp[j];
	            perp[j]=temp;
	 
	            temporder=order[i];//冒泡對order[]排序
	            order[i]=order[j];
	            order[j]=temporder;
	 
	            temp = v[i];//冒泡對v[]排序
	            v[i]=v[j];
	            v[j]=temp;
	 
	            temp=w[i];//冒泡對w[]排序
	            w[i]=w[j];
	            w[j]=temp;
	        }
    }
}

//回溯函數
void backtrack(int i)
{   //i用來指示到達的層數(第幾步,從0開始),同時也指示當前選擇了幾個物品
    double bound(int i);
    if(i>n) //遞歸結束的判定條件
    {
        bestp = cp;
        return;
    }
    //如若左子節點可行,則直接搜索左子樹;
    //對於右子樹,先計算上界函數,以判斷是否將其減去
    if(cw+w[i]<=c)//將物品i放入揹包,搜索左子樹
    {
        cw+=w[i];//同步更新當前揹包的重量
        cp+=v[i];//同步更新當前揹包的總價值
        put[i]=1;
        backtrack(i+1);//深度搜索進入下一層
        cw-=w[i];//回溯復原
        cp-=v[i];//回溯復原
    }
    if(bound(i+1)>bestp)//如若符合條件則搜索右子樹
        backtrack(i+1);
}
 
//計算上界函數,功能爲剪枝
double bound(int i)
{   //判斷當前揹包的總價值cp+剩餘容量可容納的最大價值<=當前最優價值
    double leftw= c-cw;//剩餘揹包容量
    double b = cp;//記錄當前揹包的總價值cp,最後求上界
    //以物品單位重量價值遞減次序裝入物品
    while(i<=n && w[i]<=leftw)
    {
        leftw-=w[i];
        b+=v[i];
        i++;
    }
    //裝滿揹包
    if(i<=n)
        b+=v[i]/w[i]*leftw;
    return b;//返回計算出的上界
}
 
int main()
{
    int i;
    printf("請輸入物品的數量和揹包的容量:");
    scanf("%d %lf",&n,&c);
    /*printf("請輸入物品的重量和價值:\n");
    for(i=1;i<=n;i++)
    {
        printf("第%d個物品的重量:",i);
        scanf("%lf",&w[i]);
        printf("第%d個物品的價值是:",i);
        scanf("%lf",&v[i]);
        order[i]=i;
    }*/
    printf("請依次輸入%d個物品的重量:\n",n);
    for(i=1;i<=n;i++)
	{
        scanf("%lf",&w[i]);
        order[i]=i;
    }
 
    printf("請依次輸入%d個物品的價值:\n",n);
    for(i=1;i<=n;i++){
        scanf("%lf",&v[i]);
    }
 
 
    knapsack();
    backtrack(1);
 
    printf("最優價值爲:%lf\n",bestp);
    
    printf("\n需要裝入的物品編號是:");
    for(i=1;i<=n;i++)
    {
        if(put[i]==1)
            printf("%d ",order[i]);
    }
    
    return 0;
}

6.最大團問題


給定無向圖G=(V, E),U是V的子集。如果對任意u,v屬於U有(u,v)屬於E,則稱U是G的完全子圖。G的完全子圖U是G的團當且僅當U不包含在G的更大的完全子圖中。G的最大團是指G中所含頂點數最多的團。如果對任意u,v屬於U有(u, v)不屬於E,則稱U是G的空子圖。G的空子圖U是G的獨立集當且僅當U不包含在G的更大的空子圖中。G的最大獨立集是G中所含頂點數最多的獨立集。G的補圖G'=(V', E')定義爲V'=V且(u, v)屬於E'當且僅當(u, v)不屬於E。
如圖所示,給定無向圖G={V, E},其中V={1,2,3,4,5},E={(1,2),(1,4),(1,5),(2,3),(2,5),(3,5),(4,5)}。根據最大團定義,子集{1,2}是圖G的一個大小爲2的完全子圖,但不是一個團,因爲它包含於G的更大的完全子圖{1,2,5}之中。{1,2,5}是G的一個最大團。{1,4,5}和{2,3,5}也是G的最大團。右側圖是無向圖G的補圖G'。根據最大獨立集定義,{2,4}是G的一個空子圖,同時也是G的一個最大獨立集。雖然{1,2}也是G'的空子圖,但它不是G'的獨立集,因爲它包含在G'的空子圖{1,2,5}中。{1,2,5}是G'的最大獨立集。{1,4,5}和{2,3,5}也是G'的最大獨立集。

最大團問題可以用回溯法在O(n2^n)的時間內解決。首先設最大團爲一個空團,往其中加入一個頂點,然後依次考慮每個頂點,查看該頂點加入團之後仍然構成一個團。程序中採用了一個比較簡單的剪枝策略,即如果剩餘未考慮的頂點數加上團中頂點數不大於當前解的頂點數,可停止回溯。用鄰接矩陣表示圖G,n爲G的頂點數,cn存儲當前團的頂點數,bestn存儲最大團的頂點數。當cn+n-i<bestn時,不能找到更大的團,利用剪枝函數剪去。

#include<iostream>
using namespace std;
const int maxnum=101;
bool graph[maxnum][maxnum];
bool use[maxnum],bestuse[maxnum]; 
int cn,bestn,v,e;
 
void backtrack(int i)
{
    if(i>v)
    {
    	if(cn>bestn)
    	{
        	bestn=cn;
        	for(int j=1;j<=v;j++) bestuse[j]=use[j];
        	return;
    	}
    }
    bool flag=true;
    for(int j=1;j<i;j++)
    {
    	if(use[j]&&!graph[j][i])
        {
            flag=false;
            break;
        }
    }
    if(flag)
    {
        cn++;
        use[i]=true;
        backtrack(i+1);
        use[i]=false;
        cn--;
    }
    if(cn+v-i>bestn)  
    {
        use[i]=false;
        backtrack(i+1);
    }
}
 
int main()
{
    cin>>v>>e;
    for(int i=1;i<=e;i++)
    {
    	int p1,p2;
    	cin>>p1>>p2;
  		graph[p1][p2]=true;
  		graph[p2][p1]=true;
    }
    backtrack(1);
    cout<<bestn<<endl;
    for(int i=1;i<=v;i++) 
	{
		if(bestuse[i]) cout<<i<<" ";
	}
    cout<<endl;  
}

7.圓排列問題


給定n個大小不等的圓c1,c2,…,cn,現要將這n個圓排進一個矩形框中,且要求各圓與矩形框的底邊相切。圓排列問題要求從n個圓的所有排列中找出有最小長度的圓排列。例如,當n=3,且所給的3個圓的半徑分別爲1,1,2時,這3個圓的最小長度的圓排列如圖所示。其最小長度爲2+4\sqrt{2}

注意,下面代碼中圓排列的圓心橫座標以第一個圓的圓心爲原點。所以,總長度爲第一個圓的半徑+最後一個圓的半徑+最後一個圓的橫座標。

#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
float minlen=10000,x[4],r[4];//當前最優值,當前圓排列圓心橫座標,當前圓排列
int n;//圓排列中圓的個數
 
//計算當前所選擇圓的圓心橫座標
float center(int t)
{
    float temp=0;
	for(int j=1;j<t;j++)
	{
		//由x^2=sqrt((r1+r2)^2-(r1-r2)^2)推導而來
        float valuex=x[j]+2.0*sqrt(r[t]*r[j]);
		if(valuex>temp) temp=valuex;
    }
    return temp;
}
 
//計算當前圓排列的長度
void compute()
{
    float low=0,high=0;
	for(int i=1;i<=n;i++)
	{
		if(x[i]-r[i]<low) low=x[i]-r[i];
        if(x[i]+r[i]>high) high=x[i]+r[i];
    }
    if(high-low<minlen) minlen=high-low;
}
 
void backtrack(int t)
{
    if(t>n) compute();
    else
	{
		for(int j=t;j<=n;j++)
		{
			swap(r[t],r[j]);
			float centerx=center(t);
			if(centerx+r[t]+r[1]<minlen)
			{
				x[t]=centerx;
				backtrack(t+1);
			}
			swap(r[t],r[j]);
		}
	}
}
 
int main()
{
	n=3; 
	r[1]=1,r[2]=1,r[3]=2;
	cout<<"各圓的半徑分別爲:"<<endl;
	for(int i=1;i<=3;i++) cout<<r[i]<<" ";
	cout<<endl;
	cout<<"最小圓排列長度爲:";
	backtrack(1);
	cout<<minlen<<endl;
}

上述算法尚有許多改進的餘地。例如,像1,2,…,n-1,n和n,n-1, …,2,1這種互爲鏡像的排列具有相同的圓排列長度,只計算一個就夠了。而且,如果所給的n個圓中有k個圓有相同的半徑,則這k個圓產生的k!個完全相同的圓排列,也只需要計算一個。 


8.連續郵資問題


假設國家發行了k種不同面值的郵票,並且規定每張信封上最多隻允許貼h張郵票。連續郵資問題要求對於給定的k和h的值,給出郵票面值的最佳設計,在1張信封上可貼出從郵資1開始,增量爲1的最大連續郵資區間。例如,當k=5和h=4時,面值爲(1,3,11,15,32)的5種郵票可以貼出郵資的最大連續郵資區間是1到70。UVA165就是一道這樣的典型例題。用stampval來保存各個面值,用maxval來保存當前所有面值能組成的最大連續面值。那麼,stampval[0] 一定等於1,因爲1是最小的正整數。相應的,maxval[0]=1*h。接下去就是確定第二個,第三個......第k個郵票的面值了。對於stampval[i+1],它的取值範圍是stampval[i]+1~maxval[i]+1。 stampval[i]+1是因爲這一次取的面值肯定要比上一次的面值大,而這次取的面值的上限是上次能達到的最大連續面值+1, 是因爲如果比這個更大的話, 那麼就會出現斷層, 即無法組成上次最大面值+1這個數了。 舉個例子, 假設可以貼3張郵票,有3種面值,前面2種面值已經確定爲1,2, 能達到的最大連續面值爲6, 那麼接下去第3種面值的取值範圍爲3~7。如果取得比7更大的話會怎樣呢? 動手算下就知道了,假設取8的話, 那麼面值爲1,2,8,將無法組合出7。直接遞歸回溯所有情況, 便可知道最大連續值了。
 

#include<cstdio>  
#include<cstdlib>  
#include<cstring>   
#include<iostream>  
#define MAXN 200  
using namespace std;  
int h,k,ans[MAXN],stampval[MAXN],maxval[MAXN],maxstampval;  
bool vis[MAXN];  
 
//標記每種取到的錢數 
void mark(int n,int m,int sum)
{  
    if(m>h) return;  
	vis[sum]=true;
    for(int i=1;i<=n;++i) mark(n,m+1,sum+stampval[i]);    
}  
  
void backtrack(int cur)
{  
    if(cur>k)
	{  
        if(maxval[cur-1]>maxstampval)
		{  
            maxstampval=maxval[cur-1];  
            memcpy(ans,stampval,sizeof(stampval));  
        }  
        return;  
    }  
    for(int i=stampval[cur-1]+1;i<=maxval[cur-1]+1;++i)
	{  
        memset(vis,0,sizeof(vis));  
        stampval[cur]=i;  
        mark(cur,0,0);  
        int num=0,j=1;  
        while(vis[j++]) ++num;  
        maxval[cur]=num;  
        backtrack(cur+1);  
    }  
}  
  
int main()
{   
	while(scanf("%d %d",&h,&k),h+k)
	{  
		maxval[1]=h;  
        stampval[1]=1;  
        maxstampval=-1;  
        backtrack(2);  
        for(int i=1;i<=k;++i) printf("%3d",ans[i]);  
        printf("->%3d\n",maxstampval);  
    }    
} 

直接遞歸的求解複雜度太高,不妨嘗試計算用不超過m張面值爲x[1:i]的郵票貼出郵資k所需的最少郵票數y[k]。通過y[k]可以很快推出r的值。事實上,y[k]可以通過遞推在O(n)時間內解決。這裏就不再講解了。


9.符號三角形問題


下圖是由14個“+”和14個“-”組成的符號三角形,第一行有n個符號。2個同號下面都是“+”,2個異號下面都是“-”。

符號三角形問題要求對於給定的n,計算有多少個不同的符號三角形,使其所含的“+”和“-”的個數相同。在第1行前i個符號x[1:i]確定後,就確定了1個由i(i+1)/2個符號組成的三角形。下一步確定第i+1個符號後,在右邊再加1條邊,就可以擴展爲前i+1個符號x[1:i+1]對應的新三角形。這樣依次擴展,直到x[1:n]。最終由x[1:n]所確定的符號三角形中含"+"號個數與"-"個數同爲n(n+1)/4。因此,當前符號三角形所包含的“+”個數與“-”個數均不超過n*(n+1)/4,可以利用這個條件剪支。對於給定的n,當n*(n+1)/2爲奇數時,顯然不存在包含的"+"號個數與"-"號個數相同的符號三角形。在回溯前需要簡單的判斷一下。
 

#include<iostream>  
using namespace std;   
int n,half,counts,p[100][100],sum;
//第一行的符號個數,n*(n+1)/4,當前"+"號個數,符號三角矩陣,已找到的符號三角形數               
  
void backtrack(int t)  
{  
    if((counts>half)||(t*(t-1)/2-counts>half)) return;  
    if(t>n) sum++;    
    else  
    {  
		for(int i=0;i<2;i++)   
        {  
            p[1][t]=i;//第一行符號  
            counts+=i;//當前"+"號個數  
            for(int j=2;j<=t;j++)   
            {  
                p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];  
                counts+=p[j][t-j+1];  
            }  
            backtrack(t+1);  
            for(int j=2;j<=t;j++)  
            {  
                counts-=p[j][t-j+1];  
            }  
            counts-=i;  
        }  
    }  
}   
 
int main()  
{
	cin>>n;  	
	half=n*(n+1)/2;  
    if(half%2==1)
    {
		cout<<"共有0個不同的符號三角形。"<<endl;
		return 0;
    }
    half=half/2;   
    backtrack(1);  
    cout<<"共有"<<sum<<"個不同的符號三角形。"<<endl;
} 

10.集合劃分問題


給定一個圖,圖中任意兩點的距離已知,請你把這個圖的所有的點分成兩個子集,要求兩個子集之間的所有點的距離和最大。對於圖中的每一個點,我們可以設一個數組,用0和1表示屬於哪個子集。

#include<iostream> 
using namespace std;   
int graph[25][25]; 
int set[25]; 
int ans,n;
    
void backtrack(int x,int sum) 
{  
    int temp;
    if(x>n) 
    {  
        if(sum>ans) ans=sum; 
        return; 
    } 
    //不選
    set[x]=0;
    temp=0;
    for(int i=1;i<=x;i++) 
    { 
        if(!set[i]) continue;
        temp+=graph[i][x];
    } 
    backtrack(x+1,sum+temp); 
    //選
    set[x]=1;
    temp=0; 
    for(int i=1;i<=x;i++) 
    { 
        if(set[i]) continue; 
        temp+=graph[i][x];
    }   
    backtrack(x+1,sum+temp);   
}  
   
int main() 
{  
    cin>>n;  
    for(int i=1;i<=n;i++) 
    { 
        for(int j=1;j<=n;j++) 
        { 
            cin>>graph[i][j];  
        } 
    }   
    backtrack(1,0); 
    cout<<ans<<endl;
}

關於回溯算法的基礎知識就簡要介紹到這裏,希望能作爲大家繼續深入學習的基礎。

發佈了17 篇原創文章 · 獲贊 11 · 訪問量 2382
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章