编程珠玑第二章问题B: n元一维向量旋转问题之java实现

问题:
这里写图片描述

本文以书上提到的四种解决办法用JAVA来实现

解决方法一:借助临时数组


基本思想

首先将向量s的前i个元素复制到临时数组中,然后将数组索引从i到n-1的元素向左移动i个位置,最后把临时数组中的值复制到s余下的i个位置。

代码实现

    /**
     * 借助临时数组来实现向量旋转
     * 
     * @param s s向量
     * @param i 向左旋转值
     */
    private static void moveString1(char[] s, int i) {
        int n = s.length;

        // 1.将向量s的前i个元素复制到临时数组中
        char[] a = new char[i];
        for (int j = 0; j < i; j++) {
            a[j] = s[j];
        }

        // 2.数组索引从i到n-1的元素向左移动i个位置
        for (int x = 0, j = i; j < n; j++, x++) {
            s[x] = s[j];
        }

        // 3.把临时数组中的值复制到s余下的i个位置
        for (int x = 0, j = n - i; j < n; j++, x++) {
            s[j] = a[x];
        }
    }

优缺点

执行速度还是蛮快的,但是存在多余i个空间的消耗。

解决方法二:整体移动法


基本思想

有点像排队,排在头的那个人买完票,走出队列,人群都往前走一步,之后发现少买一张,这时候已经出队伍了,他必须重新排队,就只有排在队伍的末尾。

代码实现

/**
 * 整体移动法
 * 
 * @param s s向量
 * @param i 向左旋转值
 */
private static void moveString2(char[] s, int i) {
    int n = s.length;

    // 4. 循环执行i次
    for (int x = 0; x < i; x++) {
        // 1. 头部数据缓存到t
        char t = s[0];
        // 2. 后面的n - 1个数整体向左移一位
        for (int j = 0; j < n - 1; j++) {
            s[j] = s[j + 1];
        }
        // 3. t赋值给最末尾
        s[n - 1] = t;
    }
}

优缺点

比起第一种实现,不用过多占用空间,但是在时间上消耗比较大。

解决方法三:复杂的杂技算法


基本思想

比较复杂的实现方式,书上比喻为耍杂技:缓存s[0]到t,s[0]被s[i]替换,s[i]被s[i+i]替换,s[i+i]又被s[i+i+i]替换,直到需要被s[0]替换。(注意,索引值需对n取余,比如 i%n, 2i%n,循环替换)。这相当于一次置换。

如果次数小于i和n的最大公约数,继续从s[1]开始:缓存s[1]到t,s[1]被s[1+i]替换,s[i+1]又被s[i+1+i]替换,类推,一直到需要被s[1]替换,同样索引值需要对n取余。

如此循环,一共执行i和n的最大公约数次置换。

代码实现

/** 杂技算法
 * @param s s向量
 * @param i 向左旋转值
 */
private static void moveString3(char[] s, int i) {
    // n为字符总长度
    int n = s.length;
    // 需要最大公约数次置换
    int numbers = gcd(n, i);

    // 执行n和i的最大公约数次置换
    for (int j = 0; j < numbers; j++) {
        // 记录每一次替换所需步数
        int steps = 0;
        // 1. 每次置换开始的数组索引缓存到t
        char t = s[j];
        // 2. 需要替换的数组游标
        int index = j;
        // 5. 需要从置换开始的数组索引取值时,循环结束
        while ((index + i) % n != j) {
            // 3. 被其后面的第i个值替换,需要对长度取余
            s[index % n] = s[(index + i) % n];
            // 4.游标后移i个位置
            index += i;
            steps++;
        }
        // 6. 需要从置换开始的数组索引取值时,将t赋值给它
        s[index % n] = t;
        System.out.println("第" + (j + 1) + "次置换,移动次数为:" + steps);
    }
}

// 递归方式求最大公约数
private static int gcd(int a, int b) {
    if (b != 0) {
        return gcd(b, a % b);
    }
    return a;
}

优缺点

好复杂的算法,比前面两种实现复杂得多。我是在小本本上人工实现置换,刚开始不知道需要最大公约数次置换,以为是i次(用的是n = 12, i = 3,碰巧最大公约数是3),然后增大i值到5的时候,发现结果是错误的。就挨个数据更改,把i、n和置换次数总结了出来。

置换次数还是比较喜感的,最大公约数是1的时候,只需要置换一次。
至于为什么时置换最大公约数次,需要了解欧几里得算法。

解决方法四:数组分段求逆法


基本思想

把数组从i位置分成两段xy,x求逆,y求逆,然后xy整体求逆。
比如向量abc n=3,i=2
x = ab , y = c
x求逆= ba
y求逆= c
xy = bac
xy求逆 = cab

代码实现

int i = 3;
reverse(s, 0, i - 1); 
reverse(s, i, s.length - 1);
reverse(s, 0, s.length - 1);

// 数组[i,j]区间的值反转
private static void reverse(char[] s, int i, int j) {
    for (; i <= j; i++, j--) {
        char temp = s[i];
        s[i] = s[j];
        s[j] = temp;
    }
}

优缺点

果然是“啊哈,灵机一动”,这种算法相比前面的简单得多。
实际上公式就是:(ab)^r –> (a^r b^r)^r。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值