【算法学习笔记】贪心算法

一、贪心算法

1. 贪心算法的特点是:

-分阶段逐步构建解决方案。

-在每一次选择中,总是做出当前看来最好的选择

-不考虑已经做出选择,也不在后期修改它们。

-需要定的一个目标或最优解。

优势:高效,易于设计,易于实施
缺点:可能无法达到最佳解决方案,可能找不到解决方案,即使它存在,如果无法证明最优性,这是一种不完美的方法。

2. 贪心算法的步骤和要求:
- 设计一份候选名单以形成解决方案。
- 确定已使用的候选名单。
- 设计一个解决方案函数来知道一组候选何时是问题的解决方案
- 设计可行性标准(当一组候选可以扩展以形成最终解决方案时)
- 设计一个最有希望的候选函数作为解决方案的一部分
- 有一个目标最小化/最大化函数。

3. 贪心算法模板

Function S = Voracious (candidate vector C)
S = Ø
While (C! = Ø) and (S is not a solution) do:
    x = C candidate selection
    C = C \ {x}
    If it is feasible (S U {x}) then S = S U {x}
End-While

If S is solution then
    Return S
in another case
    Return "There is no solution"

二、经典问题

1. 找零问题

1. 问题描述:
        假设我们在一台机器上买了一杯饮料,付款后,它必须以最少的硬币数返回零钱 n,那么机器应该遵循什么算法?

2. 问题分析:   

贪心算法设计:     

        - 候选名单:可能返回的硬币
        - 已经使用过的候选列表:已选择的返回硬币
        - 解决方案函数:所选货币的总和是必须返回的确切金额。
        - 选择方案函数:所选货币相加且不超过返回金额的最高值的货币。
        - 可行性标准:当前选择的硬币总和不超过要退回的金额。
        - 目标函数:最小化数量 n 返回的硬币数量。

3. 算法实现(伪代码)

Function S = Voracious (candidate vector C)
S = Ø; s = 0;
While (s! = N) do:
    x = the largest element of C such that s + x <= n
    If there is no such element, then
        Return "I can't find the solution"
    End If
    S = S U {a coin of value x}
    C = C \ {x}
    s = s + x
End-While
Return S

C 是机器的硬币集合,S 是要返回的硬币集合,s 是这些硬币的总和。 返回零钱 n。 

2. 最小生成树(Prim算法 & Kruskal算法)

1. 问题描述:

  • 设G =(V,E)是无向连通带权图,即一个网络。(V是顶点集合,E是边集合)
  • 如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成树上各边权的总和称为该生成树的耗费
  • 在G的所有生成树中,耗费最小的生成树称为G的最小生成树

2. 问题分析:

有一个加权的、连通的、无向图。取一个部分图(相同的节点,边的子集),不是环并且连接所有节点。 要求:边的权重之和最小。

        2.1. Kruskal算法:

        最初,解决方案将是一个空的边集。在每一步中,我们将通过从候选列表的边中选择最低的边来构建最终解决方案。如果所述边没有与那些已经选择的边形成循环,它将被添加到解决方案中。 否则,它将被丢弃。由于在每个步骤中我们都在选择最低的边,因此为了方便我们可以先把所有的边排序好,以提高效率。

贪心算法设计:

        - 候选名单:图的边。
        - 已经使用过的候选列表:从原始图中选择的边,无论它们是否已添加到解决方案中。
        - 解决方案函数:选中的边连接图的所有节点,不形成环。
        - 选择方案函数:选择成本最低的边。
        - 可行性标准:所选边的集合不形成环。
        - 目标函数:最小化形成解的边的之和。

2.2. Prim算法:

        解决方案,就像在 Kruskal 中一样,是一组边,但是,Prim 的总体思路不是选择边,而是选择节点。最初,解决方案将是一组空边,我们将使用一个初始节点作为候选节点。
在每一步中,我们将通过选择一个节点将其与我们已经选择的节点连接来构建最终解决方案,条件是连接该节点与一些已经选择的节点的边具有最小成本。

贪心算法设计:

        - 候选名单:原始图的节点。
        - 已经使用过的候选列表:用于获取形成解决方案的边的节点。
        - 解决方案函数:选中的节点数等于图中的节点数。。
        - 选择方案函数:选择未使用的节点,使得将节点与已使用节点连接的边的成本最小。
        - 可行性标准:所选节点中不应有环。 因为我们总是用选择函数选择一个新节点,所以总是满足可行性标准。
        - 目标函数:最小化形成解的边的之和。

3. 算法实现(伪代码)

        3.1. Kruskal算法:

ALGORITHM T = Kruskal (graph G = (V, A))
Sort A in increasing order of weights
N = Number of vertices in V
T = Ø // T is the Solution to build
Repeat:
    a = Shortest edge of A not considered
    A = A \ {a}
    If (T U {a}) does not form cycles in V then
        T = T U {a}
Until the number of edges in T is equal to N-1
Return T

算法效率:O(|V|^2)

        3.2. Prim算法:

ALGORITHM T = Prim (graph G = (V, A))
B = {any element of V} // Candidates used
T = Ø // Solution to create
While (| B | ¡= | C |) do:
    Select a node v such that there exists an edge a = (b, v), of minimum weight,                 that joins a node b in B and another node v in V \ B
    T = T U {a}
    B = B U {v}
End-While
Return T

算法效率: O(|A|*log(|V|))

3. 最短路径

1. 问题描述:

设G =(V,E)是一个每条边有非负长度的有向图,有一个特异顶点s称为源。单源最短路径问题,或者简称为最短路径问题, 是要确定从s 到 V 中每一个其他顶点的距离, 这里从顶点s到顶点x距离定义为从s到x 的最短路径长度。

2. 问题分析:

我们有一个图,其中我们选择一个节点作为原点 S,所追求的想法是通过从原点到图的任何其他节点必须经过的东西来找到节点,这样旅程就有了最低成本。

Dijkstra算法:
        -假设节点编号在 0 和 n-1 之间。
        -解是两个向量 P 和 D。
        -P [i] 包含必须在给定初始节点 S 和节点 i 之间的最小路径上传递的前一个元素。
        -D [i] 包含给定初始节点 S 和节点 i 之间最小路径的现有距离。
        -一个矩阵 L,其中每个分量 L [i] [j] 表示从节点 i 直接旅行到节点 j 的成本(邻接矩阵)。
        1)最初,我们假设 S 和任何其他节点之间的最小距离是在邻接矩阵中将 S 直接连接到该节点的边的权重:D[i]= L[S, i]

        2)接下来,对于每个节点 i,我们会问自己是直接从 S 到达 i,还是经过另一个节点 v 更好。 

        3)我们将对每个可以通过的节点 i 和每个中间节点 v 问问自己这个问题,直到我们用尽所有可能性。

        4)在每一步更新最小路径。

贪心算法设计:

        - 候选名单:原始图的节点,除了 S。
        - 已经使用过的候选列表:用于获取构成解决方案的边的节点。
        - 解决方案函数:V|-1 个图的节点已被选中。
        - 选择方案函数:选择值D [v] 最小的未使用节点v。
        - 可行性标准:在最优解中插入一个节点总是可行的。 
        - 目标函数:最小化从初始节点S到图中其余节点的距离。

3. 算法实现(伪代码):

ALGORITHM [D, P] = Dijkstra (G = (V, A), L, S) 
n = | V | 
C = V \ {S} // Unused candidates 
For i = 1 to n, do: 
    D [i] = L [S] [i]; 
    P [i] = S; // Initialize D and P 

Repeat n-2 times:
    v = element of C such that D [v] is minimal
    C = C \ {v}
    For each w in C, do: // Check path for the other nodes
        If D [w]> D [v] + L [v] [w], then: // If it is better to go through v ...
            D [w] = D [v] + L [v] [w]; P [w]  v;
        End If
    End for
End-Repeat
Return D, P

算法效率: O(|V|^2 )  密集图        O(|A|·log(|V|))  非密集图

4.图形着色

1. 问题描述:

设 G = <V, A> 为无向图。 我们想为图形的每个节点分配一种颜色,以便:

-没有两个相邻的节点具有相同的颜色。

-使用尽可能少的颜色。

2. 问题分析:

最初,我们假设图中的任何节点都不会被着色。我们将从图中随机选择一个节点,并为它涂上颜色。将选择所有尚未着色的选定节点的非相邻节点,并将它们涂上相同的颜色。重复前面的过程,直到图的所有节点都完成。

贪心算法设计:

         - 候选名单:图的节点。
        - 已经使用过的候选列表:已经着色的节点。
        - 解决方案函数:图的所有节点都着色。
        - 选择方案函数:随机选择一个节点。
        - 可行性标准:所选节点不能着色。 
        - 目标函数:最小化用于为图形着色的颜色数量。

3. 算法实现(伪代码):

ALGORITHM T = Color (G = <V, A>)

C = V // Set of candidates 
T = {0}_n // Vector of n colors assigned to the graph, with no value 
K = 1; // Current color 
While (| C |> 0) do:
    Select i = Any node of C 
    C = C \ {i} 
    T [i] = K // Assign color K to node i 
    For each node j in C not adjacent to i, if feasible, do:
        T [j] = K, 
        C = C \ {j} 
    End-For 
    K = K + 1 
End-While 
Return T

5. 完全背包问题

1. 问题描述:

定n个物品和一个容量为M的背包,物品i的重量是w_i,其价值为b_i,背包问题是如何选择入背包的物品,使得装入背包的物品的总价值最大,与0-1背包(要么选取某个物品,要么不能选取,不能只选取一个物品的一部分)的区别是,在完全背包问题中,可以将物品的一部分装入背包,但不能重复装入。

启发方法:

1-选择收益最大的物品的最大数量

2-选择重量最小的物品的最大数量。

3-选择选择对象 i ,使得 i = max_i {b_i / w_i}。

2. 问题分析:

最初,我们假设背包是空的,并且没有携带任何物品。

在每一步中,将根据我们想的 3 个启发方法中之间进行选择。

将放入背包该元素的最大可能的数量。

将重复此过程,直到没有要添加的物品或背包的容量已完成。

贪心算法设计:

         - 候选名单:携带的物品。
        - 已经使用过的候选列表:背包中已有或丢弃的物品。
        - 解决方案函数:背包中不能再插入任何数量的物体。
        - 选择方案函数:根据选择的启发方法。
        - 可行性标准:所包含物品的重量不超过背包的容量。 
        - 目标函数:在不超出背包容量的情况下,最大限度地发挥背包内物品的利益。

3. 算法实现(伪代码):

ALGORITHM T = Continuous Backpack (M, B [0..n-1], W [0..n-1]) 
C = {0..n-1} // Set of possible objects 
T = {0}_n // Vector of n quantities to bring to 0. Solution to create

While (∑ T_j ∗ w_j < M) and there are candidates in C, do: 
    Select i = best remaining object in C
    C = C \ {i}
    If ∑ T_j ∗ w_j + w_i <= M then
        T_i = 1, 
    otherwise
        T_i = (M-∑ T_j ∗ w_j) / w_i

End-While 
Return T

6. TSP问题(Travelling Salesman Problem)旅行推销员问题

1. 问题描述:

TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

设 G = <V, A> 是一个每条边有非负长度的无向图。 找到图 G 的最小哈密顿回路。

2. 问题分析:

贪心算法设计:

        - 候选名单:原始图的节点。
        - 已经使用过的候选列表:先前选择过的节点。
        - 解决方案函数:解中的节点数为|V|
        - 选择方案函数:选择一个节点 v,使得连接它与最后访问节点的边 a 具有最小成本。
        - 可行性标准:不能形成循环。 
        - 目标函数:找到图的最小哈密顿圈。

将假设我们从图中的任何节点开始。

接下来,将选择最近的未访问相邻节点,并将连接它的边加入到解决方案中。

最后,我们将最后一个选定节点的边缘与第一个连接起来,以关闭图。

3. 算法实现(伪代码):

ALGORITHM TravelingSalesmanProblem (G = (V, A)) 
i = s // Select any node of V 
C = V \ {s} // Candidates 
T = Ø // Solution to create 
Repeat until | C | = 0:
    v = Select node in C where a = (s, v) has minimum weight
    C = C \ {v}
    T = T U {(s, v)}
    Update s = v 
End-Repeat 
T = T U {(s, i)} // Remaining edge (s, i) left to close the circuit 
Return T

算法效率: O(n^2)

7. 任务规划(时间规划)

1. 问题描述:

最小化每个任务等待完成的时间。

设 n 是待执行的任务。
知道每个任务 i 需要时间来执行,找到这些任务的执行顺序,最小化所有任务在完成执行之前等待的总时间 T。

2. 问题分析:

在这个问题中,当任务按照执行时间的从小到大排序时,可以获得最佳规划。也就是说:最短的先跑; 然后下一个最短,等。

贪心算法设计:

        - 候选名单:要执行的任务。
        - 已经使用过的候选列表:已经计划好的任务。
        - 解决方案函数:计划任务数为n.
        - 选择方案函数:选择任务 i 使其执行时间 t_i 最小。
        - 可行性标准:任务 i 必须具有最短执行时间 t_i。 
        - 目标函数:最小化 T=\sum_{i+1}^{n}\left ( t_i + \sum_{j=1}^{i-1} t_{p(j)} \right ),其中 p (j) 是在 j-th 位置执行的任务。

3. 算法实现(伪代码):

ALGORITHM P = PlanificacionTasas (vector t, n) 
C = {1..n} // Candidates, tasks to be executed 
P = {0} n // Vector of planned tasks, solution to return 
For i = from 1 to n, do :
    j = Select task j in C where t [j] is minimum
    C = C \ {j}
    P (i) = j 
End-For
Return P

算法效率:O(n)(假设任务向量按增加 t [j] 排序)
算法效率:O(n * log(n))(假设任务向量没有排序)

8. 任务分配问题 

1. 问题描述:

最小化将 n 个工人分配给 n 个任务的成本。

设 n 是一个正整数,T = {T_1..T_n} 一组 n 任务 和 P = {P_1..P_n} 一组 n 工人。 我们将 X = [x_ij]n, n 表示为一个矩阵,该矩阵表示工人和任务之间的分配。 如果 x = 1,我们会说工人 i 被分配任务 j,如果 x_ij = 0,则不分配。

X 分配仅在一项任务没有被分配超过一个工人并且一个工人没有参与多个任务时才有效(X 的行/列只有一个为 1 的组件)。

我们还将表示 B = [b_ij]n, n 为成本矩阵,其中 b_ij 是分配工人 i 来执行任务 j 的成本。

2. 问题分析:

该问题有 2 个的启发式方法(两者都不是最优的):

1. 将每项任务分配给最好的工人。

2. 为每个工人分配最好的任务。

贪心算法设计:

        - 候选名单:工人(如果我们使用启发式 1)或任务(如果我们使用启发式 2)。
        - 已经使用过的候选列表:已经分配的工作人员(如果我们使用启发式 1)或已经分配的任务(如果我们使用启发式 2)
        - 解决方案函数:已分配所有工人/任务。
        - 选择方案函数:每个任务的最低成本工人(如果我们使用启发式 1)或每个工人的最低成本任务(如果我们使用启发式 2)。        

        - 可行性标准:一个工人/任务只能分配一次。。 
        - 目标函数:最小化分配每个工人到每个任务的总成本。

ALGORITHM P = AssignmentTasksHeuristica1 (Matrix C, n) 
U = {1..n} // Candidates, workers to assign 
P = {0} n // Vector of assigned workers, solution to return 
For i = from 1 to n, do :
    j = Select worker j in U where C [j] [i] is minimal
    U = U \ {j}
    P (j) = i 
End-For 
Return P

ALGORITHM P = AsignaciónTasasHeuristica2 (Matrix C, n) 
U = {1..n} // Candidates, tasks to be assigned 
P = {0} n // Vector of tasks assigned, solution to return 
For i = from 1 to n, do :
    j = Select task j in U where C [i] [j] is minimum
    U = U \ {j}
    P (i) = j 
End-For 
Return P

9.实验项目

        实验1:

        实验2:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值