块交换问题算法设计与分析

块交换问题

对于长度为m+n的数组(m,n均为正整数),编制程序,将数组的前m个数与后n个数进行变换,要求不能使用另外的数组。
例如,对于m=4,n=6,长为10的数组:(0 1 2 3 4 5 6 7 8 9),变换后的结果为:(4, 5, 6, 7, 8, 9, 0, 1, 2, 3)。

1、循环移位块交换算法


通过m次循环左移或n次循环右移可以完成数组元素交换任务,每次循环移位需执行m+n+1次赋值操作。当m>n时,右移较左移为佳;当m<n时,左移较右移为佳。因此,当m>n时,进行右移;当m<n时,进行左移;m=n时,两者相同。程序的时间复杂性为min(m,n)*(m+n+1)。
void CircleSwap(int *lpItem, int nHeadElements, int nTailElements)
{
    int nShift;
    int  nSize = nHeadElements + nTailElements;

    if (nHeadElements > nTailElements)
    for (nShift = 0; nShift < nTailElements; nShift++)
       CircleRightShift(lpItem, nSize);
    else
   for (nShift = 0; nShift < nHeadElements; nShift++)
       CircleLeftShift(lpItem, nSize);

}

void CircleRightShift(int *lpItem, int nSize)
{
    int nTmpElement;
    int nIndex;

    nTmpElement = *(lpItem + nSize - 1);
    for (nIndex = nSize - 1; nIndex > 0; nIndex--)
    *(lpItem + nIndex) = *(lpItem + nIndex - 1);
    *lpItem = nTmpElement;
}

void CircleLeftShift(int *lpItem, int nSize)
{
    int nTmpElement;
    int nIndex;

    nTmpElement = *lpItem;
    for (nIndex = 0; nIndex < nSize - 1; nIndex++)
 *(lpItem + nIndex) = *(lpItem + nIndex + 1);

    *(lpItem + nSize - 1) = nTmpElement;
}

 

2、块对换方法


以前面的例子来说明。
第一步:将前面四个元素与后面六个元素中的前四个元素(4~7)对换。结果为
(4,5,6,7,0,1,2,3,8,9)
这时,问题变为将数组元素4~7与8~9交换位置。
第二步:将4~7中的前面两个元素与后面两个元素8~9对换。结果为
(4,5,6,7,8,9,2,3,0,1)。
第三步:剩下的任务是将元素6~7与8~9进行对换。结果为
(4,5,6,7,8,9,0,1,2,3)
上述算法可以递归地描述为:将数组中start开始的m+n个元素中的前m个与后n个交换位置:
BlockSwap(int *a, int start, int m, int n)
{
    int i;

    switch (sgn(m-n))
 {
 case -1:
     for (i=start; I <start+m; i++)
  {
  t=a[i]; a[i]=a[i+m]; a[i+m]= t;
  }
     BlockSwap(a, start+m, m, n-m);
     break;
 case 0:
     for (i=start; I <start+m; i++)
  {
  t=a[i]; a[i]=a[i+m]; a[i+m]= t;
  }
     break;
 case 1:
     for (i=start; I <start+n; i++)
  {
  t=a[i]; a[i]=a[i+m]; a[i+m]= t;
  }
     BlockSwap(a, start+n, m-n, n);
     break;
 }
}

两个大小为b的块进行对换,需执行3b次赋值操作。当交换的两个块一个大小为b[0],另一块大小为b[1]时,则执行q[1]次大小为b[1]的块对换操作,q[1]由下式确定:
  b[0]=q[1]b[1]+b[2] (0≤b[2]<b[1])
若b[2]≠0,则需进一步执行大小为b[1]、b[2]的两个块的交换任务,相应地,应执行q[2]次大小为b[2]的块对换操作,q[2]由下式确定:
  b[1]=q[2]b[2]+b[3]   (0≤b[3]<b[2])
这样下去,一直到某个b[k]|b[k-1]为止。上述操作中确定{q[i]}的表达式如下所示:
  b[0]=q[1]b[1]+b[2] (0≤b[2]<b[1])
  b[1]=q[2]b[2]+b[3] (0≤b[3]<b[2])
  ......
  b[i-1]=q[i]b[i]+b[i+1] (0≤b[i+1]<b[i]    ......
  b[k-2]=q[k-1]b[k-1]+b[k] (0≤b[k]<b[k-1])
  b[k-1]=q[k]b[k]
式中b[i-1]=q[i]b[i]+b[i+1]表示进行q[i]次大小为b[i]的块对换操作,这一过程实质上等同于辗转相除法求最大公约数的过程,其中b[k]=(b[0],b[1])。
于是,块对换算法总的赋值操作次数为
 3(q[1]b[1]+q[2]b[2]+...+q[k]b[k])=3(b[0]+b[1]-b[k])
由于在这一问题中,b[0]=max(m,n),b[1]=min(m,n),b[k]=(b[0],b[1]),从而,块对换算法赋值操作执行次数为3(m+n-(m,n))。
实际的程序如下:
void BlockSwap(int *lpItem, int nHeadElements, int nTailElements)
{
    int *lpCurrItem;

    lpCurrItem = lpItem;
    while (nHeadElements != nTailElements)
 if (nHeadElements > nTailElements)
     {
     PairsSwap(lpCurrItem, lpCurrItem + nHeadElements, nTailElements);
     lpCurrItem += nTailElements;
     nHeadElements -= nTailElements;
     }
 else
     {
     PairsSwap(lpCurrItem, lpCurrItem + nHeadElements, nHeadElements);
     lpCurrItem += nHeadElements;
     nTailElements -= nHeadElements;
     }
    PairsSwap(lpCurrItem, lpCurrItem + nHeadElements, nHeadElements);
}


void RecursionBlockSwap(int *lpItem, int nHeadElements, int nTailElements)
{
    if (nHeadElements > nTailElements)
 {
 PairsSwap(lpItem, lpItem + nHeadElements, nTailElements);
 RecursionBlockSwap(lpItem + nTailElements,
      nHeadElements - nTailElements,
      nTailElements);
 }
    else
 if (nHeadElements < nTailElements)
     {
     PairsSwap(lpItem, lpItem + nHeadElements, nHeadElements);
     RecursionBlockSwap(lpItem + nHeadElements,
          nHeadElements,
          nTailElements - nHeadElements);
     }
 else
     PairsSwap(lpItem, lpItem + nHeadElements, nHeadElements);
}

void PairsSwap(int *lpItem1, int *lpItem2, int nSize)
{
    int nIndex;

    for (nIndex = 0; nIndex < nSize; nIndex++)
 ElementPairSwap(lpItem1 + nIndex, lpItem2 + nIndex);
}

void ElementPairSwap(int *lpItem1, int *lpItem2)
{
    int nElement;

    nElement = *lpItem1;
    *lpItem1 = *lpItem2;
    *lpItem2 = nElement;
}

 

3、最少交换次数算法


设原数组为a[0]a[1]…a[m+n-1],变换后的结果为b[0]b[1]…b[m+n-1]。显然,b[i]=a[(i+m)%(m+n)],即第i个位置被(i+m)%(m+n)个元素占据。
记[i]=a[(i+m)%(m+n)],则k个元素[i]、[i+m]、…、[i+(k-1)m]构成一个换位循环,即第i个位置由[i+m]占据,第(i+m)%(m+n)个位置由[(i+2m)]占据,…,第(i+(k-1)m)%(m+n)个位置由[i]占据。这里k=(m+n)/(m,n)。
为证明上述命题,只需证明满足i=(i+km)%(m+n)最小的正整数k为(m+n)/(m,n)即可。
不妨设km=q(m+n),(m,n)=g,m=gm0,n=gn0,从而km0=q(m0+n0)
由于(m0,n0)=1,则(m0+n0,m0)=1,故m0+n0|k, 由k的最小性,k=m0+n0=(m+n)/(m,n)。
设x是一个换位循环中下标最小的数组元素的下标,换位循环中的全体元素构成的集合记为{x}。
命题:(m,n)个换位循环{0}、{1}、…、{(m,n)-1}构成a[0]a[1]…a[m+n-1]的一个划分。即数组中的任一元素均在一个换位循环中,且没有两个不同的循环中有相同的数组元素。
证明:用反证法。设存在一个数组元素同时属于两个不同的换位循环{x}、{y}((m,n)>x,y>=0),则存在i、j,使得
(x+im)%(m+n)=(y+jm)%(m+n)
即存在q使得
x-y=q(m+n)+(j-i)m
从而
(m,n)|x-y
由于1<=x-y<(m,n),这是不可能的,故不存在两个换位循环有公共元素。又(m,n)个换位循环包括(m,n)(m+n)/(m,n)=m+n个元素,因此数组中任一元素只在一个换位循环中。
每一换位循环执行赋值操作(m+n)/(m,n)+1次,程序共执行(m,n)次换位循环,故程序的时间复杂性为m+n+(m,n)。
程序如下:
void BestSwap(int *lpItem, int nHeadElements, int nTailElements)
{
    int nSize = nHeadElements + nTailElements;
    int nShiftGroup, nGroupCount;
    int nStartIndex, nIndex, nNextIndex;
    int nTmpElement;

    nGroupCount = gcd(nHeadElements, nTailElements);

    for (nShiftGroup = 0; nShiftGroup < nGroupCount; nShiftGroup++)
 {
 nStartIndex = nShiftGroup;

 nTmpElement = *(lpItem + nStartIndex);
 nIndex = nStartIndex;
 nNextIndex = nIndex + nHeadElements;
 if (nNextIndex >= nSize)  nNextIndex -= nSize;

 while (nNextIndex != nStartIndex)
     {
     *(lpItem + nIndex) = *(lpItem + nNextIndex);
     nIndex = nNextIndex;

     nNextIndex = nIndex + nHeadElements;
     if (nNextIndex >= nSize)  nNextIndex -= nSize;
     }

 *(lpItem + nIndex) = nTmpElement;
 }
}

// Using the Euclidean algorithm to calculate the greatest common divisor of two integers
int gcd(int a, int b)
{
    int r;

    if (a < 0) a = -a;
    if (b < 0) b = -b;
    while (b != 0)
    {
      r = a % b; a = b; b = r;
    }
    return a;
}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值