数组循环移位

    从本节开依据网上的一些资料学习总结常见的一些算法问题(CSDN程序员编程艺术),第一个总结的是“数组循环移位”算法。

  算法问题:设计一个算法,把一个含有N个元素的数据,循环左移(右移)K个位置,要求时间复杂度O(N),且只允许使用两个附件的变量。

  算法示例:abc1234循环左移3位abc1234->1234abc。

  最先想到的办法,每次想做移动一个位置,循环K次,算法的时间复杂度O(K*N):

void LeftShift(char* arr, int N, int k)
{
    while(k--)
    {
        char temp =  arr[0];
        for (int i =1;i != N;i++)
            arr[i-1] = arr[i];
        a[N-1] = temp;
    }
}

  这种方法不满足题目要求,需要做进一步的修改。主要有两种思路:1)考虑如何一次到位,将元素正确的映射到需要移动到的位置;2)序列转折算法。

  对于第一种思路,先把数组中的元素顺时针方向的圆钟,那么向做移动k个位置,等价于将圆钟逆时针旋转K个位置。在编程的时候,关键是如何找到这种映射关系。上例中向左移动三个位置,第3个位置元素将会移动到第0个位置(arr[0] = arr[3]),第6个位置元素替换到第3个位置,第9个位置的元素(圆钟的第9个元素对应数组中第9%7=2位置元素)。以此类推完成替换。但这种方法需要特别注意的是:可能在某处循环移动中仅做小循环移动(如某种情况:0<-3<-6<-0),为此我们一旦出现小循环,我们需要强制从一个新的位置开始再次循环。思路一实现代码:

void left_shift(char* arr, int k)
{
    int N = strlen(arr);
    int beg_pos = 0;
    int current_pos = beg_pos;
    char temp = arr[beg_pos] ; 
    for (int i =0; i != N; i++)
    {
        int next_pos = (current_pos+k)% N;
        if (next_pos != beg_pos)
        {
            arr[current_pos] = arr[next_pos];
            current_pos = next_pos;
        }
        else
        {
            arr[current_pos] = temp;
            beg_pos++;
            temp = arr[beg_pos];
            current_pos = beg_pos;
        }
    }

}
int main()
{
    char str[] = "abcd123";
    cout<<"Please Enter shift position:";
    int k;
    cin>>k;
    cout<<"Before shift:"<<str;
    left_shift(str, k);
    cout<<"\nAfter shift:"<<str;
    system("pause");
    return 0;
}
    

  

  思路二实现方法则相对比较巧妙,运用了序列转置公式。我们观察之前的例子可以发现:数组由两个部分组成,arr[0:k]部分与arr(k:N-1]部分,左移k个元素实际上就是这两个部分对调了顺序。假设原序列Z=XY,由X,Y两部分组成,其中X含有k个元素,则左移k个元素等价于Z' =YX。为了得到Z',需要使用序列转置公式:

                                            

因此算法的步骤如下:

1) 翻转序列X。前面例子中abc->cba

2) 翻转序列Y。前面例子1234->4321

3) 组合翻转后序列,并做翻转。组合后的序列cba4321->1234abc。

算法实现如下:

void reverse(char* arr, int beg_pos, int end_pos)
{
    for(;beg_pos < end_pos;beg_pos++, end_pos--)
    {
        char temp = arr[beg_pos];
        arr[beg_pos] = arr[end_pos];
        arr[end_pos] = temp;
    }
}

void left_shift(char* arr, int k)
{
    int N = strlen(arr);
    reverse(arr, 0, k-1);
    reverse(arr, k, N-1);
    reverse(arr, 0, N-1);

}

int main()
{
    char str[] = "abcd123";
    cout<<"Please Enter shift position:";
    int k;
    cin>>k;
    cout<<"Before shift:"<<str;
    left_shift(str, k);
    cout<<"\nAfter shift:"<<str;
    system("pause");
    return 0;
}

  比较两者方法,思路一比较容易想到,但是里面可能隐藏着“小循环”不易察觉,需要做额外的注意,方法一正确性证明比较困难一点。方法二,相对比较优雅,但难以想到这种解决方案。

原文地址:https://www.cnblogs.com/wangbogong/p/3099208.html