数据结构与算法(六)分支限界法(Java)

目录

    • 一、简介
      • 1.1 定义
      • 1.2 知识回顾
      • 1.3 两种解空间树
      • 1.4 三种分支限界法
      • 1.5 回溯法与分支线定法对比
      • 1.6 使用步骤
    • 二、经典示例:0-1背包问题
      • 2.1 题目
      • 2.2 分析
        • 1)暴力枚举
        • 2)分支限界法
      • 2.3 代码实现
        • 1)实现广度优先策略遍历
        • 2)实现限界函数来剪枝

一、简介

1.1 定义

分支限界法 是使用 广度优先策略,依次生成 扩展节点 上的所有分支。就是 把问题的可行解展开,再由各个分支寻找最佳解

分支限界法回溯法 类似,都是 递归 + 剪枝,区别在于回溯法使用的是深度优先策略,而分支限界法使用的是广度优先策略。

1.2 知识回顾

  • 扩展结点: 一个 正在生成儿子 的结点,称为扩展结点。
  • 活结点: 一个 自身已生成但其儿子还没有全部生成 的结点,称为活结点。
  • 死结点: 一个 所有儿子已经全部生成 的结点,称为死结点。

深度优先策略:

  • 如果对一个扩展结点 R,一旦生成了它的一个儿子 C,就把 C 当作新的扩展结点。
  • 在完成对子树 C(以 C 为根的子树)的穷尽搜索之后,将 R 重新变成扩展结点,继续生成 R 的下一个儿子(如果存在)。

广度优先策略:

  • 在一个扩展结点变成死结点之前,它一直是扩展结点。

剪枝函数:

剪枝函数:当某个顶点没有希望,则其所在的树枝可以减去。

剪枝函数一般有两种:

  • 约束函数: 剪去不满足约束条件的路径。
  • 限界函数: 减去不能得到最优解的路径。

1.3 两种解空间树

子集树(Subset Trees):

  • 当所给问题是 从 n 个元素的集合中找出满足某种性质的子集 时,相应的解空间树称为 子集树

Sn 表示 n 结点子树的数量,在子集树中,|S0| = |S1| = ... = |Sn-1| = c,即每个结点有相同数目的子树,通常情况下 c = 2,所以子树中共有 2^n 个叶子结点。因此,遍历子集树的时间复杂度为 O(2^n)

排列树(Permutation Trees):

  • 当所给问题是 确定 n 个元素满足某种性质的排列 时,相应的解空间树称为排列树

Sn 表示 n 结点子树的数量,在排列树中,通常情况下 |S0| = n, |S1| = n-1, ..., |Sn-1| = 1。所以,排列树中共有 n! 个叶子结点。因此,遍历排列树的时间复杂度为 O(n!)

1.4 三种分支限界法

不同于回溯法,在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点,通过 剪枝函数 将导致不可行解或非最优解的儿子结点舍弃,其余 儿子结点被加入活结点表中

然后,从 活结点表 中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个 过程一直持续到 找到所需解 或 活结点表为空 为止

根据活结点表的形成方式不同分为 三种分支限界法:

  • FIFO分支限界法:活结点表是 队列,按照队列先进先出(FIFO)原则选取下一个结点为扩展结点。
  • LIFO分支限界法:活结点表是 堆栈,按照堆栈先今后出(LIFO)原则选取下一个结点为扩展结点。
  • LC分支限界法:活结点是 优先权队列(Low Cost),按照优先队列中规定的优先权选取具有最高优先级的活结点成为新的扩展结点。
    • 结点优先权: 在其分支下搜索一个答案状态需要花费的代价越小,优先级越高。

1.5 回溯法与分支线定法对比

算法名称对解空间树的搜索方式存储结点的常用数据结构结点存储特性求解目标空间复杂度
回溯法深度优先搜索(DFS)递归;
非递归时使用堆栈
活结点的所有可行子结点被遍历后才被从栈中弹出(结点可能多次成为扩展结点)。找出解空间树中满足约束条件的所有解。子集树:O(n)
排列树:O(n)
分支限界法广度优先搜索(BFS)
最小消耗优先搜索(LC)
队列;堆栈、优先队列每个活结点只有一次成为扩展结点的机会。找出满足约束条件的一个解,或在满足约束条件的解中找出某种意义下的最优解。子集树:O(2^n)
排列树:O(n!)

1.6 使用步骤

  1. 首先 确定一个合理的限界函数,并 根据限界函数确定目标函数的界
  2. 然后 按照广度优先策略搜索问题的解空间树
  3. 在扩展结点处,生成所有儿子结点,估算所有儿子结点对目标函数的可能取值,舍弃不可能通向最优解的结点(剪枝),将其余结点加入到活结点表(队列/栈)中
  4. 在当前活结点表中,依据相应的分支线定法(FIFO、LIFO、LC),从当前活结点表中选择一个结点作为扩展结点
  5. 重复 4~3 步,直到 找到所需的解 或 活结点表为空

二、经典示例:0-1背包问题

2.1 题目

假定有N=4件商品,分别用A、B、C、D表示。每件商品的重量分别为 3kg、2kg、5kg和4kg,对应的价值分别为 66元、40元、95元和40元。现有一个背包,可以容纳的总重量位 9kg,问:如何挑选商品,使得背包里商品的 总价值最大?

2.2 分析

0-1背包问题,实际上就是排列组合的问题。

1)暴力枚举

假如我们用A表示物品A存在;A(上划线)表示物品A不存在。暴力枚举所有可能的情况如下:

最优解: A 物品+ C 物品 = 161 价值

在这里插入图片描述

2)分支限界法

首先根据 价值/重量 进行从大到小进行排序,排序结果如下:

  1. 重量:3,价值:66,每份重量价值:22;
  2. 重量:2,价值:40,每份重量价值:20;
  3. 重量:5,价值:95,每份重量价值:19;
  4. 重量:4,价值:40,每份重量价值:10;

假如我们用A表示物品A存在;A(下划线)表示物品A不存在。解空间树 如下:

在这里插入图片描述

首先根据 A 物品的存在情况进行计算,A存在时最优价值为182A不存在时最优价值为155。选择 最优价值更高的情况A结点

物品列表:A

背包价值:66

最优队列: A(182)> A(155)

在这里插入图片描述

弹出 A 结点,再根据 B 物品的存在情况进行计算,B存在时最优价值为182B不存在时最优价值为171。选择 最优价值更高的情况B结点

物品列表:A、B

背包价值:106

最优队列: B(182)> B(171)> A(155)

在这里插入图片描述

弹出 B 结点,再根据 C 物品的存在情况进行计算,C存在时超重×C不存在时最优价值为146。选择 最优价值更高的情况B结点

物品列表:A

背包价值:66

最优队列: B(171)> A(155)> C(146)

在这里插入图片描述

弹出 B 结点,再根据 C 物品的存在情况进行计算,C存在时最优价值为171C不存在时最优价值为106,由于 106 不大于已有最大价值 161,舍弃。选择 最优价值更高的情况C结点

物品列表:A、C

背包价值:161

最优队列: C(171)> A(155)> C(146)

在这里插入图片描述

弹出 C 结点,再根据 D 物品的存在情况进行计算,D存在时超重×D不存在时最优价值为161由于此结点已为叶子结点,退出循环

物品列表:A、C

背包价值:161

在这里插入图片描述

2.3 代码实现

为了方便理解,这里分两步实现:

  • 实现广度优先策略遍历;
  • 实现限界函数来剪枝。
1)实现广度优先策略遍历
public static void main(String[] args) {
    Solution solution = new Solution();
    int[][] arr1 = {{3,66},{2,40},{5,95},{4,40}};
    long start1 = System.currentTimeMillis();
    long start2 = System.nanoTime();
    // 执行程序
    int result = solution.knapsack(arr1, 9);
    long end1 = System.currentTimeMillis();
    long end2 = System.nanoTime();
    System.out.println(result);
    System.out.println("耗时:" + (end1 - start1) + " ms," + (end2 - start2) + " ns");
}

public int knapsack(int[][] nums, int capacity) {
    Node rootNode = new Node(0, 0, 0);
    Queue<Node> queue = new ArrayDeque<>();
    queue.add(rootNode);
    int maxValue = 0;
    while (!queue.isEmpty()) {
        Node node = queue.poll();
        if (node.index >= nums.length) {
            break;
        }
        // 左节点:放入背包
        if (node.bagWeight + nums[node.index][0] <= capacity) {
            Node newLeftNode = new Node(node.index + 1, node.bagWeight + nums[node.index][0], node.bagValue + nums[node.index][1]);
            queue.add(newLeftNode);
            maxValue = Math.max(maxValue, newLeftNode.bagValue);
        }
        // 右节点:不放入背包
        Node newRightNode = new Node(node.index + 1, node.bagWeight, node.bagValue);
        queue.add(newRightNode);
    }
    return maxValue;
}

static class Node {
    /**
     * 索引(第几个物品)
     */
    private int index;
    /**
     * 背包容量
     */
    private int bagWeight;
    /**
     * 背包价值
     */
    private int bagValue;

    public Node(int index, int bagWeight, int bagValue) {
        this.index = index;
        this.bagWeight = bagWeight;
        this.bagValue = bagValue;
    }
}
2)实现限界函数来剪枝
public static void main(String[] args) {
    Solution solution = new Solution();
    int[][] arr1 = {{3,66},{2,40},{5,95},{4,40}};
    long start1 = System.currentTimeMillis();
    long start2 = System.nanoTime();
    // 执行程序
    int result = solution.knapsack(arr1, 9);
    long end1 = System.currentTimeMillis();
    long end2 = System.nanoTime();
    System.out.println(result);
    System.out.println("耗时:" + (end1 - start1) + " ms," + (end2 - start2) + " ns");
}

public int knapsack(int[][] nums, int capacity) {
    // 由于使用了贪心算法,需要先进行排序
    Arrays.sort(nums, Comparator.comparingDouble(o -> -1.0 * o[1] / o[0]));
    Node rootNode = new Node(0, 0, 0);
    // 优先队列(贪心算法,按照最优价值排序)
    PriorityQueue<Node> queue = new PriorityQueue<>();
    queue.add(rootNode);
    int maxValue = 0;
    // 遍历,直到最大最优价值为某一叶子结点
    while (queue.size() > 0 && queue.peek().index < nums.length) {
        Node node = queue.poll();
        // 左节点:放入背包
        Node newLeftNode = new Node(node.index + 1, node.bagWeight + nums[node.index][0], node.bagValue + nums[node.index][1]);
        int newLeftUpBound = newLeftNode.getUpBound(nums, capacity);
        if (newLeftUpBound >= maxValue && newLeftNode.bagWeight <= capacity) {
            queue.add(newLeftNode);
            maxValue = Math.max(maxValue, newLeftNode.bagValue);
        }
        // 右节点:不放入背包
        Node newRightNode = new Node(node.index + 1, node.bagWeight, node.bagValue);
        int newRightUpBound = newRightNode.getUpBound(nums, capacity);
        if (newRightUpBound >= maxValue) {
            queue.add(newRightNode);
        }
    }
    return maxValue;
}

static class Node implements Comparable<Node> {
    /**
     * 索引(例:第 1个物品索引为 1)
     */
    private final int index;
    /**
     * 背包容量
     */
    private final int bagWeight;
    /**
     * 背包价值
     */
    private final int bagValue;
    /**
     * 背包最优价值(上界)
     */
    private int upBound;

    public Node(int index, int bagWeight, int bagValue) {
        this.index = index;
        this.bagWeight = bagWeight;
        this.bagValue = bagValue;
    }

    public int getUpBound(int[][] nums, int capacity) {
        if (this.upBound > 0) {
            return this.upBound;
        }
        int newUpBound = this.bagValue;
        int bagLeft = capacity - bagWeight;
        int i = this.index;
        while (i < nums.length && bagLeft - nums[i][0] >= 0) {
            bagLeft -= nums[i][0];
            newUpBound += nums[i][1];
            i++;
        }

        // 背包未满,切割后放入
        if (i < nums.length) {
            newUpBound += 1.0 * bagLeft / nums[i][0] * nums[i][1];
        }

        return this.upBound = newUpBound;
    }

    @Override
    public int compareTo(Node o) {
        // 倒叙
        return o.upBound - this.upBound;
    }
}

整理完毕,完结撒花~ 🌻





参考地址:

1.算法分析与设计:分支限界法,https://blog.csdn.net/weixin_44712386/article/details/105532881

2.(五) 分支限界算法,https://www.jianshu.com/p/538e7612f68d

3.【算法】四、分支限界法,https://blog.csdn.net/m0_64403412/article/details/130694294

4.单源最短路径问题——分支限界法(Java),https://zhuanlan.zhihu.com/p/601400758

5.java 0-1背包问题 动态规划、回溯法、分支限界,https://blog.csdn.net/Dl_MrE/article/details/119572322

6.分支限界法求解0/1背包问题动画演示,https://www.bilibili.com/video/BV1gb411G7FH/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/163520.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

命令行参数(C语言)

目录 什么是命令行参数 main函数的可执行参数 不传参打印 传参打印 IDE传参 cmd传参 命令行参数的应用&#xff08;文件拷贝&#xff09; 什么是命令行参数 概念&#xff1a;命令行参数指的是在运行可执行文件时提供给程序的额外输入信息。它们通常以字符串形式出现&am…

什么是web组态?一文读懂web组态

随着工业4.0的到来&#xff0c;物联网、大数据、人工智能等技术的融合应用&#xff0c;使得工业领域正在经历一场深刻的变革。在这个过程中&#xff0c;web组态技术以其独特的优势&#xff0c;正在逐渐受到越来越多企业的关注和认可。那么&#xff0c;什么是web组态&#xff1f…

2024年SEO策略:如何优化您的知识库?

如今很多人在遇到问题时都会求助于谷歌。谷歌已经成为提供解决方案不可或缺的工具。作为全球搜索引擎的巨头&#xff0c;拥有大量用户流量。这就是为什么确保您的产品和服务在谷歌搜索结果中排名靠前是至关重要的&#xff0c;如果您想获得更多的客户&#xff0c;SEO是一个非常关…

10 大 Mac 数据恢复软件深度评测

对于任何依赖计算机获取重要文件&#xff08;无论是个人照片还是重要商业文档&#xff09;的人来说&#xff0c;数据丢失可能是一场噩梦。值得庆幸的是&#xff0c;有多种专门为 Mac 用户提供的数据恢复工具&#xff0c;可以帮助检索丢失或意外删除的文件。在本文中&#xff0c…

C语言搭建项目-学生管理系统(非链表)

、 目录 搭建offer.h文件 搭建offer.c中的main函数 密码登入系统 搭建my_oferr.c中的接口函数 使用帮助菜单接口函数 增加学生信息接口函数 查询学生信息接口函数 删除学生信息接口函数 保存学生信息接口 打开文件fopen 关闭文件fclose 判断是否保存文件fwrite 退出执行文件…

Unity 2022 + Android 接入微信登录

实现Unity接入安卓端的微信登录 分为五个大步骤 生成keystore安卓应用开发者签名在微信开放平台申请移动应用接入编写Java Android部分代码, 生成arr文件编写 Unity C# 代码Unity打apk包, 安装到手机中进行测试 建议按照顺序逐步进行, 1, 2 步如果已经完成, 可直接跳过. 虽然…

HttpComponents: 领域对象的设计

1. HTTP协议 1.1 HTTP请求 HTTP请求由请求头、请求体两部分组成&#xff0c;请求头又分为请求行(request line)和普通的请求头组成。通过浏览器的开发者工具&#xff0c;我们能查看请求和响应的详情。 下面是一个HTTP请求发送的完整内容。 POST https://track.abc.com/v4/tr…

【C#】序列化和反序列化,以及System.Text.Json和Newtonsoft.Json比较

给自己一个目标&#xff0c;然后坚持一段时间&#xff0c;总会有收获和感悟&#xff01; 序列化和反序列化&#xff0c;在实际项目开发过程中用的最多。特别是有对接接口的小伙伴就深有体会。本篇文章就简单聊聊这个知识点。 目录 一、基本概念1.1、序列化1.2反序列化1.3、举例…

EM32DX-C4【C#】站15

1外观: J301 直流 24V 电源输入 CAN0 CAN0 总线接口 CAN1 CAN1 总线接口 J201 IO 接线段子 S301-1、S301-2 输出口初始电平拨码设置 S301-3~S301-6 模块 CAN ID 站号拨码开关 S301-7 模块波特率拨码设置 S301-8 终端电阻选择开关 2DI: 公共端是: 0V【GND】 6100H …

.NET Core 依赖注入 Microsoft.Extensions.DependencyInjection

文章目录 前言什么是依赖注入C# 使用依赖注入框架介绍 Microsoft.Extensions.DependencyInjectionNuget安装简单单例使用打印结果 自动装配举例自动装配测试用例打印结果自动装配执行顺序测试用例有歧义构造函数渐进式构造函数循环依赖 自动装配结论 手动装配手动注入别名注入 …

修改移远提供的GobiNet、quectel-CM源码,使其支持有方N720 4G模块

最近在研究imx6ull linux下4G模块驱动的移植&#xff0c;参考的移远ec20的移植方法&#xff0c;添加了GobiNet驱动&#xff0c;编译了quectel-CM工具&#xff0c;并且可以正常拨号&#xff0c;分配到ip&#xff0c;如下&#xff1a; ping外网也没有压力&#xff0c;如下…

视频汇聚/音视频流媒体视频平台/视频监控EasyCVR分享页面无法播放,该如何解决?

国标GB28181安防视频监控/视频集中存储/云存储EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统…

分类信息发布小程序效果如何

信息发布系统连接信息供需双方&#xff0c;打造信息聚合平台&#xff0c;用户可获取和发布需求信息、参与互动交流&#xff0c;适用于同城、社区交流、客户互动、业务员/经纪人发布信息场景。 制作分类信息小程序后&#xff0c;商家后台设置信息项&#xff0c;发布者填写内容发…

TQ2440开发板-按键驱动程序设计

目录 按键测试底板原理图核心板原理图使用轮询方式设计按键程序 按键测试底板原理图 TQ2440开发板有4个用户可编程按键&#xff0c;它们直接与CPU的GPIO相连&#xff0c;低电平触发中断&#xff0c;资源占用如下图所示&#xff1a; 核心板原理图 使用轮询方式设计按键程序 按…

mmdetection测试保存到新的文件夹,无需标签

这个是用demo这个代码测试的&#xff0c;需要先训练一个pth文件夹&#xff0c;训练之后再调用pth文件夹进行测试。测试的代码文件名是&#xff1a;image_demo_new.py&#xff0c;代码如系所示&#xff1a; # Copyright (c) OpenMMLab. All rights reserved. import asyncio fr…

uni-app 设置tabBar的setTabBarBadge购物车/消息等角标

目录 一、效果二、代码实现二、全部代码1.index.vue2.cart.vue 三、真实案例参考最后 一、效果 二、代码实现 只要使用uni.setTabBarBadge和uni.removeTabBarBadge来进行对红点的设置和移除。 主要代码&#xff1a; //设置红点 uni.setTabBarBadge({index: 1, // 底部菜单栏…

Endnote使用教程

原由 最近要进行开题报告&#xff0c;要求不低于60文献的阅读与引用&#xff0c;单独插入引入我觉得是非常繁琐的事情&#xff0c;所以就借助Endnote这个工具&#xff0c;减少我们的工作量。 使用方法 第一步&#xff1a;先新建一个数据库&#xff0c;这样子可以在这个数据库…

【数据结构】——队列实现二叉树的功能

前言&#xff1a;二叉树的实现方式多种多样&#xff0c;有数组实现满二叉树&#xff0c;有链表实现完全二叉树&#xff0c;今天我们就用队列来实现二叉树。 创建二叉树&#xff1a; typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTre…

Linux信息收集

Linux信息收集 本机基本信息 #管理员 $普通用户 之前表示登录的用户名称&#xff0c;之后表示主机名&#xff0c;再之后表示当前所在目录 / 表示根目录 ~表示当前用户家目录1、内核&#xff0c;操作系统和设备信息 uname -a 打印所有可用的系统信息 uname -r 内核版本 u…

JS原生实现浏览器滚动条滚动侧边栏高亮响应

目录 演示 ​编辑 需求 代码 css html script 代码解释 1、获取所有link-content 2、定义一个rectContent数组&#xff0c;然后循环allContents调用getClientRects()[0]获取每个link-content元素与浏览器视口的关系 3、为数组追加link-content&#xff0c;用于设置侧…
最新文章