C# 对元素进行洗牌,使任何元素都不应位于其原始索引处

C# 对元素进行洗牌,使任何元素都不应位于其原始索引处,c#,algorithm,random,permutation,shuffle,C#,Algorithm,Random,Permutation,Shuffle,我有一个对象元素列表 SourceList ResultList (Expected) Obj_A Obj_F Obj_B Obj_C Obj_C Obj_G Obj_D Obj_B Obj_E Obj_A Obj_F Obj_B Obj_G Obj_E

我有一个对象元素列表

SourceList    ResultList (Expected)

Obj_A                 Obj_F

Obj_B                 Obj_C

Obj_C                 Obj_G

Obj_D                 Obj_B

Obj_E                 Obj_A

Obj_F                 Obj_B

Obj_G                 Obj_E
源列表中的元素洗牌,这样,在结果列表中,任何元素都不应位于其原始索引(在源列表中)

例如,在源列表中,C位于索引2处,因此它不能位于结果列表中的索引2处


到目前为止,我已经研究过了,但algo给了我可能的安排,我只需要一个。

进行洗牌,让所有索引未更改的元素,然后旋转所有这些错误元素的位置。

这对我来说很有效:

var SourceList = new List<string>()
{
    "Obj_A", "Obj_B", "Obj_C", "Obj_D", "Obj_E", "Obj_F", "Obj_G",
}; 

var rnd = new Random();

var ResultList =
    Enumerable
        // create an exceedingly long sequence for retries
        .Repeat(0, int.MaxValue)
        // select and randomly order all of the indices of `SourceList`
        .Select(_ => Enumerable.Range(0, SourceList.Count)
            .OrderBy(x => rnd.Next())
            .ToArray())
        // Discard the indices if they have any matching positions
        .Where(x => !x.Where((y, i) => y == i).Any())
        // only take one successful set of indices
        .Take(1)
        // flatten the list of lists to a list
        .SelectMany(x => x)
        // select the elements from `SourceList` from their index positions
        .Select(x => SourceList[x])
        // convert to list
        .ToList();
var SourceList=newlist()
{
“对象A”、“对象B”、“对象C”、“对象D”、“对象E”、“对象F”、“对象G”,
}; 
var rnd=新随机数();
var结果列表=
可枚举
//创建一个非常长的重试序列
.重复(0,int.MaxValue)
//选择SourceList的所有索引并随机排序`
.Select(=>Enumerable.Range(0,SourceList.Count)
.OrderBy(x=>rnd.Next())
.ToArray())
//如果索引有任何匹配位置,则丢弃索引
.Where(x=>!x.Where((y,i)=>y==i).Any())
//只取一组成功的索引
.采取(1)
//将列表列表展平为一个列表
.SelectMany(x=>x)
//从“SourceList”的索引位置选择元素
.选择(x=>SourceList[x])
//转换为列表
.ToList();
使用
.OrderBy(x=>rnd.Next())
会产生一个统一的顺序(无偏差)-我在过去已经证实了这一点。

您可以将其用作一个黑盒,并重复地洗牌数组,直到结果是一个去范围

伪代码:

while true:
     arr = [1,2,...,n]
     shuffle(arr)
     flag = true
     for i from 0 to n:
        if arr[i] == i: //not a dearrangement
           flag = false 
     if flag == true: //it's a dearrangement
        break

shuffle(arr): //fisher yates
    for i from 0 to n:
       j = rand(i,n)
       swap(arr,i,j)
此方法的属性:

  • 这保证了的一致性和普遍性,因为每个有效的重新排列 在每次迭代中获取完全相同的几率
  • 因为Fisher-Yates生成所有排列,我们只使去排列无效-每个去排列都是可以实现的
  • 去排列的概率为1/e1,这意味着平均情况下需要(1-1/e)^-1~=1.56次洗牌,这意味着该算法在
    O(n)
    预期时间复杂度下运行


(1) 是
int(n!/e+1/2)
,这意味着数组的去范围概率是
(n!/e+1/2)/n!~=1/e
,对于较大的
n

值,我假设您不必对只有一个元素的列表进行操作。如果您只需旋转数组(将每个元素的索引移动1,并将最后一个元素移到前面),就可以实现这一点。您需要如何“洗牌”生成的数组?还要注意,通过均匀随机洗牌(如fisher-yates),您有概率
1/e
获得重新排列。这意味着你可以重复这个过程,直到你得到一个,这将花费预期的e~=2.7个排列,因此需要
O(n)
平均情况才能得到一个去排列,并且保证是无偏的,所有的去排列都是可以实现的。这个解决方案对我来说似乎有偏见。这将是一个简单的方法。我猜每个元素的旋转(洗牌后索引相同)不会成为问题。是的,它可能会,因为我怀疑你得到一些排列的概率比其他的高。例如,你有两种(至少)可能性使
[2,3,4,5,6,1]
,这和其他排列只有一种可能性(我假设[3,2,4,5,6,1]是其中之一,尽管不确定)在一个算法标记问题中(实际上,在任何问题中,但特别是在算法中)“这对我有效”+代码是一个糟糕的答案。请描述一下逻辑。社区期望高代表性用户提供更高质量的答案。请在解释中详细说明:(1)解释(2)为什么它有效(3)为什么它是公正的(4)为什么每一次取消限制都是可以实现的。@amit-解释补充。解释不令人满意。它解释了为什么你会得到一个随机排列,而不是一个随机去排列(你得到了吗?)。而且,“我过去已经证实了这一点”也很难解释。此外,它在时间复杂度方面效率低下。(查找置换cab可以在一次过程中完成,无需排序)。@amit-如果索引有任何匹配位置,请重新读取“//丢弃索引”。而且,这几乎不会有可怕的时间复杂性。在我的测试中,我最多重试12次,其中1次为中位数。