回溯法—子集樹與排列樹


    回溯法有“通用解題法”之稱。用它可以系統地搜索問題的所有解。回溯法是一個既帶有系統性又帶有跳躍性的搜索算法。

    在包含問題的所有解的解空間樹中,按照深度優先搜索的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,如果包含,就從該結點出發繼續探索下去,如果該結點不包含問題的解,則逐層向其祖先結點回溯。(其實回溯法就是對隱式圖的深度優先搜索算法)。若用回溯法求問題的所有解時,要回溯到根,且根結點的所有可行的子樹都要已被搜索遍才結束。 而若使用回溯法求任一個解時,只要搜索到問題的一個解就可以結束。

 

1.回溯法的解題步驟

(1)針對所給問題,定義問題的解空間;

(2)確定易於搜索的解空間結構;

(3)以深度優先方式搜索解空間,並在搜索過程中用剪枝函數避免無效搜索。

 

2.子集樹與排列樹

下面的兩棵解空間樹是回溯法解題時常遇到的兩類典型的解空間樹。

(1)當所給問題是從n個元素的集合S中找出S滿足某種性質的子集時,相應的解空間樹稱為子集樹。例如從n個物品的0-1背包問題(如下圖)所相應的解空間樹是一棵子集樹,這類子集樹通常有2^n個葉結點,其結點總個數為2^(n+1)-1。遍歷子集樹的算法需Ω(2^n)計算時間。


(2)當所給問題是確定n個元素滿足某種性質的排列時,相應的解空間樹稱為排列樹。例如旅行售貨員問題(如下圖)的解空間樹是一棵排列樹,這類排列樹通常有n!個葉結點。遍歷子集樹的算法需Ω(n!)計算時間。



用回溯法搜索子集樹的一般算法可描述為:


   
   
  
  
          
  1. /**
  2. * output(x) 記錄或輸出得到的可行解x
  3. * constraint(t) 當前結點的約束函數
  4. * bount(t) 當前結點的限界函數
  5. * @param t t為當前解空間的層數
  6. */
  7. void backtrack(int t){
  8. if(t >= n)
  9. output(x);
  10. else
  11. for ( int i = 0; i <= 1; i++) {
  12. x[t] = i;
  13. if(constraint(t) && bount(t))
  14. backtrack(t+ 1);
  15. }
  16. }

用回溯法搜索排列樹的一般算法可描述為:


   
   
  
  
          
  1. /**
  2. * output(x) 記錄或輸出得到的可行解x
  3. * constraint(t) 當前結點的約束函數
  4. * bount(t) 當前結點的限界函數
  5. * @param t t為當前解空間的層數
  6. */
  7. void backtrack(int t){
  8. if(t >= n)
  9. output(x);
  10. else
  11. for ( int i = t; i <= n; i++) {
  12. swap(x[t], x[i]);
  13. if(constraint(t) && bount(t))
  14. backtrack(t+ 1);
  15. swap(x[t], x[i]);
  16. }
  17. }

3.回溯法的應用例子

(a)子集樹

(為了便於描述算法,下列方法使用了較多的全局變量

I.輸出集合S中所有的子集,即limit為all;

II.輸出集合S中限定元素數量的子集,即limit為num

III.輸出集合S中元素奇偶性相同的子集,即limit為sp。


   
   
  
  
          
  1. public class Subset {
  2. private static int[] s = { 1, 2, 3, 4, 5, 6, 7, 8};
  3. private static int n = s.length;
  4. private static int[] x = new int[n];
  5. /**
  6. * 輸出集合的子集
  7. * @param limit 決定選出特定條件的子集
  8. * 注:all為所有子集,num為限定元素數量的子集,
  9. * sp為限定元素奇偶性相同,且和小於8。
  10. */
  11. public static void all_subset(String limit){
  12. switch(limit){
  13. case "all":backtrack( 0); break;
  14. case "num":backtrack1( 0); break;
  15. case "sp":backtrack2( 0); break;
  16. }
  17. }
  18. /**
  19. * 回溯法求集合的所有子集,依次遞歸
  20. * 注:是否回溯的條件為精髓
  21. * @param t
  22. */
  23. private static void backtrack(int t){
  24. if(t >= n)
  25. output(x);
  26. else
  27. for ( int i = 0; i <= 1; i++) {
  28. x[t] = i;
  29. backtrack(t+ 1);
  30. }
  31. }
  32. /**
  33. * 回溯法求集合的所有(元素個數小於4)的子集,依次遞歸
  34. * @param t
  35. */
  36. private static void backtrack1(int t){
  37. if(t >= n)
  38. output(x);
  39. else
  40. for ( int i = 0; i <= 1; i++) {
  41. x[t] = i;
  42. if(count(x, t) < 4)
  43. backtrack1(t+ 1);
  44. }
  45. }
  46. /**
  47. * (剪枝)
  48. * 限制條件:子集元素小於4,判斷0~t之間已被選中的元素個數,
  49. * 因為此時t之后的元素還未被遞歸,即決定之后的元素
  50. * 是否應該被遞歸調用
  51. * @param x
  52. * @param t
  53. * @return
  54. */
  55. private static int count(int[] x, int t) {
  56. int num = 0;
  57. for ( int i = 0; i <= t; i++) {
  58. if(x[i] == 1){
  59. num++;
  60. }
  61. }
  62. return num;
  63. }
  64. /**
  65. * 回溯法求集合中元素奇偶性相同,且和小於8的子集,依次遞歸
  66. * @param t
  67. */
  68. private static void backtrack2(int t){
  69. if(t >= n)
  70. output(x);
  71. else
  72. for ( int i = 0; i <= 1; i++) {
  73. x[t] = i;
  74. if(legal(x, t))
  75. backtrack2(t+ 1);
  76. }
  77. }
  78. /**
  79. * 對子集中元素奇偶性進行判斷,還需元素的數組和小於8
  80. * @param x
  81. * @param t
  82. * @return
  83. */
  84. private static boolean legal(int[] x, int t) {
  85. boolean bRet = true; //判斷是否需要剪枝
  86. int part = 0; //奇偶性判斷的基准
  87. for ( int i = 0; i <= t; i++) { //選擇第一個元素作為奇偶性判斷的基准
  88. if(x[i] == 1){
  89. part = i;
  90. break;
  91. }
  92. }
  93. for ( int i = 0; i <= t; i++) {
  94. if(x[i] == 1){
  95. bRet &= ((s[part] - s[i]) % 2 == 0);
  96. }
  97. }
  98. int sum = 0;
  99. for( int i = 0; i <= t; i++){
  100. if(x[i] == 1)
  101. sum += s[i];
  102. }
  103. bRet &= (sum < 8);
  104. return bRet;
  105. }
  106. /**
  107. * 子集輸出函數
  108. * @param x
  109. */
  110. private static void output(int[] x) {
  111. for ( int i = 0; i < x.length; i++) {
  112. if(x[i] == 1){
  113. System.out.print(s[i]);
  114. }
  115. }
  116. System.out.println();
  117. }
  118. }


(b) 排列樹

(為了便於描述算法,下列方法使用了較多的全局變量)

I.輸出集合S中所有的排列,即limit為all;

II.輸出集合S中元素奇偶性相間的排列,即limit為sp。


   
   
  
  
          
  1. public class Permutation {
  2. private static int[] s = { 1, 2, 3, 4, 5, 6, 7, 8};
  3. private static int n = s.length;
  4. private static int[] x = new int[n];
  5. /**
  6. * 輸出集合的排列
  7. * @param limit 決定選出特定條件的子集
  8. * 注:all為所有排列,sp為限定元素奇偶性相間。
  9. */
  10. public static void all_permutation(String limit){
  11. switch(limit){
  12. case "all":backtrack( 0); break;
  13. case "sp":backtrack1( 0); break;
  14. }
  15. }
  16. /**
  17. * 回溯法求集合的所有排列,依次遞歸
  18. * 注:是否回溯的條件為精髓
  19. * @param t
  20. */
  21. private static void backtrack(int t){
  22. if(t >= n)
  23. output(s);
  24. else
  25. for ( int i = t; i < n; i++) {
  26. swap(i, t, s);
  27. backtrack(t+ 1);
  28. swap(i, t, s);
  29. }
  30. }
  31. /**
  32. * 回溯法求集合中元素奇偶性相間的排列,依次遞歸
  33. * @param t
  34. */
  35. private static void backtrack1(int t){
  36. if(t >= n)
  37. output(s);
  38. else
  39. for ( int i = t; i < n; i++) {
  40. swap(i, t, s);
  41. if(legal(x, t))
  42. backtrack1(t+ 1);
  43. swap(i, t, s);
  44. }
  45. }
  46. /**
  47. * 對子集中元素奇偶性進行判斷
  48. * @param x
  49. * @param t
  50. * @return
  51. */
  52. private static boolean legal(int[] x, int t) {
  53. boolean bRet = true; //判斷是否需要剪枝
  54. //奇偶相間,即每隔一個數判斷奇偶相同
  55. for ( int i = 0; i < t - 2; i++) {
  56. bRet &= ((s[i+ 2] - s[i]) % 2 == 0);
  57. }
  58. return bRet;
  59. }
  60. /**
  61. * 元素交換
  62. * @param i
  63. * @param j
  64. */
  65. private static void swap(int i, int j,int[] s) {
  66. int tmp = s[i];
  67. s[i] = s[j];
  68. s[j] = tmp;
  69. }
  70. /**
  71. * 子集輸出函數
  72. * @param x
  73. */
  74. private static void output(int[] s) {
  75. for ( int i = 0; i < s.length; i++) {
  76. System.out.print(s[i]);
  77. }
  78. System.out.println();
  79. }
  80. }


參考文獻:

1. 《算法設計與分析



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM