摘自《Programming Pearls》2rd 第2章
问题:将一个具有n个元素的一维向量想左旋转k个位置。
例如,假设n=8,k=3,那么向量abcdefgh旋转后得到向量defghabc。
程序给定的限制是仅使用几十字节的微小内存,花费与n成比例的时间来完成旋转。
我的思考:
该问题看起来可以用一个典型的递归思路来解决。
例如题目中所给的例子,可以按如下步骤完成:
(1),将(abc)与原向量的最后三个元素进行一一置换,得到记过(fgh)de(abc),即(fghde)abc。(这里的括号只是为了标记以提示重点,无其他意义)。
(2).现在可以观察到abc已移动到最终位置,下面只需对剩余的5个元素组成的向量(fghde)向左旋转3个位置,来变为(defgh),如此,原始问题转化为一个规模更小的子问题。
(3).递归的终止条件?不难看出,当剩余的元素数量为k的2倍时,执行完(1)中的置换后,就已完成了旋转,因此不需要进行递归。此外,当最初的k%n的结果为0时,己不需要进行置换,也不需要进行递归——原向量与旋转后的向量相同。
然而,上面的思考是不全面的:即始终假设每次递归中剩余元素的数量n'至少是k的2倍。事实上一定会出现n'<2k的情况的,这种情况下需要进行一些变形,才能继续递归。方法如下
由于n'<2k,令k'=n‘-k<k , 再以n',k'为参数完成上述的步骤(1);之后,此时,n'中的最后k个元素已移动至最终位置,因此需要对剩余的n'-k'个元素进行递归,其递归参数为n,k-k' 。
上述算法的实现如下:
#
include <stdio.h>
#
include <stdlib.h>
typedef unsigned int size_t;
typedef char ELEMENT;
int
Min
( int A
,
int B);
void SwapElement(char
*
str
,
int index1
,
int index2);
void Swap(char
*
str
,
size_t size
,
int cnt);
void Whirl (char
*
str
,
size_t size
,
int cnt);
int main (int argc
,
char
*
argv[])
{
ELEMENT target[]
=
{
'
a
'
,
'
b
'
,
'
c
'
,
'
d
'
,
'
e
'
,
'
f
'
,
'
g
'
};
Whirl(target
,
sizeof
(target)
,
atoi(argv[
1
]) );
int index;
for
(index
=
0
; index
<
sizeof
(target);index
++
)
printf
(
"
%c
"
,
target[index]);
printf
(
"
"
);
}
int
Min
( int A
,
int B)
{
if
(A
<=
B)
return
A;
else
return
B;
}
//
swap the array 's two elements: str[index1]<=>str[index2]
void SwapElement(ELEMENT
*
str
,
int index1
,
int index2)
{
char tmp
=
str[index1];
str[index1]
=
str[index2];
str[index2]
=
tmp;
}
//
swap the array's two part: str[0]~str[cnt-1] <=> str[size-cnt]~str[size-1]
void Swap(ELEMENT
*
str
,
size_t size
,
int cnt)
{
int index;
for
(index
=
0
;index
<
cnt;index
++
)
SwapElement(str
,
index
,
size
-
cnt
+
index);
}
//
Whirl the array specified by str by number of cnt
void Whirl (ELEMENT
*
str
,
size_t size
,
int cnt)
{
cnt
%=
size;
if
(cnt
==
0
)
return
;
if
(cnt
==
size
-
cnt)
{
Swap(str
,
size
,
cnt);
return
;
}
else
if
(cnt
<
size
-
cnt)
{
Swap(str
,
size
,
cnt);
Whirl(str
,
size
-
cnt
,
cnt);
}
else
if
(cnt
>
size
-
cnt)
{
int newcnt
=
size
-
cnt;
Swap(str
,
size
,
newcnt);
Whirl(str
+
newcnt
,
size
-
newcnt
,
cnt
-
newcnt);
}
}
写完程序后,参看了书中给出的思路,我想到的这种递归方法是其中的一种,作者给出的评论为:“根据这个递归的算法可以到得到一个非常优雅的程序,但是该程序要求编码细腻,还需要深思熟虑,以确保程序具有足够的效率。
此外,作者还给出了另外一个非常简洁而富有效率的算法,让我觉得不服不行,为啥我就做不到这么直接、清晰的思考问题呢?
该方法的思路如下:
将向量的元素视为数组元素,那么问题可以转化为将最初形式为ab数组,转化为形式为ba的数组;其中a是原数组的前k个元素,而b是剩余元素。
解决该问题, 只需三个步骤:
reverse(0,k-1) //将a部分进行180度的旋转,即将a中位置对称的元素互换,得到r(a)b
reverse(k,n) //将b部分进行180度的旋转 ,得到r(a)r(b)
reverse(0,n-1) //将整个数组进行180度的旋转,得到r(r(a)r(b)) =ba
算法的实现如下:
#include
<
stdio.h
>
#include
<
stdlib.h
>
typedef unsigned
int
size_t;
typedef
char
ELEMENT;
void
Reverse(ELEMENT
*
str,size_t size);
void
Whirl( ELEMENT
*
str, size_t size,
int
cnt);
void
SwapElement(ELEMENT
*
str,
int
index1,
int
index2);
int
main (
int
argc,
char
*
argv[])
...
{
char target[]=...{'a','b','c','d','e','f','g'};
Whirl(target,sizeof(target),atoi(argv[1]) );
int index;
for(index=0; index < sizeof(target);index++)
printf("%c",target[index]);
printf(" ");
}
void
SwapElement(ELEMENT
*
str,
int
index1,
int
index2)
...
{
char tmp=str[index1];
str[index1]=str[index2];
str[index2]=tmp;
}
void
Reverse(ELEMENT
*
str,size_t size)
...
{
int index;
for(index=0;index<size/2;index++)
SwapElement(str,index,size-index-1);
}
void
Whirl( ELEMENT
*
str, size_t size,
int
cnt)
...
{
cnt%=size;
Reverse(str,cnt);
Reverse(str+cnt,size-cnt);
Reverse(str,size);
}
从算法的实现也可以看出第二种算法的优越性,不仅有更少的代码量,而且逻辑更清晰。