Loading

插入排序的优化

插入排序是基础的排序方法之一,针对插入排序的优化,网上常见的就两种:

  • 查找插入位置时,使用二分法查找
  • 衍生出的希尔排序

经代码测试,二分法优化的插入排序感觉并不理想,从时间复杂度上来看,与普通插入排序也相差无几;

对普通的插入排序稍加改造,性能就已经超越二分法优化的情形了。

如下是普通的插入排序代码:

public static void sort(int[] source) {
  StopWatch watch = new StopWatch();
  watch.start();
  // 外层循环:从第二个数开始取值往前插入
  for (int i = 1, len = source.length; i < len; i++) {
    int targetNum = source[i];

    // 内层循环:将当前数与前面排好序的数列进行比较
    int j;
    for (j = i - 1; j >= 0; j--) {
      if (source[j] > targetNum) {
        // 当插入的数比前面的一个数小,则往前移动
        source[j+1] = source[j];
      }
    }
    source[j+1] = targetNum;
  }
  watch.stop();
  log.info("插入排序耗时: {}", watch.getTotalTimeMillis());

}

经过二分法查找优化的插入排序:

public static void sortWithBinary(int[] source) {
        StopWatch watch = new StopWatch();
        watch.start();
        // 外层循环:从第二个数开始取值往前插入
        for (int i = 1, len = source.length; i < len; i++) {
            int targetNum = source[i];
            int left = 0;
            int right = i - 1;

            // 经过二分查找之后,最终left坐标即为当前数要插入的位置
            while (left <= right) {
                int middle = (left + right) / 2;
                if (source[middle] <= targetNum) {
                    left = middle + 1;
                } else {
                    right = middle - 1;
                }
            }
            // 将left坐标之后的数都右移一位
            for (int j = i - 1; j >= left; j--) {
                source[j+1] = source[j];
            }

            source[left] = targetNum;

        }
        watch.stop();
        log.info("二分法优化耗时: {}", watch.getTotalTimeMillis());
    }

耗时对比:

public static void main(String[] args) {
  int[] source = new int[20000];
  for (int i = 0; i < 20000; i++) {
    source[i] = RandomUtil.randomInt(20000);
  }
  int[] clone = ArrayUtil.clone(source);
  sort(source);
  sortWithBinary(clone);
}

algorithms.sort.InsertionSort - 插入排序耗时: 233
algorithms.sort.InsertionSort - 二分法优化耗时: 94

以2w个数字排序测试,二分法优化确实比原始插入排序快不少;

对原始插入排序进行如下优化:

public static void sort(int[] source) {
  StopWatch watch = new StopWatch();
  watch.start();
  // 外层循环:从第二个数开始取值往前插入
  for (int i = 1, len = source.length; i < len; i++) {
    int targetNum = source[i];

    // 内层循环:将当前数与前面排好序的数列进行比较
    int j;
    for (j = i - 1; j >= 0; j--) {
      if (source[j] > targetNum) {
        // 当插入的数比前面的一个数小,则往前移动
        source[j+1] = source[j];
      } else {
        // 对比原始插入排序, 增加此else分支
        // 如果前面一位数已经比当前插入的数小了,则不用继续往前面循环比较了
        break;
      }
    }
    source[j+1] = targetNum;
  }
  watch.stop();
  log.info("插入排序耗时: {}", watch.getTotalTimeMillis());

}

耗时比较:

algorithms.sort.InsertionSort - 插入排序耗时: 56
algorithms.sort.InsertionSort - 二分法优化耗时: 104

若有错误,望指出交流。

posted @ 2021-09-08 23:24  星空赶路人  阅读(327)  评论(1)    收藏  举报