完美洗牌算法以及python实现

'''
#完美洗牌算法
长度为2n的数组{a1,a2,...,an,b1,b2,...,bn},经过整理后变成{a1,b1,a2,b2,...,an,bn},要求时间复杂度O(n),空间复杂度O(1)

1.步步前移
    观察变换后两个序列的特点,我们可以做如下一系列操作:
    1.确定b1的位置,即让它跟它前面的a2,a3,a4交换:
        a1,b1,a2,a3,a4,b2,b3,b4
    2.接着确定b2的位置,即让b2跟它前面的a3,a4交换:
        a1,b1,a2,b2,a3,a4,b3,b4
    3.b3跟它前面的a4交换位置:
        a1,b1,a2,b2,a3,b3,a4,b4
    b4无需交换
    移动n-1次,第i次将n-i个元素后移。时间复杂度为O(N^2)
    
2.中间交换
    每次让序列中最中间的元素进行交换
    对于a1,a2,a3,a4,b1,b2,b3,b4
    1.交换最中间的两个元素a4,b1:
        a1,a2,a3,b1,a4,b2,b3,b4
    2.让最中间的两对元素各自交换[(a3,b1),(a4,b2)]:
        a1,a2,b1,a3,b2,a4,b3,b4
    3.交换,最中间的三对元素[(a2,b1),(a3,b2),(a4,b3)]:
        a1,b1,a2,b2,a3,b3,a4,b4
    
    时间复杂度:n-1+n-2+...+1=n^2/2--O(N^2)
'''

'''
完美洗牌算法:2004年,macrosoft的Peiyush Jain在其发表的一篇名为"A Simple In-Place Algorithm for In-Shuffle"的论文中提出。

位置变换
    a1,a2,...,an,b1,b2,...,bn-->b1,a1,b2,a2,...,bn,an
    设定数组下标范围[1,2n],考察元素的最终位置:
        以n=4为例,前n个元素中
        第1个元素a1到了原第2个元素a2的位置,即1->2
        第2个元素a2到了原第4个元素a4的位置,即2->4
        第3个元素a3到了原第6个元素b2的位置,即3->6
        第4个元素a4到了原第8个元素b4的位置,即4->8
        前n个元素中,第i个元素的最终位置为(2*i)
        
        后n个元素,可以看出:
        第5个元素b1到了原第1个元素a1的位置,即5->1  
        第6个元素b2到了原第3个元素a3的位置,即6->3  
        第7个元素b3到了原第5个元素b1的位置,即7->5
        第8个元素b4到了原第7个元素b3的位置,即8->7  
        后n个元素,第i个元素的最终位置为:2*(i-n)-1=2*i-2*n-1=(2*i)%(2*n+1)
     因此元素位置均可表述为 (2*i)%(2*n+1)
     
两个圈
    我们得到两个圈
        1->2->4->8->7->5->1
        3->6->3
          
'''

 

#数组下标从1开始,f是圈的头部,mod为2*n+1
def cycleLeader(a,f,mod):

    # for i in range(f*2%mod,5,2*i/mod)
    i=2*f%mod
    while(i!=f):
        t=a[i-1]
        a[i-1]=a[f-1]
        a[f-1]=t
        # print(a[i-1],a[f-1])
        i=2*i%mod
    # print(a)
# l2=[1,2,3,4,5,6,7,8,9,10]
# mod=len(a)+1
# cycleLeader(l2,1,mod)

'''
K个圈:对于2*n=(3^k-1)这种长度的数组,恰好只有k个圈,且每个圈的起始位置分别是1,3,9,...3^(k-1)

对于2n!=(3^k-1)长度的数组,
    找到最大2m=(3^k-1)的部分,且3^k<=2*n<3^(k+1)
    把a[m+1...m+n]部分循环右移m为(循环左移n-m位)
    对每个i=0,1,2,...,k-1,3^i是每个圈的起始位置,做cycleLeader算法
        因为子数组长度为m,所以对2*m+1取模
    对数组的剩余部分A[2m+1...2n]继续使用本算法
'''
def reverseString(s,f,to):
    while (f<to):
        # print(f)
        t=s[f]
        s[f]=s[to]
        s[to]=t
        f+=1
        to-=1
    # print(s)
def leftRotateString(s,n,m):
    m%=n
    reverseString(s,0,m-1)
    reverseString(s,m,n-1)
    reverseString(s,0,n-1)
    # print(''.join(s))
    # print(s)
def perfectShuffle2(a,n):
    # print(n)
    final=[]
    while(n>1):
        n2=n*2
        m=1
        k=0
        while((n2+1)/m>=3):
            k+=1
            m*=3
        m=math.floor(m/2)
        # print(m)
        t1=a[:m]
        t2=a[m:m+n]
        t3=a[m+n:]
        leftRotateString(t2,n,n-m)
        i=0
        t=1
        a=t1+t2+t3
        # print(a)
        # print(k)
        while(i<k):
            cycleLeader(a,t,m*2+1)
            i+=1
            t*=3
        final+=a[:2*m]
        a=a[2*m:]
        n-=m
    if n==1:#仅剩两个元素时
        t=a[0]
        a[0]=a[1]
        a[1]=t
        final+=a
    print(final)
l3=[1,2,3,4,5,6,7,8,'a','b','c','d','e','f','g','h']
# l3=[1,2,3,4,5,'a','b','c','d','e']
perfectShuffle2(l3,int(len(l3)/2))

 算法原理图示:

 

 部分原理解释及问题拓展:

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值