ToB企服应用市场:ToB评测及商务社交产业平台

标题: 算法筹划与分析实行报告——贪心算法应用 [打印本页]

作者: 泉缘泉    时间: 2025-1-12 02:32
标题: 算法筹划与分析实行报告——贪心算法应用
一、实行目的
1、深入明白贪心计谋的根本头脑。
2、能精确接纳贪心计谋筹划相应的算法,办理现实问题。
  3、把握贪心算法时间空间复杂度分析,以及问题复杂性分析方法
二、实行内容
问题1:用贪心算法编程求解以下使命安排问题
一个单元时间使命是恰恰必要一个单元时间完成的使命。给定一个单元时间使命的有限集S。关于S的一个时间表用于描述S中单元时间使命的执行序次。时间表中第1个使命从时间0 开始执行直至时间1 结束,第2 个使命从时间1 开始执行至时间2 结束,…,第n个使命从时间n-1 开始执行直至时间n结束。
具有截止时间和误时处罚的单元时间使命时间表问题可描述如下。
(1) n个单元时间使命的集合S={1,2,…,n};
(2) 使命i的截止时间di ,1≤i≤n,1≤ di ≤n,即要求使命i在时间di 之前结束;
(3) 使命i的误时处罚wi ,1≤i≤n,即使命i未在时间di 之前结束将招致wi 的处罚;若按时完成则无处罚。
已知:给定的n 个单元时间使命,各使命的截止时间di ,各使命的误时处罚wi ,1≤i≤n,
要求:确定S的一个时间表(最优时间表)使得总误时处罚达到最小。

问题2:用最少硬币数找n分零钱问题
假定有25分,10分,5分,1分四种面额硬币,筹划贪心算法求解,并验证找到最优解。
如果是10分,7分,1分三种面额硬币,以上贪心算法验证是否能找到最优解,阐明缘故原由。


1.贪心计谋的根本头脑贪心计谋的根本头脑是在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致效果是全局最好或最优的算法。贪心计谋在办理问题时,通常将问题分解为一系列的子问题,然后逐个办理子问题,每一步都选择当前状态下的最优解,并期望通过这样的局部最优选择来达到全局最优解。
2.贪心算法(GreedySlector)的根本头脑:贪心算法通过一系列选择来得到问题的解,所做的每个选择都是当前状态下局部最好选择,即贪心选择。贪心算法没有固定的算法框架,算法筹划的关键是贪心计谋的选择。可以用贪心算法求解的问题一样平常具有两个重要的性子:贪心选择和最优子结构。
3.贪心选择性子:贪心选择性子是指,所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个根本要素,也是贪心算法与动态规划算法的重要区别在动态规划算法中,每步所做的选择往往依靠于相关子问题的解。因而只有在解出相关子问题后,才华做出选择。而在贪心算法中,仅在当前状态下做出最好选择,即局部最优选择。再去解做出这个选择后产生的相应的子问题。贪心算法所做的贪心选择可以依靠以往所做过的选择,但决不依靠未来所做的选择,也不依靠子问题的解。正是由于这种差别,动态规划算法通常以自底向上的方式解各子问题,贪心算法则通常以自顶向下的方式进行,以迭代的方式做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择性子,必须证实每步所做的贪心选择最终导致问题的整体最优解,通常可以用类似于证实运动安排问题的贪心选择性子时所接纳的方法来证实。起首观察问题的一个整体最优解,并证实可修改这个最优解,使其以贪心选择开始。做了贪心选择后,原问题简化为规模更小的类似子问题。然后用数学归纳法证实,通过每一步做贪心选择,最终可得到问题的整体最优解。此中,证实贪心选择后的问题简化为规模更小的类似子问题的关键在于,利用该问题的最优子结构性子。
4.最优子结构性子:当一个问题的最优解包罗其子文体的最优解时,称此问题具有最优子结构性子。问题的最优子结构性子是该问题可用动态规划问题或贪心算法求解的关键特性。
5.问题一运动安排问题算法筹分别析:此问题是具有截止时间误时处罚的单元时间使命时间表问题,必要筹划一个贪心算法来只管最小化总误时处罚。这个问题可以看作是一个具有特殊束缚的调度问题,此中每个使命都有固定的开始和结束时间(单元时间),以及对应的截止时间和误时处罚。
使命的执行时间是固定的(一个单元时间);每个使命都有一个截止时间,必要在该时间之前完成;如果使命没有在截止时间之前完成,将会产生误时处罚。一个直观的贪心计谋是按照使命的截止时间进行排序,并优先安排截止时间较早的使命。然而,这并不总是最优的,因为还必要考虑误时处罚的影响;一个更好的计谋大概是团结截止时间和误时处罚来排序使命。


6.问题二用最少硬币数找n分零钱问题的算法筹分别析:假定有25分,10分,5分,1分四种面额硬币,必要给某顾客找零钱,必要拿出硬币数n最少且面值k与找零数额相等面额的硬币找给顾客。找硬币算法为:起首找不凌驾找给顾客面额的且现有最大的硬币,然后找给顾客面额减去现有最大面额算出剩下还需多少面额的硬币,云云一直下去直到找完顾客所有的零钱。本问题可以利用动态规划问题但是用贪心算法更简单。本题关键点:关键点一是每种硬币的个数不限,这意味着可以重复使用每一种硬币;关键点二是最少硬币数找n分零钱。
将其可以看作一个完全背包问题:在完全背包问题中,每个物品都可以无限次地放入背包中,而在这个问题中,每种面值的硬币都可以无限定地使用。我们的目标是找出组成n分零钱所需的最少硬币数目。


1.运动安排问题:
定义使命:对于每个使命i,有一个二元组(di, wi),此中di是截止时间,wi是误时处罚。
使命排序:按照某种贪心计谋对使命进行排序。一种有效的排序计谋是起首按照误时处罚wi从大到小排序,如果两个使命的wi相同,则按照截止时间di从早到晚排序。这样可以确保误时处罚最高的使命有最高的优先级,而且如果有多个使命具有相同的最高误时处罚,则更早截止的使命会被优先安排。
安排使命:按照排序后的顺序实行安排使命。从时间0开始,查抄当前时间是否可以安排下一个使命。如果可以(即当前时间小于使命的截止时间),则安排该使命并更新当前时间为下一个时间点(即当前时间+1)。如果当前时间已经凌驾了使命的截止时间,则跳过该使命(因为它已经错过了截止时间)。
计算总误时处罚:在安排完所有使命后,计算由于错过截止时间而导致的总误时处罚。这可以通过遍历所有使命并累加那些错过截止时间的使命的误时处罚来完成。

源代码:
template <class Type>

void GreedySelector(int n, Type s[], Type f[], bool A[]) {

A[1] = true;

int j = 1;

for (int i = 2; i <= n; i++)

if (s >= f[j]) {

A = true;

j = i;

}

else

A = false;

}

2.用最少硬币数找n分零钱问题
定义状态:设dp为组成金额i所需的最少硬币数目。
初始化:对于所有i(0 <= i < 面值数组中的最小值),dp应初始化为一个不大概的值(比方无穷大),因为小于最小面值的金额无法用硬币表示。对于dp[0],它应初始化为0,因为组成0分不必要任何硬币。
状态转移:对于每个金额i(从面值数组中的最小值开始,直到n),遍历所有硬币的面值coin。如果i - coin是非负的,那么可以选择使用一枚面值为coin的硬币,然后问题转化为找出组成i - coin金额所需的最少硬币数目。选择所有大概的选择中的最小值,并加1(因为我们使用了一枚面值为coin的硬币)。数学表达式为:dp = min(dp, dp[i - coin] + 1)。
效果:dp[n]即为组成n分所需的最少硬币数目。

源代码:
void Knapsack(int n, float M, float v[], float w[], float x[]) {

Sort(n, v, w)

int  i;

for (i = 1, i <= n; i++)

x = 0;

float c = M;

for (i = 1; i <= n; i++) {

if (w > c)

break;

x = 1;

c -= w;

}

if (i <= n)

x = c / w;

}




  • 测试与分析
1.测试代码:
#include <iostream>  

#include <vector>  

#include <algorithm>  


// 定义一个使命的结构体  

struct Task {

    int deadline; // 截止时间  

    int penalty;  // 误时处罚  


    // 排序时使用的比较函数  

    bool operator<(const Task& other) const {

        if (this->penalty != other.penalty) {

            return this->penalty > other.penalty; // 先按误时处罚降序排序  

        }

        return this->deadline < other.deadline; // 误时处罚相同时,按截止时间升序排序  

    }

};


// 贪心算法实现  

std::pair<std::vector<int>, int> greedy_schedule(const std::vector<Task>& tasks) {

    std::vector<Task> sorted_tasks = tasks;

    std::sort(sorted_tasks.begin(), sorted_tasks.end()); // 对使命进行排序  


    int n = sorted_tasks.size();

    std::vector<int> schedule(n, -1); // 初始化时间表为-1表示未安排  

    int total_penalty = 0; // 初始化总误时处罚为0  

    int current_time = 0;  // 当前时间  


    for (int i = 0; i < n; ++i) {

        if (current_time < sorted_tasks.deadline) { // 如果当前时间早于截止时间  

            schedule = current_time; // 安排使命  

            ++current_time; // 更新当前时间  

        }

        else {

            total_penalty += sorted_tasks.penalty; // 错过截止时间,累加误时处罚  

        }

    }


    return { schedule, total_penalty };

}


// 测试代码  

int main() {

    std::vector<Task> tasks = { {3, 10}, {1, 5}, {2, 2}, {4, 7} }; // 使命列表  


    auto result = greedy_schedule(tasks);

    std::vector<int> schedule = result.first;

    int total_penalty = result.second;


    std::cout << "时间表: ";

    for (int time : schedule) {

        std::cout << time << " ";

    }

    std::cout << std::endl;


    std::cout << "总误时处罚: " << total_penalty << std::endl;


    return 0;

}
运行效果:

时间复杂度分析阐明:

1.时间复杂度

排序:使用标准库中的std::sort函数对使命列表进行排序。在最坏情况下,排序算法的时间复杂度是O(n log n),此中n是使命的数目。
安排使命:这个步骤遍历排序后的使命列表一次,为每个使命安排时间或计算误时处罚。因此,这部门的时间复杂度是O(n)。
由于排序步骤的时间复杂度支配了整体时间复杂度,以是整个算法的时间复杂度是O(n log n)。
2.空间复杂度

使命列表:算法必要一个额外的列表来存储输入的使命(如果输入不是直接以列表形式给出的)。这个列表的空间复杂度是O(n),此中n是使命的数目。
时间表:算法创建了一个与使命列表大小相同的时间表,用于存储每个使命的安排时间。这个列表的空间复杂度也是O(n)。
排序:std::sort函数在排序过程中大概必要额外的空间(尽管它通常是原地排序,即不必要额外的空间,但在最坏情况下大概必要O(n)的额外空间)。
这个贪心算法的时间复杂度是O(n log n),空间复杂度是O(n)。



2.测试代码:
#include <iostream>  

#include <vector>  

#include <algorithm>  

#include <climits>  


int coinChange(std::vector<int>& coins, int amount) {

    std::vector<int> dp(amount + 1, INT_MAX); // 初始化dp数组为最大值  

    dp[0] = 0; // 组成0分不必要硬币  


    // 遍历所有大概的金额  

    for (int i = 1; i <= amount; ++i) {

        // 遍历所有硬币面值  

        for (int coin : coins) {

            // 如果当前硬币面值小于等于当前金额i  

            if (coin <= i) {

                // 更新dp,实利用用当前硬币  

                dp = std::min(dp, dp[i - coin] + 1);

            }

        }

    }


    // 如果dp[amount]的值仍然是初始的最大值,阐明无法组成amount分零钱  

    if (dp[amount] == INT_MAX) {

        return -1; // 返回-1表示无法组成  

    }

    else {

        return dp[amount]; // 返回组成amount分所需的最少硬币数目  

    }

}


int main() {

    std::vector<int> coins = { 25, 10, 5 ,1}; // 硬币面值  

    int amount = 14; // 目标金额  

    int result = coinChange(coins, amount);

    std::cout << "组成" << amount << "分所需的最少硬币数目为:" << result << std::endl;

    return 0;

}
运行效果:
14分:

29分:

32分:

时间复杂度分析阐明:

外层循环遍历从1到amount的每个金额i,以是它的时间复杂度是O(amount)。内层循环遍历硬币面值数组coins中的每个硬币面值coin,假设硬币面值的数目是n(即coins.size()),那么内层循环的时间复杂度是O(n)。因此,两层循环的总时间复杂度是外层循环和内层循环时间复杂度的乘积,即O(amount * n)。
10,7 ,1面额    14分:

19分:

98分:
面额:25,10,5,1

第一部门:25分,10分,5分,1分四种面额硬币

对于这个问题,我们可以使用贪心算法来求解,该算法会只管使用面额大的硬币来减少硬币的总数。以下是算法步骤:
1.如果n大于或等于25,则使用25分的硬币,并更新n = n - 25。
2.如果n小于25但大于或等于10,则使用10分的硬币,并更新n = n - 10。
3.如果n小于10但大于或等于5,则使用5分的硬币,并更新n = n - 5。
4.如果n小于5,则使用1分的硬币,并更新n = n - 1。
验证最优解

由于25, 10, 5, 1这四种面额硬币的选取满足贪心选择性子(即局部最优解能导致全局最优解),以是该贪心算法可以找到最优解。
面额:10,7,1

第二部门:10分,7分,1分三种面额硬币

对于这个问题,如果直接使用类似上述的贪心算法,大概无法找到最优解。
算法步骤如下:
1.如果n大于或等于10,则使用10分的硬币,并更新n = n - 10。
2.如果n小于10但大于或等于7,则使用7分的硬币,并更新n = n - 7。
3.如果n小于7,则使用1分的硬币,并更新n = n - 1。
但是,这种贪心计谋在某些情况下大概不是最优的。比方,当n = 14时,按照上述贪心计谋,会使用1个10分硬币和4个1分硬币,总共5个硬币。但是,使用2个7分硬币只必要2个硬币,这是一个更优的解。
缘故原由

对于10分,7分,1分这三种面额硬币,它们不满足贪心选择性子。即局部最优解(每次都选择当前可用的最大面额硬币)不一定会导致全局最优解。因此,对于这类问题,大概必要使用动态规划等其他算法来确保找到最优解。

六,实行总结与体会
经过本次实行,我深入了解了贪心算法在现实问题中的应用,特殊是在办理单元时间使命安排问题和最少硬币数找零钱问题中的有效性。
在单元时间使命安排问题中,我接纳了先按误时处罚降序排序,再按截止时间升序排序的贪心计谋。这种计谋确保了高误时处罚的使命优先被安排,而且只管在截止时间前完成。通过编程实现这一算法,我成功地找到了一个总误时处罚最小的使命时间表。这一过程中,我体会到了贪心算法在办理优化问题时的直观性和有效性。
在最少硬币数找零钱问题中,我起首使用了贪心算法来实行办理。对于25分、10分、5分和1分的硬币组合,贪心算法可以或许找到最优解。然而,当硬币组合变为10分、7分和1分时,我意识到贪心算法大概不再适用。这是因为某些情况下,贪心计谋选择的硬币组合并不是最优的。这一发现让我对贪心算法的适用性和范围性有了更深刻的明白。
通过这次实行,我深刻体会到了算法筹划的重要性。不同的算法适用于不同的问题,而选择合适的算法是办理问题的关键。贪心算法作为一种直观且有效的优化算法,在办理某些问题时可以或许取得很好的效果。但是,它也有其范围性,不能适用于所有问题。因此,在现实应用中,我们必要根据问题的特点选择合适的算法。

别的,实行也让我认识到了编程实践的重要性。通过编程实现算法,我可以或许更深入地明白算法的原理和实现过程。同时,编程实践也让我发现了本身在算法筹划和编程本领方面的不足,为我今后的学习和进步指明白方向。
最后,我想说的是,实行是一个不断学习和进步的过程。通过不断地实践和总结,我们可以或许不断进步本身的算法筹划和编程本领,为办理更复杂的问题打下坚固的底子。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4