回溯算法

回溯法

回溯法的算法框架

1. 綜述

  • 從問題的 解空間樹 中,按照 深度優先 的策略,從根節點出發搜索解空間樹。
  • 回溯法求所有解時,最終需要回溯到根,并且所有節點的字數都已被搜索遍才結束。求一個解時,遇到一個解便可以結束。
  • 回溯法適用于組合數較大的問題。

2. 解空間

  • 解空間應該至少包含問題的一個解
  • 解空間應該很好地組織起來,通常組織成樹或者圖

3. 基本思想

  • 活結點、擴展結點、死結點
  • 約束函數剪去不滿足約束條件的子樹
  • 限界函數剪去得不到最優解的子樹
  • 基本步驟
  1. 針對所給的問題,定義問題的解空間;
  2. 確定易于搜索的解空間;
  3. 以深度優先方式搜索解空間,并在搜索的過程中用剪枝函數避免無效搜索。

4. 遞歸回溯

/*
  t:遞歸深度
  n:最大深度
  f(n,t):當前擴展結點處未搜索過的子樹的起始編碼
  g(n,t):當前擴展結點處未搜索過的子樹的終止編碼
  Constraint(t):約束函數
  Bound(t):限界函數


  自頂向下,
  對每個結點的分支進行遞歸調用  for(int i=f(n,t);i<=g(n,t);i++)
*/
void Backtrack(int t)
{
  if(t > n) Output(x);  //是否遞歸結束
  else
  {
    for(int i=f(n,t);i<=g(n,t);i++)  //保證所有子樹要不被遍歷,要么被剪枝
    {
      t=i;
      if(Constraint(t)&&Bound(t)) Backtrack(i+1);
    }
  }
}

5. 迭代回溯

/*
自頂向下,
對每個結點的分支進行迭代  for(int i=f(n,t);i<=g(n,t);i++)
*/
void IterativeBacktrack(void)
{
  int t=1;
  while(t > 0)
  {
    if(f(n,t) <= g(n,t))
    {
      for(int i=f(n,t);i<=g(n,t);i++)
      {
        t=i;
        if(Constraint(t)&&Bound(t))
        {
          if(Solution(t)) Output(x); //Solution(t)用于判斷問題是都得以解決
          else t++;
        }
        else t--;
      }
    }
  }
}

6. 子集樹

從結合S中尋找滿足某種性質的子集時,相應的解空間樹稱為子集樹,如0-1背包問題。
子集樹一般為完全二叉樹,也就是由“要、不要、要、不要等”形成。

void Backtrack(int t)
{
  if(t > n) Output(x);  //是否遞歸結束
  else
  {
    for(int i=0;i<=1;i++)  //保證所有子樹要不被遍歷,要么被剪枝
    {
      t=i;
      if(Constraint(t)&&Bound(t)) Backtrack(i+1);
    }
  }
}

7. 排列樹

確定n個元素滿足某種性質的排列時,相應的解空間樹稱為排列樹。排列樹通常有n!個葉結點。例如旅行售貨員問題。

void Backtrack(int t)
{
  if(t > n) Output(x);
  else
  {
    for(int i=t;i<=n;i++)
    {
      Swap(x[t],x[i]);
      if(Constraint(t)&&Bound(t)) Backtrack(i+1);
      Swap(x[i],x[t]);
    }
  }
}

貨箱裝載

1. 問題描述

兩艘船,n個貨箱。第一艘載重量c1,第二艘載重量c2。wi是貨箱i的重量,∑wi<=c1+c2。確定一種方法把n個貨箱全部裝上船。
∑wi<=c1=c2,原問題等價于子集之和問題;c1=c2,原問題等價于分割問題。這兩個問題都是NP-復雜問題。
解決辦法 :盡可能將第一艘船轉載到它的轉載極限,在將剩余的裝載到第二艘。
為了將第一艘船盡可能裝滿,需要一個貨箱的子集,使得他們的總重量接近于c1。這個問題可以通過0/1背包問題來解決。

2. 遞歸回溯算法

屬于上述的子集樹解決辦法。

/*
貨箱重量weight[1:numberOfContainers]
rLoad(1):返回<=capacity的最大子集之和
*/
void rLoad(int currentLevel)
{
  //從currentLevel處的節點開始搜索
  if(currentLevel > numberOfContainers)
  {
    //到達一個葉節點處
    if(weightOfCurrentLoading > maxWeightSoFar)
    maxWeightSoFar = weightOfCurrentLoading;
    return;
  }
  //還未到達葉節點,檢查子樹
  if(weightOfCurrentLoading + weight[currentLevel] <= capacity)
  {
    //搜索左子樹,即x[currentLevel]=1
    weightOfCurrentLoading += weight[currentLevel];
    rLoad(currentLevel + 1);
    weightOfCurrentLoading -= weight[currentLevel];
  }
  //搜索左子樹,即x[currentLevel]=0,既然為0那么可以無需檢查而得以繼續
  rLoad(currentLevel + 1);
}

3. 尋找最優子集

增加代碼來尋找到當前的最優子集,為此使用一組數組bestLoadingSoFar,當且僅當bestLoadingSoFar[i]=1時,貨箱i屬于最優子集。

/*
報告最有裝載的預處理程序
*/
int maxLoading(int *theWeight, int theNumberOfContainers, int theCapacity, int *bestLoading)
{
  /*
  數組theWeight[1:theNumberOfContainers]是貨箱重量
  theCapacity是船的載貨量
  數組bestLoading[1:theNumberOfContainers]是解
  返回最大載重量
  */
  //初始化全局變量
  numberOfContainers = theNumberOfContainers;
  weight =theWeight;
  capacity = theCapacity;
  weightOfCurrentLoading = 0;
  maxWeightSoFar = 0;
  currentLoading = new int [numberOfContainers+1];
  bestLoadingSoFar = bestLoading;

  //remainingWeight的初始值是所有貨箱重量之和
  for(int i=1;i<=numberOfContainers;i++)
  {
    remainingWeight += weight[i];
  }

  //計算最優裝載的重量
  rLoad(1);
  return maxWeightSoFar;
}
/*
報告最優裝載的回溯算法
*/
void rLoad(int currentLevel)
{
  //從currentLevel處開始搜索
  if(currentLevel > numberOfContainers)
  {
    //到達了一個葉節點,存儲一個更優解
    for(int j=1; j <= numberOfContainers; j++)
      bestLoadingSoFar[j] = currentLoading[j];
    maxWeightSoFar = weightOfCurrentLoading;
    return;
  }

  //沒有到達一個葉節點,檢查子樹
  remainingWeight -= weight[currentLevel];
  if(weightOfCurrentLoading + weight[currentLevel] <= capacity)
  {
    //搜索左子樹
    currentLoading[currentLevel] = 1;
    weightOfCurrentLoading += weight[currentLevel];
    rLoad(currentLevel + 1);
    weightOfCurrentLoading -= weight[currentLevel];
  }

  if(weightOfCurrentLoading + remainingWeight > maxWeightSoFar)
  {
    //搜索右子樹
    rLoad(currentLevel + 1);
  }

  remainingWeight += weight[currentLevel];
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 目錄 1.回溯算法1.1 回溯算法簡介1.2 一般回溯方法 2.收費公路重建問題(通過考慮最大值策略,對可能性空間...
    王偵閱讀 12,772評論 0 3
  • 1.基本概念 回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件...
    RavenX閱讀 8,319評論 1 2
  • 引言:這道題目老師強調了肯定要考,所以只有硬著頭皮將其復習了;下面是自己學習回溯算法的學習,僅供參考;一:基本概念...
    cp_insist閱讀 8,650評論 4 3
  • 貪心算法 先來比較一下貪心算法和動態規劃 貪心算法是指在對問題求解時,總是做出在當前看來是最好的選擇,不考慮整體,...
    Moonsmile閱讀 2,813評論 0 1
  • 回溯算法 主要思想 回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。八皇后問題就是回...
    愛撒謊的男孩閱讀 1,110評論 0 3