WHAT - 贪心场景和算法实现

介绍

贪心算法(Greedy Algorithm)是一种解决优化问题的简单且直观的算法策略。它在每个决策步骤中选择当前最佳选项,希望这样的局部最优选择能够导致全局最优解。贪心算法的特征和应用场景如下:

贪心算法的特征:

  1. 局部最优选择:在每个决策阶段,贪心算法都会做出当前看起来最优的选择,而不考虑前后步骤的影响。
  2. 简单直观:贪心算法通常易于理解和实现。相对于其他复杂的算法策略,贪心算法的思路更加直接。
  3. 无回溯:贪心算法在每个决策阶段都会做出不可逆的选择,不会回溯以寻找其他可能的解决方案。
  4. 快速求解:由于贪心算法在每个阶段都做出最优选择且无需回溯,因此通常能够快速找到解决方案。

贪心算法的应用场景:

  1. 最小生成树问题:Kruskal 算法和 Prim 算法都是贪心算法的应用,用于在给定的带权无向图中找到最小生成树。
  2. 最短路径问题:Dijkstra 算法是求解单源最短路径问题的贪心算法,用于在带权有向图中找到从源节点到其他所有节点的最短路径。
  3. 负载均衡问题:在处理服务器负载均衡时,可以使用贪心算法为每个任务分配服务器资源,以使得总负载最小。
  4. 哈夫曼编码:哈夫曼编码是一种用于数据压缩的贪心算法,通过构建哈夫曼树为字符生成最优前缀编码。
  5. 零钱兑换问题:在给定面值的硬币集合下,贪心算法可以用于找到组成特定金额所需的最少硬币数量。
  6. 区间覆盖问题:在给定一组区间的情况下,贪心算法可以用于找到最少数量的点,使得这些点覆盖了所有的区间。

需要注意的是,贪心算法并不总是能得到全局最优解,只有在具有最优子结构和贪心选择性质的问题中,贪心算法才能保证找到全局最优解。在实际应用中,需要根据问题的特点来判断贪心算法是否适用。

coin

方案:

  1. 函数 greedyCoinChange 接受两个参数:一个包含硬币面值的数组 coins 和一个表示目标金额的整数 amount。
  2. 首先,对硬币面值进行降序排序,以便在后续的循环中从最大的面值开始处理。
  3. 初始化一个空数组 result 用于存储找到的硬币,以及一个变量 remainingAmount 用于追踪剩余的金额。
  4. 遍历硬币面值数组。对于每个面值,计算在不超过剩余金额的情况下,可以使用的最大硬币数量。将这些硬币添加到结果数组中,并从剩余金额中减去它们的总面值。
  5. 如果剩余金额为0,表示已找到组成目标金额所需的最少硬币数量,跳出循环。
  6. 返回结果数组。

需要注意的是,这种贪心算法实现仅在硬币面值满足特定条件时才能保证找到最优解(例如美元硬币面值:25、10、5、1)。在其他情况下,可能需要使用动态规划等更复杂的算法来解决零钱兑换问题。


const greedyCoinChange = (coins, amount) => {

  const sortedCoins = coins.sort((a, b) => b - a);

  let result = [];
  let remainingAmount = amount;
  for (let i = 0; i < sortedCoins.length; i++) {

    const value = sortedCoins[i];
    let maxCoinCount = Math.floor(remainingAmount / value);
    for (let j = 0; j < maxCoinCount; j++) {
      
      result.push(value);
      remainingAmount -= value;
    }

    if (remainingAmount === 0) {
      
      break;
    }
  }
  
  return result;
}

const coins = [25, 10, 5, 1];
const amount = 63;
const result = greedyCoinChange(coins, amount);
console.log(result); // 输出: [ 25, 25, 10, 1, 1, 1 ]

dijkstra

方案

  1. 函数 dijkstra 接受两个参数:一个表示带权有向图的对象 graph,其中键表示节点,值表示相邻节点及其权重;以及一个表示源节点的字符串 source。
  2. 定义 distance 对象来存储每个节点到源节点的距离,初始化为无穷大(Infinity),并将源节点的距离设置为 0。
  3. 定义两个 Set 对象 visited 和 unvisited 分别存储已访问和未访问的节点。
  4. 使用 currentNode 变量追踪当前节点,初始化为源节点。
  5. 当 unvisited 集合非空时,执行以下操作:
    a. 遍历当前节点的邻接节点,并更新它们到源节点的距离。
    b. 将当前节点标记为已访问,并从未访问节点集合中移除。
    c. 在未访问节点中找到具有最小距离的节点,将其设置为下一个当前节点。
    d. 如果所有未访问节点的距离都为无穷大,表示无法到达这些节点,跳出循环。
  6. 返回包含每个节点到源节点的最短距离的 distance 对象。

需要注意的是,这个实现没有使用优先队列来优化性能。在大型图中,使用优先队列可以显著提高 Dijkstra 算法的执行速度。

const dijkstra = (graph, source) => {

  let distance = {};
  for (const node in graph) {
    
    distance[node] = node === source ? 0 : Infinity;
  }

  let visited = new Set();
  let unvisited = new Set(Object.keys(graph));

  let currentNode = source; // 'A'
  while (unvisited.size > 0) {

    console.log('current node: ', currentNode);
    console.log('visited: ', visited);
    console.log('unvisited: ', unvisited);
    
    for (const neighbor in graph[currentNode]) {

      const neighborDistance = graph[currentNode][neighbor];

      // 关键
      // 要加上前面计算得到的距离值 比如第一轮 a-c=4,a-b=1 但到第二轮发现 b-c=2,a-c=(a-b-c)=3更短了
      const newDistance = distance[currentNode] + neighborDistance;
      if (newDistance < distance[neighbor]) {
        
        distance[neighbor] = newDistance;
      }
    }

    visited.add(currentNode);
    unvisited.delete(currentNode);

    // 关键
    // 在未访问节点中找到具有最小距离的节点,将其设置为下一个当前节点
    // 这一步可保证图节点的连续关系
    let minDistance = Infinity;
    for (const node of unvisited) {
      
      if (distance[node] < minDistance) {
        minDistance = distance[node];
        currentNode = node;
      }
    }

    // 注意
    // 如果所有未访问节点的距离都是无穷大,表示无法到达,跳出循环
    if (minDistance === Infinity) {
    
      break;
    }
  }

  return distance;
}

// 示例
const graph = {
  A: { 
    B: 1,
    C: 4 
  },
  B: { 
    A: 1, 
    C: 2, 
    D: 5 
  },
  C: { 
    A: 4, 
    B: 2, 
    D: 1 
  },
  D: { 
    B: 5, 
    C: 1 
  }
};

const source = "A";
const result = dijkstra(graph, source);
console.log(result); // 输出: { A: 0, B: 1, C: 3, D: 4 }

coverage

方案:

  1. 函数 greedyIntervalCovering 接受一个参数:一个包含区间的数组 intervals,其中每个区间表示为一个长度为 2 的数组,例如 [a, b]。
  2. 首先,对区间按照右端点进行升序排序,以便在后续的循环中从最早结束的区间开始处理。
  3. 初始化一个空数组 result 用于存储选择的覆盖点,以及一个变量 currentPoint 用于追踪当前的覆盖点。将其初始值设置为负无穷大(-Infinity),以确保它不在任何输入区间内。
  4. 遍历排序后的区间数组。对于每个区间,检查当前覆盖点是否在该区间内。如果当前覆盖点不在区间内,则选择该区间的右端点作为新的覆盖点,并将其添加到结果数组中。
  5. 返回结果数组,包含所有选择的覆盖点。

贪心算法的关键在于,始终选择最早结束的区间作为覆盖点,这样可以留出更多的空间来覆盖后面的区间。这种策略能够保证找到最少数量的覆盖点。

function greedyIntervalCovering(intervals) {

  // 对区间按照右端点进行升序排序
  intervals.sort((a, b) => a[1] - b[1]);
  // console.log('sorted intervals of the second number: ', intervals);
  // [1, 4],
  // [2, 5],
  // [3, 7],
  // [6, 8],
  // [9, 10],

  let result = [];
  let currentPoint = -Infinity; // 初始化当前覆盖点为负无穷大

  // 遍历排序后的区间
  for (let i = 0; i < intervals.length; i++) {

    let interval = intervals[i];
    if (currentPoint < interval[0]) {
      
      currentPoint = interval[1];
      result.push(interval[1])
    }
  }

  return result;
}

// 示例
const intervals = [
  [1, 4],
  [2, 5],
  [6, 8],
  [3, 7],
  [9, 10],
];
const result = greedyIntervalCovering(intervals);
console.log(result); // 输出: [ 4, 8 ]

Huffman

Kruskal+Prim

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值