回溯法
在問題的解空間樹中,按深度優先策略,根節點出發搜索解空間樹,算法搜索至解空間的任意節點時,先判斷該節點是否包含問題的解,若肯定不包含,則跳過對以該節點爲根的子樹的搜索,逐層向其祖先結點回溯。否則進入該子樹,繼續按根深度優先搜索。
子集樹和排列樹的區別
書上寫的
- 當所給問題是從n個元素的集合S中找出滿足某種性質的子集時,相應的解空間樹稱爲子集樹。子集樹的時間複雜度爲2^n
- 當所給的問題時確定n個元素滿足某種性質的排列時,相應的解空間樹成爲排列樹。排列樹的時間複雜度爲n!
我自己的概括
- 子集樹的思想爲深度優先算法,從根節點出發,不斷訪問符合條件的子節點,(如果不符合,則進行剪枝並訪問該結點的兄弟節點),當訪問到葉節點的時候,進行輸出或者計數等操作,然後對葉節點的兄弟結點進行訪問,直到訪問結束,再訪問葉節點的根節點的兄弟結點(回溯)。
- 排列樹也是從根節點觸發,不斷訪問符合條件的子節點,但是排列樹的t值和子集樹的t值不一樣,子集樹的t值可以代表每一個作業,而排列樹的t值僅僅可以代表作業的順序。
// n後問題
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#define M 100
int sum = 0;
int ok(int k,int *x) {
for (int j= 1; j < k; j++) {
if ((abs(k - j) == abs(x[j] - x[k]))||(x[j]==x[k])) {
return 0;
}
}
return 1;
}
void BackTrack(int t,int *x,int n) {
if (t > n) {
sum++;
for (int i = 1; i <= n; i++) {
printf("%d", x[i]);
}
printf("\n");
}
else
for (int i = 1; i <= n; i++) {
x[t] = i;
if (ok(t,x)) BackTrack(t + 1,x,n);
}
}
int main()
{
int n;
scanf("%d", &n);
int x[M];
for (int i = 0; i <= n; i++){
x[i] = 0;
}
BackTrack(1, x, n);
printf("%d", sum);
}
排列樹
排列樹是我利用了排列樹的思想,在子集樹的基礎上修改得來的
把排列樹改成子集樹,有幾點需要注意:
- 子集樹求得是集合,所以初始化集合的時候,把他們都賦值爲0,表示都沒有放東西,然後在回溯的時候,會將他們一一賦值。而排列樹求的是排列,所以我們要把需要被排列的值作爲初值賦給數組。
- 子集樹在從根節點到子節點的過程中,每個結點的取值都有n中可能性,我們爲了不重複,需要通過合法性判斷這個子集有沒有重複,而排列樹不需要這麼做,排列樹選完一個值之後,就會從剩下的值中繼續挑選,這樣我們就不需要判斷有沒有重複了。
// n後問題
//
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#define M 100
int sum = 0;
int ok(int k,int *x) {
for (int j= 1; j < k; j++) {
if ((abs(k - j) == abs(x[j] - x[k])) {
return 0;
}
}
return 1;
}
void swap(int* x, int t, int i) {
int q = x[t];
x[t] = x[i];
x[i] = q;
}
void BackTrack(int t,int *x,int n) {
if (t > n) {
sum++;
for (int i = 1; i <= n; i++) {
printf("%d", x[i]);
}
printf("\n");
}
else
for (int i = t; i <= n; i++) {
swap(x,t, i);
if (ok(t,x)) BackTrack(t + 1,x,n);
swap(x,t, i);
}
}
int main()
{
int n;
scanf("%d", &n);
int x[M];
for (int i = 0; i <= n; i++){
x[i] = i;
//初始化排列
}
BackTrack(1, x, n);
printf("%d", sum);
}