切金条问题(贪心思想+哈夫曼树+小根堆使用)

问题描述

假设将一根金条切成两半,是需要花费和金条长度相同的钱。比如,一根80cm的金条,切成两半,需要花80块钱。现在,一群人想分一整块金条,每个人分得的长度保存在一个数组中,例如[10,20,30]意思就是将一根60cm的金条,划分成10,20,30三段,请问,怎样切分最省钱?
例如:
先将金条切分成10和50,需要60块钱,然后,再将50切分成20和30,需要50块,总共需要110块钱。
又或者,先将金条切分成30和30,花60块钱,然后再将30切分成10和20,花30块钱,总共就是90块钱。
90<110,所以第二种划算。

思路分析

首先,观察题目,可以发现,每切一次,只能切出来一段最终结果,另一端肯定还有继续切割,这一段最终结果是不需要再次切分了,也就说明,后面切分时就不需要再算这一段的钱了,因此,这一段最终结果的数值越大,总的代价就越小。也就是说,我们先把大的切出来,越省钱,大的不会再次计算价钱了,越小的,我们最后切出来。
因此,从上面分析出来的结果可以看出,其实,本质就是,减少数值大的遍历次数,增加数值小的遍历次数,这样总体代价最低。
哈夫曼树
这样的贪心算法有一种数据结构能够轻松实现,那就是哈夫曼树,哈夫曼树的构建过程,就是先把最小的两个相连,然后组成一个新节点,然后放入总的集合中,再从中找出最小的两颗子树再次相连,再将结果放入集合中,最终集合中只剩下一棵树,这棵树就是哈夫曼树。这道题其实就是哈夫曼树的应用。
小根堆
此题用哈夫曼树解决,为什么会提到小根堆呢?因此,哈夫曼树的构建过程,每次需要将两个子树合成的结果再次放入集合中,然后,再从集合中取出两个最小值。这个过程,如果用常规数组的话,每次放入后,是不是还需要再次排序呀?这个再次排序的过程,是不是小根堆效率很好啊?只需要调整小根堆就行了,最多logn,如果是一般方法,是不是每次都需要重新排序,效率极低的。
最终解题思路
先将数组构建成小根堆,然后,从小根堆中弹出两个。把这两个加在总和money上,然后这两个数相加,相加后,再次入小根堆。然后再从中弹出两个最小值,重复上述操作,直到小根堆中只剩一个数,结束,计算money。

代码

小根堆代码

public class littleHeap {

    int[] arr;
    int index;//堆中的最后一个元素的索引

    public littleHeap(int[] a) {
        arr = new int[a.length * 2];
        index = 0;
        for (int i = 0; i < a.length; i++) {
            arr[++index]=a[i];
            insert(arr,index);
        }
    }

    //弹出堆顶,并调整小根堆
    public int getTop(){
        if(index<1){
            System.out.println("见底了");
            return 0;
        }else if(index==1){
            index--;
            return arr[1];
        }else {
            int temp=arr[1];
            arr[1]=arr[index];
            arr[index]=temp;
            index--;
            int left=2;
            int father=1;
            while (left<=index){
                int right=left+1;
                int min;
                if(right>index){
                    min=left;
                }else {
                    min=arr[left]<arr[right]?left:right;
                }
                if(arr[min]<arr[father]){
                    temp=arr[min];
                    arr[min]=arr[father];
                    arr[father]=temp;
                    father=min;
                    left=father*2;
                }else {
                    break;
                }
            }
            return arr[index+1];
        }
    }

    //插入一个数
    public void insert(int num){
        arr[++index]=num;
        insert(arr,index);
        if(index==arr.length-1){
            int[] newArr=new int[arr.length*2];
            for (int i = 1; i < arr.length; i++) {
                newArr[i]=arr[i];
            }
            arr=newArr;
        }
    }

    private void insert(int[] arr, int index) {
        int father=index/2;
        while(father>=1&&arr[father]>arr[index]){
            int temp=arr[father];
            arr[father]=arr[index];
            arr[index]=temp;
            index=father;
            father=index/2;
        }
    }
    
}

本题代码

        int[] arr=new int[]{20,10,30};
        littleHeap heap=new littleHeap(arr);
        int money=0;

        //index表示堆中末尾索引
        while (heap.index>1){
            int min1=heap.getTop();
            int min2=heap.getTop();
            money+=min1+min2;
            heap.insert(min1+min2);
        }
        System.out.println("最小花费为:"+money);

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值