子集和问题以及NP完全问题的可解性

时间:2010-03-01 01:58:22

标签: lisp np-complete subset-sum

当我想出解决它的通用算法时,我正在阅读子集和问题:

(defun subset-contains-sum (set sum)
    (let ((subsets) (new-subset) (new-sum))
        (dolist (element set)
            (dolist (subset-sum subsets)
                (setf new-subset (cons element (car subset-sum)))
                (setf new-sum (+ element (cdr subset-sum)))
                (if (= new-sum sum)
                    (return-from subset-contains-sum new-subset))
                (setf subsets (cons (cons new-subset new-sum) subsets)))
            (setf subsets (cons (cons element element) subsets)))))

“set”是不包含重复项的列表,“sum”是搜索子集的总和。 “子集”是缺点单元的列表,其中“car”是子集列表,“cdr”是该子集的总和。新的子集是在O(1)时间内从旧的子集创建的,只需将元素加到前面即可。

我不确定它的运行时复杂性是什么,但看起来随着每个元素“sum”的增长,“子集”的大小增加一倍,加上一个,所以在我看来至少是二次的。 / p>

我发布这个是因为我以前的印象是NP完全问题往往是难以处理的,并且最好的人通常希望是启发式的,但这似乎是一个通用的解决方案,假设你有CPU周期,总是给你正确的答案。有多少其他NP完全问题可以解决这个问题?

5 个答案:

答案 0 :(得分:7)

NP完全问题是可以解决的,而不是在多项式时间内(据我们所知)。也就是说,NP完全问题可能有O(n*2^n)算法可以解决它,但它不会有O(n^3)算法来解决它。

有趣的是,如果找到任何NP完全问题的快速(多项式)算法,那么NP中的每个问题都可以在多项式时间内求解。这就是P = NP的意思。

如果我正确理解您的算法(这更多地基于您的评论而不是代码),那么它等同于O(n*2^n)算法here。有2^n个子集,由于您还需要对每个子集求和,因此算法为O(n*2^n)

关于复杂性的另一件事 - O(whatever)仅表示特定算法的扩展程度。你不能比较两种算法,并说一种算法比另一种算法更快。 Big-O表示法并不关心实现细节和优化 - 可以编写同一算法的两个实现,其中一个比另一个快得多,即使它们可能都是O(n^2)。制作婴儿的一名妇女是O(n)手术,但这可能比你执行的大多数O(n*log(n))手术要花费更长的时间。基于此,您可以说的是,对于n上非常大的值,排序会更慢。

答案 1 :(得分:5)

所有NP完全问题都有解决方案。只要你愿意花时间计算答案,那就是。仅仅因为没有高效的算法,并不意味着没有。例如,您可以迭代每个可能的解决方案,并最终得到一个。这些问题在现实世界的计算中被广泛使用。如果您需要指数时间(或者更糟!)来解决问题,您只需要小心自己为自己设置的问题。

答案 2 :(得分:3)

  

我不确定运行时是什么   它的复杂性,但似乎   随着每个元素“sum”的增长,   “子集”的大小加倍,加一,   所以在我看来至少是这样   二次的。

如果N的每次增加的运行时间加倍,那么您正在查看O(2 ^ N)算法。这也是我期望访问集合的所有子集(或集合的所有成员),因为它恰好是2 ^ N个成员(如果你包括rhe空集)。

在所有迄今为止看到的集合中添加或不添加元素的事实很快并不意味着总处理速度很快。

答案 3 :(得分:2)

这里发生的事情可以更简单地表达使用递归:

(defun subset-sum (set sum &optional subset)
  (when set
    (destructuring-bind (head . tail) set
      (or (and (= head sum) (cons head subset))
          (subset-sum tail sum          subset)
          (subset-sum tail (- sum head) (cons head subset))))))

最后的两个递归调用清楚地表明我们正在遍历深度为n的二叉树,即给定集合的大小。正如预期的那样,二叉树中的节点数为O(2 ^ n)。

答案 4 :(得分:0)

多项式时间是可以减少的。使用堆栈或二进制搜索上限将Karp减少到决策问题O(nM)是log(M * 2 ^ M)= logM + log(2 ^ M)= logM + Mlog2 Ergo时间:O(nM)< / p>