寻找和为定值的两个数/多个数

时间:2021-04-25 20:31:34

一、寻找和为定值的两个数

1、若无序,先排序后二分,时间复杂度总为O(n*logn),空间复杂度为O(1)

2、扫描一遍X-S[i]  映射到一个数组或构造hash表,时间复杂度为O(n),空间复杂度为O(n)

3、两个指针两端扫描(若无序,先排序后扫描),时间复杂度最后为:有序 O(n),无序O(n*logn+n)=O(n*logn),空间复杂度都为O(1)。

 

 

二、寻找和为定值的多个数

回溯法

下面是n=3时的0-1背包问题用完全二叉树表示的解空间:

寻找和为定值的两个数/多个数

 

递归回溯:

回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。

void backtracking (int t)

{

    if (t > n) {

       // 到达叶子结点,将结果输出

       output (x);

    }

    else {

       // 遍历结点t的所有子结点,即枚举t所有可能的路径   

      // f(n,t)=下界;g(n,t)=上界;

       for (int i = f(n,t); i <= g(n,t); i ++ ) {//

           x[t] = h[i];//满足界限函数和约束函数

           // 如果不满足剪枝条件,则继续遍历,进入下一层

           if (constraint (t) && bound (t)) 

              backtrack (t + 1);

       }

    }

}

t是递归深度;
n是深度控制,即解空间树的的高度;
可行性判断有两方面的内容:不满约束条件则剪去相应子树;若限界函数越界,也剪去相应子树;两者均满足则进入下一层;

 

 

实例:

问题描述:有n件物品和一个容量为c的背包。第i件物品的价值是v[i],重量是w[i]。求解将哪些物品装入背包可使价值总和最大。所谓01背包,表示每一个物品只有一个,要么装入,要么不装入。
     今天下午的算法复习课,老师提的各种算法经典问题时,出现频率就是01背包问题了!动态规划、回溯法、分支限界法,在贪心算法时也提到注意背包问题,当然01背包问题不能用贪心算法实现,不能保证能得到最优解。回溯法是最近学的,所以试着用C语言将其实现了下,下面作以分析,后期将会继续用其他两种算法实现01背包问题,并做比较。
   回溯法:01背包属于找最优解问题,用回溯法需要构造解的子集树。在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树时,先计算上界函数,以判断是否将其减去,剪枝啦啦!
   上界函数bound():当前价值cw+剩余容量可容纳的最大价值<=当前最优价值bestp。
   为了更好地计算和运用上界函数剪枝,选择先将物品按照其单位重量价值从大到小排序,此后就按照顺序考虑各个物品。

#include <stdio.h>
#include <conio.h>

int n;//物品数量
double c;//背包容量
double v[100];//各个物品的价值
double w[100];//各个物品的重量
double cw = 0.0;//当前背包重量
double cp = 0.0;//当前背包中物品价值
double bestp = 0.0;//当前最优价值
double perp[100];//单位物品价值排序后
int order[100];//物品编号
int put[100];//设置是否装入

//按单位价值排序
void knapsack()
{
    int i,j;
    int temporder = 0;
    double temp = 0.0;

    for(i=1;i<=n;i++)
        perp[i]=v[i]/w[i];
    for(i=1;i<=n-1;i++)
    {
        for(j=i+1;j<=n;j++)
            if(perp[i]<perp[j])//冒泡排序perp[],order[],sortv[],sortw[]
        {
            temp = perp[i];
            perp[i]=perp[i];
            perp[j]=temp;

            temporder=order[i];
            order[i]=order[j];
            order[j]=temporder;
            temp = v[i];
            v[i]=v[j];
            v[j]=temp;

            temp=w[i];
            w[i]=w[j];
            w[j]=temp;
        }
    }
}

//回溯函数
void backtrack(int i)
{
    double bound(int i);
    if(i>n)
    {
        bestp = cp;
        return;
    }
    if(cw+w[i]<=c)
    {
        cw+=w[i];
        cp+=v[i];
        put[i]=1;
        backtrack(i+1);
        cw-=w[i];
        cp-=v[i];
    }
    if(bound(i+1)>bestp)//符合条件搜索右子数
        backtrack(i+1);
}

//计算上界函数
double bound(int i)
{
    double leftw= c-cw;
    double b = cp;
    while(i<=n&&w[i]<=leftw)
    {
        leftw-=w[i];
        b+=v[i];
        i++;
    }
    if(i<=n)
        b+=v[i]/w[i]*leftw;
    return b;

}


int main()
{
    int i;
    printf("请输入物品的数量和容量:");
    scanf("%d %lf",&n,&c);
    printf("请输入物品的重量和价值:");
    for(i=1;i<=n;i++)
    {
        printf("第%d个物品的重量:",i);
        scanf("%lf",&w[i]);
        printf("价值是:");
        scanf("%lf",&v[i]);
        order[i]=i;
    }
    knapsack();
    backtrack(1);
    printf("最有价值为:%lf\n",bestp);
    printf("需要装入的物品编号是:");
    for(i=1;i<=n;i++)
    {
        if(put[i]==1)
            printf("%d ",order[i]);
    }
    return 0;
}

算法复杂度分析:上界函数bound()需要O(n)时间,在最坏的情况下有O(2^n)个右子结点需要计算上界,回溯算法backtrack需要的计算时间为O(n2^n)。