创建序列的幂集

时间:2013-11-10 14:21:48

标签: c# .net algorithm

我正在尝试创建一个程序,它是创建序列,字符串或数字的可能组合的基础。这是某种加密/解密程序。我正在使用Visual Studio 2013和C#。我想要做的是从序列中生成一个功率集,但我有点困惑,无法继续进行。这是代码。

public static void randomSeq(){
    int temp = 0;
    string seq = "1234";
    StringBuilder sb = new StringBuilder();
    char[] bits = seq.Select((char c) => c).ToArray();
    Console.Write("Given Sequence: ");
    Console.Write(seq);
    Console.WriteLine();
    Console.WriteLine("Generated possiblities");
    foreach (char item in bits){
        Console.WriteLine(item);
    }
    do{
        if (temp <= 2){
            for (int i = temp + 1; i < bits.Length; i++){
                 sb.Append(bits[temp]);
                 sb.Append(bits[i]);
                 Console.WriteLine(sb);
                 sb.Clear();
            }
        }else{
            if (temp > 2){
                for (int k = 0; k < temp; k++){
                    sb.Append(bits[k]);
                }
                for (int l = temp + 1; l < bits.Length; l++){
                    sb.Append(bits[temp]);
                    sb.Append(bits[l]);
                    Console.WriteLine(sb);
                    sb.Clear();
                }
            }
        }
        temp++;
    }
    while (temp != bits.Length);
}

我希望这段代码是通用的,即我传递任何序列,它会为我生成一个幂集。然后我想在我的程序中进一步重用它。我可以完成剩下的工作,我只是停留在发电机组中。有人能帮助我吗?。

4 个答案:

答案 0 :(得分:27)

如果熟悉位,则很容易生成功率集。对于N元素集,将有2^N个子集将进入幂集(包括空集和初始集)。因此,每个元素将是IN或OUT(换句话说,10)。考虑到这一点,很容易将该集的子集表示为位掩码。通过列举所有可能的位掩码,可以构建整个功率集。为了做到这一点,我们需要检查位掩码中的每个位,并在该位置有1时获取输入集的元素。下面是string(字符集合)作为输入的示例。它可以很容易地重写,以便收集任何类型的值。

private static List<string> PowerSet(string input)
{
    int n = input.Length;
    // Power set contains 2^N subsets.
    int powerSetCount = 1 << n;
    var ans = new List<string>();

    for (int setMask = 0; setMask < powerSetCount; setMask++)
    {
        var s = new StringBuilder();
        for (int i = 0; i < n; i++)
        {
            // Checking whether i'th element of input collection should go to the current subset.
            if ((setMask & (1 << i)) > 0)
            {
                s.Append(input[i]);
            }
        }
        ans.Add(s.ToString());
    }

    return ans;
}

实施例

假设您有字符串"xyz"作为输入,它包含3个元素,而不是幂集中的2^3 == 8元素。如果您要从0迭代到7,您将获得下表。列:( 10基整数;位表示(2基);初始集的子集)。

0   000    ...
1   001    ..z
2   010    .y.
3   011    .yz
4   100    x..
5   101    x.z
6   110    xy.
7   111    xyz

您可以注意到第三列包含初始字符串"xyz"

的所有子集

另一种方法(两倍快)和通用实现

受Eric的想法的启发,我已经实现了这种算法的另一种变体(现在没有位)。我也把它变成了通用的。我相信这段代码几乎可以为Power Set计算编写最快的代码。它的复杂性与位方法O(n * 2^n)相同,但对于这种方法,常数减半。

public static T[][] FastPowerSet<T>(T[] seq)
{
    var powerSet = new T[1 << seq.Length][];
    powerSet[0] = new T[0]; // starting only with empty set
    for (int i = 0; i < seq.Length; i++)
    {
        var cur = seq[i];
        int count = 1 << i; // doubling list each time
        for (int j = 0; j < count; j++)
        {
            var source = powerSet[j];
            var destination = powerSet[count + j] = new T[source.Length + 1];
            for (int q = 0; q < source.Length; q++)
                destination[q] = source[q];
            destination[source.Length] = cur;
        }
    }
    return powerSet;
}

答案 1 :(得分:4)

谢尔盖的方法非常合理。这是另一种思考它的方法。

出于本答案的目的,我将假设“集合”是有限序列。

我们递归地定义函数P如下。

  • 集合为空,或单个项目H后跟集合T。
  • P(empty) --> { empty }
  • P(H : T) --> P(T)的联合以及P(T)的每个元素都加上H

让我们尝试一下。 {Apple, Banana, Cherry}的权力集是什么?

它不是空集,因此{Apple, Banana, Cherry}的幂集是{Banana, Cherry}的幂集,加上由Apple前置{Banana, Cherry}组成的集合。

所以我们需要知道{Cherry}的功率集。它是Banana的幂集加上集合形式,前缀为{Cherry}

所以我们需要知道Cherry的功率集。它是空集的幂集,加上由{ {} }前置的集合。

所以我们需要知道空集的功率集。它是包含空集的集合。 Cherry

现在在每个元素前加{ {Cherry}, {} }并取结合。那是{ Cherry }。这给了我们{Banana, Cherry}的权力集。请记住,我们需要找到Banana的幂集,因此我们将其与每个{ {Banana, Cherry}, {Banana}, {Cherry}, {}}联合起来并获得{Banana, Cherry},这就是{Apple, Banana, Cherry}的幂集。

现在我们需要获得Apple的权力集,因此将其与{ {Apple, Banana, Cherry}, {Apple, Banana}, {Apple, Cherry}, {Apple}, {Banana, Cherry}, {Banana}, {Cherry}, {}}联合,并且每个人都有static IEnumerable<T> Prepend<T>(this IEnumerable<T> tail, T head) { yield return head; foreach(T item in tail) yield return item; } ,我们已经完成了static IEnumerable<IEnumerable<T>> PowerSet<T>(this IEnumerable<T> items) { if (!items.Any()) yield return items; // { { } } else { var head = items.First(); var powerset = items.Skip(1).PowerSet().ToList(); foreach(var set in powerset) yield return set.Prepend(head); foreach(var set in powerset) yield return set; } }

代码应该很容易编写。首先,我们需要一个辅助方法:

class ImmutableList<T>
{
    public static readonly ImmutableList<T> Empty = new ImmutableList<T>(null, default(T));
    private ImmutableList(ImmutableList<T> tail, T head)
    {
        this.Head = head;
        this.Tail = tail;
    }
    public T Head { get; private set; }
    public ImmutableList<T> Tail { get; private set; }
    public ImmutableList<T> Push(T head)
    {
        return new ImmutableList<T>(this, head);
    }
    public IEnumerable<ImmutableList<T>> PowerSet()
    {
        if (this == Empty)
            yield return this;
        else
        {
            var powerset = Tail.PowerSet();
            foreach (var set in powerset) yield return set.Push(Head);
            foreach (var set in powerset) yield return set;
        }
    }
}

现在代码是对算法描述的直接翻译:

{{1}}

有意义吗?

-----------更新----------------

谢尔盖正确地指出我的代码有Schlemiel the Painter算法,因此消耗了大量的时间和内存;好抓住谢尔盖。这是一个使用不可变堆栈的高效版本:

{{1}}

答案 2 :(得分:1)

使用Linq提及相同的算法SergeyS(其中inputSet是输入,outputPowerSet是输出):

int setLength = inputSet.Count;
int powerSetLength = 1 << setLength;
for (int bitMask = 0; bitMask < powerSetLength; bitMask++)
{
    var subSet = from x in inputSet 
                 where ((1 << inputSet.IndexOf(x)) & bitMask) != 0 
                 select x;
    outputPowerSet.Add(subSet.ToList());
}

答案 3 :(得分:0)

游戏很晚,但为什么不采用下面的方法呢?它似乎比这里发布的建议简单得多:

    /*
    Description for a sample set {1, 2, 2, 3}:
    Step 1 - Start with {}:
    {}
    Step 2 - "Expand" previous set by adding 1:
    {}
    ---
    {1}
    Step 3 - Expand previous set by adding the first 2:
    {}
    {1}
    ---
    {2}
    {1,2}
    Step 4 - Expand previous set by adding the second 2:
    {}
    {1}
    {2}
    {1,2}
    ---
    {2}
    {1,2}
    {2,2}
    {1,2,2}
    Step 5 - Expand previous set by adding 3:
    {}
    {1}
    {2}
    {1,2}
    {2}
    {1,2}
    {2,2}
    {1,2,2}
    ---
    {3}
    {1,3}
    {2,3}
    {1,2,3}
    {2,3}
    {1,2,3}
    {2,2,3}
    {1,2,2,3}
    Total elements = 16 (i.e. 2^4), as expected.
    */

    private static void PowerSet(IList<int> nums, ref IList<IList<int>> output)
    {
        // ToDo: validate args
        output.Add(new List<int>());
        ExpandSet(nums, 0, ref output);
    }

    private static void ExpandSet(IList<int> nums, int pos, ref IList<IList<int>> output)
    {
        if (pos == nums.Count)
        {
            return;
        }

        List<int> tmp;
        int item = nums[pos];

        for (int i = 0, n = output.Count; i < n; i++)
        {
            tmp = new List<int>();
            tmp.AddRange(output[i]);
            tmp.Add(item);
            output.Add(tmp);
        }

        ExpandSet(nums, pos + 1, ref output);
    }