完美洗牌算法简析与代码实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/junbujianwpl/article/details/82315272

 

题目需求

数组[a1,a2,a3,a4,b1,b2,b3,b4],洗牌后变成[b1,a1,b2,a2,b3,a3,b4,a4]。

不得使用额外的空间,即空间复杂度要求为O(1)。因为如果用线性空间,直接变成2个链表归并就行,特别简单。

 

算法思路

1)定义与约定

数组长度为2n,下标i从1开始计数,且i的范围[1,2n]。

2)算法思路

  • 公式
    1)i->(2*i)%(2n+1)   理解,第i个元素值替换到第(2*i)%(2n+1)个元素,依此类推,直到完成一圈代换
    2)2^n=3^k-1           理解,当长度正好为3^k-1时,数组恰好可以切分为k个环,没有多余元素
    3)3^k                      理解,环头。环的起始位置为1、3、9、27等3的指数位置
  • 算法
    1)将数组分成2个部分,前一部分正好是3^k-1个元素
    2)对前一部分进行环代换算法
    3)对剩余的部分,递归调用当前算法

算法理解

1)为什么能切分数组为2个部分然后再洗牌并递归

假设2m=3^k-1。则洗牌后前一半(前n个元素)中的前m个元素与后一半(后n个元素)中的前m个元素占据着前2m个槽位。那么,我们可以在洗牌之前提前先将这2m个元素放在一起。即预处理后A1区块与A2区块是挨着的,B1区块与B2区块是挨着的,因为我们知道洗牌后A1与A2共同占用前2m个槽位,B1与B2共同占用后2n-2m个槽位。预处理后排列如下图所示。

有了这个前提之后,我们就可以先将整个数组重新排一下,排成上图所示的样子,其中A1与A2组成的部分就可以直接用圈代换算法,将所有的元素都洗牌好。对于剩下的B1与B2组成的部分,再递归调用前面的算法。

2)如何交互A2与B1并保证其原来的顺序

用循环称位即可将B1按原来的顺序移动到A2前面,且A2与B1均保持原来的内部顺序。具体就是将A2B1视为一个整体,然后循环右移B1区块的长度即可。

循环移位算法,直接移位效率比较低,可以用两次局部逆序与一次整体逆序来实现。即B×A=(A^{-1} B^{-1})^{-1}

3)圈代换算法是如何来的

这个算法,主要也就是圈头位置,恰好被分割为若干个圈的数组长度两部分内容,国外专门有一篇论文讲这个just in-place代换,有一些数学推理与证明在里面,具体感兴趣的可以去研究下。:)此处就不多提。

 

代码实现

#ifndef SHUFFLE_H
#define SHUFFLE_H


#include <vector>
#include <math.h>

#include "common.h"

template<class T>
class Shuffle
{
public:
    Shuffle(const std::vector<T>& vec);

    /**
     * @brief rightCycleMove    right shift a array
     * @param start             start position,included
     * @param stop              stop  position,not include
     * @param num               right shift number
     */
    static void rightCycleMove(typename std::vector<T>::iterator start,
                               typename std::vector<T>::iterator stop,unsigned int num);

    /**
     * @brief reverse     reverse the span
     * @param start       start position
     * @param stop        stop position, not include
     */
    static void reverse(typename std::vector<T>::iterator start,typename std::vector<T>::iterator stop);

    /**
     * @brief findRightShiftPos  find the length (2^n)=log3(K)-1
     * @param start                  start position
     * @param stop					 stop position
     * @param pos                    length (2^n)
     * @return 						 find or not
     */
    static bool findRightShiftPos (typename std::vector<T>::iterator start,typename std::vector<T>::iterator stop,unsigned int & pos);

    /**
     * @brief splitInto2Part   split array into 2 part,one is in perfect length,the remain we can recursivie
     * @param start
     * @param stop
     * @param middle			split position
     * @return
     */
    static bool splitInto2Part(typename std::vector<T>::iterator start, typename std::vector<T>::iterator stop, typename std::vector<T>::iterator &middle);

    /**
     * @brief shuffle   recursive function,split into 2 part, exchange perfect part, and recursive
     * @param start
     * @param stop
     * @return
     */
    static bool shuffle(typename std::vector<T>::iterator start,typename std::vector<T>::iterator stop);

    /**
     * @brief shuffle  shuffle function for object
     * @return
     */
    bool  shuffle();

    std::vector<T> getData() const{return m_vecData__;}

private:
    std::vector<T> m_vecData__;				///< data tobe shuffle
};

template<class S>
Shuffle<S>::Shuffle(const std::vector<S> &vec)
{
    m_vecData__=vec;
}

template<class T>
void Shuffle<T>::reverse(typename std::vector<T>::iterator start, typename std::vector<T>::iterator stop)
{
    typename std::vector<T>::iterator l,r;

    l=start;
    r=stop;
    r--;
    for(;l<r;++l,--r){
        T iter=*l;
        *l=*r;
        *r=iter;
    }
}

template<class T>
bool Shuffle<T>::findRightShiftPos(typename std::vector<T>::iterator start, typename std::vector<T>::iterator stop, unsigned int &pos)
{
    int num=stop-start;
    if (num==0) return true;
    if(num<0 || (num%2)!=0) return false;

    double dlExp=log(double(num+1))/log(double(3));
    int nFloor=floor(dlExp);
    pos=pow(3,nFloor)-1;
    return true;
}

template<class T>
bool Shuffle<T>::splitInto2Part(typename std::vector<T>::iterator start, typename std::vector<T>::iterator stop, typename std::vector<T>::iterator &middle)
{
    int num = stop-start;
    if(num == 0) return true;
    unsigned int pos;
    if(!findRightShiftPos(start,stop,pos))	return false;

    rightCycleMove(start+pos/2,start+num/2+pos/2,pos/2);
    middle=start+pos;
    return true;
}

template<class T>
bool Shuffle<T>::shuffle(typename std::vector<T>::iterator start, typename std::vector<T>::iterator stop)
{
    typename std::vector<T>::iterator middle=stop;
    if(!splitInto2Part(start,stop,middle)) return false;

    int num = middle-start;
    if(num==0) 		 return true;
    else if(num <0) 			 return false;

    int nLog=log(num+1)/log(3);
    if(pow(3,nLog)-1 !=num) return false;

    for(int i=0;i<nLog;++i){
        int head=pow(3,i);
        T temp=*(head+start-1);
        T newest;
        for(int former=2*head%(num+1);former!=head;former=former*2%(num+1)){
            newest=*(start+former-1);
            *(start+former-1)=temp;
            temp=newest;

        }
        *(start+head-1)=temp;
    }


    return shuffle(middle,stop);
}

template<class T>
bool Shuffle<T>::shuffle()
{
   return shuffle(m_vecData__.begin(),m_vecData__.end());
}


template<typename T>
void Shuffle<T>::rightCycleMove( typename std::vector<T>::iterator start, typename std::vector<T>::iterator stop, unsigned int num)
{
    if(num==0) return;
    typename std::vector<T>::iterator split= stop-num%(stop-start);
    reverse(start,split);
    reverse(split,stop);
    reverse(start,stop);
}

#endif // SHUFFLE_H

具体的代码工程见github:https://github.com/junbujianwpl/LeetcodePro/blob/master/Shuffle.h

测试结果

1
2:num 2 1 
1
4:num 3 1 4 2 
1
6:num 4 1 5 2 6 3 
1
8:num 5 1 6 2 7 3 8 4 
1
10:num 6 1 7 2 8 3 9 4 10 5 
1
12:num 7 1 8 2 9 3 10 4 11 5 12 6 
1
14:num 8 1 9 2 10 3 11 4 12 5 13 6 14 7 
1
16:num 9 1 10 2 11 3 12 4 13 5 14 6 15 7 16 8 
1
18:num 10 1 11 2 12 3 13 4 14 5 15 6 16 7 17 8 18 9 
1
20:num 11 1 12 2 13 3 14 4 15 5 16 6 17 7 18 8 19 9 20 10 
1
22:num 12 1 13 2 14 3 15 4 16 5 17 6 18 7 19 8 20 9 21 10 22 11 
1
24:num 13 1 14 2 15 3 16 4 17 5 18 6 19 7 20 8 21 9 22 10 23 11 24 12 
1
26:num 14 1 15 2 16 3 17 4 18 5 19 6 20 7 21 8 22 9 23 10 24 11 25 12 26 13 
1
28:num 15 1 16 2 17 3 18 4 19 5 20 6 21 7 22 8 23 9 24 10 25 11 26 12 27 13 28 14 
1
30:num 16 1 17 2 18 3 19 4 20 5 21 6 22 7 23 8 24 9 25 10 26 11 27 12 28 13 29 14 30 15 
1
32:num 17 1 18 2 19 3 20 4 21 5 22 6 23 7 24 8 25 9 26 10 27 11 28 12 29 13 30 14 31 15 32 16 
1
34:num 18 1 19 2 20 3 21 4 22 5 23 6 24 7 25 8 26 9 27 10 28 11 29 12 30 13 31 14 32 15 33 16 34 17 
1
36:num 19 1 20 2 21 3 22 4 23 5 24 6 25 7 26 8 27 9 28 10 29 11 30 12 31 13 32 14 33 15 34 16 35 17 36 18 
1
38:num 20 1 21 2 22 3 23 4 24 5 25 6 26 7 27 8 28 9 29 10 30 11 31 12 32 13 33 14 34 15 35 16 36 17 37 18 38 19 

 

ps:不错的在线画图网站

有一个很不错的在线画图网站 http://draw.io

文中前面2幅图就是在这个网站上画的。:)

展开阅读全文

没有更多推荐了,返回首页