算法与心法:需要二维数组吗?

这次是对前三篇算法的一个小扩展。
为什么起名叫做“算法与心法”?算法就不用说了,在这里简单的说一下心法
当你看到一道题时,题目只会给你输入数据和输出数据的要求,你只需要根据输入数据得到正确的输出数据就好了,没有人管你是如何得到的。大家现在可以回头看看我之前讲的那三个问题。01背包和完全背包这两个问题我只求得了最大价值,没求最大价值对应的该拿走的物品,爱思考的同学已经发现,最大价值对应的该拿走的东西可以直接从二维数组中得到,具体要怎么得到,我还是少说点的好,让大家自己思考比较好。而最长递增子序列问题我也只求到了最长的长度,没有求最长递增子序列本身,但是这个序列本身我们也可以通过建立一个二维数组用来追踪序列的变化情况,从而得到最长递增子序列本身,当然,如何追踪序列变化也留给大家自己思考,我就不多说了。
大家应该已经发现,这三道题的空间复杂度都是O(n^2),并且我们可以用这个空间复杂度的算法得到那个要求的最值和当得到最值时的选择情况,但是我们要求的只是那个最大值,并没有要求当得到最值时的选择情况。有人说我可以求得最值和当得到最值时的选择情况,然后只将最值输出就好了啊。当然,如果你这样想,我也无话可说了。。。
下面开始核心部分:
许多人知道评价算法的标准有时间复杂度和空间复杂度,但是还有其他的性质。我知道的也并不多,但是比较熟悉的两个是:解的完整性,和得到正解的概率性。当然,得到正解的概率性和大数据是紧密相连的,这里的初级算法是用不到的。而解的完整性是相对的。我们都熟知算法想要提高效率可以用空间换时间,想要减少内存使用可以用时间换空间,那么我们是否可以用解的完整性换时间或者是换空间呢?答案是肯定的。
那么下面就是心法
当你发现你的算法可以得到除答案以外的一些其他跟答案相关的值时,计算出来的那些其他值就是算法的庸余部分,庸余的部分是不需要的,这时你就可以考虑优化了,舍弃庸余的部分,从而换取空间或时间。(大部分情况下换到的是空间,有些时候可以在换到空间的情况下利用一些小技巧降低时间复杂度。)
当然,心法说没用,也就没什么用,因为这毕竟只是一个判断技巧,有他只可以让你简单的判断你的算法是否还可以继续优化,然而,可不可以成功优化,这就得看自己的能力了。
前面讲的三个算法都可以通过重复利用一个一维数组迭代写值得到结果。重复利用数组虽然可以节省内存空间,但是用得不好有可能留下BUG,所以要格外小心。不过出于节约内存的考虑,有时候必须要重复利用数组。也存在通过重复利用数组时使用小技巧进一步降低复杂度的问题。

这三道题优化的迭代情况我就不多说了,思想很简单,大家可以自己思考,也可以参考我的代码。
下面我放出这三道题的优化后的代码:

01背包问题:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int rec(int *, int *, int *, int, int);

static int max(int, int);

int main()
{
    int n, W;
    printf("Please input n & W : \n");
    scanf("%d %d",&n,&W);
    int sdp = W+1;
    int * w = (int *)malloc(n * sizeof(int));
    int * v = (int *)malloc(n * sizeof(int));
    int * dp = (int *)malloc(sdp * sizeof(int));
    for (int i = 0; i < n; i++)
    {
        scanf("%d %d",&w[i],&v[i]);
    }
    memset(dp,0,sdp * sizeof(int));
    printf("%d\n",rec(w,v,dp,n,W));
    return 0;
}

int rec(w,v,dp,n,W)
int * w, * v;
int * dp;
int n, W;
{
    for (int i = 0; i < n; i++)
    {
        for (int j = W; j >= w[i]; j--)
        {
            dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
        }
    }
    return dp[W];
}

int max(int i, int j)
{
    if (i > j)
        return i;
    else
        return j;
}

算法时间复杂度为O(n*W),空间复杂度为O(n)。

完全背包问题:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int rec(int *, int *, int *, int, int);

static int max(int, int);

int main()
{
    int n, W;
    printf("Please input n & W : \n");
    scanf("%d %d",&n,&W);
    int sdp = W+1;
    int * w = (int *)malloc(n * sizeof(int));
    int * v = (int *)malloc(n * sizeof(int));
    int * dp = (int *)malloc(sdp * sizeof(int));
    for (int i = 0; i < n; i++)
    {
        scanf("%d %d",&w[i],&v[i]);
    }
    memset(dp,0,sdp * sizeof(int));
    printf("%d\n",rec(w,v,dp,n,W));
    return 0;
}

int rec(w,v,dp,n,W)
int * w, * v;
int * dp;
int n, W;
{
    for (int i = 0; i < n; i++)
    {
        for (int j = w[i]; j <= W; j++)
        {
            dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
        }
    }
    return dp[W];
}

int max(int i, int j)
{
    if (i > j)
        return i;
    else
        return j;
}

同样,算法时间复杂度为O(n*W),空间复杂度为O(n)。

最长递增子序列:

#include <stdio.h>

int len, max[] = {};

int Search(int *, int, int);

int Length(int *, int);

int main()
{
    int size, arr[] = {1,3,5,9,8,2,6,7,4};
    size = sizeof(arr) / sizeof(arr[0]);
    printf("%d\n",Length(arr,size));
}

int Search(max, size, x)
int * max;
int size, x;
{
    int left = 0, right = size-1, mid;
    while(left <= right)
    {
        mid = (left+right)/2;
        if(max[mid] <= x)
            left = mid+1;
        else
            right = mid-1;
    }
    return left;
}

int Length(arr, size)
int * arr;
int size;
{
    max[0] = arr[0];
    len = 1;
    for(int i = 1; i < size; ++i)
    {
        if(arr[i] > max[len-1])
        {
            max[len++] = arr[i];
        }
        else
        {
            int pos = Search(max,len,arr[i]);
            max[pos] = arr[i];
        }
    }
    return len;
}

这个算法在插入值时使用了二分查找,所以算法时间复杂度为O(n*logn),空间复杂度为O(n)。

今天就讲到这里,下次我会给大家带来不一样的思路。
我是算法吹,以后会给大家带来更多精彩的算法。这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值