编程珠玑一维向量旋转

编程珠玑第二章问题B提到了n元一维向量的旋转问题,并且提出了5种思路.
问题描述:
将一个n元一维向量向左旋转i个位置。例如当n=8且i=3时,向量abcdefgh旋转为defghabc。简单的代码使用一个n元的中间向量在n步内完成该工作。你能否仅使用数十个额外字节的存储空间,在正比于n的时间内完成向量的旋转?

思路1:开辟一个长度为i(这里说的i介于[0-n]之间,超过这个范围需要对n取模,n为字符串s的长度)的临时空间存储s[0-i),然后将s向左移动i个单位,最后将临时空间中的数据放到s的后半部分。该方法空间消耗比较大O(i),代码如下:

void rotate(string &s, int rotdist){
    int *tmp = new int[rotdist], i, len = s.length();
    for(i = 0; i < rotdist; ++i)
        tmp[i] = s[i];
    for(i = rotdist; i < len; ++i)
        s[i-rotdist] = s[i];
    for(i = len - rotdist; i < len; ++i)
        s[i] = tmp[rotdist+i-len];
}

思路2:时间换空间,每一次向左移动1个单位,总共移动i(同上述i)次,这样的空间复杂度为O(1),代码如下:

void rotateOnce(string &s){
    int t = s[0];
    for(int i = 0; i < s.length()-1; ++i)
        s[i] = s[i+1];
    s[s.length()-1] = t;
}
void rotate(string &s, int rotdist){
    for(int i = 0; i < rotdist; ++i)
        rotateOnce(s);
}

思路3:要在有限的资源内解决该问题,显然需要更加复杂的程序。书中,提到一个“杂技”方法:移动s[0]到临时变量t,然后移动s[i]至s[0],s[2i]至s[i],一次类推(将s中的所有下标对n取模),直至返回到取s[0]中的元素,此时改为从t取值然后终止过程。代码如下:

int gcd(int a, int b)//最小公约数
{
    while (a != b)
    {
        if (a > b)
            a -= b;
        else
            b -= a;
    }

    return a;
}
void acrobatics(string &s, int rotdist){
    int t, j, i, k, n = s.length();
    int mod = gcd(rotdist, n);
    for(i = 0; i < mod; ++i){
        t = s[i];
        j = i;
        while(1){
            k = j + rotdist;
            if(k >= n)
                k -= n;
            if(k == i)
                break;
            s[j] = s[k];
            j = k;
        }
        s[j] = t;
    }
}

思路4:从另外一面考察这个问题,可以得到一个不同的算法:旋转向量s其实就是交换向量ab的两段,得到向量ba。这里a代表s中的前i个元素。假设a比b短,将b分成bl和br,是的br具有与a相同的长度。交换a和br,也就将a(bl)(br)转换成(br)(bl)a。序列a此时位于其最终的位置,因此现在的问题就集中到交换b的两个部分。由于新问题与原来的问题具有相同的形式,因此可以递归求解。代码如下:

void swapBlock(string &s, int a, int b, int m){//表示交换s[a,a+m)和s[b,b+m)
    for(int i = 0; i < m; ++i){
        swap(s[a+i], s[b+i]);
    }
}
void rotate(string &s, int rotdist){
    int i, p, j, n;
    n = s.length();
    if(rotdist < 0)
        rotdist += n;
    i = p = n - rotdist % n;
    j = n - p;
    while(i != j){
        cout<<i<<"\t"<<j<<endl;
        if(i > j){
            swapBlock(s, p-i,p, j);
            i -= j;
        }
        else{
            swapBlock(s, p-i, p+j-i, i);
            j -= i;
        }
    }
    swapBlock(s, p-i, p, i);
}

思路5:翻手法(求逆法)推荐的方法,快速易懂。将问题看作是把数组ab转换成ba,同时假定我们拥有一个函数可以将数组中特定部分的元素求逆。从ab开始,首先对a求逆,得到arb,然后对b求逆得到arbr。最后对整体求逆,得到(arbr)r。此时正好是ab
reverse(0, i-1) /*cbadefgh*/
reverse(i, n-1) /*cbahgfed*/
reverse(0, n-1) /*defghabc*/
代码如下:

void reverse(string &s, int start, int end){//reverse s[start, end]
    int i = start, j = end;
    while(i < j){
        swap(s[i], s[j]);
        i++;
        j--;
    }
}
void reverseRotation(string &s, int rotdist){
    reverse(s, 0, rotdist - 1);
    reverse(s, rotdist, s.length() - 1);
    reverse(s, 0, s.length() - 1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值