I just read this other question about the complexity of next_permutation and while I'm satisfied with the response (O(n)), it seems like the algorithm might have a nice amortized analysis that shows a lower complexity. Does anyone know of such an analysis?
我刚刚读到另一个关于next_per突变的复杂性的问题,当我对响应(O(n))感到满意时,似乎这个算法可能有一个很好的平平化分析,它显示了较低的复杂度。有人知道这样的分析吗?
3 个解决方案
#1
16
So looks like I'm going to be answering my own question in the affirmative - yes, next_permutation
runs in O(1) amortized time.
所以看起来我要用肯定的方式回答我自己的问题-是的,next_per突变在O(1)平摊时间内运行。
Before I go into a formal proof of this, here's a quick refresher on how the algorithm works. First, it scans backwards from the end of the range toward the beginning, identifying the longest contiguous decreasing subsequence in the range that ends at the last element. For example, in 0 3 4 2 1
, the algorithm would identify 4 2 1
as this subsequence. Next, it looks at the element right before this subsequence (in the above example, 3), then finds the smallest element in the subsequence larger than it (in the above example, 4). Then then exchanges the positions of those two elements and then reverses the identified sequence. So, if we started with 0 3 4 2 1
, we'd swap the 3 and 4 to yield 0 4 3 2 1
, and would then reverse the last three elements to yield 0 4 1 2 3
.
在正式证明这一点之前,我们先来快速回顾一下这个算法是如何工作的。首先,它从距离的末尾向后扫描到开始,确定在最后一个元素结束的范围内最长的连续递减子序列。例如,在0 3 4 2 1中,该算法将确定4 2 1作为这个子序列。接下来,它在子序列之前(在上面的例子中,3)查看元素,然后在子序列中找到最小的元素(在上面的例子中,4),然后交换这两个元素的位置,然后反转被识别的序列。所以,如果我们从0 3 4 2 1开始,我们把3和4交换成0 4 3 2 1,然后把最后3个元素反过来,得到0 4 1 2 3。
To show that this algorithm runs in amortized O(1), we'll use the potential method. Define Φ to be three times the length of the longest contiguously decreasing subsequence at the end of the sequence. In this analysis, we'll assume that all the elements are distinct. Given this, let's think about the runtime of this algorithm. Suppose that we scan backwards from the end of the sequence and find that the last m elements are part of the decreasing sequence. This requires m + 1 comparisons. Next, we find, of the elements of that sequence, which one is the smallest larger than the element preceding this sequence. This takes in the worst case time proportional to the length of the decreasing sequence using a linear scan for another m comparisons. Swapping the elements takes, say, 1 credit's worth of time, and reversing the sequence then requires at most m more operations. Thus the real runtime of this step is roughly 3m + 1. However, we have to factor in the change in potential. After we reverse this sequence of length m, we end up reducing the length of the longest decreasing sequence at the end of the range to be length 1, because reversing the decreasing sequence at the end makes the last elements of the range sorted in ascending order. This means that our potential changed from Φ = 3m to Φ' = 3 * 1 = 3. Consequently, the net drop in potential is 3 - 3m, so our net amortized time is 3m + 1 + (3 - 3m) = 4 = O(1).
为了证明该算法在平摊O(1)中运行,我们将使用势能方法。Φ定义为三次最长连续减少子序列的长度的序列。在这个分析中,我们假设所有的元素都是不同的。考虑到这一点,我们来考虑一下这个算法的运行时间。假设我们从序列的末尾向后扫描,发现最后的m元素是递减序列的一部分。这需要m + 1比较。接下来,我们发现这个序列的元素,其中一个比这个序列前面的元素要小。这是在最坏的情况下,用线性扫描进行另一个m比较,与递减序列的长度成比例。交换元素,比如说,1个学分的时间,然后改变顺序,然后需要最多m的操作。因此,这个步骤的真正运行时大约是3m + 1。然而,我们必须考虑到潜力的变化。当我们逆转这个长度m的序列后,我们最终会减少长度为1的最长递减序列的长度,因为在末端反转递减序列使最后的元素按升序排序。这意味着,我们的势能从= 3m变为' = 3 * 1 = 3。因此,电位的净下降是3 - 3m,所以我们的净平摊时间是3m + 1 + (3 - 3m) = 4 = O(1)。
In the preceding analysis I made the simplifying assumption that all the values are unique. To the best of my knowledge, this assumption is necessary in order for this proof to work. I'm going to think this over and see if the proof can be modified to work in the case where the elements can contain duplicates, and I'll post an edit to this answer once I've worked through the details.
在前面的分析中,我做了一个简化的假设,即所有的值都是唯一的。据我所知,这一假设是必要的,以便证明能有效。我将仔细考虑这个问题,看看是否可以修改这个证明,以便在元素可以包含重复的情况下工作,并且在我完成了细节之后,我将对这个答案进行编辑。
#2
13
I am not really sure of the exact implementation of std::next_permutation, but if it is the same as Narayana Pandita's algorithm as desribed in the wiki here: http://en.wikipedia.org/wiki/Permutation#Systematic_generation_of_all_permutations,
我不确定std的具体实现::next_per突变,但如果它与Narayana Pandita的算法相同,就像在wiki上的desribed一样:http://en.wikipedia.org/wiki/per突变# systematic_generation_of_all_per突变,
assuming the elements are distinct, looks like it is O(1) amortized! (Of course, there might be errors in the below)
假设元素是不同的,看起来是O(1)平摊!(当然,下面可能有错误)
Let us count the total number of swaps done.
让我们计算一下交换的总数。
We get the recurrence relation
我们得到了递归关系。
T(n+1) = (n+1)T(n) + Θ(n2)
T(n + 1)=(n + 1)T(n)+Θ(n2)
(n+1)T(n) comes from fixing the first element and doing the swaps for the remaining n.
(n+1)T(n)来自于固定第一个元素并为剩余的n做掉期。
Θ(n2) comes from changing the first element. At the point we change the first element, we do Θ(n) swaps. Do that n times, you get Θ(n2).
(n2)来自于改变第一个元素。在我们改变第一个元素的时候,我们做的是交换。这样做n次,得到Θ(n2)。
Now let X(n) = T(n)/n!
现在让X(n) = T(n)/n!
Then we get
然后我们得到
X(n+1) = X(n) + Θ(n2)/(n+1)!
(n + 1)= X(n)+Θ(n2)/(n + 1)!
i.e there is some constant C such that
我。e有一个常数C。
X(n+1) <= X(n) + Cn2/(n+1)!
X(n+1) <= X(n) + Cn2/(n+1)!
Writing down n such inequalities gives us
写下n这样的不等式给我们。
X(n+1) - X(n) <= Cn2/(n+1)!
X(n+1) - X(n) <= Cn2/(n+1)!
X(n) - X(n-1) <= C(n-1)2/(n)!
X(n) - X(n-1) <= C(n-1)2/(n)!
X(n-1) - X(n-2) <= C(n-2)2/(n-1)!
X(n-1) - X(n-2) <= C(n-2)2/(n-1)!
...
…
X(2) - X(1) <= C12/(1+1)!
X(2) - X(1) <= C12/(1+1)!
Adding these up gives us X(n+1) - X(1) <= C(\sum j = 1 to n (j^2)/(j+1)!)
.
添加这些给我们(n + 1)- X(1)< = C(\和j = 1到n(j ^ 2)/(j + 1)!)。
Since the infinite series \sum j = 1 to infinity j^2/(j+1)!
converges to C', say, we get X(n+1) - X(1) <= CC'
自从无穷级数\和j = 1到无穷大^ 2 /(j + 1)!收敛到C',我们得到X(n+1) - X(1) <= CC'
Remember that X(n) counts the average number of swaps needed (T(n)/n!)
记住,X(n)计算所需掉期的平均数(T(n)/n!)
Thus the average number of swaps is O(1).
因此,掉期的平均数是O(1)。
Since finding the elements to swap is linear with the number of swaps, it is O(1) amortized even if you take other operations into consideration.
因为找到交换的元素与交换的数量是线性的,所以即使你考虑其他的操作,它也是平摊的。
#3
0
Here n
stands for the count of elements in the container, not the total count of possible permutations. The algorithm must iterate through an order of all elements at each call; it takes a pair of bidirectional iterators, which implies that to get to one element the algorithm must first visit the one before it (unless its the first or last element). A bidirectional iterator allows iterating backwards, so the algorithm can (must, in fact) perform half as many swaps as there are elements. I believe the standard could offer an overload for a forward iterator, which would support dumber iterators at the cost of n
swaps rather than half n
swaps. But alas, it didn't.
这里n代表容器中元素的计数,而不是可能排列的总数。该算法必须遍历每个调用的所有元素的顺序;它需要一对双向迭代器,这意味着要得到一个元素,算法必须首先访问一个元素(除非它是第一个或最后一个元素)。双向迭代器允许向后迭代,因此算法可以(实际上必须)执行一半的交换,因为其中有元素。我相信这个标准可以为一个前向迭代器提供一个重载,它可以支持dumber迭代器,而不是使用n个交换器。但是唉,它没有。
Of course, for n
possible permutations the algorithm operates in O(1).
当然,对于n个可能的排列,算法在O(1)中操作。
#1
16
So looks like I'm going to be answering my own question in the affirmative - yes, next_permutation
runs in O(1) amortized time.
所以看起来我要用肯定的方式回答我自己的问题-是的,next_per突变在O(1)平摊时间内运行。
Before I go into a formal proof of this, here's a quick refresher on how the algorithm works. First, it scans backwards from the end of the range toward the beginning, identifying the longest contiguous decreasing subsequence in the range that ends at the last element. For example, in 0 3 4 2 1
, the algorithm would identify 4 2 1
as this subsequence. Next, it looks at the element right before this subsequence (in the above example, 3), then finds the smallest element in the subsequence larger than it (in the above example, 4). Then then exchanges the positions of those two elements and then reverses the identified sequence. So, if we started with 0 3 4 2 1
, we'd swap the 3 and 4 to yield 0 4 3 2 1
, and would then reverse the last three elements to yield 0 4 1 2 3
.
在正式证明这一点之前,我们先来快速回顾一下这个算法是如何工作的。首先,它从距离的末尾向后扫描到开始,确定在最后一个元素结束的范围内最长的连续递减子序列。例如,在0 3 4 2 1中,该算法将确定4 2 1作为这个子序列。接下来,它在子序列之前(在上面的例子中,3)查看元素,然后在子序列中找到最小的元素(在上面的例子中,4),然后交换这两个元素的位置,然后反转被识别的序列。所以,如果我们从0 3 4 2 1开始,我们把3和4交换成0 4 3 2 1,然后把最后3个元素反过来,得到0 4 1 2 3。
To show that this algorithm runs in amortized O(1), we'll use the potential method. Define Φ to be three times the length of the longest contiguously decreasing subsequence at the end of the sequence. In this analysis, we'll assume that all the elements are distinct. Given this, let's think about the runtime of this algorithm. Suppose that we scan backwards from the end of the sequence and find that the last m elements are part of the decreasing sequence. This requires m + 1 comparisons. Next, we find, of the elements of that sequence, which one is the smallest larger than the element preceding this sequence. This takes in the worst case time proportional to the length of the decreasing sequence using a linear scan for another m comparisons. Swapping the elements takes, say, 1 credit's worth of time, and reversing the sequence then requires at most m more operations. Thus the real runtime of this step is roughly 3m + 1. However, we have to factor in the change in potential. After we reverse this sequence of length m, we end up reducing the length of the longest decreasing sequence at the end of the range to be length 1, because reversing the decreasing sequence at the end makes the last elements of the range sorted in ascending order. This means that our potential changed from Φ = 3m to Φ' = 3 * 1 = 3. Consequently, the net drop in potential is 3 - 3m, so our net amortized time is 3m + 1 + (3 - 3m) = 4 = O(1).
为了证明该算法在平摊O(1)中运行,我们将使用势能方法。Φ定义为三次最长连续减少子序列的长度的序列。在这个分析中,我们假设所有的元素都是不同的。考虑到这一点,我们来考虑一下这个算法的运行时间。假设我们从序列的末尾向后扫描,发现最后的m元素是递减序列的一部分。这需要m + 1比较。接下来,我们发现这个序列的元素,其中一个比这个序列前面的元素要小。这是在最坏的情况下,用线性扫描进行另一个m比较,与递减序列的长度成比例。交换元素,比如说,1个学分的时间,然后改变顺序,然后需要最多m的操作。因此,这个步骤的真正运行时大约是3m + 1。然而,我们必须考虑到潜力的变化。当我们逆转这个长度m的序列后,我们最终会减少长度为1的最长递减序列的长度,因为在末端反转递减序列使最后的元素按升序排序。这意味着,我们的势能从= 3m变为' = 3 * 1 = 3。因此,电位的净下降是3 - 3m,所以我们的净平摊时间是3m + 1 + (3 - 3m) = 4 = O(1)。
In the preceding analysis I made the simplifying assumption that all the values are unique. To the best of my knowledge, this assumption is necessary in order for this proof to work. I'm going to think this over and see if the proof can be modified to work in the case where the elements can contain duplicates, and I'll post an edit to this answer once I've worked through the details.
在前面的分析中,我做了一个简化的假设,即所有的值都是唯一的。据我所知,这一假设是必要的,以便证明能有效。我将仔细考虑这个问题,看看是否可以修改这个证明,以便在元素可以包含重复的情况下工作,并且在我完成了细节之后,我将对这个答案进行编辑。
#2
13
I am not really sure of the exact implementation of std::next_permutation, but if it is the same as Narayana Pandita's algorithm as desribed in the wiki here: http://en.wikipedia.org/wiki/Permutation#Systematic_generation_of_all_permutations,
我不确定std的具体实现::next_per突变,但如果它与Narayana Pandita的算法相同,就像在wiki上的desribed一样:http://en.wikipedia.org/wiki/per突变# systematic_generation_of_all_per突变,
assuming the elements are distinct, looks like it is O(1) amortized! (Of course, there might be errors in the below)
假设元素是不同的,看起来是O(1)平摊!(当然,下面可能有错误)
Let us count the total number of swaps done.
让我们计算一下交换的总数。
We get the recurrence relation
我们得到了递归关系。
T(n+1) = (n+1)T(n) + Θ(n2)
T(n + 1)=(n + 1)T(n)+Θ(n2)
(n+1)T(n) comes from fixing the first element and doing the swaps for the remaining n.
(n+1)T(n)来自于固定第一个元素并为剩余的n做掉期。
Θ(n2) comes from changing the first element. At the point we change the first element, we do Θ(n) swaps. Do that n times, you get Θ(n2).
(n2)来自于改变第一个元素。在我们改变第一个元素的时候,我们做的是交换。这样做n次,得到Θ(n2)。
Now let X(n) = T(n)/n!
现在让X(n) = T(n)/n!
Then we get
然后我们得到
X(n+1) = X(n) + Θ(n2)/(n+1)!
(n + 1)= X(n)+Θ(n2)/(n + 1)!
i.e there is some constant C such that
我。e有一个常数C。
X(n+1) <= X(n) + Cn2/(n+1)!
X(n+1) <= X(n) + Cn2/(n+1)!
Writing down n such inequalities gives us
写下n这样的不等式给我们。
X(n+1) - X(n) <= Cn2/(n+1)!
X(n+1) - X(n) <= Cn2/(n+1)!
X(n) - X(n-1) <= C(n-1)2/(n)!
X(n) - X(n-1) <= C(n-1)2/(n)!
X(n-1) - X(n-2) <= C(n-2)2/(n-1)!
X(n-1) - X(n-2) <= C(n-2)2/(n-1)!
...
…
X(2) - X(1) <= C12/(1+1)!
X(2) - X(1) <= C12/(1+1)!
Adding these up gives us X(n+1) - X(1) <= C(\sum j = 1 to n (j^2)/(j+1)!)
.
添加这些给我们(n + 1)- X(1)< = C(\和j = 1到n(j ^ 2)/(j + 1)!)。
Since the infinite series \sum j = 1 to infinity j^2/(j+1)!
converges to C', say, we get X(n+1) - X(1) <= CC'
自从无穷级数\和j = 1到无穷大^ 2 /(j + 1)!收敛到C',我们得到X(n+1) - X(1) <= CC'
Remember that X(n) counts the average number of swaps needed (T(n)/n!)
记住,X(n)计算所需掉期的平均数(T(n)/n!)
Thus the average number of swaps is O(1).
因此,掉期的平均数是O(1)。
Since finding the elements to swap is linear with the number of swaps, it is O(1) amortized even if you take other operations into consideration.
因为找到交换的元素与交换的数量是线性的,所以即使你考虑其他的操作,它也是平摊的。
#3
0
Here n
stands for the count of elements in the container, not the total count of possible permutations. The algorithm must iterate through an order of all elements at each call; it takes a pair of bidirectional iterators, which implies that to get to one element the algorithm must first visit the one before it (unless its the first or last element). A bidirectional iterator allows iterating backwards, so the algorithm can (must, in fact) perform half as many swaps as there are elements. I believe the standard could offer an overload for a forward iterator, which would support dumber iterators at the cost of n
swaps rather than half n
swaps. But alas, it didn't.
这里n代表容器中元素的计数,而不是可能排列的总数。该算法必须遍历每个调用的所有元素的顺序;它需要一对双向迭代器,这意味着要得到一个元素,算法必须首先访问一个元素(除非它是第一个或最后一个元素)。双向迭代器允许向后迭代,因此算法可以(实际上必须)执行一半的交换,因为其中有元素。我相信这个标准可以为一个前向迭代器提供一个重载,它可以支持dumber迭代器,而不是使用n个交换器。但是唉,它没有。
Of course, for n
possible permutations the algorithm operates in O(1).
当然,对于n个可能的排列,算法在O(1)中操作。