實驗5 回溯法

承歡。發表於2020-12-09

一、實驗目的與要求:

1、通過回溯法的示例程式理解回溯法的基本思想;
2、運用回溯法解決實際問題進一步加深對回溯法的理解和運用;

二、實驗內容:

1、分析並掌握“符號三角” 問題的回溯法求解方法;
2、練習使用回溯法求解問題。

三、實驗步驟

1.理解回溯演算法思想和演算法示例;
2.上機輸入和除錯演算法示例程式;
3.理解實驗題的問題要求;
4.上機輸入和除錯自己所編的實驗題程式;
5.驗證並分析實驗題的實驗結果;
6.整理出實驗報告;

四、示例程式:符號三角形問題

符號三角問題:下面都是“-”。 下圖是由14個“+”和14個“-”組成的符號三角形。2個同號下面都是“+”,2個異號下面都是“-”。

+   +   -   +   -   +   +
   +   -   -   -   -   +
     -   +   +   +   -
       -   +   +   -
         -   +   -
           -   -
            +

在一般情況下,符號三角形的第一行有n個符號。符號三角形問題要求對於給定的n,計算有多少個不同的符號三角形,使其所含的“+”和“-”的個數相同。
參考程式碼如下,請在此基礎上,寫出主函式,實現如下功能:

  1. 分別輸出n的值為1----20時,對應的符號三角形個數,如沒有滿足條件的符號三角形,則輸出0
  2. 輸入一個整數n,輸出對應的符號三角形的個數,並依次顯示出所有的符號三角形。

執行程式碼以及個人註釋:


#include<iostream>
#include<cmath>
using namespace std;

class Triangle
{    
    //用於建立資料初始化
    //使用友元函式可以不受在類中宣告位置的影響
    friend int Compute(int n);
    private:
      //回溯函式的具體實現
      void Backtrack(int t);
      //n用來記錄存放的資料,count用來記錄‘-’出現的次數,而half用來一半的個數
      //用於剪枝操作
       int n, count, half;
       //用於建立二維陣列
      int **p;
      //用於統計三角形的個數
      int sum;
};

/**
 * 
 * 使用基本的回溯框架
 * t用於表示第一行元素中的第t個數
 */
void Triangle::Backtrack(int t)
{
   if((count>half)||(t*(t-1)/2-count>half))return;//剪去不滿足約束的子樹
   if(t>n)
    {   cout<<endl;
        sum++; //找到一個滿足要求的三角形
        for (int i=1;i<=n;i++)
        {   
            //用於對齊三角形
           for(int j=1; j<i; j++)
              cout<<" ";
           for(int j=1;j<=n-i+1;j++)
              if (p[i][j]==1)
                 cout<<'-'<<' ';
              else if (p[i][j]==0)
                 cout<<'+'<<' ';
           cout<<endl; 
        }
    } 
   else
   /**
    * 第一行中所有元素的狀態要麼為0,要麼為1
    * 即‘+’或‘-’
    * 所以  i要麼為1或為0
    * 
    */
    for(int i=0;i<2;i++)
    {//子樹
       p[1][t]=i;
       count+=i;
       /**
        * 在這裡用j來計算行
        */
       for(int j=2;j<=t;j++) //該子樹形成的三角形
       { 
            //例如p[1][1]=0,p[1][2]=0 經過異或運算可以的出p[2][1]=0 
          p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
          count+=p[j][t-j+1];
       }          
       Backtrack(t+1);
       for(int j=2;j<=t;j++) //回溯恢復
         count-=p[j][t-j+1];
       count-=i;
   }
}
int Compute(int n)
{
            Triangle X; 
            X.n=n;
            X.count=0;
            X.sum=0;
            /**
             * 計算half
             */
            X.half=n*(n+1)/2;
            if (X.half%2==1) return 0;
            X.half=X.half/2;

           /**
            *建立動態二維陣列的過程
            *如果這裡使用vector感覺會更加方便
            */
            int**p=new int*[n+1];
            for(int i=0;i<=n;i++)   
               p[i]=new int[n+1];   
            for(int i=0; i<=n;i++)
                 for(int j=0; j<=n;j++)
                    p[i][j]=0;
            X.p=p;

            /**
             * 為了方便直接,所以從1開始
             */
            X.Backtrack(1);

            /**
             * 回收空間
             */
            for(int i=0;i<=n;i++)   
               delete []p[i];   
            delete []p;
            p=0;
            return X.sum;
}
int main()
{
 Triangle MainName;
 int n;
 cin>>n;
 cout<<Compute(n)<<endl;
 system("pause");
 return 0;
}

執行結果:
在這裡插入圖片描述

五、實驗題

1.演算法實現題:整數變換問題。

整數i的兩種變換定義為 , (向下取整);設計一個演算法求給定兩個整數a和b,用最少次數的 和 變換將整數a變換為b;例如
實現提示:
觀察f和g兩個操作可知,f總是使得i變大,g總是使得i變小。因此在決定讓x執行哪個操作之前可以先判斷i和目標值m之間的大小關係。如果x>m,就讓其執行g操作;反之,執行f操作。
問題的解分為兩種情況,一種是有解,即n可以通過函式變換成m;另一種是無解,即n無法通過函式變換成m。
有解的情況比較容易,只需要判斷最後的i是否等於m即可。如果i等於m,那麼說明n已經被變換成m了,遞迴返回。
無解的情況可用下例分析。假設我們的輸入n=9,m=5。
n>m,執行g,n=[9/2]=4
n<m,執行f,n=3*4=12
n>m,執行g,n=[12/2]=6
n>m,執行f,n=[6/2]=3

n<m,執行g,n=3*3=9
n>m,執行f,n=[9/2]=4
如果n的值陷入了一個重複的迴圈,如果在遞迴的過程中,出現了前面計算過的元素,那就說明n是無法轉換成m的。這種方法實現稍微複雜,需要判斷當前所求出的數值之前是否出現過。 另一種簡單的處理方式: 對於m無論如何變換都不能變為n的情況,可以加一個判斷條件,比如深度達一個較大值為止(如1000)。

執行程式碼:

#include<iostream>
using namespace std;
int a,b;
int k,found;
int *p;

int f(int i)
 {
   return 3*i;
 }

int g(int i)
   {
   return i/2;
   }

//判斷是否存在,m代表層數,n表示需要變化的數

int changed(int m,int n)
 {
  int i,s;
  //終止條件
  if(m>k)return 0;
  for(i=0;i<2;i++)
{
  s=n;
 if(i==0)s=g(s);
 else s=f(s);
 p[m]=i;
 //回溯
 if(s==b||changed(m+1,s))
{
   found=1;
   return 1;
}
}
   return 0;
}
void compared()
{
 k=1;
 found=0;
 while(!changed(1,a))
{
  k++;
 if(k>100)break;
 if(found!=0)break;
}
}
int  main()
{
p=new int[100];

int i;

for(i=0;i<100;i++)p[i]=0;

cout<<"請依次輸入a和b的值: "<<endl;

cin>>a>>b;

compared();

if(found)

{
cout<<"運算次數為:"<<k<<endl;
cout<<"運算轉換過程為: ";

for(i=k;i>=1;i--)

{
if(p[i]==0)cout<<"g";

if(p[i]==1)cout<<"f";

}

}

else cout<<a<<" 無法轉換成 "<<b<<endl;

system("pause");
return 0;

}

2. 子集和問題。

問題描述:給定集合S,S中有n個正整數,M是一個正整數。子集和問題判定是否存在S的一個子集S1,使得S1中各元素之和等於M。請設計回溯法求解子集和問題,如果問題無解,輸出“No Solution”,問題有解,則輸出滿足子集S1中各元素的值。
子集樹問題:
執行程式碼:

#include<iostream>
using namespace std;
bool found=false;
int sum,tempSum;
int n,*p,*number;
void Backtrack(int t)
{
    if(t==n) 
    {
        if(sum==tempSum)
        {
          found=true;
          cout<<"子集為:";
          for(int i=0;i<n;i++)
           {
               if(p[i]) cout<<" "<<number[i];
           }
           cout<<endl;
        }
        return;
    }

    for(int i=0;i<2;i++)
    {
        p[t]=i;
        if(i==1) tempSum=tempSum+number[t];
        Backtrack(t+1);
        if(i==1) tempSum=tempSum-number[t];
    }
   
}
int main()
{
    cout<<"輸入陣列長度:";
    cin>>n;

    cout<<endl<<"輸入資料和:";
    cin>>sum;
    tempSum=0;
    number=new int [n];
    p=new int [n];
    cout<<endl<<"輸入陣列:";
    for(int i=0;i<n;i++)
     {
         p[i]=0;
         cin>>number[i];
     }
     Backtrack(0);
     if(!found) cout<<"No Solution! "<<endl;
     system("pause");
    return 0;
}

執行結果:
在這裡插入圖片描述

3. 工作分配問題。

問題描述:設有n件工作分配給n個人。將工作i分配給第j個人的費用為cij,請設計演算法,為每個人都分配1件不同的工作,並使得總費用達到最小。
實現提示:該問題的解空間是一棵排列樹,可用搜尋排列樹的回溯框架實現。
執行程式碼:

#include<iostream>
#include<string.h>
#define MaxSize 20
using namespace std;
int nowVal=0,minVal=10000;
int a[MaxSize][MaxSize];//二維陣列
int n,*work;//work陣列代表工作是否被佔領,初始化為0
void Input()
{
    int i,j;
    cin>>n;
    work=new int[n];
    for(i=0;i<n;i++) work[i]=0;
    for(i=0;i<n;i++)
      for(j=0;j<n;j++)
        cin>>a[i][j];

}
void BackTrack(int row)
{
    int col;
    if(row==n&&nowVal<minVal)
    {
     minVal=nowVal;
     return ;
    }
    else
    {
        for(col=0;col<n;col++)
        {
            if(work[col]==0)
            {
             work[col]=1;
             nowVal=nowVal+a[row][col];
             if(nowVal<minVal) BackTrack(row+1);//剪枝操作
             nowVal=nowVal-a[row][col];//回溯演算法實現
             work[col]=0;
            }
        }
        
    }
    
}

int main()
{
    Input();
    BackTrack(0);
    cout<<minVal<<endl;
    system("pause");
    return 0;
}

執行結果:
在這裡插入圖片描述

相關文章