バックトラッキングとサブセット和問題

1.解空間を探索

どのようなバックトラッキングが行うことは簡単です。これは単に、すべての可能な解決策を通過され、問題の、解空間、使用してDSF(ディープ検索ファースト)を、最適なものを見つけました。すべてのソリューションは、同じまたは他のものと異なるサイズとセットで書かれています。データ量が膨大な場合は特に、それはすべての可能な解決策を通過するようなアルゴリズムは、大きな時間の複雑さを持っているようです。しかし、それが実際に実行するよりも良い期待される方法の2種類が常に存在しているので、剪定機能バインドされた機能制約関数の不要なソリューションを評価するスキップするために使用さは、時間の複雑さを低減することができるように、。

サブセット和問題を解決する方法は、アルゴリズムを説明するために、次のセクションで導入されます。詳細な情報は、以下に示されている7.材料サブ集合であると仮定
[\ {2 \ 2 \ 6 \ 5 \ 4 \} \] \
とターゲット合計10基本的に我々のタスクは、から選択される数の和を作るために与えられたセット内の数字を選択することですセットには、ターゲット和に等しいです。


2.解空間とソリューションの木

2.1。解空間

すべてのソリューションバックトラックアルゴリズムでは、の形で表現されるセットそして、解決策の全ては、解空間を構築しますここで、サブセット和問題のために、解空間である:
\ [\ {0 \ 0 \ 0 \ 0 \ 0 \} \]
\ [\ {1 \ 0 \ 0 \ 0 \ 0 \} \]
\ [\ {0 \ 1 \ 0 \ 0 \ 0 \} \]
\ [\ vdots \]
\ [\ {1 \ 1 \ 1 \ 1 \ 0 \} \]
\ [\ {1 \ 1 \ 1 \ 1 \ 1 \} \]

番号が選択された場合にセット中の各桁が示します。選択するように設定で5つの数字があり、各数値は、または、選択された2つの状態であってもなくてもよい換言します。このような問題のための解決策の総数であります

\ [2 ^ 5 = 32 \]

2.2。ソリューションツリー

一般的に解空間をツリーとして表現されます。エッジツリーのは、意思決定を表していますセット内の指定された数が選択される場合ここでの問題のために各エッジを表します。それを選択することを選ぶかどうか:最初の数2のための2つの可能なアクションがあります。したがって、ツリー内の第一および第二の層との間に2つのエッジがあります。番号が選択されていることを左側の示し、番号を選択していない右側の意味の1のエッジを言ってみましょう。二つのエッジが存在するので、第二層内の2つのノードがあります。ノードツリーでは示しエッジで反射され、決定後の状態を行われます。ここでの問題のために左の子ノードは、現在の合計最初の番号を追加した後、最初の数が選択されないように右の子ノードは、第1層目のノードと同じステータスを格納します。


3. DFS

DFSは、木の上にiteraterに使用されています。したがって、このアルゴリズムは、再帰関数の形として表示されます。

void backTrack(int t) {
  if (currentSum == c) {
    some operations
  } else if (t > n) {
    return;
  } else {
    some operations
    }
  }
}

4.バックトラック

Let's say now we get a solution \(\{1, 0, 1, 0, 1\}\) by going though related edges in the tree with the process described above. The current solution may not be the optimal solution. We should compare it with the optimal solution, and if the current one is better, replace it with the original optimal one. The solution now, however, may not be the optimal one since we have not gone through the whole solution space or the whole tree. To make it we should reset the status to that of the parent node of the current node. This is called back tracking, and it's exactly where the name of the algorithm comes from. Below the image shows the process.

the code related to the back tracking will be like

currentSum += S[t];
numbers[t] = 1;  // numbers[] stores the status (whether a specified number is chosen)

backTrace(t + 1);  // chooses the number, and evaluates the left child

currentSum -= S[t];  // back tracking
numbers[t] = 0;  // back tracking

backTrace(t + 1);  // does not choose the number, and evaluate the right child


5. Subset Trees and Permutation Trees

There are generally two types of solution set tree. One is called subset tree and the other is called permutation tree. Solutions of a subset tree is about choosing some of the elements in the data set, meaning that some of the elements in the set can be abandoned, while those of a permutation tree is about reordering the elements in the data set to make the final result optimal, and all elements in the set should be kept.


6. Pruning Functions

There are two pruning functions used to avoid evaluating all solutions, enabling the algorithm to have a relatively good time complexity: the constraint function and the bound function.

6.1. Constraint Function

The constraint function is used to avoid evaluating solutions that don't satisfied the requirement of the problem.

For the problem here, when the current sum is greater than the target sum. we need not consider the remaining numbers in the set anymore.

Say that the current sum is 13 which comes from
\[\{1\ 0\ 1\ 1\ ?\}\]

? means that we have not decided if the number 4 should be chosen. Now since the current sum is greater than 10, we need not consider if 4 should be chosen. What's reflected in the tree is that we need not consider the sub trees taking the nodes storing the status after choosing 5 as their root.

So the code now is

void backTrack(int t) {
  if (currentSum == c) {
    some operations
  } else if (t > n) {
    return;
  } else {
    if (currentSum <= c) {
      currentSum += S[t];
      numbers[t] = 1;  // numbers[] stores the status (whether a specified number is chosen)

      backTrace(t + 1);  // chooses the number, and evaluates the left child

      currentSum -= S[t];  // back tracking
      numbers[t] = 0;  // back tracking
    }

    backTrace(t + 1);  // does not choose the number, and evaluate the right child
  }
}

the condition representing the constraint function can also be written as a real function

bool constraint() {
  if (currentSum) <= c) {
    return true;
  } else {
    return false;
  }
}

and the code will be like

void backTrack(int t) {
  if (currentSum == c) {
    some operations
  } else if (t > n) {
    return;
  } else {
    if (constraint()) {
      currentSum += S[t];
      numbers[t] = 1;  // numbers[] stores the status (whether a specified number is chosen)

      backTrace(t + 1);  // chooses the number, and evaluates the left child

      currentSum -= S[t];  // back tracking
      numbers[t] = 0;  // back tracking
    }

    backTrace(t + 1);  // does not choose the number, and evaluate the right child
  }
}

6.2. Bound function

The bound function is used to avoid considering solutions that when their leaves, reflected in the tree, are reached, meaning that the solutions are completely constructed, the solutions will not be the ones we want..

For the problem here let's say we've chosen some numbers and add them up. And if the sum is less than the target sum even after we add the remaining numbers to the current number, we need not consider the related solutions.

Say we now have a current sum of 0 which comes from
\[\{0\ 0\ 0\ ?\ ?\}\]

And the sum will be 9 after we add up all of the remaining numbers, 5 and 4 here. Since it is less than the target sum, we need not consider the solutions
\[\{0\ 0\ 0\ 0\ 0\}\]
\[\{0\ 0\ 0\ 0\ 1\}\]
\[\{0\ 0\ 0\ 1\ 0\}\]
\[\{0\ 0\ 0\ 1\ 1\}\]

What's reflected in the tree is that we need should consider the sub trees whose parent is the node storing the status after not choosing 6.

the bound function will be like

int bound(int t) {
  int bound = 0;
  for (int i = t; i <= n; i++) {
    bound += numbers[i];  // numbers[] stores given numbers
  }

  return bound;
}

and the code now is

void backTrack(int t) {
  if (currentSum == c) {
    some operations
  } else if (t > n) {
    return;
  } else {
    if (constraint()) {
      currentSum += S[t];
      numbers[t] = 1;  // numbers[] stores the status (whether a specified number is chosen)

      backTrace(t + 1);  // chooses the number, and evaluates the left child

      currentSum -= S[t];  // back tracking
      numbers[t] = 0;  // back tracking
    }

    if (currentSum + bound(t) >= c) {
      backTrace(t + 1);  // does not choose the number, and evaluate the right child
    }
  }
}

7. General Structure

According to the analysis above it's not hard to work out the general structure of the algorithm, as follows

void backTrack(int t) {  // t: the node keeping status after making some decisions
  if (t > n) {  // n: number of the data set
    if (the current solution is better than the original one) {
      assign the current solution to the original one or
      perform some other operations.
    } else {
      for (int i = 1; i <= n; i++) {  // considers all possible status of the current value
        if (constraint(t) && bound(t)) {
          back tracking

          backTrack(t + 1);

          back tracking
        }
      }
    }
  }
}

8. Pair Work

The first question of the practice this time is the 0-1 knap sack problem. A strict bound function should be used to decrease the time complexity, or the running time will be longer than required. We worked for a pretty long time to solve it and finally found there was a mistake in the condition of an if statement evaluating if the bound is satisfied. The second problem can be solved using a permutation tree. I'm not pretty familiar with this kind of tree and Yang Yizhou found the way to solve.


9. Materials


references:
[1] 王晓东. 计算机算法设计与分析. 北京: 电子工业出版社, 2018.

おすすめ

転載: www.cnblogs.com/Chunngai/p/12067033.html