【问题标题】:How to use stack and a queue to generate all possible subsets on n-element set nonrecursively?如何使用堆栈和队列以非递归方式在 n 元素集上生成所有可能的子集?
【发布时间】:2013-04-12 04:47:04
【问题描述】:

这是来自 Michael T Goodrich 和 Robert Tamassia 的 Java 数据结构和算法的问题。这该怎么做?任何帮助表示赞赏。

这是我的想法,如果我错了,请纠正我:

将元素存储在堆栈中。弹出第一个元素并将其存储在队列中,堆栈中的剩余元素形成一个子集。恢复堆栈,现在弹出第二个元素(队列中的第一个弹出,队列中的第二个弹出,然后从队列中推送)和堆栈中的剩余元素从另一个子集中。同样弹出第三个元素,然后弹出第四个。现在,轮到对两个元素和三个元素做同样的事情了吗?我是否误解了这个问题并将其延伸得太远了?

【问题讨论】:

  • 如果我正确理解了您的解决方案,问题是您只删除了相邻的元素。因此,例如,假设您要在 {1、2、3、4、5} 上“一次删除两个元素”。然后你会删除 {3, 4} 但不是 {2, 4}。不过你是对的......因为有一种系统的方法可以弹出 ith 元素,那么你可以使用下面 Pieter 建议的带有位掩码的简单解决方案。但我认为这不是最干净/预期的解决方案

标签: algorithm data-structures stack queue push


【解决方案1】:

我已经定义了 ArrayStack() 和 ArrayQueue() 类

n=[1,2,3,4,5]
st=ArrayStack()
q=ArrayQueue()

q.enqueue(set())

for i in range(len(n)):
    st.push(n[i])


while st.is_empty()==False:
    cur_el=st.pop()
    print('cur',cur_el)
    for i in range(len(q)):
        a=q.dequeue()
        print('a',a)
        q.enqueue(a)
        b=a|{cur_el}
        q.enqueue(b)
        print('b',b)

while q.isempty()==False:
    x=q.dequeue()
    print(x)

OUTPUT
cur 5
a set()
b {5}
cur 4
a set()
b {4}
a {5}
b {4, 5}
cur 3
a set()
b {3}
a {4}
b {3, 4}
a {5}
b {3, 5}
a {4, 5}
b {3, 4, 5}
cur 2
a set()
b {2}
a {3}
b {2, 3}
a {4}
b {2, 4}
a {3, 4}
b {2, 3, 4}
a {5}
b {2, 5}
a {3, 5}
b {2, 3, 5}
a {4, 5}
b {2, 4, 5}
a {3, 4, 5}
b {2, 3, 4, 5}
cur 1
a set()
b {1}
a {2}
b {1, 2}
a {3}
b {1, 3}
a {2, 3}
b {1, 2, 3}
a {4}
b {1, 4}
a {2, 4}
b {1, 2, 4}
a {3, 4}
b {1, 3, 4}
a {2, 3, 4}
b {1, 2, 3, 4}
a {5}
b {1, 5}
a {2, 5}
b {1, 2, 5}
a {3, 5}
b {1, 3, 5}
a {2, 3, 5}
b {1, 2, 3, 5}
a {4, 5}
b {1, 4, 5}
a {2, 4, 5}
b {1, 2, 4, 5}
a {3, 4, 5}
b {1, 3, 4, 5}
a {2, 3, 4, 5}
b {1, 2, 3, 4, 5}
set()
{1}
{2}
{1, 2}
{3}
{1, 3}
{2, 3}
{1, 2, 3}
{4}
{1, 4}
{2, 4}
{1, 2, 4}
{3, 4}
{1, 3, 4}
{2, 3, 4}
{1, 2, 3, 4}
{5}
{1, 5}
{2, 5}
{1, 2, 5}
{3, 5}
{1, 3, 5}
{2, 3, 5}
{1, 2, 3, 5}
{4, 5}
{1, 4, 5}
{2, 4, 5}
{1, 2, 4, 5}
{3, 4, 5}
{1, 3, 4, 5}
{2, 3, 4, 5}
{1, 2, 3, 4, 5}

【讨论】:

    【解决方案2】:

    只是半开玩笑:

    1. 创建堆栈并忽略它;
    2. 创建队列并忽略它;
    3. 以长度为 N 的二进制形式输出(迭代地)从 0 到 (2^N)-1 的数字,前导零。

    通过将每个 1 位解释为包含,将每个 0 位解释为排除,可以轻松地从生成的整数的二进制表示生成任何所需的子集替代表示。

    从技术上讲,这符合标准,因为它 (a) 创建了一个堆栈; (b) 创建队列; (c) 非递归地生成 N 个元素集的所有可能子集。对于迭代解决方案,堆栈和队列是严格冗余的,所以为什么在没有任何进一步指导/约束的情况下要求它们。

    更新:

    对输入集的随机访问可以通过使用队列来代替,将其视为循环缓冲区。 (当然,它不能用于其他任何事情。)可以将标记元素添加到队列中以指示每个循环何时完成,但对于上述算法来说更自然的是一次处理 N 个元素, N 是预先知道的。随着每个元素出队,它会被处理(为当前子集添加或忽略)并再次入队。

    【讨论】:

    • 您的解决方案假定您有一个存储原始元素的索引集合。我认为问题的关键在于仅使用堆栈和队列来完成。不切实际的?当然。但这是一个家庭作业问题。
    • @roliu:听起来不错;但是如何以非递归方式恢复原始堆栈以生成第二个以及所有后续子集?
    • 我只是在上面的回答中回答这个问题,因为我不能在这里真正格式化。
    【解决方案3】:

    我想我有一个合理的解决方案是从这里偷来的:http://arstechnica.com/civis/viewtopic.php?f=20&t=96354&sid=e74a29103e9297050680afbba6b72f32&start=40

    所以这个想法是队列将保存子集,而您的堆栈将保存您的原始集合。空集是每个集的子集,因此我们用它初始化队列。然后,对于堆栈上的每个元素,我们将其弹出。现在对于队列中的每个子集,我们将该子集出列并入列两个副本:1)一个没有新元素(即与原始元素相同),2)一个有新元素。棘手的部分是跟踪何时需要弹出堆栈的下一个元素(即,当您完成当前元素时)。一种方法是检查整个 Queue 是否有匹配集(意味着您已经添加了您构建的这个子集......所以停止)。但是一个更好/更干净的方法是使用空集作为标记。

    基本上你有典型的递归解决方案:

    GenerateSubsets(Set set)
    {
        if (set == Set.EmptySet)
            return new List<Set>(set);
    
        var elem = set.Remove();
        var subsets = GenerateSubsets(set);
        // Add all of thew subsets that contain elem (i.e. partition all subsets
        // by whether they contain elem or do not contain elem)
        subsets.AddRange(subsets.Map(subset => subset.Add(elem));
    
        return subsets;
    }
    

    我们使用完全相同的想法,队列迭代构造subsets,原始集合存储在堆栈中。

    GenerateSubsets(Stack originalSetAsStack)
    {
        var queue = new Queue { new Set() };
        while (!originalSetAsStack.IsEmpty)
        {
            var elem = originalSetAsStack.Pop();
            while (true)
            {
                var currSubset = queue.Dequeue();
                // This is key. This is how we know when to start
                // the next iteration!
                // This also assumes that any two empty sets are equal...
                if (currSubset == new Set())
                {
                    break;
                }
    
                var subsetWithElem = currSubset.Clone();
                subsetWithElem.Add(elem);
                // Add back in current subset. This has to be first!
                // for our break to work above
                queue.Queue(currSubset);
                queue.Queue(subsetWithElem);
            }
        }
    
        return queue;
    }
    

    说明为什么这个解决方案源于递归解决方案。观察: 我们迭代地构造子集:

    -Start with the empty set => ({})
    -Take some element from the stack
    -Now for each element in the Queue, enqueue two: one with the current
    element, and one without => ({}, {elem})
    -Now take the next element and do the same => ({}, {elem}, {nextElem}, 
    {elem, NextElem})
    -Now take the third element and do the same => ({}, {elem}, {nextElem}, 
    {elem, nextElem}, {thirdElem}, {thirdElem, elem}, {thirdElem, nextElem}, 
    {elem, nextElem, thirdElem})
    -...
    

    【讨论】:

    • 第一个子集构造完成后,原始集合如何存储在Stack中?
    • 不是。我猜名字不好?它从原始集合开始,但随着我们处理元素,它的大小会缩小
    • 需要第三个存储:对原始集的随机访问,或对 powerset 的所有元素的进行中的工作的随机访问。显然,第一个性能要好得多。
    • 我的解决方案如何需要随机访问“正在进行的工作”电源集。它将它们存储在队列中......而不是列表。但是你是对的,这个问题的措辞非常糟糕。我认为这是预期的解决方案,因为他们特别指定不应该有递归解决方案。
    猜你喜欢
    • 2011-05-05
    • 2013-08-11
    • 1970-01-01
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多