【问题标题】:Perfect shuffle and unshuffle, with no auxiliary array完美的洗牌和解洗,没有辅助阵列
【发布时间】:2015-05-22 12:21:30
【问题描述】:

在提出任何具体问题之前,请注意我的目标不是随机洗牌,而是执行完美的洗牌,就像理想的庄家对一组牌所做的那样,即将牌分成两半,然后进行一次洗牌(将半副牌中的一张牌与另一半副牌中的一张牌交错)。 (这实际上是 Sedgewick 编写的 C 第三版算法中的一个练习:nbr 11.3 第 445 页)

所以,我对 Fisher-Yates shuffle 等算法不感兴趣。

也就是说,我的意思是在执行洗牌时避免使用任何辅助数组,我能够交付的代码如下:

template<typename T>
void two_way_shuffle(vector<T>& data,int l,int r)
{
    int n=(r-l)+1;
    int m=(r+l)/2;
    if(n%2==0) ++m;
    int s,p{l+1};
    for(;p<=r;p+=2,m++)
    {
        s=data[m]; //Make space
        //Shift the elements
        for(int i{m};i>p;i--)
            data[i]=data[i-1];
        //Put the element in the final position
        data[p]=s;
    }
}


template<typename T>
void two_way_unshuffle(vector<T>& data,int l,int r)
{
    int n=(r-l)+1;
    if(n%2!=0){
        --r;
        --n;
    }
    int m=(r+l)/2;
    int s,p{l+1},w{r-1};
    for(;p<=w;p++,w--)
    {
        s=data[w];
        for(int i{w};i>p;i--)
            data[i]=data[i-1];
        data[p]=s;
    }
    //Complete the operation
    for(p=l+1,w=m;p<w;p++,w--)
        swap(data[p],data[w]);
}

洗牌操作的基本思想是跟踪数组'p'中应该插入下一个元素的位置,然后从位置'w'的数组数据中复制该元素留下一个空白空间,然后将数组从左向右移动以便将空白空间精确地移动到位置“p”,一旦完成,代码只需移动到 data[p] 之前保存的值“s”。很简单.. 它看起来可以正常工作(一些洗牌示例)

FROM: 12345678
TO:   15263748

FROM: IS THIS WORKING CORRECTLY OR NOT?
TO:   ICSO RTRHEICST LWYO ROKRI NNGO T?

FROM: SHUFFLING TEST
TO:   SNHGU FTFELSIT

我的问题在于 unshuffle 操作,我相信可以避免最后一个 for 循环中的交换序列,但我无法找到一种更有效的方法来做到这一点。问题是主循环 unshuffle 执行移位操作,最终使数组前半部分的元素以错误的顺序排列(因此,需要在第二个 for 中进行交换)。

我几乎可以肯定,必须有一种聪明的方法来完成这项工作,而无需如此复杂的代码..

您对 unshuffle 算法的改进有什么建议吗?或者对我所做的任何其他建议?

谢谢

【问题讨论】:

  • 这不是c,恕我直言。
  • 我们称它为“Objectless c++”。说真的,我们不应该创建一个名为“Objectless C++”的标签吗?
  • 我认为你做的很自然。这个效率更高但更复杂stackoverflow.com/questions/12338654/…
  • @user3528438 为什么称它为“无对象 C++”?您不仅有一个对象vector&lt;T&gt;&amp; data,而且还使用了模板。
  • 最后一项可以洗牌吗?

标签: c++ algorithm shuffle


【解决方案1】:

我认为您的代码保留了太多变量。当你洗牌数组时,你正在处理一个减小大小的范围[lo, hi],你将最右边的条目hi与左边的元素块交换[lo, hi)。你可以只用这两个变量来表达算法:

0  1  2  3  4  5
   -------              swap [1, 3) and [3]
0  3  1  2  4  5
         ----           swap [3, 4) and [4]
0  3  1  4  2  5
               -        [5, 5) is empty: break

这对于奇数个元素也是一样的,只是空范围超出了界限,这没关系,因为我们不访问它。这里的操作是:向右移动块,将旧的hi元素放在lo位置,增加lohi,步幅为2和1。

当您取消洗牌时,您必须还原步骤:在最后一个元素处开始 lohi 用于偶数大小的数组,以及在数组之外的一个元素用于奇数大小的数组。然后以相反的顺序执行所有步骤:首先减少lohi,然后将块左移并将旧的lo放在hi位置。当lo 达到 1 时停止。(它将达到 1,因为我们从一个奇数索引开始,然后递减 2。)

您可以通过打印lohi 来测试您的算法:洗牌和解洗时它们必须相同,只是顺序相反。

所以:

template<typename T>
void weave(std::vector<T>& data)
{
    size_t lo = 1;
    size_t hi = (data.size() + 1) / 2;

    while (lo < hi) {
        T m = data[hi];

        for (size_t i = hi; i-- > lo; ) data[i + 1] = data[i];

        data[lo] = m;
        lo += 2;
        hi++;
    }
}

template<typename T>
void unweave(std::vector<T>& data)
{
    size_t n = data.size();
    size_t lo = n + n % 2 - 1;
    size_t hi = lo;

    while (hi--, lo-- && lo--) {
        T m = data[lo];

        for (size_t i = lo; i < hi; i++) data[i] = data[i + 1];

        data[hi] = m;
    }    
}

我删除了左右索引,这使代码不太灵活,但希望更清晰易读。您可以将它们放回原处,并且只需要它们来计算 lohi 的初始值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 2018-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-14
    相关资源
    最近更新 更多