完美的洗牌和洗牌,没有辅助阵列

时间:2015-05-22 12:21:30

标签: c++ algorithm shuffle

在提出任何具体问题之前,请注意我的目标不是随意地随机播放阵列,而是要进行完美的洗牌,因为理想的经销商可以使用一套牌,即将牌组分成两半并且执行一个洗牌通道(从一个半牌中插入一张牌,另一半牌中插入一张牌)。 (这实际上是Sedgewick的C第三版Algorithms的一个练习:nbr 11.3第445页)

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

说,我的观点是在执行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]);
}

洗牌操作旁边的基本思想是跟踪阵列中的位置&#39; p&#39;应该插入下一个元素的位置,然后从位置&#39; w&#39;的数组数据中复制该元素。留下一个空的空间,然后将数组从左向右移动,以便将空的空间精确地移动到位置,一旦完成,代码只需移动到数据[p]先前保存的值&#39; S&#39 ;.非常简单..它看起来工作正常(一些随机的例子)

FROM: 12345678
TO:   15263748

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

FROM: SHUFFLING TEST
TO:   SNHGU FTFELSIT

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

我几乎可以肯定,如果没有这样的代码复杂功能,必须有一种聪明的方法来完成这项工作。

您对非洗牌算法可以改进的内容有什么建议吗?或者关于我做过什么的任何其他建议?

由于

1 个答案:

答案 0 :(得分:0)

我认为您的代码保留了太多变量。当您对阵列进行随机播放时,您将处理大小逐渐减小的范围[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的初始值。