以唯一给定索引在O(n)内获取排列

7 投票
5 回答
1696 浏览
提问于 2025-04-18 15:49

我想要一个叫做 get_permutation 的函数,它接收一个列表 l 和一个索引 i,返回一个 l 的排列。这个排列要确保对于所有大于 0 且小于 n!i,排列都是唯一的(这里 n = len(l))。

也就是说,如果 ij 不相等(i!=j),那么 get_permutation(l,i)get_permutation(l,j) 的结果也应该不一样,前提是 0 <= i 和 j < len(l)!

此外,这个函数的运行时间要在 O(n) 的范围内。

举个例子,如果这个函数不涉及指数级的复杂度,它就能满足这些要求:

def get_permutation(l, i):
     return list(itertools.permutations(l))[i]

有没有人能提供一个解决上述问题的方法?

编辑:我想要的是从索引获取排列,而不是从排列获取索引。

5 个回答

0

有点晚了……下面是C#代码,应该能给你想要的结果:

using System;
using System.Collections.Generic;

namespace WpfPermutations
{
    public class PermutationOuelletLexico3<T>
    {
        // ************************************************************************
        private T[] _sortedValues;

        private bool[] _valueUsed;

        public readonly long MaxIndex; // long to support 20! or less 

        // ************************************************************************
        public PermutationOuelletLexico3(T[] sortedValues)
        {
            if (sortedValues.Length <= 0)
            {
                throw new ArgumentException("sortedValues.Lenght should be greater than 0");
            }

            _sortedValues = sortedValues;
            Result = new T[_sortedValues.Length];
            _valueUsed = new bool[_sortedValues.Length];

            MaxIndex = Factorial.GetFactorial(_sortedValues.Length);
        }

        // ************************************************************************
        public T[] Result { get; private set; }

        // ************************************************************************
        /// <summary>
        /// Return the permutation relative to the index received, according to 
        /// _sortedValues.
        /// Sort Index is 0 based and should be less than MaxIndex. Otherwise you get an exception.
        /// </summary>
        /// <param name="sortIndex"></param>
        /// <param name="result">Value is not used as inpu, only as output. Re-use buffer in order to save memory</param>
        /// <returns></returns>
        public void GetValuesForIndex(long sortIndex)
        {
            int size = _sortedValues.Length;

            if (sortIndex < 0)
            {
                throw new ArgumentException("sortIndex should be greater or equal to 0.");
            }

            if (sortIndex >= MaxIndex)
            {
                throw new ArgumentException("sortIndex should be less than factorial(the lenght of items)");
            }

            for (int n = 0; n < _valueUsed.Length; n++)
            {
                _valueUsed[n] = false;
            }

            long factorielLower = MaxIndex;

            for (int index = 0; index < size; index++)
            {
                long factorielBigger = factorielLower;
                factorielLower = Factorial.GetFactorial(size - index - 1);  //  factorielBigger / inverseIndex;

                int resultItemIndex = (int)(sortIndex % factorielBigger / factorielLower);

                int correctedResultItemIndex = 0;
                for(;;)
                {
                    if (! _valueUsed[correctedResultItemIndex])
                    {
                        resultItemIndex--;
                        if (resultItemIndex < 0)
                        {
                            break;
                        }
                    }
                    correctedResultItemIndex++;
                }

                Result[index] = _sortedValues[correctedResultItemIndex];
                _valueUsed[correctedResultItemIndex] = true;
            }
        }

        // ************************************************************************
        /// <summary>
        /// Calc the index, relative to _sortedValues, of the permutation received
        /// as argument. Returned index is 0 based.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public long GetIndexOfValues(T[] values)
        {
            int size = _sortedValues.Length;
            long valuesIndex = 0;

            List<T> valuesLeft = new List<T>(_sortedValues);

            for (int index = 0; index < size; index++)
            {
                long indexFactorial = Factorial.GetFactorial(size - 1 - index);

                T value = values[index];
                int indexCorrected = valuesLeft.IndexOf(value);
                valuesIndex = valuesIndex + (indexCorrected * indexFactorial);
                valuesLeft.Remove(value);
            }
            return valuesIndex;
        }

        // ************************************************************************
    }
}
1

更新:可能是重复的问题,详情请见如何找到第n个排列而不计算其他排列,那里面有算法。

如果len(l)的值比较小,你可以提前计算出perm_index = permutations(range(len(l))),然后把它当作一个包含索引的列表,方便你访问实际的数据。

此外,如果你已经有了一个从range(len(l))生成的排列列表,而你需要的是range(len(l) - 1)的排列,你可以这样做:

[x - 1 for x in perm_index[i][1:]]

这个方法利用了生成的排列是按顺序排列的这个特点。

1

根据这个链接 http://www.2ality.com/2013/03/permutations.html,这里有一个可能的解决方案。正如@Gassa提到的,elements.pop这个操作不是恒定的,所以这个解决方案在列表长度上不是线性的。因此,我不会把这个标记为被接受的答案。不过,它确实能完成任务。

def integerToCode(idx, permSize):                                                                                                                                       
    if (permSize <= 1):                                                                                                                                                 
        return [0]                                                                                                                                                      
    multiplier = math.factorial(permSize-1)                                                                                                                             
    digit =idx / multiplier                                                                                                                                             
    return [digit] +  integerToCode(idx % multiplier, permSize-1)                                                                                                       


def codeToPermutation(elements, code):                                                                                                                                  
    return map(lambda i: elements.pop(i), code)                                                                                                                         

def get_permutation(l, i):                                                                                                                                              
    c = integerToCode(i, len(l))                                                                                                                                        
    return codeToPermutation(list(l), c)
1

这个解决方案的运行速度是 O(1)(也就是查找字典的平均成本):

代码

#!/usr/bin/env python

import itertools


def get_permutation():
    memoize = {}

    def _memoizer(l, i):
        if str(l) in memoize and i not in memoize[str(l)]:
            memoize[str(l)][i] = memoize[str(l)]['permutations'].next()
        else:
            p = itertools.permutations(l)
            memoize[str(l)] = {'permutations': p}
            memoize[str(l)][i] = memoize[str(l)]['permutations'].next()
        return memoize[str(l)][i]
    return _memoizer

if __name__ == '__main__':
    get_permutation = get_permutation()
    l1 = list(range(10))
    l2 = list(range(5))
    print(get_permutation(l1, 1))
    print(get_permutation(l1, 20))
    print(get_permutation(l2, 3))

输出

(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
(0, 1, 2, 3, 4, 5, 6, 7, 9, 8)
(0, 1, 2, 3, 4)

原理

这段代码把所有之前的调用都存储在一个字典里。同时,它也保存了排列对象。所以如果有新的排列请求,就会使用下一个排列。

这段代码使用了 itertools.permutations 这个库。

6

如果你不在乎每个排列对应哪个索引,那么有一种O(n)的解决方案是可能的,前提是我们认为用任意整数进行的算术运算是O(1)的。

比如,可以参考Wendy Myrvold和Frank Ruskey的论文“线性时间内排列的排名和反排名”。

简单来说,这里有两个主要的思路。


(1) 考虑使用Fisher-Yates洗牌方法来生成一个随机排列(下面是伪代码):

p = [0, 1, ..., n-1]
for i := 0 upto n-1:
    j := random_integer (0, i)
    exchange p[i] and p[j]

这个转换是单射的:如果我们给它一个不同的随机整数序列,它一定会生成一个不同的排列。所以,我们可以把随机整数换成非随机的:第一个是0,第二个是0或1,……最后一个可以是从0到n-1的任何整数。


(2) 对于n个元素,有n!种排列。我们现在想做的是把一个从0到n!-1的整数写成阶乘数系统:最后一位总是0,前一位是0或1,……第一位有n种可能,从0到n-1。因此,我们会得到一个唯一的序列来输入到上面的伪代码中。

现在,如果我们把这个数字除以1到n之间的整数视为O(1)的操作,那么将数字转换为阶乘系统需要O(n)次这样的除法。严格来说,这并不完全正确:对于较大的n,数字n!大约包含O(n log n)个二进制位,而除法的成本与位数有关。


实际上,对于小的n,O(n^2)或O(n log n)的方法来排名或反排名一个排列,以及需要O(2^n)或O(n!)内存来存储一些预计算值的方法,可能比涉及整数除法的O(n)方法更快,因为在现代处理器上,整数除法相对较慢。对于足够大的n,以至于n!无法放入一个机器字中,“如果阶乘级整数运算是O(1)的,那么就是O(n)”的论点就不成立了。因此,对于小n和大n来说,如果你不坚持理论上是O(n),可能会更好一些。

撰写回答