基于C++的钻石金字塔问题算法设计

算法设计与分析 钻石金字塔问题

一、 问题描述

在这里插入图片描述

现在你的任务是从金字塔的顶端向金字塔的底端收集钻石,并且尽可能收集价值高的钻石,但是只能从一块砖斜向左下或斜向右下走到另一块砖上,如从上图从用红色 A 标记的砖走向用蓝色 B 标记的砖上。富翁希望 heimengnan 找到一个收集最高价值钻石的路线,并且把可能收集的最大价值告诉富翁。

二、 算法描述及思考过程

拿到矿工问题,我第一个想法就是贪心算法,从上往下,每次都取大的。但是遇到以下状况时,贪心算法取不到最优解。最优解应该为 5+1+100=106,但是贪心算法求的是 5+10+2=17。

在这里插入图片描述

第二个想法是先遍历一遍,找到该金字塔中价值最大的位置,再根据它往上往下找,但是这样复杂度太高,而且也不能保证取得最优解。

所以想到了用分治法。

在这里插入图片描述

如图,比如现在要填价值为“3”这个位置的和,则判断下它的左边和右边哪个价值和更大,则 选 择 哪 边 , 那 么 3 这 个 位 置 的 和 则 为 3+ 更 大 的 一 方 。 即 SUM[i]max=A[i]+max{SUM[left],SUM[right]};//SUM 为存储此位置能拿到的最大价值,A 为原金字塔数组。

2.1 代码如下:

int firstSolution(int A[],int top,int nf,int n,int sf,int sum[])
/*A[]-金字塔原数组,top-当前处于塔顶元素的序号,nf-当前 top 元素所处层数,n-金字塔元素总数,sf-金字塔总层数,sum-当前求得和*/
{   int left,right;
    if(top>=n-sf+1&&top<=n)//top 元素位于最后一行  	  	return(sum[top]=A[top]);  	else
    {
        left=firstSolution(A,top+nf,nf+1,n,sf,sum);
        right=firstSolution(A,top+nf+1,nf+1,n,sf,sum);
//和等于 top 处元素和左右之间大的那个相加
        if(left>right)
        {
            return(sum[top]=left+A[top]);
        }  	 	else
        {
            return(sum[top]=right+A[top]);
        }
    }
}

因为还要寻找路径,所以根据 sum 数组的值,查找路径。

2.2 代码如下:

void findway(int sum[],int A[],int sf)
{
    int *way;
    way=new int[sf+1];
    int k=1;
    for(int i=1; i<=sf; i++) //i 为当前层数
    {
        if(sum[k+i]>=sum[k+i+1])//左边大
        {
            way[i]=A[k+i];
            k+=i;
        }
        else
        {
            way[i]=A[k+i+1];
            k+=i+1;
        }
    }
    way[0]=A[1];//第一个必须经过   cout<<"路径"<<endl;
    for(int j=0; j<sf; j++)
    {
        cout<<way[j]<<" ";
    }
    cout<<endl;
}

运行结果如下:

在这里插入图片描述

结果是正确的。

通过之前几次测试也可知,递归算法在相同时间复杂度时所耗费的时间远大于非递归算法,所以要想办法消除递归。

分治法的递归是从头向下走,递归深入金字塔,再把值返回来比较,所以要消除递归,我们可以从底下往上算。此时的复杂度为 O(n)。

2.3 代码如下:

int secondSolution(int A[],int n,int sf,int sum[]) //消除递归,从底往上走
//*A[]-金字塔原数组,n-金字塔总元素总数,sf-金字塔总层数,sum-当前求得和*/
{   int i;
    int nf;//当前层数
    for(i=(sf*(sf-1)/2)+1; i<=n; i++) //最后一行
    {
        sum[i]=A[i];
    }
    nf=sf-1;
    for(i=sf*(sf-1)/2; i>=1; i--) {
        if(sum[i+nf]>=sum[i+nf+1])
        {
            sum[i]=A[i]+sum[i+nf];
        }
        else
        {
            sum[i]=A[i]+sum[i+nf+1];
        }
        if(i==(nf*(nf-1)/2)+1)
        {
            nf--;//到了每行开头,则行数-1,因为下一个元素就是上一行的了
        }
    }
    return sum[1];
}

运行结果如下:

在这里插入图片描述

运行结果正确。

此算法和张舒阳同学在课上讲的 betterpyramid 算法基本一致,所以借鉴她之后的优化,可以降低空间复杂度,即直接把算出来的 sum 存到 A 数组中,因为不会再回头,所以 A 中用过的元素不会再用第二遍,所以可以直接替换掉。大大降低了空间复杂度,也省去了填充最后一行的 sum 数组,时间复杂度变为 O(n-sf)

2.4 代码如下:

int thirdSolution(int A[],int n,int sf)//直接存 A 数组中,不用 sum 数组
{
    int i,nf;
    nf=sf-1;
    if(sf==1)//只有一层
    {   return A[1];
    }    else
    {
        for(i=n-sf; i>=1; i--)
        {
            if(A[i+nf]>=A[i+nf+1])
            {
                    =A[i]+A[i+nf];
            }  	 	 	else
            {
                    =A[i]+A[i+nf+1];
            }
            if(i==(nf*(nf-1)/2)+1)
            {
                nf--;//到了每行开头,则行数-1,因为下一个元素就是上一行的了
            }
        }
    }
    return A[1];
}

运行结果如下:

在这里插入图片描述

运行结果正确。

三、 时间复杂度测试:

在这里插入图片描述

根据数据得出以下折线图:

在这里插入图片描述

经几次测试发现,在层数逐渐增大时,分治法所用时间明显增加,斜率也十分大。可见其时间复杂度之高。

于是抛弃分治法,用了更高的重复次数和更多的层数测试后两个算法之间的差距。

测试了几组,发现因为差距不大,用 s 作为单位观察不出差距,所以把单位换成 ms。

得到数据如下:

在这里插入图片描述

根据数据得到如下折线图:

在这里插入图片描述

发现两个算法增长的斜率几乎一致,但是仍在层数增大时花费时间差增大,因为差的是 O(sf),和层数有关。

四、 测试截图:

包括分治法,重复次数为 1000 次
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值