《算法图解》第八章之贪婪算法

65 篇文章 2 订阅

目录

教室调度问题​

集合覆盖问题​

背包问题​

如何识别 NP 完全问题

小结

NP完全问题

一、时间复杂度

二、多项式时间或多项式复杂度

三、P型问题与NP型问题

四、NPC问题(NP完全性问题、NP完全问题)

五、后话

示例代码

Python

C#

Java

JS


教室调度问题

很多人都跟我说,这个算法太容易、太显而易见,肯定不对。但这正是贪婪算法的优点——
简单易行!贪婪算法很简单:每步都采取最优的做法。在这个示例中,你每次都选择结束最早的
课。用专业术语说,就是你每步都选择局部最优解,最终得到的就是全局最优解。信不信由你,
对于这个调度问题,上述简单算法找到的就是最优解!
 

集合覆盖问题

背包问题

从这个示例你得到了如下启示:在有些情况下,完美是优秀的敌人。有时候,你只需找到一
个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的
结果又与正确结果相当接近。

如何识别 NP 完全问题
 

但如果要找出经由指定几个点的的最短路径,就是旅行商问题——NP完全问题。简言之,
没办法判断问题是不是NP完全问题,但还是有一些蛛丝马迹可循的。
 元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变得非常慢。
 涉及“所有组合”的问题通常是NP完全问题。
 不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
 如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题。
 如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题。
 如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题。
 

小结


 贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。
 对于NP完全问题,还没有找到快速解决方案。
 面临NP完全问题时,最佳的做法是使用近似算法。
 贪婪算法易于实现、运行速度快,是不错的近似算法。
 

NP完全问题

转载于 NP完全问题_五月花-CSDN博客

从浅入深来介绍相关的概念。

一、时间复杂度

       时间复杂度并不是表示一个算法解决问题需要花多少时间,而是当问题规模扩大后,算法需要的时间长度增长得有多快。不管数据有多大,如果算法处理数据所用的时间始终基本不变,我们就说这个算法很好,具有O(1)的时间复杂度,也称常数级时间复杂度。而当数据规模变得有多大,算法所花的时间也跟着变得有多长,这个算法的时间复杂度就是O(n),比如找n个数中的最大值。而像冒泡排序、插入排序等,数据扩大2倍,时间变慢4倍的,属于O(n^2)的复杂度。还有一些穷举类的算法,所需时间长度成几何阶数上涨,这就是O(a^n)的指数级复杂度,甚至O(n!)的阶乘级复杂度。

考虑这两个时间复杂度:O(n^100)、O(1.01^n)  (注意,前面那个是n的100次方)

是的,当n取值很小的时候,确实有O(n^100) 大于 O(1.01^n)。但前者时间随着数据规模的增长,而增长地得慢,最终在某个点之后,O(1.01^n)的复杂度将远远超过O(n^100)。所以我们说O(1.01^n)的复杂度无论如何都远远大于O(n^100)。

       经过上面的分析,可以看到,这些时间复杂度分为两种类别:第一种是O(1)、O(n)、O(nlogn)、O(n^2)... O(n^k)。另外一种是O(2^n)、O(3^n)... O(n!)。后者的复杂度无论如何都远远大于前者。对于第一种复杂度,我们把它叫做多项式级的时间复杂度,因为它的规模n总是出现在底数的位置上。而对于第二种时间复杂度,我们称之为非多项式级的,我们一般称其为指数级的复杂度,其复杂度计算机往往不能承受。

       当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的时间太多,往往会超时,除非是数据规模非常小。

二、多项式时间或多项式复杂度

       有了上面的概念,我们再来看什么是多项式时间。比如一个问题输入有n项,如果解决时间为n或nlogn或n^2或n^3乃至n^100000,那么就可以叫多项式时间,但是如果要用2^n的时间,那么就不能叫多项式时间了。时间复杂度为多项式时间的,就叫做具有多项式时间复杂度。不是多项式时间算法的算法被称之为“指数时间算法”。

三、P型问题与NP型问题

       对于一个问题,如果可以找到一个解决该问题的算法,且该算法的时间复杂度为多项式时间,则就称这个问题属于P问题。其中P是英文单词Polynomial(多项式)的首字母。用书上的话来讲,P问题 就是 多项式时间内可以被确定型图灵机求解的问题。

       知道了什么是P问题,接下来介绍什么是NP问题。注意,提前警告,NP问题不是非P类问题。不要把N理解为非。对于一个问题,如果不能判定该问题是否为P问题,但是,提供一个可能解,如果我们可以在多项式时间内验证该解是否正确,那么我们就称该问题为NP问题(为Non-deterministic Polynomial)。用书上的话来讲,NP问题 就是 多项式时间内可以通过确定型图灵机验证解的问题。

       再说的明白一些吧。先抛开P,来讲讲什么是N,N就是Non-deterministic ,也就是非确定性问题。什么是非确定性问题呢?有些问题是确定性的解法的,比如1到100求和,你只要按照公式推导,按部就班一步步来,就可以得到结果。有些问题,是没有现成公式的,是无法按部就班直接地计算出来。比如,找下一个质数的问题。有没有一个公式,你只管套用公式,就可以一步步推算出来,下一个质数是多少。答案是没有这样的公式。这种问题的答案,是无法直接计算得到的,只能通过间接的“猜算”来得到结果,先猜一个结果,然后去验证,如果正确,运气不错,如果不正确,再猜。这就是非确定性问题。如果验证那个猜测值所用的时间是多项式时间,那么我们就是这个问题是NP问题。

       所有的P问题都是NP问题,这个结论从两者的定义出发即可得。需注意的是求解问题和对问题验证解之间的区别。给定一个问题,求解通常难于验证解。所以我们说,从这个角度来看,NP问题比P问题简单。所以经常有人开玩笑说,说一个问题是NP的,恰恰不是说这个问题很难,而是说这个问题很简单,这个问题有多简单呢,用非确定性图灵机在P时间就能解决(Non-deterministic Turing machine Polynomial time solvable)。

       有了上述分析,再用一句大白话来总结一下,P就是能在多项式时间内解决的问题,NP就是能在多项式时间验证答案正确与否的问题。说一个问题是NP的,并不是说这个问题不能在多项式时间内解决,而是说目前为止,可能暂时尚未找到解法。所以,再强调一遍,NP不是P的否定。NP与P不是对立的,因为所有的P问题都是NP问题。

四、NPC问题(NP完全性问题、NP完全问题)

       NP-Complete问题,也叫做NP完全问题。NPC问题的定义非常简单。同时满足下面两个条件的问题就是NPC问题。

1) 它得是一个NP问题;

2) 所有的NP问题都可以约化到它。

“可约化”是指的可“多项式地”约化(Polynomial-time Reducible),即变换输入的方法是能在多项式的时间里完成的。约化的过程只有用多项式的时间完成才有意义。

       NP中所有问题都可以在多项式时间内规约至某一子类问题,称这一类子问题为NP-Complete。因此,NP-Complete是NP的一个子集。直觉上说,就是NP问题中最难的一类子问题。因为给定一个NP问题A,只要可以多项式时间内解决任意一个NP-Complete问题B,那么就可以通过多项式的时间将A问题转化为B问题进行求解,使得求解A问题仍具有多项式复杂度。

       复杂性理论中最具理论意义的,就是NP完全性问题(NPC问题)。伟大的数学家们已经证明了如下结论:任取NP类中的一个问题,再任取NP完全类中的一个问题,则一定存在一个具有多项式时间复杂性的算法,可以把前者转变成后者。也就是说,对于任意一个NP问题 和 任意一个NP完全问题,总是存在一个多项式时间算法,可以把这个NP问题转化为指定的NP完全问题。这就表明,只要能证明NP完全问题中存在一个问题是属于P类的,那么就证明了NP类中的所有问题都是P类的,即证明了NP=P。不过,现在还没有被证明出来。归根结底,就是要证明P问题是否是NP问题的真子集,谁知道呢?

五、后话

       在学习算法设计与分析时,经常会提到NP完全性问题。目前已知的NP完全性问题就有2000多个,在图上定义的许多组合优化问题是NP完全性问题,如货郎问题、调度问题、最大团问题、最大独立集合问题、Steiner树问题、背包问题、装箱问题等,遇到这类问题时,通常从以下几个方面来考虑,并寻求解决办法:

(1) 动态规划法:较高的解题效率。 
(2) 分枝限界法: 较高的解题效率。 
(3) 概率分析法: 平均性能很好。 
(4) 近似算法: 近似解代替最优解。 
(5)启发式算法:根据具体问题的启发式搜索策略在求解,在实际使用可能很有效,但有时很难说清它的道理。
 

示例代码

Python

# You pass an array in, and it gets converted to a set.
states_needed = set(["mt", "wa", "or", "id", "nv", "ut", "ca", "az"])

stations = {}
stations["kone"] = set(["id", "nv", "ut"])
stations["ktwo"] = set(["wa", "id", "mt"])
stations["kthree"] = set(["or", "nv", "ca"])
stations["kfour"] = set(["nv", "ut"])
stations["kfive"] = set(["ca", "az"])

final_stations = set()

while states_needed:
  best_station = None
  states_covered = set()
  for station, states in stations.items():
    covered = states_needed & states
    if len(covered) > len(states_covered):
      best_station = station
      states_covered = covered

  states_needed -= states_covered
  final_stations.add(best_station)

print(final_stations)

C#

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var statesNeeded = new HashSet<string> { "mt", "wa", "or", "id", "nv", "ut", "ca", "az" };
            var stations = new Dictionary<string, HashSet<string>>();
            stations.Add("kone", new HashSet<string> { "id", "nv", "ut" });
            stations.Add("ktwo", new HashSet<string> { "wa", "id", "mt" });
            stations.Add("kthree", new HashSet<string> { "or", "nv", "ca" });
            stations.Add("kfour", new HashSet<string> { "nv", "ut" });
            stations.Add("kfive", new HashSet<string> { "ca", "az" });
            var finalStations = new HashSet<string>();
            while (statesNeeded.Any())
            {
                string bestStation = null;
                var statesCovered = new HashSet<string>();
                foreach (var station in stations)
                {
                    var covered = new HashSet<string>(statesNeeded.Intersect(station.Value));
                    if (covered.Count > statesCovered.Count){
                        bestStation = station.Key;
                        statesCovered = covered;
                    }
                }
                statesNeeded.RemoveWhere(s => statesCovered.Contains(s));
                finalStations.Add(bestStation);
            }
            Console.WriteLine(string.Join(", ", finalStations));
        }
    }
}

Java

import java.util.*;

public class SetCovering {

    public static void main(String[] args) {
        Set<String> statesNeeded = new HashSet(Arrays.asList("mt", "wa", "or", "id", "nv", "ut", "ca", "az"));
        Map<String, Set<String>> stations = new LinkedHashMap<>();

        stations.put("kone", new HashSet<>(Arrays.asList("id", "nv", "ut")));
        stations.put("ktwo", new HashSet<>(Arrays.asList("wa", "id", "mt")));
        stations.put("kthree", new HashSet<>(Arrays.asList("or", "nv", "ca")));
        stations.put("kfour", new HashSet<>(Arrays.asList("nv", "ut")));
        stations.put("kfive", new HashSet<>(Arrays.asList("ca", "az")));

        Set<String> finalStations = new HashSet<String>();
        while (!statesNeeded.isEmpty()) {
            String bestStation = null;
            Set<String> statesCovered = new HashSet<>();

            for (Map.Entry<String, Set<String>> station : stations.entrySet()) {
                Set<String> covered = new HashSet<>(statesNeeded);
                covered.retainAll(station.getValue());

                if (covered.size() > statesCovered.size()) {
                    bestStation = station.getKey();
                    statesCovered = covered;
                }
                statesNeeded.removeIf(statesCovered::contains);

                if (bestStation != null) {
                    finalStations.add(bestStation);
                }
            }
        }
        System.out.println(finalStations); // [ktwo, kone, kthree, kfive]
    }
}

JS

'use strict';

// You pass an array in, and it gets converted to a set.
let states_needed = new Set(["mt", "wa", "or", "id", "nv", "ut", "ca", "az"]);

const stations = {};
stations["kone"] = new Set(["id", "nv", "ut"]);
stations["ktwo"] = new Set(["wa", "id", "mt"]);
stations["kthree"] = new Set(["or", "nv", "ca"]);
stations["kfour"] = new Set(["nv", "ut"]);
stations["kfive"] = new Set(["ca", "az"]);

const final_stations = new Set();


while (states_needed.size) {
  let best_station = null;
  let states_covered = new Set();
  for (let station in stations) {
    let states = stations[station];
    let covered = new Set([...states_needed].filter((x) => states.has(x)));
    if (covered.size > states_covered.size) {
      best_station = station;
      states_covered = covered;
    }
  }
  states_needed = new Set([...states_needed].filter((x) => !states_covered.has(x)));
  final_stations.add(best_station);
}

console.log(final_stations); // Set { 'kone', 'ktwo', 'kthree', 'kfive' }

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值