背包算法-Android再爱我一次(3)

被xx跳动大佬使劲儿蹂躏了一把,赶紧回来总结总结
别看到是背包问题就跑掉了昂, 这篇博客是头条大佬丢给我的最后一个问题,给你们踩坑了

首先看这样一个界面

背包算法-Android再爱我一次(3)_第1张图片

在领域偏好里面,花花绿绿的展示了一堆标签。这里用的瀑布流做的没啥好说的,但是头条大佬抛出这样一个问题: 如果我返回了N个标签,但是我只想展示在一个单行文本里面,尽量让文本框占满,改怎么筛选这些标签?
大佬就是大佬,能从你的app里发现问题并提出假设。
好了回到问题:尽量占满,这不就和背包的思路相似么?有容积固定的背包,拿到价值最高的财宝该怎么拿。这里的背包也就是我们定长的文本框,财宝就是文本,体积就是文本长度。
但是,价值是什么呢?这时候,我们就要自己定义一些东西了:

1. 创造价值概念

背包问题中有这样的概念:按照性价比排序。文本本身没有价值这一说,更不用说性价比了。所以我们可以吧文本看成平级的,即性价比相等。所以,当标签长度为3的时候,我们将它的价值默认为3,以此类推,得到所返回标签的价值。此时的性价比都为1,所以这里不用针对性价比排序。

2. 背包算法的思想

经过转换之后,我们的问题就完全符合0-1背包的模型了。
我们假设,获得的字符串为

  1. H
  2. SX
  3. GCX
  4. YYWX

背包算法是动态规划的一种,所有的问题都能通过分为小问题的形式来解决。我们这里分的小问题,就是放不放第N个。如果能放,计算当前的最大价值,如果不放,沿用上一个规模的价值。既然是动态规划 , 那我们免不了要画一个二维数组或者表格来辅助理解问题了 .

以i 为纵轴,代表物品编号 , j 为横轴 , 代表当背包容量为j时 , dp 代表当前最大价值
为什么画表呢 ?这是为了缩减问题规模 . 比如物品为1 背包容量为1 是的最优解很定很好处理 , 那么我们以此为依据 , 开始扩充问题规模, 物品为12 , 背包容量为2 为3 为4是的情况 .

我们可以得出状态转换方程:

  1. dp [ i, j ]=0 , i | j=0 (背包0容量时价值为0; 不放任何物品时,价值为0)
  2. dp [ i, j ]=MAX( dp[ i-1, j ] , dp [ i-1, j- w[i] ]+v[i]) (背包容量不为0时 , 看强行放入物品时的价值与不放入物品的价值 , 这里的强行放入指的是移除已放入物品 , 直到可放入为止 . 看上去是不是不好理解 ?其实我们在前面缩减问题规模的时候已经解出来了)
    这里举几个例子辅助理解一下
  • 根据步骤1 我们先能画出如图的表格(原谅我屎一般的excel):


    背包算法-Android再爱我一次(3)_第2张图片

接下来要填写dp(1,1)单元格的数据了 , 根据上面的公式带入i=1,j=1
得到公式:
dp(1,1)=MAX(dp (0,1 ) , dp(0 , i- 1) +1) = 1 依次填满第二行


背包算法-Android再爱我一次(3)_第3张图片

再举个例子: dp[3,5]
dp[3,5]=MAX(dp[ 2,5 ] , dp[ 2,5-3] +3 ) =MAX(3, 2+3) =5
按照公式一次填完表格 ,.


背包算法-Android再爱我一次(3)_第4张图片

填完后的表格如图所示 , 在dp(4,8)的位置即为我们获取到的最大价值.


背包算法-Android再爱我一次(3)_第5张图片

核心思想其实就是将问题规模从大变小了而已.
有了方程式我们就可以开始写代码了

3 .开始编码

首先我们获取到的信息: 一串字符串 , 还有一行文本框的最大长度。

    /**
     * 初始化输入规模
     * 
     * @param strs      待排序的字符串
     * @param maxLength 一行文本框的长度
     */
    public void init(String[] strs, int maxLength) {
        this.strs = strs;
        this.maxLength = maxLength;

        buildPairs(strs);
        caculateMaxValue();
    }

为了构造出相对应的模型, 我们首先对输入进行处理,构造成键值对的形式。为了最后能表示出哪些字符呗选中,额外放置一个boolean标记

    class Pair {
        int value;
        int weight;
        boolean choosed;
        String content;
    }


// 初始化v:w对 因为文字不具有价值概念 , 所以规定所有文字的平均价值相等
    private void buildPairs(String[] st) {
        this.pairs = new ArrayList();
//      this.pairs=new Pair[st.length]; // 创建vw对
        for (int i = 0; i < st.length; i++) {
            Pair pair = new Pair();
            pair.value = pair.weight = st[i].length();
            pair.content = st[i];
            pair.choosed = false;
            // 保存所有的文本信息
            pairs.add(pair);
        }
    }

有了基础数据后 ,我们开始写矩阵

private void caculateMaxValue() {
        // 创建二维表 , 规定纵轴为pair对, 横轴为背包重量 , dp为当前背包下当前选择中的最大价值
        dp = new int[strs.length + 1][maxLength + 1];

        // 第一行填充0与第一列填充0不用再写
        // 此时从坐标点(1,1)开始填写

        for (int i = 1; i < dp.length; i++) {
            // 首先按列遍历
            for (int j = 1; j < dp[0].length; j++) {
                // 一行一行的填写
                if (j - pairs.get(i - 1).weight < 0) {
                    // 放不下时 沿用上一种策略
                    dp[i][j] = dp[i - 1][j];
                    pairs.get(i - 1).choosed = false;
                } else {
                    // 放得下时, 比较最大值填写
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - pairs.get(i - 1).weight] + pairs.get(i - 1).value);
                }
            }
        }

        // 输出动态规划矩阵
        for (int i = 0; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                System.out.print(dp[i][j] + " ");
            }
            System.out.println("");
        }
    }

最后在main函数中调用
//01背包问题

String[] str=new String[]{"H","SX","GCX","YYWX"};
        
ZeroOnePackage dPackage=new ZeroOnePackage();
dPackage.init(str, 8);

输出结果如图所示


背包算法-Android再爱我一次(3)_第6张图片

到这里结束了么?不存在的 , 这个算法只是帮我们选出了最大价值 , 并没有拿到我们要的那些字符串 , 我们需要回溯找到解的组成

  1. V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j);
  2. V(i,j)=V(i-1,j-w(i))+v(i)实时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i));
  3. 一直遍历到i=0结束为止,所有解的组成都会找到。
    这里就不演示过程了, 直接给出代码吧
// 输出选中的键值对
FindWhat(dp.length-1,dp[0].length-1);
for (Pair pair : pairs) {
    if (pair.choosed) {
        System.out.print(pair.content + " ");
    }
}

/**
     * 找到最优解方案
     * @param i
     * @param j
     */
    void FindWhat(int i,int j)//寻找解的组成方式
    {
        if(i>=1)
        {
            if(dp[i][j]==dp[i-1][j])//相等说明没装
            {
                pairs.get(i-1).choosed=false;//全局变量,标记未被选中
                FindWhat(i-1,j);
            }
            else if( j-pairs.get(i-1).weight>=0 && dp[i][j]==dp[i-1][j-pairs.get(i-1).weight]+pairs.get(i-1).value )
            {
                pairs.get(i-1).choosed=true;//标记已被选中
                FindWhat(i-1,j-pairs.get(i-1).weight);//回到装包之前的位置
            }
        }
    }

至此 , 问题解决


背包算法-Android再爱我一次(3)_第7张图片

如果说有什么不完美的地方的话 , 也就是文字间距的问题了 , 给文字设置weight时加上 间距/2 即可.

你可能感兴趣的:(背包算法-Android再爱我一次(3))