学习目标:
- 掌握算法入门知识
学习内容:
- 分支限界的定义
- 例题详细步骤讲解(找牛)
1. 分支限界的定义
分支限界法是一种用于求解 组合优化问题 的算法框架,通过 系统性地搜索解空间树,并结合 剪枝策略 来避免无效搜索,从而高效地找到最优解(如最小化代价或最大化收益)。其核心思想是:
-
分支(Branch):将当前问题分解为若干子问题(即扩展解空间树的分支)。例如:在旅行商问题(TSP)中,从一个城市出发,分支为所有可能的下一站城市。
-
限界(Bound):计算每个子问题的 代价下界(对于最小化问题) 或 收益上界(对于最大化问题)。若某分支的当前界限 差于已知最优解,则直接剪枝,不再继续搜索该分支。
队列式分支限界法:将活结点表组织成一个队列,并按照队列先进先出(FIFO)原则选取下一个结点为扩展结点。步骤如下:
(1)将根结点加入活结点队列。
(2)从活结点队中取出队头结点,作为当前扩展结点。
(3)对当前扩展结点,先从左到右地产生它的所有孩子结点,用约束条件检查,把所有满足约束条件的孩子结点加入活结点队列。
(4)重复步骤(2)和(3),直到找到一个解或活结点队列为空为止。
2. 例题详细步骤讲解(找牛)
农夫知道一头牛的位置,想要抓住它。
农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于K(0<=K<=100000)。假设牛没有意识到农夫的行动,站在原地不动。农夫最少要花多少时间才能抓住牛?
农夫有两种移动方式:
<1> 从X移动到X-1或X+1,每次移动花费一分钟
<2> 从X移动到2*X,每次移动花费一分钟
假设农夫起始位于点2,牛位于7。即N=2,K=7。最右边是8。 如何搜索到一条走到7的路径?
思路:分支限界法采用广度优先搜索。
初始化Queue = [ (2, 0) ](初始位置 2,步数 0),visited = [0, 0, 1, 0, 0, 0, 0, 0, 0](2 已访问)
(1)当前位置 为2,可能的移动:2 → 1(左移)2 → 3(右移)2 → 4(跳跃)
更新队列:Queue = [ (1,1), (3,1), (4,1) ],visited 标记 1,3,4 为已访问
(2)当前位置 = 1,可能的移动:1 → 0(左移)1 → 2(右移,2 已访问)1 → 2(跳跃,2 已访问)
更新队列:Queue = [ (3,1), (4,1), (0,2) ],visited 标记 0 为已访问
(3)当前位置 = 3,可能的移动:3 → 2(左移,2 已访问)3 → 4(右移,4 已访问)3 → 6(跳跃)
更新队列:Queue = [ (4,1), (0,2), (6,2) ],visited 标记 6 为已访问
(4)当前位置 = 4,可能的移动:4 → 3(左移,3 已访问)4 → 5(右移)4 → 8(跳跃)
更新队列:Queue = [ (0,2), (6,2), (5,2), (8,2) ],visited 标记 5,8 为已访问
(5)当前位置 = 0,可能的移动:0 → -1(左移,超出范围,忽略)0 → 1(右移,1 已访问)0 → 0(跳跃,无意义)
无新位置可扩展,队列更新:Queue = [ (6,2), (5,2), (8,2) ]
(6)当前位置 = 6,可能的移动:6 → 5(左移,5 已访问)6 → 7(右移)6 → 12(跳跃,12 > 8,超出范围(限界))
发现 7 是目标!当前步数:2(从 2→4→6) + 1(从 6→7) = 3 分钟
得到最优路径:2 → 4 → 6 → 7(共 3 分钟)
代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#define MAXN 100000int N, K;
int visited[MAXN + 10]; // 判重标记数组,visited[i]=1表示位置i已经访问过// 定义结构体表示每一步的状态
typedef struct {int x; // 当前位置int steps; // 到达当前位置所需的步数
} Step;// 队列结构定义
typedef struct {Step data[MAXN + 10]; // 队列数据存储int front; // 队首指针int rear; // 队尾指针
} Queue;// 初始化队列
void initQueue(Queue *q) {q->front = 0;q->rear = -1;
}// 判断队列是否为空
int isEmpty(Queue *q) {return q->front > q->rear;
}// 入队操作
void enqueue(Queue *q, Step s) {if (q->rear < MAXN + 9) { // 防止队列溢出q->data[++(q->rear)] = s;}
}// 出队操作
Step dequeue(Queue *q) {return q->data[(q->front)++];
}int main() {// 输入农夫和牛的位置scanf("%d %d", &N, &K);// 初始化访问标记数组memset(visited, 0, sizeof(visited));Queue q;initQueue(&q);// 将初始状态加入队列enqueue(&q, (Step){N, 0});visited[N] = 1; // 标记初始位置已访问while (!isEmpty(&q)) {Step current = dequeue(&q);// 如果当前位置就是牛的位置,输出步数并结束if (current.x == K) {printf("%d\n", current.steps);return 0;}// 尝试三种可能的移动方式// 1. 向左移动一步if (current.x - 1 >= 0 && !visited[current.x - 1]) {enqueue(&q, (Step){current.x - 1, current.steps + 1});visited[current.x - 1] = 1; // 标记已访问}// 2. 向右移动一步if (current.x + 1 <= MAXN && !visited[current.x + 1]) {enqueue(&q, (Step){current.x + 1, current.steps + 1});visited[current.x + 1] = 1; // 标记已访问}// 3. 跳跃到两倍位置if (current.x * 2 <= MAXN && !visited[current.x * 2]) {enqueue(&q, (Step){current.x * 2, current.steps + 1});visited[current.x * 2] = 1; // 标记已访问}}return 0;
}