130 阅读4分钟

在日常生活中, 经常会遇到条件执行. 通常是往 for 或者 while 的参数中写一个条件控制循环.

有时候, 条件可能不止 1 个, 这时候就会使用多个判断表达式作为条件, 并用 && 连接.

条件判断的时候, 计算机就会从左往后先后执行判断表达式, 当判断表达式都满足时, 进行指定的条件执行.

这个时候, 当条件判断中有加减法的时候, 就需要注意了.

因为看上去, 就是符合条件执行, 不符合条件就不执行离开执行后面的, 仅此而已.

但是计算机, 它真的是会去计算的.

拿一个循环举例, 现在要将最后一个位置的数据往前插入到第一个比它小的位置.

循环因子 in 从 n 为起点, 往前循环, 直到 n>0 n 递减.

现在要比较前一个元素, 如果遇到比 temp 值小的时候, 便移动元素.

temp, 是原来 in 位置元素的拷贝.

在插入数组元素中, 这是常见的操作.

显然, 这里的条件需要另外插入一个 a[n-1]>=temp

程序

public void insert() {    
        int in = a.length-1;
        long temp = a[in];
        while (in > 0 && a[in-1] >= temp) {
            a[in] = a[in-1];	//	向后移动: 小坐标给大坐标, 左大右小
            in--;
        }
        //首次抵达比自己小的元素的后面
        a[in] = temp;
    }
}

程序执行, a[length-1] 顺利地来到第一个比它小的元素后边, 站在它的后边.

原来在写这个时候, 是在实现插入排序的.

插入排序的简单思路复现:

假设前半部分总是有序的, 实际上, 第一个位置的元素相对于 1 个元素的序列来说, 它就是有序的(虽然听上去有些牵强, 但是你不得不承认它是事实)
从第 1 个开始, 这个位置就是有序部分和无序部分中的中间点, 无序序列的起点, 后面的插入排序都将从它开始.
这个时候, 控制这个部分, 还需要另一个循环因子控制下一次循环的起点.
因为每次将当前位插入到有序序列中去后, 前面部分就部分有序了, 下一次循环将从有序序列的后一位开始.
即前面有序序列不需要另外比较了. 
于是, 有了 out 和 in 2 个循环因子控制.
先说思路, 主循环中以 in 为起点, 往前遍历, 在没到 0 之前以及遇到比自己更小的元素之前, 元素都需要后移并递减.

实现就是这样:

public void insertSort() {
    int in;
    int out;
    for (out = 1; out < nElems; out++) {
            in = out;
            long temp = a[in];
            while (in > 0 && a[in-1] >= temp) {
                    a[in] = a[in-1];	//	向后移动: 小坐标给大坐标, 左大右小
                    in--;
            }
            //首次抵达比自己小的元素的后面
            a[in] = temp;
    }
}

原来是这样, 我以为就结束了, 直到我从自己对插入排序的认识出发手写一遍的时候, 执行没有完成, 还抛出了越界的异常 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10

每个点都确认是基于插入排序写出来的, 也确认无误, 和参考答案一一比对. 接着调试,

[77 99 44 55 22 88 11 0 66 33 ] 为例

直到 in 等于 2, 之前的 99 后移, 77 后移也都符合预期, 直到下一步 n = 0 的时候, F6 按下去后, 调试窗口跳出了 ArrayIndexOutOfBoundsException 的异常.

后面的排序自然没有成功.

这是当时的程序

public void insertSort() {
    int in;
    int out;
    for (out = 1; out < nElems; out++) {
            in = out;
            long temp = a[in];
            while ( a[in-1] >= temp && in > 0 ) {
                    a[in] = a[in-1];	//	向后移动: 小坐标给大坐标, 左大右小
                    in--;
            }
            //首次抵达比自己小的元素的后面
            a[in] = temp;
    }
}

发现问题:

原来在写主循环的时候, 我私以为 a[in]-1 > temp 判断前一个是不是比旗帜位更大才是更重要的, 至于循环终点 n>0 控制终点只是其次.

问题就出现在这里.

当旗帜位指向 44 的时候, 前面正常比对并交换, 眼看着 44 就要插入到 n = 0 的位置晋升第一位最小元素的时候, 数组越界了.

原因在于 这时候计算机不是判断不符合就走了的, 条件里的有数组的引用, 有加减.

当走 while 条件的时候, 它会拿着等于 0 的 n 值先后 a[in-1] >= temp 做完减法后做引用去引用数组元素.

做到减法的时候, 完了, 遇到 a[-1] 这样的项, 数组怎么可能有那样的项嘛.

感悟

难怪, se 中会强调写那些有关条件且或非多条件的执行过程哪.

如经常看到这样的写, 执行完第一个就不会继续往后执行后面的执行.

int n = 0;
while (n < 0 || xxx) {
    
}

判断条件的先后决定计算机执行的先后, 看上去的判断对计算机来说有时候除了判断还有还是执行.

平时遇到的一个小问题, 衍生出来的篇幅有点拉, 自行阅读哈 hh.