题目:有一个长度为2n的数组{a1,a2,a3,…,an,b1,b2,b3,…,b4};排序后为
{a1,b1,a2,b2,a3,b3,…,an,bn}
方法一:
让b1,b2,b3,…bn;步步前移,移到正确的位置
void solution(int *a,int n)
{
int i,j,k,tmp;
for(i=0;i<n;i++)
{
tmp = a[n+i];
k = n-i-1; // 前移步长
for(j = n+i; k > 0;j--,k--)
{
a[j] = a[j-1];
}
a[j] = tmp;
}
}
方法二:每次让中间的元素交换;
void solution(int *a,int n)
{
int left=n-1,right=n;
for(int i=0;i<n-1;i++)
{
for(j=left;j<right;j+=2)
{
swap(a[j],a[j+1]);
}
--left;
++right;
}
}
方法3:完美洗牌
位置置换算法
根据初始序列与最终序列的位置对比;可以得出任意第 i 个位置都换到了 (2 i)%(2n+1)的位置;
void LocationReplace(int *a,int n)
{
int n2 = n*2,i;
int *b = (int*)malloc(sizeof(int)*n2);
for(i=1;i<=n2;i++) // 下标从1开始
{
b[(2*i)%(n2+1)] = a[i]; // 放到该放的地方
}
for(i=i;i<=n2;i++)
{
a[i] = b[i];
}
for(i = 1;i<=n2;i+=2) // 调整顺序,使得 a 在 b 的前面
{
swap(a[i],a[i+1]);
}
}
走环算法
位置置换法,产生了2个圈,1->2->4->8->7->5->1, 3->6->3;
// from 为一个环的开始,mod 为 2n+1;
void CycleLeader(int *a,int from,int mod)
{
int i;
for(i = from * 2 % mod; i!=from; i = i*2%mod)
{
swap(a[i],a[from]);
}
}
完美洗牌算法: 结论 2n = 3^k+1;
对于2n长度的数组;恰好有k个圈,且每个圈头的起始位置分别是 1,3,9,…,3^k-1;
对于给定的任意长度的 n , 可以采取分治的思想,将数组一分为2,即拆成2个部分,让一部分长度 m 满足结论,剩下的n - m 部分单独计算。
对于{ a1,a2,a3,a4,a5,a6,a7,b1,b2,b3,b4,b5,b6,b7}; k 只能取 2, n = m =4;
对于数组进行交换得到 {a1,a2,a3,a4,b1,b2,b3,b4,a5,a6,a7,b5,b6,b7};对长度m部分应用结论;再单独解决后半部分;
算法流程:
1.找到 2m = 3^k-1; 找到 k;
2.把数组中的 m+1,…,n+m 那部分循环右移m位。
3.因对于2m长度的数组,刚好有k个圈,且每个圈的头部位 3^i;其中 i =0,1,2…,k-1;
对于每个圈执行走环算法,且因数组长度为m,所以需要 对2m+1取模;
4.对数组后面部分继续使用本算法,相当于 n 减小了 m。
// 反转字符串
void reverse(int *a,int from,int to)
{
while(from<to)
{
swap(a[from++],a[to--]);
}
}
void RightRotate(int *a,int num,int n) // 三步反转
{
reverse(a,1,n-num);
reverse(a,n-num+1,n);
reverse(a,1,n);
}
// 完美洗牌算法
void PerfectShuffle(int *a,int n)
{
int n2,m,i,k,t;
while(n>1)
{
// 第一步
n2 = n*2;
for(k=0,m=1;(n2/m)>=3;k++,m*=3);
m /= 2;
// 第二步
RightRotate(a+m,m,n);
// 第三步
for(i=0,t=1;k>i;i++,t*=3)
{
CycleLeader(a,t,m*2+1);
}
// 第四步
n -= m;
a += m*2;
}
// n = 1 时
swap(a[1],a[2]);
}
void Shuffle(int *a,int n)
{
int i,n2 = n * 2;
PerfectShuffle(a,n);
for(i = 1;i<=n2;i+=2) // 交换 a 和 b 的前后顺序
{
swap(a[i],a[i+1]);
}
}