独特的排列,没有镜像或循环重复

发布于 2024-07-27 02:16:51 字数 1602 浏览 3 评论 0原文

一些背景:我正在编写一个或多或少的强力搜索算法来解决我遇到的问题。 为了做到这一点,我需要生成并评估所有可能性,以找出最好的。 由于评估实际上需要一些时间,我宁愿生成尽可能少的完全覆盖我的搜索空间的解决方案。 此外,我可以执行此操作的元素越多越好。 对于任何数字 K,通常都有 K! 排列,对于大于 10 的数字来说,生成所有排列将很困难。

真正的问题:搜索空间应该包含两个元素的所有排列(N 乘以 el1 和 M 乘以 el2,其中 K=M+N),但有这些限制:

  1. 它们必须是唯一的(即我只想要 [aabbb] 一次
  2. )不需要任何排列的相反(即如果我有 [aab],我也不需要 [baa])
  3. 我认为排列是循环的,所以 [aab] = [aba] = [baa]

如果我如果能够做到这一点,可能性的数量就会大大减少。 由于 K 理想情况下会很大,因此首先生成所有排列然后根据这些标准过滤它们是不可行的。 我已经完成了第一个限制(见下文),它将 Matlab 正常排列函数(perms)的数量从 2^K 减少到 K!/N!M!,这是一个巨大的胜利。 第二个限制只会将可能性的数量减少一半(在最好的情况下),但我认为第三个限制也应该能够真正减少可能性的数量。

如果有人知道如何做到这一点,最好还知道如何计算有多少种可能性,那将对我有很大帮助! 我想要一个解释,但代码也很好(我可以阅读类 C 语言、Java(Script)、Python、Ruby、Lisp/Scheme)。


对于感兴趣的人:这是我迄今为止仅获取唯一排列的算法:

function genPossibilities(n, m, e1, e2)
     if n == 0
         return array of m e2's
     else
         possibilities = genPossibilities(n-1, m, e1, e2)
         for every possibility:
             gain = number of new possibilities we'll get for this smaller possibility*
             for i in max(0,(m+n-gain))
                 if possibility(i) is not e1
                     add possiblity with e1 inserted in position i
         return new possibilities
  • 如果您拥有 N-1 和 M 的所有排列,那么您可以通过将 e1 插入其中来使用它们来查找 N 和 M 的排列。 但你不能随处插入,因为那样你会得到重复项。 我不知道为什么会这样,但你可以计算从旧可能性中产生的新可能性的数量(我称之为“增益”)。 对于第一个旧排列,该数字从 M+1 开始,对于每个旧排列减少 1,直到它变为零,此时它返回到 M,依此类推(仅当 M>=N 时才有效)。 因此,如果您想计算 N=3 和 M=3 的排列,并且您有 N=2 和 M=3 的 10 个排列,那么它们的增益将为 [4 3 2 1 3 2 1 2 1 1]。 从排列的长度中减去这个增益,您就可以得到可以开始插入新元素而无需重复的索引。

Some background: I'm writing a more or less brute force search algorithm for solving a problem that I have. In order to do this, I need to generate and evaluate all possibilities to find out which is best. Since the evaluation actually takes some time I would prefer to generate as little as possible solutions that completely cover my search space. Furthermore, the more elements I can do this for, the better. For any number K there are normally K! permutations, and generating them all will be hard for numbers higher than ~10.

Real problem: The search space should contain all permutations of two elements (N times el1 and M times el2, where K=M+N), with these restrictions:

  1. they have to be unique (i.e. I only want [a a b b b] once)
  2. I don't need the reverse of any permutation (i.e. if I have [a a b], I don't also need [b a a])
  3. I consider the permutations to be circular, so [a a b] = [a b a] = [b a a]

If I would be able to do this, the number of possibilities would be decreased drastically. Since K will ideally be large, it is not feasible to first generate all permutations and then filter them according to these criteria. I have already done the first restriction (see below) and it cut back the number from 2^K for Matlab's normal permutations function (perms) to K!/N!M!, which is a huge win. The second restriction will only cut the number of possiblities in half (in the best case), but I think the third should also be able to really cut down the number of possibilities.

If anyone knows how to do it, and preferably also how to calculate how many possibilities there will be, that would help me a lot! I would prefer an explanation, but code is also fine (I can read C-like languages, Java(Script), Python, Ruby, Lisp/Scheme).


For the interested: Here is the algorithm for getting only unique permutations that I have so far:

function genPossibilities(n, m, e1, e2)
     if n == 0
         return array of m e2's
     else
         possibilities = genPossibilities(n-1, m, e1, e2)
         for every possibility:
             gain = number of new possibilities we'll get for this smaller possibility*
             for i in max(0,(m+n-gain))
                 if possibility(i) is not e1
                     add possiblity with e1 inserted in position i
         return new possibilities
  • If you have all permutations for N-1 and M, then you can use them to find the permutations for N and M by inserting e1 into them. You can't just insert everywhere though, because then you'll get duplicates. I don't know why this works, but you can calculate the number of new possibilities that you'll generate from an old one (I call this 'gain'). This number starts at M+1 for the first old permutation and decreases by one for each old permutation until it would become zero, at which point it goes back to M, etc. (only works if M>=N). So if you want to calculate the permutations for N=3 and M=3 and you have the 10 permutations for N=2 and M=3, their gains will be [4 3 2 1 3 2 1 2 1 1]. Subtract this gain from the length of the permutation and you get the index at which you can start inserting new elements without making duplicates.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

罗罗贝儿 2024-08-03 02:16:51

您所追求的是 2 元手链的子集(该子集由字符 A 的 n 和字符 B 的 m 定义)。 这套所有手镯允许 A 和 B 的数量发生变化。

以下代码打印出您要查找的序列,并按词汇顺序和恒定的摊销时间执行此操作。 它基于 Sawada 的这篇论文中的通用算法 -有关其工作原理的说明,请参阅该论文。

#include <stdlib.h>
#include <stdio.h>

static int *a;
static int n;

void print_bracelet(int n, int a[])
{
    int i;

    printf("[");
    for (i = 0; i < n; i++)
        printf(" %c", 'a' + a[i]);
    printf(" ]\n");
}

int check_rev(int t, int i)
{
    int j;

    for (j = i+1; j <= (t + 1)/2; j++)
    {
        if (a[j] < a[t-j+1])
            return 0;
        if (a[j] > a[t-j+1])
            return -1;
    }

    return 1;
}

void gen_bracelets(int n_a, int n_b, int t, int p, int r, int u, int v, int rs)
{
    if (2 * (t - 1) > (n + r))
    {
        if (a[t-1] > a[n-t+2+r])
            rs = 0;
        else if (a[t-1] < a[n-t+2+r])
            rs = 1;
    }
    if (t > n)
    {
        if (!rs && (n % p) == 0)
            print_bracelet(n, a + 1);
    }
    else
    {
        int n_a2 = n_a;
        int n_b2 = n_b;

        a[t] = a[t-p];

        if (a[t] == 0)
            n_a2--;
        else
            n_b2--;

        if (a[t] == a[1])
            v++;
        else
            v = 0;

        if ((u == (t - 1)) && (a[t-1] == a[1]))
            u++;

        if ((n_a2 >= 0) && (n_b2 >= 0) && !((t == n) && (u != n) && (a[n] == a[1])))
        {
            if (u == v) {
                int rev = check_rev(t, u);

                if (rev == 0)
                    gen_bracelets(n_a2, n_b2, t + 1, p, r, u, v, rs);

                if (rev == 1)
                    gen_bracelets(n_a2, n_b2, t + 1, p, t, u, v, 0);
            }
            else
                gen_bracelets(n_a2, n_b2, t + 1, p, r, u, v, rs);
        }

        if (u == t)
            u--;

        if (a[t-p] == 0 && n_b > 0)
        {
            a[t] = 1;

            if (t == 1)
                gen_bracelets(n_a, n_b - 1, t + 1, t, 1, 1, 1, rs);
            else
                gen_bracelets(n_a, n_b - 1, t + 1, t, r, u, 0, rs);
        }
    }
}

int main(int argc, char *argv[])
{
    int n_a, n_b;

    if (argc < 3)
    {
        fprintf(stderr, "Usage: %s <a> <b>\n", argv[0]);
        return -2;
    }

    n_a = atoi(argv[1]);
    n_b = atoi(argv[2]);

    if (n_a < 0 || n_b < 0)
    {
        fprintf(stderr, "a and b must be nonnegative\n");
        return -3;
    }

    n = n_a + n_b;
    a = malloc((n + 1) * sizeof(int));

    if (!a)
    {
        fprintf(stderr, "could not allocate array\n");
        return -1;
    }

    a[0] = 0;

    gen_bracelets(n_a, n_b, 1, 1, 0, 0, 0, 0);

    free(a);
    return 0;
}

What you are after is a subset of 2-ary bracelets (the subset is defined by exactly n of character A and m of character B). The set of all bracelets allows for the number of A's and B's to vary.

The following code prints out the sequences you are after, and does so in lexical order and in constant amortised time. It is based on the general algorithm in this paper by Sawada - for an explanation of how it works, see that paper.

#include <stdlib.h>
#include <stdio.h>

static int *a;
static int n;

void print_bracelet(int n, int a[])
{
    int i;

    printf("[");
    for (i = 0; i < n; i++)
        printf(" %c", 'a' + a[i]);
    printf(" ]\n");
}

int check_rev(int t, int i)
{
    int j;

    for (j = i+1; j <= (t + 1)/2; j++)
    {
        if (a[j] < a[t-j+1])
            return 0;
        if (a[j] > a[t-j+1])
            return -1;
    }

    return 1;
}

void gen_bracelets(int n_a, int n_b, int t, int p, int r, int u, int v, int rs)
{
    if (2 * (t - 1) > (n + r))
    {
        if (a[t-1] > a[n-t+2+r])
            rs = 0;
        else if (a[t-1] < a[n-t+2+r])
            rs = 1;
    }
    if (t > n)
    {
        if (!rs && (n % p) == 0)
            print_bracelet(n, a + 1);
    }
    else
    {
        int n_a2 = n_a;
        int n_b2 = n_b;

        a[t] = a[t-p];

        if (a[t] == 0)
            n_a2--;
        else
            n_b2--;

        if (a[t] == a[1])
            v++;
        else
            v = 0;

        if ((u == (t - 1)) && (a[t-1] == a[1]))
            u++;

        if ((n_a2 >= 0) && (n_b2 >= 0) && !((t == n) && (u != n) && (a[n] == a[1])))
        {
            if (u == v) {
                int rev = check_rev(t, u);

                if (rev == 0)
                    gen_bracelets(n_a2, n_b2, t + 1, p, r, u, v, rs);

                if (rev == 1)
                    gen_bracelets(n_a2, n_b2, t + 1, p, t, u, v, 0);
            }
            else
                gen_bracelets(n_a2, n_b2, t + 1, p, r, u, v, rs);
        }

        if (u == t)
            u--;

        if (a[t-p] == 0 && n_b > 0)
        {
            a[t] = 1;

            if (t == 1)
                gen_bracelets(n_a, n_b - 1, t + 1, t, 1, 1, 1, rs);
            else
                gen_bracelets(n_a, n_b - 1, t + 1, t, r, u, 0, rs);
        }
    }
}

int main(int argc, char *argv[])
{
    int n_a, n_b;

    if (argc < 3)
    {
        fprintf(stderr, "Usage: %s <a> <b>\n", argv[0]);
        return -2;
    }

    n_a = atoi(argv[1]);
    n_b = atoi(argv[2]);

    if (n_a < 0 || n_b < 0)
    {
        fprintf(stderr, "a and b must be nonnegative\n");
        return -3;
    }

    n = n_a + n_b;
    a = malloc((n + 1) * sizeof(int));

    if (!a)
    {
        fprintf(stderr, "could not allocate array\n");
        return -1;
    }

    a[0] = 0;

    gen_bracelets(n_a, n_b, 1, 1, 0, 0, 0, 0);

    free(a);
    return 0;
}
甜嗑 2024-08-03 02:16:51

我想你想生成 2 元免费项链。 请参阅此问题获取链接、论文和一些代码。

I think you want to generate 2-ary free necklaces. See this question for link, papers, and some code.

路弥 2024-08-03 02:16:51

您正在寻找与顺序无关的组合。 Matlab 使用 K!/N!M! 正确计算了这一点 这正是计算组合数的公式。

You are looking for combinations - which are order independent. Matlab calculated this correctly with K!/N!M! which is precisely the formula for calculating the number of combinations.

终陌 2024-08-03 02:16:51

假设您有一个包含所有排列的数组,您可以将数组的内容放入哈希中。 然后这就会起作用(有点暴力,但它是一个开始):

for each (element in array of permutations){
  if (element exists in hash){
    remove each circular permutation of element in hash except for element itself
  }
}

Assuming you have an array of all permutations, you can put the contents of the array into a hash. Then this will work (a little brute force, but its a start):

for each (element in array of permutations){
  if (element exists in hash){
    remove each circular permutation of element in hash except for element itself
  }
}
夏雨凉 2024-08-03 02:16:51

如果只有两个元素,则空间要小得多:2^k 而不是 k!。

尝试这样的方法:

  1. 遍历从 1 到 2^k 的所有数字。
  2. 将数字写成二进制形式。
  3. 将所有 0 转换为 a,将 1 转换为 b。 现在你有了一个排列。
  4. 获取序列并通过循环排列和反转生成 2k 序列。 您只需评估这 2k 序列中的 1 个。
  5. 在 2k 个序列中,选择按字母顺序排序第一的序列。
  6. 检查您的日志,看看您是否已经完成了这一操作。 如果是这样,请跳过。
  7. 如果这是新的,请对其进行评估,然后添加到“完成”日志中。 (如果空间允许,您可以将“族”的所有 2k 个元素添加到完成的日志中,这样您就可以将步骤 (6) 移到步骤 (3) 之后。您还可以存储数字,而不是 a 的序列和 b,在“完成”日志中。)

如果您有 j 个可能的符号,而不仅仅是两个,请执行相同的操作,但使用基数 j 而不是基数 2。

If you have only two elements, your space is much smaller: 2^k rather than k!.

Try an approach like this:

  1. Run through all numbers from 1 through 2^k.
  2. Write the number in binary form.
  3. Translate all 0s to a and 1s to b. Now you have a permutation.
  4. Take your sequence and generate 2k sequences by cyclic permutations and reversal. You only need to evaluate 1 of these 2k sequences.
  5. Among the 2k sequences, choose the one that sorts first alphabetically.
  6. Check your log to see if you've already done this one. If so, skip.
  7. If this one is new, evaluate it, and add to the "done" log. (If space permits, you could add all 2k elements of the "family" to the done log, so you could move step (6) to right after step (3). You could also store the number, rather than the sequence of a's and b's, in the "done" log.)

If you have j possible symbols, rather than just two, do the same thing but use base j rather than base 2.

疧_╮線 2024-08-03 02:16:51

我对 k 进制情况有一个想法,如下:

参考文献:

  1. Python 算法:项链生成/ 循环排列
  2. 如何使用生成器在 Python 中生成没有“反向重复项”的列表排列
def findPermutations(n):
    """
    Descriptions
    ------------
    Find all possible permutations for a given positive integers n such that the elements are 0, 1, 2,..., n-1,  
    excluding mirrored or circular repetitions.
    Example: if n = 3, there in only one possible permutation:
            [0, 1, 2]
    Parameters
    ----------
    n : positive integers

    Returns
    -------
    x : list
        x[i][:] refers to the site order in the ith permutation
    """
    
    ls = np.arange(0,n).tolist()
    permutations = []
    for p in itertools.permutations( ls[1:] ):
        if p <= p[::-1]:
            permutations += [p]
           
    for end in permutations:
        yield [ls[0]] + list(end) 

x = list(findPermutations(4))

输出应该是

[[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3]]

I got an idea for the k-ary case as follows:

References:

  1. Python Algorithms: Necklace Generation / Circular Permutations
  2. How to generate permutations of a list without “reverse duplicates” in Python using generators
def findPermutations(n):
    """
    Descriptions
    ------------
    Find all possible permutations for a given positive integers n such that the elements are 0, 1, 2,..., n-1,  
    excluding mirrored or circular repetitions.
    Example: if n = 3, there in only one possible permutation:
            [0, 1, 2]
    Parameters
    ----------
    n : positive integers

    Returns
    -------
    x : list
        x[i][:] refers to the site order in the ith permutation
    """
    
    ls = np.arange(0,n).tolist()
    permutations = []
    for p in itertools.permutations( ls[1:] ):
        if p <= p[::-1]:
            permutations += [p]
           
    for end in permutations:
        yield [ls[0]] + list(end) 

x = list(findPermutations(4))

The output should be

[[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3]]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文