【问题标题】:Finding the (lexicographic) index of a permutation of a given array.查找给定数组排列的(字典)索引。
【发布时间】:2012-08-27 17:48:48
【问题描述】:

给定一个数组“bca”,我需要找到在字典上大于给定排列的排列数。

因此,在本例中,cab、cba 是更大的排列。因此答案是 2。

我尝试通过查找数组的字典排序来解决这个问题,但我无法为这个说法设计一个有效的算法。

感谢任何正确方向的帮助/指针!

【问题讨论】:

  • 您尝试过回溯方法吗?它实际上效率不高,但确实有效。如果你想让我发布一些代码告诉我。
  • @kilotaras:不,排列必须按照通常的定义使用相同数量的文字形成
  • @Thanatos:不,我不知道回溯方法。你能解释一下并发布一些代码吗?谢谢!
  • @PaulTomblin - 问题本质上是“什么是执行 X 的有效算法?”。没有(伪)代码,谁能回答这个问题?

标签: arrays algorithm permutation


【解决方案1】:

让我们看一下排列dacb。这在 4 中按字典顺序排列在哪里! = abcd的24个排列?

  • 考虑第一个字母d。在剩余的字母(acb)中,有 3 个字母小于 d,还有 3 个! = 6 个排列,从每个排列开始,总共 18 个排列。
  • 考虑前两个字母da。在剩余的字母 (cb) 中,没有小于 a 的字母(如果有的话,将有 2!= 2 个以 d 开头的排列加上一个),总共 0 个排列。李>
  • 考虑前三个字母dac。在剩余的字母(b)中,有一个字母小于c,还有一个! = 以 dab 开头的 1 个排列,总共 1 个排列。

所以总共有 19 个排列小于dacb。让我们检查一下。

>>> from itertools import permutations
>>> list(enumerate(''.join(p) for p in permutations('abcd')))
[(0, 'abcd'), (1, 'abdc'), (2, 'acbd'), (3, 'acdb'),
 (4, 'adbc'), (5, 'adcb'), (6, 'bacd'), (7, 'badc'),
 (8, 'bcad'), (9, 'bcda'), (10, 'bdac'), (11, 'bdca'),
 (12, 'cabd'), (13, 'cadb'), (14, 'cbad'), (15, 'cbda'),
 (16, 'cdab'), (17, 'cdba'), (18, 'dabc'), (19, 'dacb'),
 (20, 'dbac'), (21, 'dbca'), (22, 'dcab'), (23, 'dcba')]

看起来不错。所以有4个! − 19 − 1 = 4 个大于dacb 的排列。

现在应该清楚如何概括方法来制作算法了。这是 Python 中的一个实现:

from math import factorial

def lexicographic_index(p):
    """
    Return the lexicographic index of the permutation `p` among all
    permutations of its elements. `p` must be a sequence and all elements
    of `p` must be distinct.

    >>> lexicographic_index('dacb')
    19
    >>> from itertools import permutations
    >>> all(lexicographic_index(p) == i
    ...     for i, p in enumerate(permutations('abcde')))
    True
    """
    result = 0
    for j in range(len(p)):
        k = sum(1 for i in p[j + 1:] if i < p[j])
        result += k * factorial(len(p) - j - 1)
    return result

def lexicographic_followers(p):
    """
    Return the number of permutations of `p` that are greater than `p`
    in lexicographic order. `p` must be a sequence and all elements
    of `p` must be distinct.
    """
    return factorial(len(p)) - lexicographic_index(p) - 1

【讨论】:

  • 使用你的方法,“acbd”索引为 2:实际上它为 3。“abdc”索引为 2。
  • @SSV: abcd = 0, abdc = 1, acbd = 2。这有什么问题?
  • 有兴趣的朋友可以在here找到我的C++实现。
【解决方案2】:

有一种基于阶乘数字系统和 Lehmer 代码的非常简洁的方法。这个想法是为每个可能的排列分配一个数字代码,该排列编码值出现的顺序(Lehmer code)。然后,您可以将 Lehmer 代码转换为一个数字,该数字确定所有排列列表中排列的索引(这使用 factorial number system)。给定排列的索引,然后您可以计算 (n! - 1) 并减去索引以确定还有多少排列。

如果你好奇如何做到这一点,我有an implementation of this algorithm,它可以让你从排列映射到索引,反之亦然。我还给了talk on how to do this;详情在幻灯片的后半部分。

希望这会有所帮助!

【讨论】:

  • 我很确定这不适用于多组。我不知道如何做到这一点的好答案。你绝对应该问这个问题!
【解决方案3】:

这是回溯解决方案:

程序对给定字符串的所有解决方案进行置换,并返回解决方案列表以及其中的数量。

例如: 对于acb,它返回:

c a b
c b a
b a c
b c a
4

代码:

#include <iostream>
#include <stdio>

using namespace std;

int v[100], n, cnt;
char *str;

void init(int k)
{
    v[k] = -1;
}

bool solutionReached( int k ) 
{
    if (k == n + 1)
        return true;
    return false;
}

void printSolution( int k ) 
{
    for (int i = 1; i < k; i++)
    {
        printf("%c ", str[v[i]]);
    }

    printf("\n");

    cnt++;
}

bool hasSuccesor( int k ) 
{
    if(v[k] < n - 1)
    {
        v[k]++;
        return true;
    }
    return false;
}

bool isValid( int k ) 
{
    for (int i = 1; i < k; i++)
    {
        if (v[i] == v[k])
        {
            return false;
        }
    }

    if (k == n)
    {
        char *cuv = (char *) malloc(n * sizeof(char));

        for (i = 0; i < n; i++)
            cuv[i] = str[v[i + 1]];

        if (strcmp(cuv, str) > 0)
        {
            return true;
        }
        else
            return false;
    }

    return true;
}

void bkt(int k)
{
    if(solutionReached(k))
        printSolution(k);
    else
    {
        init(k);
        while(hasSuccesor(k))
            if(isValid(k))
                bkt(k + 1);
    }
}

int main(int argc, char* argv[])
{
    str = "bca";

    n = strlen(str);
    bkt(1);

    printf("%i \n", --cnt);

    return 0;
}

【讨论】:

    【解决方案4】:

    简单的 Python 解决方案依赖于 Python 的排列生成器将从初始排序字符串按字典顺序生成这一事实。

    In [68]: from itertools import permutations
    
    In [69]: from math import factorial
    
    In [70]: def lexigreaterperms(perm):
        ...:     return factorial(len(perm)) - 1 -  list(permutations(sorted(perm))).index(tuple(perm))
    
    In [71]: lexigreaterperms('bca')
    Out[71]: 2
    
    In [72]: lexigreaterperms('dacb')
    Out[72]: 4
    

    【讨论】:

    • 如果你尝试lexigreaterperms(range(20))会发生什么?
    • @Gareth Rees:如果您在没有衡量任务需求的情况下过早优化,通常会发生什么。我很清楚算法的复杂性。除了所述之外,我不知道该任务的任何其他需求。您的评论可能相当于“但是如果您在熔岩上行走会发生什么”,因为有人要求穿凉鞋在炎热的海滩上行走。开始简单。获得正确且可以衡量的东西。如果有必要,您可以随时使用它来测试另一个更有效的实现。但只有那么
    • OP 使用了“高效”这个词,我认为它的意思是“不是通过生成所有排列,这将花费指数时间”。但无论如何,即使指数时间是可以接受的,对list 的调用确保也需要指数空间。
    猜你喜欢
    • 2012-12-10
    • 1970-01-01
    • 2012-02-14
    • 2017-08-17
    • 2011-07-05
    • 1970-01-01
    • 2018-08-31
    • 2022-09-23
    • 2010-10-28
    相关资源
    最近更新 更多