EasyLearn--JAVA实现32个经典算法设计(二):集束算法

集束算法的理解上相对来说还是比较简单的,该算法不是求解最优解而是尽可能的靠近最优解的算法。当集束层级达到12层以上每个子节点不超过5个时,节点数最高可达到了30W个节点左右,而需要从此得出最优解可能用穷尽法来算尽节点数也未尝是“最优”了。

这边推荐比较好去理解此算法的两篇博客。

集束搜索1    集束搜索2

下面具体用代码来说明吧!看集束算法上有分别使用图和树的模型来演示算法规则,此处只实现了树形模型,图形的大同小异就不实现了。

第一步,定义集束实体BEAM

import java.io.Serializable;

/**
 * 集束算法的实体Bean,每个元素的定义
 * 集束bean中包含了 名称 概率值 哪个层级 该层级对应的集合号
 *
 */
public class Beam implements Serializable {
    /*
     * 集束名称
     */
    private String beamName;
    /*
     * 集束值
     */
    private Double beamValue;
    /*
     * 集束层级
     */
    private Integer beamLevel;
    /*
     * 集束同一层级对应的那个集合号 1 2 3 4
     */
    private Integer beamNo;
    /*
     * 当前集束bean的父集束bean
     */
    private Beam parentBeam;

    public Beam() {
        super();
    }

    public Beam(String beamName, Double beamValue, Integer beamLevel, Integer beamNo) {
        super();
        this.beamName = beamName;
        this.beamValue = beamValue;
        this.beamLevel = beamLevel;
        this.beamNo = beamNo;
    }

    public String getBeamName() {
        return beamName;
    }

    public void setBeamName(String beamName) {
        this.beamName = beamName;
    }

    public Double getBeamValue() {
        return beamValue;
    }

    public void setBeamValue(Double beamValue) {
        this.beamValue = beamValue;
    }

    public Integer getBeamLevel() {
        return beamLevel;
    }

    public void setBeamLevel(Integer beamLevel) {
        this.beamLevel = beamLevel;
    }

    public Integer getBeamNo() {
        return beamNo;
    }

    public void setBeamNo(Integer beamNo) {
        this.beamNo = beamNo;
    }

    public Beam getParentBeam() {
        return parentBeam;
    }

    public void setParentBeam(Beam parentBeam) {
        this.parentBeam = parentBeam;
    }
}

第二步,定义父子集束关系

import java.util.List;

public class TreeBeam {
    /*
     * 父层-集束号 规则 1-1第一层的第一个集束集合号
     */
    private String parentBeamNo;
    /*
     * 当前层-集束集合号 规则 1-1第一层的第一个集束集合号
     */
    private String nowBeamNo;
    /*
     * 集束beam的集合
     */
    private List<Beam> beamList;

    public TreeBeam(String parentBeamNo, String nowBeamNo, List<Beam> beamList) {
        super();
        this.parentBeamNo = parentBeamNo;
        this.nowBeamNo = nowBeamNo;
        this.beamList = beamList;
    }

    public String getParentBeamNo() {
        return parentBeamNo;
    }

    public void setParentBeamNo(String parentBeamNo) {
        this.parentBeamNo = parentBeamNo;
    }

    public String getNowBeamNo() {
        return nowBeamNo;
    }

    public void setNowBeamNo(String nowBeamNo) {
        this.nowBeamNo = nowBeamNo;
    }

    public List<Beam> getBeamList() {
        return beamList;
    }

    public void setBeamList(List<Beam> beamList) {
        this.beamList = beamList;
    }
}

第三步,初始化集束集合,该集束为树形结构

package com.george.easylearn.algorithm.beam;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 集束工厂的作用:
 * 1.定义好每个元素的信息,包括元素的概率值
 * 2.定义每个元素的层级关系
 * 3.定义每个元素可衍生出元素个数(随机或者固定)
 * 4.定义每个层级中元素集中包含beam的个数
 * 5.将上述定义好的元素存储到Map集合中 key 层级 value 为List<TreeBeam>
 *
 */
public class TreeBeamMap implements BeamMap{
    // 定义该集束的层级
    private final Integer levelCount = 6;
    // 集束的值范围 1-100之间
    private final Integer beamValueRange = 100;

    // 每次衍生出元素是否随机或者固定个数
    private boolean randFlag = true;
    // 每次衍生出元素的随机个数为1-5个
    private Integer randCount = 5;
    // 每次衍生出元素个数 默认固定2个也可根据随机来获取
    private Integer beamCount = 3;
    // 每个集束集中包含beam的个数
    private final Integer beamListCount = 6;
    // 定义每个元素的名称
    private char beamName = 'a';

    // 存储个元素层级的beam元素 key-Integer层数 key-String层数+集合号 规则为1-1 第一层的一个集合 0-表示其实层无父层级
    private Map<Integer, List<TreeBeam>> beamListMap = new HashMap<>();

    public TreeBeamMap(){
        // 初始化第一层的集束数据集
        initBeamListMap();
        // 从第二层开始推算后面的元素
        for (int i = 1; i < levelCount; i++) {
            // 获取上一层的集束集合号个数
            long lastLevelCount = beamListMap.get(i).stream().filter(distinctByKey(TreeBeam::getNowBeamNo)).count();
            // 上一层
            int lastBeamLevel = i;
            // 当前层
            int nowBeamLevel = i + 1;
            int nowBeanListNo = 0;
            for (int j = 0; j < lastLevelCount; j++) {
                // 集合编号
                int lastBeanListNo = j + 1;
                // 定义该层元素集合个数
                if (randFlag){
                    Random random = new Random();
                    beamCount = random.nextInt(randCount)+1;
                }
                // 集合当前层 集束Map集合
                List<TreeBeam> treeBeams = beamListMap.get(nowBeamLevel);
                if (treeBeams == null){
                    treeBeams = new ArrayList<>();
                }
                for (int m = 0; m < beamCount; m++){
                    List<Beam> beamList = new ArrayList<>();
                    nowBeanListNo = nowBeanListNo + 1;
                    for (int k = 0; k < beamListCount; k++){
                        Random random = new Random();
                        Double beamValue = random.nextInt(beamValueRange)+1.0;
                        Beam beam = new Beam(String.valueOf((char)(beamName + k)), beamValue, nowBeamLevel, nowBeanListNo);
                        beamList.add(beam);
                    }
                    String parentBeamListName = lastBeamLevel + "-" + lastBeanListNo;
                    String beamListName = nowBeamLevel + "-" + nowBeanListNo;
                    TreeBeam tempTreeBeam = new TreeBeam(parentBeamListName, beamListName, beamList);
                    treeBeams.add(tempTreeBeam);
                    beamListMap.put(nowBeamLevel, treeBeams);
                }
            }
        }
    }

    /**
     * 初始化第一层的集束数据集
     */
    private void initBeamListMap(){
        // 初始化第一层的元素,只有一个
        List<TreeBeam> firstTreeBeams = new ArrayList<>();
        List<Beam> firstBeams = new ArrayList<>();
        for (int i = 0; i < beamListCount; i++){
            Random random = new Random();
            Double beamValue = random.nextInt(beamValueRange)+1.0;
            Beam beam = new Beam(String.valueOf((char)(beamName + i)), beamValue, 1, 1);
            firstBeams.add(beam);
        }
        // 规则为1-1 第一层的一个集合 0-表示其实层无父层级
        String parentBeamListName = "0-1";
        String beamListName = "1-1";
        TreeBeam treeBeam = new TreeBeam(parentBeamListName, beamListName, firstBeams);
        firstTreeBeams.add(treeBeam);
        beamListMap.put(1, firstTreeBeams);
    }

    /**
     * 根据TreeBeam对象中的nowBeamNo当前层的
     * @param keyExtractor
     * @param <T>
     * @return
     */
    public <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor){
        Set<Object> distSet = ConcurrentHashMap.newKeySet();
        return t -> distSet.add(keyExtractor.apply(t));
    }

    /**
     * 输出树形的集束集合
     */
    @Override
    public void showBeamMap(){
        System.out.println("集束集合展示如下:");
        for (int levelTemp : beamListMap.keySet()) {
            System.out.println("层数:"+levelTemp);
            System.out.println("父集束号||当前集束号||集束名称-集束值 ");
            List<TreeBeam> tempMap = beamListMap.get(levelTemp);
            for (TreeBeam treeBeamTemp : tempMap) {
                List<Beam> beamTemp = treeBeamTemp.getBeamList();
                for (int i = 0; i < beamTemp.size(); i++) {
                    Beam v = beamTemp.get(i);
                    System.out.println(treeBeamTemp.getParentBeamNo()+"||"+v.getBeamLevel()+"-"+v.getBeamNo()+"||"+v.getBeamName()+"-"+v.getBeamValue()+" ");
                }
            }
        }
    }

    public Integer getLevelCount() {
        return levelCount;
    }

    public Map<Integer, List<TreeBeam>> getBeamListMap() {
        return beamListMap;
    }
}

第四步,实现集束算法的规则,抓取策略尤为重要

import java.util.*;
import java.util.stream.Collectors;

/**
 * 集束算法的工厂类和实现集束算法的算法步进部分
 * 1.定义集束算法的宽度(即从集束集合号对应的集合中取值最大的数量)
 * 2.根据父集束的key获取子集束的集合,而后根据子集束中集束值最大的两个集束集合
 * 3.将此父集束再赋值给上述最大值的那两个子集束来作为父集束
 * 例如:父集束key为 2-2 对应的集束List个数为5个 最大值分别为 42 86 59 72 69 其中 72和86为最大则取出他们对应的集束List作为下一个父类提供下一层使用
 * 4.以此类推,最终追溯到最后一层后得出,每条路径的总值,再将各个总值进行比较得出最佳的路径。
 */
public class TreeBeamSearch implements BeamSearch{
    // 集束抓取宽度 少于patchCount则算该集合个数
    private final int patchCount = 5;
    // 存储路径元素的集合, key为父集束序号(随着层级递进而变动),List为路径集合
    private Map<String, List<Beam>> pathMap = new HashMap<>();
    // 集束集合
    private TreeBeamMap treeBeamMap;

    public TreeBeamSearch(TreeBeamMap treeBeamMap) {
        super();
        this.treeBeamMap = treeBeamMap;
    }

    public void searchTreeBeamMap(){
        // 获取该树形集束的层数
        int levelCount = treeBeamMap.getLevelCount();
        // 获取该树形集束的所有集合
        Map<Integer, List<TreeBeam>> beamMap = treeBeamMap.getBeamListMap();
        // 初始化第一层的抓取
        TreeBeam treeBeam = beamMap.get(1).get(0);
        ArrayList<Beam> tempList = new ArrayList();
        // 从第一层开始获取出父集束号,根据父集束号来抓取子集束号最大值的固定个数
        for (int i = 1; i < levelCount; i++) {
            // 获取第i层的集束集合 将当前层作为父集束集合
            List<TreeBeam> treeBeams = beamMap.get(i);
            // 如果层数过多和分支过多时,遍历则过于缓慢 筛选掉父节点不在路径集合中的集束
            treeBeams = treeBeams.stream().filter(v -> pathMap.keySet().contains(v.getNowBeamNo())).collect(Collectors.toList());
            patchBeamByParentTreeBeams(beamMap, treeBeams, i);
        }
        // 统计出每条路径的总值,并求出最佳路径
        totalMaxBeamValueOfTreeBeam();
    }

    /**
     * 首先判断该父集束号 是否在路径中 不在路径中说明不是候选路径
     * 根据父集束号依次获取子集束集合并添加到集合内 先移除父集束 后添加子集束到路径集合中
     * @param
     * @param beamMap
     * @param parentTreeBeams
     * @param beamLevel 父集束的层号
     */
    private void patchBeamByParentTreeBeams(Map<Integer, List<TreeBeam>> beamMap, List<TreeBeam> parentTreeBeams, int beamLevel) {
        for (TreeBeam partTreeBeam : parentTreeBeams) {
            // 获取子集束集合
            List<TreeBeam> treeBeams = beamMap.get(beamLevel + 1);
            // 先排序集合,获取当前tempList中最大值的那前两个子集束集合
            sortTreeBeamList(tempList);
            addMaxBeamValueToPathMap(tempList, partTreeBeam.getNowBeamNo());
        }
    }

    /**
     * 添加tempList中最大值的那前两个子集束集合到路径集合
     * @param treeBeams
     * @param parentBeamNo
     */
    private void addMaxBeamValueToPathMap(List<TreeBeam> treeBeams, String parentBeamNo) {
        for (int i = 0; i < treeBeams.size(); i++) {
            TreeBeam treeBeam = treeBeams.get(i);
            if (i >= patchCount){
                break;
            }
            if (treeBeam != null){
                // 先获取父集束对应的集合
                List<Beam> parBeamList = pathMap.get(treeBeam.getParentBeamNo());
                beamList.add(getBeamByMaxBeamValue(treeBeam.getBeamList()));
                // 添加当前集束集合
                pathMap.put(treeBeam.getNowBeamNo(), beamList);
            }
        }
        // 移除父集束
        pathMap.remove(parentBeamNo);
    }

    /**
     * 统计出每条路径的总值,并求出最佳路径
     */
    public void totalMaxBeamValueOfTreeBeam(){
        Map<String, Double> valueMap = new HashMap<>();
        System.out.println("集束集合中候选路径展示如下:");
        for (String pathKey : pathMap.keySet()) {
            List<Beam> treeBeamList = pathMap.get(pathKey);
            // 统计每条路径中集束最大值的那条路径的sum值,添加到map集合中
            Double sumBeamValue = treeBeamList.stream().collect(Collectors.summingDouble(Beam::getBeamValue));
            valueMap.put(pathKey, sumBeamValue);
            // 打印各个路径以及对应的值
            System.out.println("候选路径的值为:" + sumBeamValue);
            printTreeBeamPath(pathKey);
        }
        // 比较各个路径中的那个最大值。
        Map.Entry<String, Double> maxEntry = sortMapByValue(valueMap, 0);
        String bestPath = maxEntry.getKey();
        // 打印出最佳路径
        System.out.println("集束集合中最佳路径展示如下:");
        System.out.println("最佳路径的值为:" + maxEntry.getValue());
        printTreeBeamPath(bestPath);
    }

    /**
     * 获取最大的那个Beam集束
     * @param beamList
     * @return
     */
    private Beam getBeamByMaxBeamValue(List<Beam> beamList) {
        Optional<Beam> beam = beamList.stream().collect(Collectors.maxBy(Comparator.comparingDouble(Beam::getBeamValue)));
        return beam.get();
    }

    /**
     * 打印各个路径以及对应的值
     * @param beamNo
     */
    private void printTreeBeamPath(String beamNo) {
        // 获取该路径的集束集合
        List<Beam> beamList = pathMap.get(beamNo);
        // 对集束集合按路径从小到大进行排序
        sortBeamListByPath(beamList);
        System.out.print("路径顺序为: ");
        beamList.stream().forEach(beam-> {
            String beamListName = beam.getBeamLevel() + "-" + beam.getBeamNo();
            System.out.print(beamListName + ":"+ beam.getBeamValue() + " ");
        });
        System.out.println();
    }

    /**
     * 使用Collections.sort的Comparator比较器将“树形集束”中的节点按F值从大到小排序
     * @param treeBeamList
     */
    private void sortTreeBeamList(List<TreeBeam> treeBeamList){
        Collections.sort(treeBeamList, (n1, n2) -> {
            if (getBeamByMaxBeamValue(n1.getBeamList()).getBeamValue() < getBeamByMaxBeamValue(n2.getBeamList()).getBeamValue()){
                return 1;
            }
            return -1;
        });
    }

    /**
     * 按集束的路径值从小到大进行排序
     * @param beamList
     */
    private void sortBeamListByPath(List<Beam> beamList) {
        Collections.sort(beamList, (b1, b2) -> {
            if (b1.getBeamLevel() > b2.getBeamLevel() || b1.getBeamNo() < b2.getBeamNo()){
                return 1;
            }
            return -1;
        });
    }

    /**
     * flag = 1 正序 从小到大
     * flag = 0 倒序 从大到小
     * @param map
     * @param flag
     * @return
     */
    private static <K, V extends Comparable<? super V>> Map.Entry<K, V> sortMapByValue(Map<K, V> map, int flag) {
        return map.entrySet().stream()
                .sorted((o1, o2) -> flag == 1 ? o1.getValue().compareTo(o2.getValue()) : o2.getValue().compareTo(o1.getValue()))
                .collect(Collectors.toList()).get(0);
    }
}

以上就是所有的集束算法的核心代码,还是那种句算法就是一个思想的结晶,当你去思考后你才能发现它的美,以及美中不足的地方。小伙伴们还是自己去编写一遍吧,还是很有意思的。如果想要源码连接在此。代码

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值