基础的排序算法(选择,插入)

226 阅读2分钟

选择排序法

  • 每轮循环都找出最小的那一个。
  • 可以进行原地排序。
  • 内存循环找出最小的那个。
  • 外层循环控制初始查找的值。 image.png

算法实现

  • 找出循环不变量。arr[0...i)是有序的,arr[i...n)是无序的。
  • 泛型必须实现Comparable接口。所以自定义类必须实现Comparable接口。并重写compareTo方法。
  • swap函数是交换数组中的元素,要传入数组。
  • 内层循环是比较当前位置的值和minIndex位置的值的大小关系,不是i位置的大小关系。
// 泛型必须实现Comparable接口。
        public static <E extends Comparable<E>> E[] sort(E[] arr) {
        // arr[0...i)是有序的,arr[i...n)是无序的。
        for(int i = 0; i < arr.length; i++) {
//            假设每次循环的第一个元素为最小
            int minIndex = i;
            for(int j = i + 1; j < arr.length; j++) {
                if(arr[minIndex].compareTo(arr[j]) >= 0) {
                    minIndex = j;
                }
            }
//            交换查找到的最小元素和开始元素的位置
            swap(arr, i, minIndex);
        }
        return arr;
    }
    
    // 这个方法是交换数组中两个元素的位置,而非两个元素值,那么会出错。
    private static <E extends Comparable<E>> void swap(E[] arr, int i, int minIndex) {
        E t = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = t;
    }

复杂度分析

image.png 测试结果

  • 当测试数组长度增大10倍,那么时间复杂度是大约100倍。 image.png

插入排序法

  • 每轮循环都和当前外层循环下标之前的元素比较。
  • 可以进行原地排序。
  • 内存循环排序arr[0, i)之间的数字。
  • 外层循环控制比较范围的最大下标。

算法实现

 public static <E extends Comparable<E>> E[] sort(E[] arr) {
        for(int i = 0; i < arr.length; i++) {
//            循环不变量,arr[0, i)已排好序, arr[i, n)未排序。
            for(int j = i; j > 0; j--) {
            // 和j < i 之前的元素都比较一下。直到遇到前面最小的一个
                if(arr[j].compareTo(arr[j - 1]) <= 0) {
                    swap(arr, j, j - 1);
                }else {
                    break;
                }
            }

        }
        return arr;
    }
    private static <E> void swap(E[] arr, int i, int minIndex) {
        E t = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = t;
    }

复杂度分析

image.png

算法小优化

我们知道上面的算法是和前一个元素比较,如果符合条件就换位置。但是我们其实没有必要每次比较都换位置。直接平移满足条件的元素即可。先保存当前比较的元素。找到合适的位置,直接将其赋值即可。

   public static <E extends Comparable<E>> E[] sort(E[] arr) {
        for(int i = 0; i < arr.length; i++) {
//            循环不变量,arr[0, i)已排好序, arr[i, n)未排序。
            E current = arr[i];
            int j;
            for(j = i; j - 1 >= 0; j--) {
                if(current.compareTo(arr[j - 1]) < 0) {
                    arr[j] = arr[j - 1];
                }else {
                    break;
                }
            }
//           在外层循环确定位置。
            arr[j] = current;
        }
        return arr;
    }

这个优化,并不能降低时间复杂度。 image.png

一个重要的特征

如果数组是有序的,那么内层循环将指示比较一下前面一个元素而已,然后就会结束。 image.png

对比插入和选择排序算法

image.png

以上测试的工具类

  • ArrayGenerator是提供生成有序和无序数组的函数。
public class ArrayGenerator {
//    生成一个有序数组,指定长度
    public static Integer[] generateOrderedArray(int n) {
        Integer[] arr = new Integer[n];
        for(int i = 0; i < n; i++) {
            arr[i] = i;
        }
        return arr;
    }
//    生成一个无序数组,指定其长度
    public static Integer[] generateRandomArray(int n, int bound) {
        Integer[] arr = new Integer[n];
        Random random = new Random();
        for(int i = 0; i < n; i++) {
            arr[i] = random.nextInt(bound);
        }
        return arr;
    }
}
  • SortingHelper为了测试算法所需时间。
public class SortingHelper {
//    判断数组是否有序
    public static <E extends Comparable<E>> boolean isSorted(E[] arr) {
        for(int i = 1; i < arr.length; i++) {
            if(arr[i - 1].compareTo(arr[i]) > 0) {
                return false;
            }
        }
        return true;
    }
//    测试各种排序组合时间复杂度
    public static <E extends  Comparable<E>> void sortTest(String className, E[] arr)  {
        try {
//            Class aClass = Class.forName(className);
//            System.out.println(aClass);
//            Method sort = aClass.getDeclaredMethod("sort");
//            sort.setAccessible(true);
            long start = System.nanoTime();
            if(className.equals("SelectSort")) {
                SelectSort.sort(arr);
            }else if(className.equals("InsertSort")) {
                InsertSort.sort(arr);
            }else if(className.equals("InsertSort1")) {
                InsertSort.sort1(arr);
            };
//            sort.invoke(aClass, arr);
            long end = System.nanoTime();
            double time = (end - start) / 1000000000.0;
            if(!SortingHelper.isSorted(arr)) {
                throw new RuntimeException(className + "failed");
            }
            System.out.println(String.format("%s, n=%d : %fs", className, arr.length, time));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

xdm,加油,又是美好的一天。