状态空间树

状态空间树:

就是问题的解空间树,分为子集树和排列树

子集树

当所给的问题是从n个元素组成的集合set中找到满足某一条件的一个子集时,相应的解空间树称为子集。
要注意,这个解空间树是一个虚拟的树,并不是构建出来的,如下面这颗树:
有三个物品n = 3,(类似于0-1背包问题)
xi = {0、1}表示第 i 个物品 ni 是否选中,
xi = 0 表示未选中,xi = 1表示选中

树有三层,第 i 层表示物品 ni,数字1、0表示 xi 的值
解空间树的所有可能,叶子上的个节点到A的路线代表了一个子集树——一种可能的解法
叶子上的个节点到A的路线代表了一个子集树——一种可能的解法,如 H 代表了111,表示n1、n2、n3都被选中,I表示110,n1、n2被选中,n3未被选中。
以下用两个例子来解释解空间树

0-1背包中使用穷举法

本文先从简单的穷举法开始介绍子集树
穷举法得到每一个子集树

  • 若当前为非叶节点
    将1压栈
    递归访问左子树
    (递归访问左子树递归到最后时,例如访问到 H 时,就要pop了)
    将0压栈
    递归访问右子树
    (递归访问右子树递归到最后时,例如访问到 I 时,就要pop了)
  • 若当前为叶节点
    顺序打印栈中内容
import java.util.Stack;

public class BruteForce {

	static Stack<Integer> stack = new Stack<>();

	static void subSetTree(int n) {
		if (n > 0) {
			stack.push(0);
			subSetTree(n - 1);
			stack.pop();
			stack.push(1);
			subSetTree(n - 1);
			stack.pop();
		} else {
			for (Integer integer : stack) {
				System.out.print(integer);
			}
			System.out.println();
		}
	}

	public static void main(String[] args) {
		subSetTree(3);
	}

}

排列树

TSP(Travelling Saleman Problem,货郎担、邮递员)问题、或者求全排列的问题。
以全排列为例:
定义:
设Set{S1, S2, S3,…, Sn}有 n 个元素,Seti = Set - {Si}
子集合 X 中元素的全排列记为 arrange(X),
(Si)arrange(Seti)表示在全排列arrange(Seti)的每一个排列前加上前缀Si得到的排列。
Seti表示Set中没有Si元素

Set的全排列可归纳如下
当n=1时,arrange(Set)=(S1),其中S1是集合Set中唯一的元素;
当n>1时,arrange(Set)由(S1)arrange(Set1),…, (Sn)arrange(Setn)构成。
实现思想
将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。

有ABC三个字母,求其全排列:
当n = 3,并且Set = {a,b,c},则:
arrange(Set)=a.arrange({b,c}) + b.arrange({a,c}) + c.arrange({a,b})
arrange({b,c})=b.arrange© + c.arrange(b)
a.arrange({b,c})=ab.arrange© + ac.arrange(b)
=ab.c + ac.b=(abc, acb)
全排列的状态空间树

全排列

public class Tsp {
	static int[] s;

	static void arrangeTree(int i, int n) {
		// 为什么是 i < n -1。因为最后一个不用交换了
		if (i < n - 1) {
			for (int j = i; j < n; j++) {
				// swap(s[i], s[j]);
				int k = s[j];
				s[j] = s[i];
				s[i] = k;
				arrangeTree(i + 1, n);
				// 这里swap,是因为arrangeTree(i + 1, n);前的swap影响了数组 s 的顺序。
				// swap(s[i], s[j]);
				k = s[j];
				s[j] = s[i];
				s[i] = k;
			}
		} else {
			for (int j : s) {
				System.out.print(j);
			}
			System.out.println();
		}
	}

	public static void main(String[] args) {
		int n = 3;
		s = new int[n];
		for (int i = 0; i < n; i++) {
			s[i] = i + 1;
		}
		arrangeTree(0, n);
	}

}

在这里插入图片描述

emmmm,全排列这个其实挺迷的

总结

  • 递归简单,但如何将问题分解成递归就不容易了
  • 递归的判出界限让人很迷
### 回溯法的状态空间树概念 状态空间树是一种用于描述回溯法解决问题过程中所有可能状态的结构化表示方法。它通过深度优先搜索的方式逐步构建,每一层代表一个问题变量的选择范围,而每一条路径则对应一种可能的解决方案[^1]。 在实际应用中,状态空间树由根节点出发,逐层扩展到叶节点为止。对于某些特定问题,如子集选择问题,其对应的解空间被称为子集树。这种情况下,若需从一个包含 \( n \) 个元素的集合中选出满足一定条件的子集,则该子集树会有 \( 2^n \) 个叶子节点以及总计 \( 2^{n+1} - 1 \) 个节点[^2]。 #### 解空间与约束条件的关系 解空间是指所有潜在可行解组成的集合。它的规模取决于具体问题定义下的元组形式及其取值域大小。例如,在经典的八皇后问题里,如果仅规定八个皇后互不同行这一特性来构造元组,则理论上存在 \( 8^8 \) 种可能性;然而进一步考虑到它们也不能处于相同列或者对角线之后,有效排列数量缩减至 \( 8! \)[^3]。 为了提高效率并降低不必要的计算负担,回溯算法会在探索过程引入剪枝操作——即一旦发现当前部分解违反任何预设好的约束或界限准则时立即停止对该分支后续发展尝试的行为模式。这样不仅可以显著减少所需考察的整体情况数目,而且还能保证最终获得的是符合条件的最佳解答之一[^1]。 ### 回溯法实现方式 以下是基于 Python 的通用框架代码展示如何运用递归来模拟整个状态空间树遍历流程: ```python def backtrack(state, choices): if is_solution(state): # 判断是否达到目标状态 process_solution(state) return for choice in choices: # 遍历所有可用选项 if valid(choice, state): # 检查当前选项目前状态下是否合法 make_move(state, choice) # 执行移动/更新状态 backtrack(state, remaining_choices(choices)) # 继续深入下一个层次 unmake_move(state, choice) # 撤销刚才的操作以便恢复原状 # 辅助函数说明: # is_solution(): 测试state是否已经构成了完整的解。 # process_solution(): 对找到的有效解做相应处理。 # valid(): 确定choice加入现有state后仍保持合法性。 # make_move() & unmake_move(): 修改和还原state以反映新旧变化。 # remaining_choices(): 返回剩余未被使用的候选列表。 ``` 此模板展示了基本逻辑架构,但针对不同类型的实际应用场景还需要定制具体的细节实现。 ### 应用实例分析 以装载问题为例(假设有若干重量不同的物品放入容量有限制的容器内),我们可以建立如下模型并通过上述机制求得最优填装方案。同样地,“旅行商问题”也可以借助类似的思路去寻找最短环游路线等等。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值