算法的优雅(九):满满的都是爱

满满的都是爱,蓝回的那么快,五秒规则也信手拈来.....

好像扯远了.....

本章讲的是很基础的由动归引深来的背包问题,如果你是算法大人.....去该干嘛干嘛吧......因为本章是为了那些面试学生写的。

首先声明,本章讲的是《背包九讲》的前三章,只不过从一个面试者角度去写,能很好地让那些该面试而不知道的学生了解这个算法,而不会深究算法的推到和时间复杂度问题。


在此,先热烈庆祝一下巫航同学被松下公司录取,以后买电器有打折的地方了~~~~


好,我们来切入正题,背包问题是很多面试公司问及算法时候喜欢问的,因为该问题源于动归,而且是一个很成型的问题,所以本文选取“01背包”、“完全背包”和“多重背包”去介绍,如果你面试的公司问了“依赖背包”或各种变异体.....那么你面试的是google吧,面试google连背包都不知道,你肯定在闹.....


背包问题是什么呢?算法课上老师肯定都说过:我有一个体积为V的包,有N个物品,每个物品有价值wi和体积ti,我想把东西放进包里让其所装的物品总价值尽可能的大。

如果你回答是使劲往里塞,塞到溢出来为止,那么,个人建议你不适合搞计算机,你应该去考公务员。


于是,我们来引入01背包思想。

01,顾名思义,就是两种状态,取或不取,于是我们不难联想到前面学过的搜索.....不过2^N这个复杂度........我这不是Intel超极本.....

所以,我们需要一个表达式级别的复杂度来友好的表示取或不取。

初始状态,我们背包是空,也就是0这个状态是必有的,之后我们往里装了一个体积为ti的物品,这时候,就变成了0和0 + ti这两个状态可到达了,于是再来一个tj物品,变成了0,0 + ti,0 + tj,0 + ti + tj....我们需要模拟这一个过程。

用f[x]表示包里装有体积为x的若干物品这个状态是否可达,可以知道f[0]必然为1,于是,我们依次拿出物品来试装j-> 1 to n,从i:[0,V]这个区间看f数组的值,如果f[i]不为0,说明i这个题解可达,那么i + tj也是可达的了。

for (int j = 1; j <= n; ++j)
{
	for (int i = V; i >= 0; --i)
	{
		if (f[i])
			f[i + t[j]) = 1;
	}
}
解释一下为什么是for (int i = V; i >= 0; --i)而不是0->V。

我们看个例子我只有一个物品,题解是3,包的大小为10,如果我是0->V循环,那么在检查0状态的时候,3状态被改为1,而循环继续,到3状态,6变成了1,明显体积6是不可能的,所以V->0这种循环方式保证了物品唯一的。

但是我们的题目要求是求最大值,修改一下代码就行了。

for (int j = 1; j <= n; ++j)
{
	for (int i = V; i >= 0; --i)
	{
		if (f[i])
			f[i + t[j]) = max(f[i + t[j]], f[i] + v[j]);
	}
}
最后只要扫一遍f数组选出最大的就行了,注意减1.

复杂度为O(Vn),比2^n复杂度小多了......


于是,我们再来看看另一个问题,就是......我的n个物品可以随便取,没有个数限制......

这个就是完全背包问题.....我想,进过上面的学习这个已经不是问题。

就是把for (int i = V; i >= 0; --i)改成for (int i = 1; i <= n; ++i),这样就保证了一个物品能取无限次了......


最后一个问题,多重背包问题。

很简单,还是n个物品,这次每个物品既不是1个也不是无限个了,而是有限制的若干个......

如果你能想出对于n个物品,每个有ci个,视为ci个物品,需要做∑ci个物品的背包了,那么不错,学以致用。

但是,往往出现一种不理想的情况,就是n很小,但是ci很大......于是有O(n*ci*V)的高复杂度。

于是我们需要优化一下,这就出来多重背包。

对于ci,我们需要知道一点,就是这是相同物品,加入我们单单对ci个物品做01背包,那么过程中肯定有很多冗余判断,假设物品体积为ti,那么ti这个状态会最早可达而且每次都要判断,显然没有必要的。

于是,我们可以对ci进行分解,分解为1,2,4,8.....(2^0, 2^1, 2^2, 2^3,....)这样的数列,不难得出,1,2,4,8....这样数列进行01背包也能得出所有情况......

(13 = 1 + 2 + 4 + 6)

于是,我们ci变为log,复杂度也变为了O(n*log∑ci*V)

送上POJ关于多重背包的一道题,传送门

#include 
#include 
#include 
using namespace std;

int map[200000];
int a[7];
int fa[7][20001];

int main()
{
    int n = 1;
    while (cin >> a[1] >> a[2] >> a[3] >> a[4] >> a[5] >> a[6])
    {
        int flag = 0;
        memset(map, 0, sizeof(map));
        memset(fa, 0, sizeof(fa));
        map[0]  = 1;
        if (a[1] == 0 && a[2] == 0 && a[3] == 0 && a[4] == 0 && a[5] == 0 && a[6] == 0)
            break;
        int sum = a[1] * 1 + a[2] * 2 + a[3] * 3 + a[4] * 4 + a[5] * 5 + a[6] * 6;
        int mid = sum / 2;
        if (sum % 2 == 0)
        {
            for (int i = 1; i <= 6; ++i)
            {
                int t = 0;
                int x = 1;
                int tmp = a[i];
                while (tmp)
                {
                    if (x < tmp)
                    {
                        fa[i][++t] = x;
                        tmp -= x;
                        x *= 2;
                    }
                    else
                    {
                        fa[i][++t] = tmp;
                        tmp = 0;
                    }
                }
                fa[i][0] = t;
            }
            for (int i = 1; i <= 6; ++i)
            {
                for (int j = 1; j <= fa[i][0]; ++j)
                {
                    int tmp = fa[i][j] * i;
                    for (int k = mid; k >= 0; --k)
                    {
                        if (map[k])
                            map[k + tmp] = 1;
                    }
                }
                if (map[mid])
                {
                    flag = 1;
                    break;
                }
            }
        }
        if (flag)
        {
            printf("Collection #%d:\n", n++);
            puts("Can be divided.");
        }
        else
        {
            printf("Collection #%d:\n", n++);
            puts("Can't be divided.");
        }
        puts("");
    }
    return 0;
}



这三个也就是面试常见算法了吧........


P.S. 最近重温《K-on!》,推荐那些在苦逼找工作和考研党听一首歌《Don't say lazy》,很燃。


你可能感兴趣的:(大学的nothing)