给定一个有序的整数列表,L = {1,2,3,4} 并且排列大小为k,
我需要生成所有' ordered'具有第一序列的长度k的排列= L [0]。
通过上面的示例,这应该产生以下结果:
k = 1
= {1}
k = 2
= {1,2},{1,3},{1,4}
k = 3
= {1,2,3},{1,2,4},{1,3,4}
k = 4
= {1,2,3,4}
这就是我提出的:
是否有更好的方法可以首先生成所有有序排列,而无需第二步消除?
答案 0 :(得分:4)
假设“按顺序”表示匹配初始列表中的顺序:
public static IEnumerable<T> Yield<T>( T value )
{
yield return value;
}
public static IEnumerable<IEnumerable<T>> GetOrderedPermutations<T>( IEnumerable<T> source, int k )
{
if( k == 0 ) return new[] { Enumerable.Empty<T>() };
int length = source.Count();
if( k == length ) return new[] { source };
if( k > length ) return Enumerable.Empty<IEnumerable<T>>();
return GetOrderedHelper<T>( source, k, length );
}
private static IEnumerable<IEnumerable<T>> GetOrderedHelper<T>( IEnumerable<T> source, int k, int length )
{
if( k == 0 )
{
yield return Enumerable.Empty<T>();
yield break;
}
int i = 0;
foreach( var item in source )
{
if( i + k > length ) yield break;
var permutations = GetOrderedHelper<T>( source.Skip( i + 1 ), k - 1, length - i );
i++;
foreach( var subPerm in permutations )
{
yield return Yield( item ).Concat( subPerm );
}
}
}
这仍然可以提高效率(通过删除递归)。但这是我能提出的最直接的算法。在你的情况下,因为你总是想要出现第一个元素,你可以通过砍掉第一个元素来运行算法,然后再把它放回去:
var permutations = GetOrderedPermutations( source.Skip( 1 ), k - 1 )
.Select( p => Yield( source.First() ).Concat( p ) );
这个算法背后的想法非常简单:通过选择排列中的第一个项目并将其前置于列表其余部分的所有排列长度k - 1
,可以找到所有排列。
如果你想删除递归,有一种方法可以迭代地查看它:
如果您想要长度k
的排列,请初始化指向源的第一个k
元素的k
指针。这些指针指向当前排列的元素。要获得下一个排列,请递增最后一个指针。如果最后一个指针越过源的末尾,则递增前一个指针并将最后一个指针设置为超过它的一个指针。在代码中:
public static IEnumerable<IEnumerable<T>> GetOrderedPermutations<T>( IList<T> source, int k )
{
if( k == 0 ) yield return Enumerable.Empty<T>();
if( k == source.Count ) yield return source;
if( k > source.Count ) yield break;
var pointers = Enumerable.Range( 0, k ).ToArray();
// The first element of our permutation can only be in the first
// Count - k + 1 elements. If it moves past here, we can't have
// anymore permutations because there aren't enough items in the list.
while( pointers[0] <= source.Count - k )
{
yield return pointers.Select( p => source[p] );
// Increment the last pointer
pointers[k - 1]++;
// The i variable will keep track of which pointer
// we need to increment. Start it at the second to
// last (since this is the one that we're going to
// increment if the last pointer is past the end).
int i = k - 2;
while( pointers[k - 1] >= source.Count && i >= 0 )
{
// Okay, the last pointer was past the end, increment
pointers[i]++;
// Reset all the pointers after pointer i
for( int j = i + 1; j < k; ++j )
{
pointers[j] = pointers[j - 1] + 1;
}
// If the last pointer is still past the end, we'll
// catch it on the next go-around of this loop.
// Decrement i so we move the previous pointer next time.
--i;
}
}
}