编程珠玑之第二章questionB: n元一维向量旋转问题

本文深入探讨了一种仅使用数十个额外字节空间,在正比于n的时间内完成n元一维向量向左旋转i个位置的方法。通过递归求最大公约数、置换群的概念及翻转求逆的性质,提供了三种不同的解决方案,每种方案都针对运行时间和内存空间进行了优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

问题解析:
1、以正比于n的时间(相当于n步内)完成该操作,那么就是每个元素的移动都差不多一步到位,如将第4位的d一步移动到第1位处,其他元素也也一样。
2、额外空间只有几十个字节,这里说明并不能将前i个保存到临时空间,这样当i很大时,会消耗过多的内存空间。
3、题目条件对运行时间和内存空间都有严格的限制,

解决方案:

作者给出的方案1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <cstdio>
#include <cstdlib>      // srand, rand
#include <cassert>      // assert

// 递归求最大公约数
int gcd(int a,  int b)
{
    assert(a > 0 && b >= 0);
    if (b != 0){ return gcd(b, a%b); }
    return a;
}

/************************************************************************/
// 函数名称:my_reverse
// 函数目的:对输入的数组循环移位进行反转
// 函数参数:arr:要反转的数组 arraysize:数组长度  reversenum:反转个数
/************************************************************************/

void my_reverse(int arr[], int arraysize, int reversenum)
{
    if (reversenum == 0 || reversenum == arraysize) { return; }
    for (int i = 0; i < gcd(reversenum, arraysize); ++i){
        int temp = arr[i];

        int j = i;
        while (1){
            int k  = j + reversenum;
            if (k >= arraysize){ k -= arraysize; }
            if (k == i) { break; }

            arr[j] = arr[k]; j = k;
        }
        arr[j] = temp;
    }

    return;
}


int main()
{
    int a[12] = {'a''b''c''d''e''f''g''h''i''j''k''l'};

    int arraysize  = 12;   // 数组长度
    int reversenum = 5;    // 反转个数

    my_reverse(a, arraysize, reversenum);

    for (int i = 0; i < 12; ++i)
    {
        printf("%c\t", a[i]);
    }
    
    return 0;
}
 运行结果如下:


心得与疑惑:
1、这里使用了最大公约数的概念,那么最大公约数在算法中有哪些具体应用?为什么这里要使用最大公约数作为置换次数?
2、作者在给出解答是提到了“置换群”的概念? 怎么去理解它?

作者给出的方案2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <cstdio>
#include <cstdlib>      // srand, rand
#include <ctime>        // time, clock_t
#include <cassert>      // assert

/************************************************************************/
// 函数名称:my_swap
// 函数目的:交换arr[index_a..index_a + swap_num -1]和arr[index_a..index_b + swap_num -1]
// 函数参数:arr:要交换的数组 arraysize:数组长度  index_a\index_b:数组索引
//           swap_num:要交换的数目
/************************************************************************/

void my_swap(int arr[], int arraysize, int index_a, int index_b, int swap_num)
{
    assert(index_a >= 0 && (index_a+swap_num) <= index_b && (index_b+swap_num) <= arraysize);

    for (int i = 0;  i < swap_num; ++i){
        int temp = arr[index_a + i];
        arr[index_a + i] = arr[index_b + i]; arr[index_b + i] = temp;
    }

    return;
}

/************************************************************************/
// 函数名称:my_reverse
// 函数目的:对输入的数组进行反转
// 函数参数:arr:要反转的数组 arraysize:数组长度  reversenum:反转个数
/************************************************************************/

void my_reverse2(int arr[], int arraysize, int reversenum)
{
    if (reversenum == 0 || reversenum == arraysize) { return; }

    int i, j, p;
    i = p = reversenum;
    j = arraysize - p;
    while ( i != j){
        if (i > j){
            my_swap(arr, arraysize, p-i, p, j);
            i -= j;
        }
        else {
            my_swap(arr, arraysize, p-i, p+j-i, i);
            j -= i;
        }

    }
    my_swap(arr, arraysize, p - i, p, j);

    return;
}


int main()
{
    int a[12] = {'a''b''c''d''e''f''g''h''i''j''k''l'};

    int arraysize  = 12;   // 数组长度
    int reversenum = 7;    // 反转个数

   // my_reverse(a, arraysize, reversenum);
    my_reverse2(a, arraysize, reversenum);

    for (int i = 0; i < 12; ++i)
    {
        printf("%c\t", a[i]);
    }
    return 0;
}
数据结果如下:


心得与疑惑:
1、说实话我还是不太怎么理解这个算法,只是依照作者提示,实现了代码!
2、下面是实现过程的分析步骤,帮助理解该算法:
如果arrsize = 5, reversenum=5,那么该程序执行如下:
(1) p = 5, i =5, j = 7
swap(0, 7, 5) 结果:hijkl fg abcde
(2) p = 5, i =5, j = 2
swap(0, 5, 2) 结果:fg jkl hi abcde
(3) p = 5, i =3, j = 2
swap(2, 5, 2) 结果:fg hi l jk abcde
(4) p = 5, i =1, j = 2
swap(4, 6, 1) 结果:fghi k j labcde
(5) p = 5, i =1, j = 1 ---->退出循环
swap(4, 5, 1) 结果:fghi j k labcde

3、作者说该段求解算法 my_reverse2是同求最大公约数的欧几里得算法是同构的,这或许对理解该段代码提供一个很好的思路,如下:  

1
2
3
4
5
6
7
8
9
10
int gcd(int i, int j)
{
    assert(i > 0 && j > 0);
    while (i != j){
        if (i > j) i -= j;
        else  j -= i;
    }
    return i;

}

作者给出的方案3:

利用翻转求逆的性质,得到最终的解决方案,该方案是三个中最完美的一个方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <cstdio>
#include <cstdlib>      // srand, rand
#include <cassert>      // assert

/************************************************************************/
// 函数名称:myswap
// 函数目的:反转arr索引在[index_a, index_b]之间的值
// 函数参数:arr:要交换的数组 arraysize:数组长度  index_a\index_b:数组索引
/************************************************************************/

void myswap(int* arr, int arraysize, int index_a, int index_b)
{
    assert(index_a >= 0 && (index_b-index_a) >= 0 && index_b < arraysize);

    int i = index_a, j = index_b;
    for (;  i <= j; i++, j--){
        int temp = *(arr+i);
        *(arr+i) = *(arr + j); *(arr + j) = temp;
    }

    return;
}

/************************************************************************/
// 函数名称:my_reverse
// 函数目的:对输入的数组循环移位进行反转
// 函数参数:arr:要反转的数组 arraysize:数组长度  reversenum:反转个数
/************************************************************************/

void my_reverse3(int* arr, int arraysize, int reversenum)
{
    assert(reversenum <= arraysize);

    myswap(arr, arraysize, 0, reversenum-1);
    myswap(arr, arraysize, reversenum, arraysize-1);
    myswap(arr, arraysize, 0, arraysize-1);
    return;
}

int main()
{
    int a[12] = {'a''b''c''d''e''f''g''h''i''j''k''l'};

    int arraysize  = 12;   // 数组长度
    int reversenum = 6;    // 反转个数

    my_reverse3(a, arraysize, reversenum);

    for (int i = 0; i < 12; ++i)
    {
        printf("%c\t", a[i]);
    }

    return 0;
}
输出结果如下:


心得与疑惑:

1、求逆:ab->a^r b-> a^r b^r ->(a^r b^r)^r 最为简洁,也最好理解,堪称完美!

2、应该把该代码作为一种常识。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值