数组元素循环左/右移问题

汇编语言和C语言里面都有循环移位指令或操作符,可以实现循环左移或右移若干个bit,但若要在数组中循环左移或右移若干个元素,却无法直接实现。下面以字符数组循环左移为例,探讨一下这类问题的解决办法。
 
对字符数组S[8] = “ABCDEFG”循环左移M = 3位,使之变成“DEFGABC”,编写函数实现之。
 
分析:
 
最容易想到的方法有两种:
  1. 申请一个3个字节的空间,把“ABC”复制进去,然后把剩下的字符从左到右依次移到相应的位置,最后把“ABC”再复制到最后,释放空间,算法结束。这样时间复杂度为O(N),空间复杂度为O(M)。
  2. 只用一个字节的辅助空间,每次把字符串循环左移一位,共移M次。这样时间复杂度为O(M·N),空间复杂度为O(1)。

以上两种方法虽然简单,但时间(或空间)复杂度令人不尽满意,有没有时间复杂度为O(N),空间复杂度为O(1)的算法呢?下面我们来研究一下。

方案一

设字符串长度为N,临时变量为t,算法步骤如下:

  1. 设i = 0
  2. j = i,保存s[j]到t
  3. 把s[(j + N) % M]向前移N位到最终位置s[j % M]
  4. j += N
  5. 如果j % M != i,则转3;否则下一步
  6. i++
  7. 如果i < gcd(M, N),则转2;否则移动完毕,算法结束

实现代码如下:

/******************************************************************
* Copyright (c) 2005-2007 CUG-CS
* All rights reserved
*
* 文件名称:StringShift.c
* 简要描述:字符串循环左移
*
* 当前版本:1.0
* 作    者:raincatss
* 完成日期:2007-11-18
* 开发环境:Windows XP Sp2 + VC6.0
* 个人博客:http://raincatss.cublog.cn/
******************************************************************/

#include <stdio.h>
#include <string.h>
#include <assert.h>

static int gcd(int m, int n);

void StringShift(char *str, int n)
{
    int i, j, len;
    char tmp;

    assert(NULL != str);
    len = strlen(str);
    if (n <= 0 || n >= len) {
        return;
    }
    
    for (i = 0; i < gcd(len, n); i++) {
        j = i;
        tmp = str[j];
        do {
            str[j] = str[(j + n) % len];
            j = (j + n) % len;
        } while (j != i);
        str[(j + len - n) % len] = tmp;
    }
}

static int gcd(int m, int n)
{
    int tmp;

    while (n != 0) {
        tmp = m % n;
        m = n;
        n = tmp;
    }

    return m;
}

在do{}while()循环中,如果k每次加n后不与len取模,可以发现k - i最后等于M、N的最小公倍数,这样保证在循环过程中无重复下标(为什么?请自己想一下)。之所以让for()循环做gcd(M, N)次,是因为每次do{}while()循环做M * N / (N * gcd(M, N)) = M / gcd(M, N)次,for()循环做gcd(M, N)次正好可以保证移动M次,即所有元素移动一次。(如果谁有此法的精确且清楚的数学证明,请发Email:raincatss#gmail.com)

方案二

把原字符串分成三部分ABlBr(Br与A长度相同),循环左移后为BlBrA。可以先把A与Br互换位置,变为BrBlA,然后再把Br与Bl互换,由此可以递归求解。

递归实现代码如下:

/******************************************************************
* Copyright (c) 2005-2007 CUG-CS
* All rights reserved
*
* 文件名称:StringShift.c
* 简要描述:字符串循环左移
*
* 当前版本:1.0
* 作    者:raincatss
* 完成日期:2007-11-18
* 开发环境:Windows XP Sp2 + VC6.0
* 个人博客:http://raincatss.cublog.cn/
******************************************************************/

#include <stdio.h>
#include <assert.h>

static void Swap(char *p, char *q);

void StringShift(char *str, int len, int n)
{
    int i;

    assert(NULL != str);
    if (len <= 0 || n <= 0 || n >= len) {
        return;
    }

    if (n < len - n) {
        for (i=0; i<n; i++) {
            Swap(&str[i], &str[len - n + i]);
        }
        StringShift(str, len - n, n);
    } else if (n > len - n) {
        for (i=len-1; i>n-1; i--) {
            Swap(&str[i], &str[i - n]);
        }
        StringShift(str + len - n, n, n + n - len);
    } else {
        for (i=0; i<n; i++) {
            Swap(&str[i], &str[n + i]);
        }
    }
}

static void Swap(char *p, char *q)
{
    char tmp = *p;
    *p = *q;
    *q = tmp;
}

为提高效率,可以把递归转换为迭代,实现代码如下:

/******************************************************************
* Copyright (c) 2005-2007 CUG-CS
* All rights reserved
*
* 文件名称:StringShift.c
* 简要描述:字符串循环左移
*
* 当前版本:1.0
* 作    者:raincatss
* 完成日期:2007-11-18
* 开发环境:Windows XP Sp2 + VC6.0
* 个人博客:http://raincatss.cublog.cn/
******************************************************************/

#include <stdio.h>
#include <assert.h>

static void Swap(char *p, char *q);

void StringShift(char *str, int len, int n)
{
    int i, tmp;
    char *s = str;

    assert(NULL != str);
    if (len <= 0 || n <= 0 || n >= len) {
        return;
    }

    while (n != len - n) {
        if (n < len - n) {
            for (i=0; i<n; i++) {
                Swap(&s[i], &s[len - n + i]);
            }
            len -=n;
        } else {
            for (i=len-1; i>n-1; i--) {
                Swap(&s[i], &s[i - n]);
            }
            s += len - n;
            tmp = n;
            n = n + n - len;
            len = tmp;
        }
    }
    for (i=0; i<n; i++) {
        Swap(&s[i], &s[n + i]);
    }
}

static void Swap(char *p, char *q)
{
    char tmp = *p;
    *p = *q;
    *q = tmp;
}

方案三

先把前N个字符逆序,再把后M - N个字符逆序,最后再把这M个字符逆序,即得到最终结果。

代码实现如下:

/******************************************************************
* Copyright (c) 2005-2007 CUG-CS
* All rights reserved
*
* 文件名称:StringShift.c
* 简要描述:字符串循环左移
*
* 当前版本:1.0
* 作    者:raincatss
* 完成日期:2007-11-18
* 开发环境:Windows XP Sp2 + VC6.0
* 个人博客:http://raincatss.cublog.cn/
******************************************************************/


#include <string.h>
#include <assert.h>

static void Swap(char *p, char *q);
static void Reverse(char *str, int i, int n);

void String_Shift_Reverse(char *str, int n)
{
    int len;
    
    assert(NULL != str);
    len = strlen(str);
    if (n <=0 || n >= len) {
        return;
    }

    Reverse(str, 0, n - 1);
    Reverse(str, n, len - 1);
    Reverse(str, 0, len - 1);
}

static void Swap(char *p, char *q)
{
    char tmp;

    tmp = *p;
    *p = *q;
    *q = tmp;
}

static void Reverse(char *str, int i, int n)
{
    int l, r;

    for (l = i, r = n; l < r; l++, r--) {
        Swap(&str[l], &str[r]);
    }
}

 

综上,方案三比较简单易行,推荐之。

参考资料:Jon Bentley, "Programming Pearls(Second Edition)", 北京:人民邮电出版社,2007

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值