5-14 布线问题(算法设计与分析)

image-20240510111308810

题目、示例分析

已知:

n \rm n n 个元件,两个元件间的布线数量 c o n n [   i   ] [   j   ] \rm conn[\ i\ ][\ j\ ] conn[ i ][ j ],两个元件在线路板的位置分别为 r 、 s r、s rs,则距离为 d i s t ( r , s ) = a b s ( r − s ) \rm dist(r,s)=abs(r-s) dist(r,s)=abs(rs)​,

布线成本 c o s t = ∑ 1 ⩽ i ⩽ j ⩽ n c o n n [ i ] [ j ] × d i s t ( r , s ) \large \rm cost=\displaystyle\sum_{1\leqslant i\leqslant j\leqslant n}conn[i][j]\times dist(r,s) cost=1ijnconn[i][j]×dist(r,s)


要求:

① 最少费用 m i n _ c o s t \rm min\_cost min_cost

② 对应的布线方案,即给出元件在布线板上的一个排列


约束:

如果能够想到布线方案就是元件的一个排列,那么约束其实就是排列的约束

进行排列选择时,看当前手中可以用的数字有哪些,把这些数字放在目前的位置上构成一个新排列,一旦这样做,该数字就从手中消失

也就是说,之前已经排列好的数字不能再使用,只能用手中还没用过的数字

这将影响到回溯中 选择部分 的代码,具体来说是循环的起始点 A A A

for i <- A to B:
	...
	backTrack()
	...

示例

  • 输入部分:

① 3,说明有 3 个元件,编号记为1、2、3

② 输入conn,这里的输入用了对称矩阵压缩

对于当前编号为 i i i 的元件,之前 1 ∼ i − 1 1\sim i-1 1i1 的元件已经给出了和 i i i 的布线数量,所以不输入

因此 第 i i i 行输入的: i + 1 ∼ n i + 1\sim n i+1n 的元件和 i i i​ 的布线数量,由示例可得:

或者说,元件记为节点,边表示布线,边的权重表示布线数量

则布线数量表示为无向图

23
1 1 − 2 、 2 − 1 1-2、2-1 1221的布线数量:2 1 − 3 、 3 − 1 1-3、3-1 1331的布线数量:3
2—— 2 − 3 、 3 − 2 2-3、3-2 2332的布线数量:3

image-20240510113538684

  • 输出部分:

① 最少费用:10

② 最优排列:①、③、②

可以计算出各个元件之间的 d i s t \rm dist dist,元件① 到 元件③ 距离为 1,① 到 ② 为 2,③ 到 ② 为 1,因此
c o s t = 1 ∗ c o n n [ 1 ] [ 3 ] + 2 ∗ c o n n [ 1 ] [ 2 ] + 1 ∗ c o n n [ 2 ] [ 3 ] = 1 ∗ 3 + 2 ∗ 2 + 1 ∗ 3 = 10 \begin{gather}\rm cost &=& \rm 1*conn[1][3]+2*conn[1][2]+1*conn[2][3]\\ &=& \rm 1*3+2*2+1*3\quad=\quad10\end{gather} cost==1conn[1][3]+2conn[1][2]+1conn[2][3]13+22+13=10



回溯法思路

代码套路

回溯法类似于DFS搜索的思想

站在当前的位置上,有很多选择 OP1、OP2、OP3……

思考做出每一个选择之后再做出选择(递归……),最终的结果如何

从中选择最优的选择 OP

def backTrack(visited, Option):  # visited记录过去做了什么选择,Option是所有选择
    # 终止条件
    if base case:
      # 可能剪枝(如果可以)
      # 根据情况操作
   	   return
    
    # 可能剪枝(如果可以)
    
    # 对于所有选择,遍历考虑结果如何
    for i in Option:
        # 可能剪枝(如果可以)
        
        remove i       # 做出选择
        visited.add(i)   # 可选,记录路径
        backTrack(visited, Option)
        visited.pop(i)   # 可选,撤销路径
        recover i      # 撤销选择

填充

按照上面的套路分析:

1. 终止条件是什么,做什么?

排列的时候,当手中没牌的时候排列结束

手里没牌说明第 n n n 张牌也在队列中,如果用一个步数计数器 s t e p step step 来表示回溯树的层,那么 s t e p = = n step ==n step==n 的时候说明结束

到达根节点的时候,需要查看是否要更新 m i n _ c o s t \rm min\_cost min_cost 和 排列方案,依据是当前排列的 c o s t \rm cost cost 更小,代码如下:

if (step == n) {
    // 求当前排列的费用
    int cur_cost = costCalculate(n);
    if (cur_cost < min_cost) {
        min_cost = cur_cost;
        // 更新路线
        // .......
    }
}



2. 选择是什么?

查看手中的牌(元件),选择一个放到当前位置(布线板)上,当前位置即回溯树的层数 s t e p step step

按照排列的思路,得到一个新排列可以将两个数的位置交换,所以代码的思路就是将当前位置的元件和手中的某个元件交换位置

for (int i = step; i <= n; ++i) {
    // 选择 i 来替换step位置的元件
    swap(step, i);
    
    backTrack();
    
    // 回溯,撤销选择
    swap(step, i);
}



数据结构、函数设计

1. 元件序列、布线数量矩阵、费用

元件序列使用 a r r a n g e [   ] \rm arrange[\ ] arrange[ ] 数组保存,值初始化为下标,表示每个元件起初都在原位置的排列

最优排列使用 a n s [   ] \rm ans[\ ] ans[ ] 数组保存

布线数量矩阵使用 c o n n [   ] [   ] \rm conn[\ ][\ ] conn[ ][ ] 数组保存,根据题目条件 n ⩽ 20 n\leqslant 20 n20​,可以初始化为 25 行、25 列

最小费用用 m i n _ c o s t \rm min\_cost min_cost 保存


2. 计算费用函数

s t e p = = n \rm step == n step==n 时,首先要计算当前方案的费用,然后进行比较看看能不能更新

根据上面定义的数组、变量,以及计算方法,可得代码:

int costCalculate(int number) {
    int sum = 0;
    
    // 元件编号从 1 开始
    for (int i = 1; i <= number; ++i) {
        for (int j = 1; j <= number; ++j) {
            // distance = abs(arrange[i] - arrange[j]);
            sum += cnn[i][j] * abs(arrange[i] - arrange[j]);
        }
    }
    
    return sum;
}

3. 主函数

① 输入元件个数,初始化序列数组

② 初始化 c n n [   ] [   ] \rm cnn[\ ][\ ] cnn[ ][ ] 布线数量函数

③ 回溯函数, s t e p = = 1 \rm step==1 step==1 开始(元件编号从 1 开始)

④ 输出费用、方案




代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// min_cost 初始化为较大的值
int n, min_cost = 0x3f3f3f3f;
int conn[25][25];
vector<int> arrange, ans;

int costCalculate() {
    int sum = 0;
    // 元件编号从 1 开始
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            sum += conn[i][j] * abs(arrange[i] - arrange[j]);

    return sum;
}

void backTrack(int step) {
    if (step == n) {
        int cur_cost = costCalculate();
        // 费用更小,更新
        if (cur_cost < min_cost) {
            min_cost = cur_cost;
            // 更新最优序列
            for (int i = 1; i <= n; ++i)
                ans[i] = arrange[i];
        }
    }
    
    // 排列选择
    for (int i = step; i <= n; ++i) {
        // 注:交换的不是下标,而是排列数组的值
        swap(arrange[step], arrange[i]);
        
        // 下一个位置
        backTrack(step + 1);
        
        swap(arrange[step], arrange[i]);
    }
}

int main()
{
    scanf("%d", &n);
    // 初始化排列数组
    arrange.resize(n + 1);
    ans.resize(n + 1);
    for (int i = 1; i <= n; ++i) 
        arrange[i] = i;
    
    for (int i = 1; i <= n; ++i) {
        for (int j = i + 1; j <= n; ++j) {
							scanf("%d", &conn[i][j]);
        }
    }
    
    backTrack(1);
    
    printf("%d\n", min_cost);
    for (int i = 1; i <= n; ++i)
        printf("%d ", ans[i]);
}

参考

算法设计与分析: 5-16 布线问题 - 代码天地 (codetd.com)

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值