小于n的最大数 - 贪心算法 - 附C++实现代码

一、问题描述?

        给定一个整数n,并从1~9中给定若干个可以使用的数字,根据上述两个条件,得到每一位都为给定可使用数字的、最大的小于整数n的数。

        例如,给定可以使用的数字为 {2,3,8} 三个数:

        给定 n=3589,输出3388;给定 n=8234,输出8233;…… 

二、解题思路

1.问题分析

        如果一个数小于另一个数,那么较小的那个数一定有至少一个数量级的位是小于大数的。而为了取到小于n又尽可能大的值,我们总是希望能够在数量级尽可能小的位置小于n。

        假定可以使用的数字为 {2,3,8} ,在尝试过程中,我们可以发现,结果有以下几类:

        1> 高位相等,最后一位取到了小于的值。例如:给定 n=8234,除最后一位外,我们都能够取到相等的值,最后一位也刚好能够取到一个小于n个位的值,输出8233;

        2> 高位相等,中间一位取到了小于的值。例如:给定 n=8534,在第二位,我们已经只能取到小于n的数了,此时,数量级更小的位也随之确定,均为给定的最大数字,输出8388;

        3> 在高位就已经无法相等,但最高位能取到更小的值。这种情况相当于第二种情况的高位位数退化到0。例如:给定 n=5555,将会输出3333;

        4> 在高位就已经无法相等,且最高位也无法取到更小的值。此时,与n相同的数量级内已经不存在解,我们向下一个数量级输出最大的位数。例如:给定 n=1999,将会输出888;

2.算法选择

        从上面的分析中不难看出,我们总是寄希望于在数量级尽可能小的位里才使我们的结果小于n,对应的,尝试使用贪心算法+回溯来求解这个问题。

3.运算示例

        给定可以使用的数字为 {2,3,8}:

        n1 = 2338,贪心,从高往低取:2 - 3 - 3 - ;此时到达最后一位,尝试取更小的,- 3;

        n2 = 2399,贪心,从高往低取:2 - 3 - ;此时只能取更小的,- 8;剩余位数均为 - 8;

        n3 = 2381,贪心,从高往低取:2 - 3 - 8 - ;此时无法取更小的,回溯,上一位能取到更小值,改 - 8 为 -3,后接max值:2 - 3 - 3 - 8;

        n4 = 1999,贪心,最高位就无法取更小的,此时已经无需回溯也无法回溯,直接确认为少一数量级(第六段中有相关番外),得到8 - 8 - 8;

        n5 = 2222,贪心,从高往低取:2 - 2 - 2 - ;此时到达最后一位,必须取更小值,已经无法取得,回溯,上一位无法取到更小值,继续回溯,仍无法取得更小值,继续回溯,无法取得,此时已经到达首位,无法再回溯,到此时,确认为少一个数量级,得到8 - 8 - 8;

4.数据结构

        1>功能:记录给定数字;                                           对应结构:vector;

        2>功能:记录结果;                                                   对应结构:vector;

        3>功能:拆分给定 n 并记录;                                    对应结构:vector;

        4>功能:从给定数字集中快速查找目标是否存在;    对应结构:map :unordered_set;

三、伪代码与接口

//-----接口1-----//
//@brief  : 从给定的集合中获取小于指定值的数
//@param  : 一个已经从大到小排序的集合, 指定值
//@return : 若集合中存在合法结果,返回给定值;否则返回0;
int getMaxDigitLessThanGivenDigit(const vector<int>& digits, int d) {
    //从大到小遍历
    {
        //若找到小于指定值的数,返回该数
    }
    //若遍历无结果,返回0
}

//-----接口2-----//
//@brief  : 从给定的集合中获取小于指定值的数
//@param  : 一个已经从大到小排序的集合, 指定值
//@return : 若集合中存在合法结果,返回给定值;否则返回0;
int getMaxNumLessThanGivenN(vector<int>& digits, int n) {
    //拆分n为单位数形式

    //排序给定值

    //定义vector格式结果

    //构建快速查找map

    //从高位往低位遍历n,根据遍历到的每一位,尝试确定结果
    {
        //对非最高位,首先尝试给定集合中是否能取到相同值
        {
            //若能取到相同值,先将结果中相同位贪心为相同值,然后继续查找下一位
        }

        //若不能取到相同值,调用接口1尝试是否能取到较小值
        {
            //若能取到较小值,赋值该位为较小值,接着后续位数均为给定值中的最大值,跳出遍历
        }

        //若不能取到较小值,需要回溯
        //若已经是最高位,无法进行回溯
        {
            //定义结果为小一数量级,跳出遍历
        }

        //进行回溯,向高一位移动
        {
            //每次回溯,先将旧结果(失效值)抹去
            
            //此时只能找较小值(相等值已经贪心过了),调用接口1尝试回溯位是否能取到较小值
            {
                //若能取到较小值,赋值该位为较小值,接着后续位数均为给定值中的最大值,跳出遍历
            }

            //若已经回溯到了最高位,仍未找到较小值,此时可以断定需要减少一个数量级
            {
                //定义结果为小一数量级,跳出遍历
            }
        }

        //若回溯已经执行完但仍执行到了这里,强制跳出遍历,否则会发生死循环(指针在高低位往返移动)
    }

    //转换单位数格式的结果为数据的结果,返回
}

//-----main-----//
//@brief  : 调用接口2并传参
//@param  : 
//@output : 输出结果
//@return : 

四、C++代码

#include <iostream>
#include<vector>
#include<unordered_set>
#include<algorithm>

using namespace std;

//Get max digit which is less than d from given digits which is sorted from big to small.
int getMaxDigitLessThanGivenDigit(const vector<int>& digits, int d) {
    for (auto it = digits.rbegin(); it < digits.rend(); it++) {   //Searching from big to small.
        if (*it < d) {   //The first one to be less than d is the max one among the less set.
            return *it;
        }
    }
    //If miss, return 0;
    return 0;
}

//Get max num consisted of digits in vector digits. The num is expected to be less than n.
int getMaxNumLessThanGivenN(vector<int>& digits, int n) {
    //-----Condition due and result delare.-----//
    //Devide n into digits.
    vector<int> n_digits;
    for (; n > 0; n /= 10)
        n_digits.push_back(n % 10);

    //Sort given digits.
    sort(digits.begin(), digits.end());

    //Declare target digits. Format {0,0,...,0}. Target format is {0,0,...,0,1,2,3}. At the end, 0 will be replace by max digit given.
    vector<int> t_digits(n_digits.size(), 0);

    //-----Construct auxiliary variable.-----//
    //Make map for fast consistence judge.
    unordered_set<int> set;
    for (auto di : digits)
        set.insert(di);

    //Begin to fill target digits. From high to low.
    for (int i = n_digits.size() - 1; i >= 0; i--) {
        //Case 1 : For those who are not highest, chioce the same num if exists.
        if (i > 0 && set.count(n_digits[i])) {  //Exist.
            t_digits[i] = n_digits[i];  //This digit use it temp, continue to go through.
            continue;
        }

        //Case 2 : If not exist same num, try to get max digit which is less than n's current digit.
        int less_d = getMaxDigitLessThanGivenDigit(digits, n_digits[i]);

        //If got one num less than n's current digit, the reslt will be sure and there is no need to continue traverse.
        if (less_d > 0) {
            t_digits[i] = less_d;
            break;   //Format {0,0,...,0,1,2,3}. 0 is excepted to be replaced by max digit given.
        }

        //Case 3 : If there is no digit less than n's current digit, flash back.
        //-----Boundary condition : include Method 1 and Method 2, only use one.-----//
        //Method 1 : if hightest, directly resize.
        if (i == n_digits.size() - 1) {
            t_digits.resize(i);
            break;
        }
        //Method 2 : move cursor only not in hightest. If not highest, move back the cursor.
        else {
            i++;
        }

        for (; i < n_digits.size(); i++) {
            //If the digit is flashed back, corresponding t_digits need to be clear. Reassign it 0.
            t_digits[i] = 0;

            //Try to be smaller while flashing back. Turn to case 2.
            less_d = getMaxDigitLessThanGivenDigit(digits, n_digits[i]);
            if (less_d > 0) {
                t_digits[i] = less_d;
                break;   //Format {0,0,...,0,1,2,3}. 0 is excepted to be replaced by max digit given.
            }

            //If fail to lessen this digit but this digit isn't the highest, just continue to flash back.

            //But if this digit is the highest, it means there are no answer in the same order of magnitude.
            if (i == n_digits.size() - 1)
                t_digits.resize(i);
        }

        //If Case 3 finished, the result has been sure here. Stop. Otherwise endless looping will happen.
        break;
    }

    //Turn target from digit format to num format.
    int target = 0;

    //From high to low, replace 0 to max.
    for (int t_d = t_digits.size() - 1; t_d >= 0; t_d--)
        target = 10 * target + (t_digits[t_d] == 0 ? digits.back() : t_digits[t_d]);

    return target;
}

int main()
{
    cout << "Testing begin..." << endl;

    //Unordered for sort test.
    vector<int> digits = { 2,8,3 };
    cout << "Given digits is:  ";
    for (auto it : digits)
        cout << it << "   ";
    cout << endl;

    //Case 1 : Smaller in last, same the head. Can be same but avoid. n = 2338.
    cout << "n = 2338   excepted output : 2333   actually output : " << getMaxNumLessThanGivenN(digits, 2338) << endl;

    //Case 2 : Smaller in last, same the head. Get smaller. n = 2339.
    cout << "n = 2339   excepted output : 2338   actually output : " << getMaxNumLessThanGivenN(digits, 2339) << endl;

    //Case 3 : Smaller in middle, same the head, tail the max, no need to flash back. n = 2349.
    cout << "n = 2399   excepted output : 2388   actually output : " << getMaxNumLessThanGivenN(digits, 2399) << endl;

    //Case 4 : Smaller in middle, same the head, tail the max, need to flash back. n = 2381.
    cout << "n = 2381   excepted output : 2338   actually output : " << getMaxNumLessThanGivenN(digits, 2381) << endl;

    //Case 5 : Smaller in order, no need to flash back. Fail in head. n = 1999.
    cout << "n = 1999   excepted output : 888    actually output : " << getMaxNumLessThanGivenN(digits, 1999) << endl;

    //Case 6 : Smaller in order, need to flash back. Fail not in head. n = 2221.
    cout << "n = 2221   excepted output : 888    actually output : " << getMaxNumLessThanGivenN(digits, 2221) << endl;

    //Case 7 : Smaller in order, need to flash back. Special test. n = 2222.
    cout << "n = 2222   excepted output : 888    actually output : " << getMaxNumLessThanGivenN(digits, 2222) << endl;

    return 0;
}

五、测试结果

        根据对应的几种可能情况进行测试:

六、番外:测试中发现缺少边界条件与查错

        第一次调试时,回溯漏了边界条件的判定,只要发生回溯,就直接把指针指往上一位,对应的C++代码段为:for (i++; i < n_digits.size(); i++),缺少了“若已经是最高位,无法进行回溯”的边界条件。这样一来,当最高位就确定出结果为减数量级情况时,无法唤起 resize,导致边界条件出错:

        检查后做了对应的边界限制,若第一位就直接唤起回溯时不会移动指针,而是直接确认结果。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,下面是使用C++实现的0-1背包问题的分支限界算法: ```c++ #include <iostream> #include <queue> #include <algorithm> using namespace std; // 物品结构体 struct Item { int weight; // 物品重量 int value; // 物品价值 int level; // 物品层级 double bound; // 当前状态下的上界 }; // 用于比较两个物品的上界大小 struct cmp { bool operator()(Item a, Item b) { return a.bound < b.bound; } }; // 用于存储当前最优解 int max_value = 0; // 用于计算当前状态下的上界 double upper_bound(int capacity, int weight, int value, int num, Item items[]) { double bound = value; int i = num; // 从当前状态的下一个物品开始计算 while (weight < capacity && i < n) { if (weight + items[i].weight <= capacity) { weight += items[i].weight; bound += items[i].value; } else { int remain = capacity - weight; bound += items[i].value * ((double) remain / items[i].weight); break; } i++; } return bound; } // 分支限界算法 void knapsack(int capacity, int weight, int value, int num, Item items[]) { // 创建优先队列,用于存储待扩展的节点 priority_queue<Item, vector<Item>, cmp> pq; Item root = {0, 0, 0, 0}; root.bound = upper_bound(capacity, weight, value, 0, items); pq.push(root); while (!pq.empty()) { Item node = pq.top(); pq.pop(); // 如果当前节点的上界小于当前最优解,则直接跳过 if (node.bound < max_value) continue; // 如果已经到达叶子节点,则更新最优解并返回 if (node.level == n) { max_value = node.value; continue; } // 分别扩展左右子节点 Item left = {weight + items[node.level].weight, value + items[node.level].value, node.level + 1, 0}; left.bound = upper_bound(capacity, left.weight, left.value, left.level, items); Item right = {weight, value, node.level + 1, 0}; right.bound = upper_bound(capacity, right.weight, right.value, right.level, items); // 如果左子节点的上界大于当前最优解,则入队 if (left.bound >= max_value) pq.push(left); // 如果右子节点的上界大于当前最优解,则入队 if (right.bound >= max_value) pq.push(right); } } int main() { // 读入数据 int capacity, n; cin >> capacity >> n; Item items[n]; for (int i = 0; i < n; i++) { cin >> items[i].weight >> items[i].value; } // 按照价值密度排序 sort(items, items + n, [](Item a, Item b) { return a.value * b.weight > b.value * a.weight; }); // 调用分支限界算法求解 knapsack(capacity, 0, 0, 0, items); // 输出最优解 cout << max_value << endl; return 0; } ``` 其中,`Item`结构体表示一个物品,包含物品的重量、价值、层级和当前状态下的上界。`cmp`结构体用于比较两个物品的上界大小,以便优先队列能够按照上界从大到小的顺序出队。`upper_bound`函数用于计算当前状态下的上界,这里使用贪心策略计算。 在`knapsack`函数中,我们首先创建一个优先队列,并将根节点压入队列中。然后不断地从队列中取出节点进行扩展,直到队列为空。在扩展节点时,我们首先判断当前节点的上界是否小于当前最优解,如果是,则直接跳过该节点;否则,如果当前节点已经到达叶子节点,则更新最优解并返回;否则,我们分别扩展当前节点的左右子节点,并计算它们的上界。如果左子节点的上界大于当前最优解,则将其入队;如果右子节点的上界大于当前最优解,则将其入队。这样,我们就能够逐步扩展状态空间树,直到找到最优解或者搜索完整个状态空间树。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值