排序算法

插入排序

  • 思想: 利用遍历数组,将第 i 位的值与前面一个数比较,如果 arr[i] 小于前面一个数则替换 arr[i]和arr[j] 的位置并重复再往前面比较直到大于或者等于前面的数

    class InsertSort{
        private static void insertSort(int[] arr){
            // 遍历数组除0位以外所有元素
            for (int i = 1; i < arr.length; i++){
                // 对于第 i 个元素将其与前面的元素比较,如果小于则替换位置直到不小于
                for (int j = i; j > 0; j--){
                if (arr[j] < arr[j - 1]){
                        // 如果小于则交换位置,并且继续对比
                        swap(arr, j);
                    } else {
                        break;
                    }
                }
            }
        }
        
        private static void swap(int[] arr, int j){
            int tmp = arr[j];
            arr[j] = arr[j - 1];
            arr[j - 1] = tmp;
        }
    }
    
    • 时间复杂度 O(n2), 空间复杂度O(1)

希尔排序

  • 思想: 由于插入排序直接对源数据进行操作,对于数据本身非常乱的话,位置靠后的较小数据需要进行很多次对比才能移动到前面,希尔排序的核心是利用较大的数据跨度比较,将较小的数据经过较少的交换次数移动到前面

    class shellSort{
        public static void main(String[] args) {
            int[] arr = new int[]{11, 15, 1, 12, 2, 13, 3, 14, 4};
            for (int gapNum = 4; gapNum > 0; gapNum >>= 1){
                shellSort(arr, gapNum);
            }
        }
    
        private static void shellSort(int[] arr, int gapNum){
            // 此处就是插入排序的做法
            for (int i = gapNum; i < arr.length; i += gapNum){
                for (int j = i; j > 0; j -= gapNum){
                    if (arr[j] < arr[j - gapNum]){
                        swap(arr, j, gapNum);
                    } else {
                        break;
                    }
                }
            }
            System.out.println(Arrays.toString(arr));
        }
    
        private static void swap(int[] arr, int j, int gapNum){
            int tmp = arr[j];
            arr[j] = arr[j - gapNum];
            arr[j - gapNum] = tmp;
        }
    }
    
    • 优化点: 上面例子是 4 2 1 跨度的,最有序列应该是 knuth 序列 3n+1
    • 时间复杂度O(n1.3),空间复杂度O(1)

归并排序

单次归并排序

class Main{
    public static void main(String[] args){
        // 新建一个无序的 但是左右两部分分别有序的数组
        int[] arr = new int[]{1, 3, 5, 7, 9, 2, 4, 6, 8};
        // 调用merge方法。第一个参数是需要排序的数组,第二个参数是需要排序的起始位置,第三个参数是右边有序部分的第一个数的位置,第四个参数是需要排序部分的结束位置
        merge(arr, 0, 5, 8);
    }
    
    static void merge(int[] arr, int left, int arrow, int right){
        // 新建一个临时数组,接收需要排序的部分排序好的顺序
        int[] tmpArray = new int[right - left + 1];
        // 创建双指针,一个指向左边有序部分的第一个位置(left), 另一个指向右边有序部分的第一个位置(arrow)
        int i = left;
        int j = arrow;
        // 创建一个变量用于指向临时数据未填充部分的第一个位置
        int k = 0;
        // 对比arr[i]与arr[j]的值,较小的依次放在临时数组k的位置
        while (i < arrow && j <= right){
            // 因为i只需要遍历右边有序前面的所有数,所以 < arrow
            // j需要遍历arrow到right里面所有的数据,所以 <= right
            if (arr[i] <= arr[j]) {
                tmpArray[k++] = arr[i++];
            } else {
                tmpArray[k++] = arr[j++];
            }
        }
        // 一顿操作猛如虎,最终会以i到达arrow值结束或j到达right+1直接,且只有一个
        // 所以需要判断一下哪一个没到达
        while (i < arrow) tmpArray[k++] = arr[i++];
        while (j <= right) tmpArray[k++] = arr[j++];
        
        // 最终将tmp位置的值全部放入arr[left ... right]中
        for (int m = 0; m < tmpArray.length; m++){
            arr[left + m] = tmpArray[m];
        }
    }
}

迭代排序

  • 二分法,取一半的一半…进行比较,直接取到1最终实现归并排序
class Main2 {
    public static void main(String[] args) {
        // 新建一个无序的 但是左右两部分分别有序的数组
        int[] arr = new int[]{1, 3, 5, 7, 9, 2, 4, 6, 8};
        // 调用merge方法。第一个参数是需要排序的数组,第二个参数是需要排序的起始位置,第三个参数是右边有序部分的第一个数的位置,第四个参数是需要排序部分的结束位置
        sort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }

    // 需要的是一个数组及排序的区间
    static void sort(int[] arr, int left, int right) {
        // 如果取到1个元素的数组,那么就不进行排序
        if (left == right) return;
        // 求出此区间的中值(防止溢出所以不是用(left + right)/2)
        int mid = left + (right - left) / 2;
        // 对左边进行排序(主要排序的对象还是2个元素的数组)
        sort(arr, left, mid);
        // 对右边进行排序
        sort(arr, mid + 1, right);
        //合并两边
        merge(arr, left, mid + 1, right);
    }

    static void merge(int[] arr, int left, int arrow, int right) {
        // 新建一个临时数组,接收需要排序的部分排序好的顺序
        int[] tmpArray = new int[right - left + 1];
        // 创建双指针,一个指向左边有序部分的第一个位置(left), 另一个指向右边有序部分的第一个位置(arrow)
        int i = left;
        int j = arrow;
        // 创建一个变量用于指向临时数据未填充部分的第一个位置
        int k = 0;
        // 对比arr[i]与arr[j]的值,较小的依次放在临时数组k的位置
        while (i < arrow && j <= right) {
            // 因为i只需要遍历右边有序前面的所有数,所以 < arrow
            // j需要遍历arrow到right里面所有的数据,所以 <= right
            if (arr[i] <= arr[j]) {
                tmpArray[k++] = arr[i++];
            } else {
                tmpArray[k++] = arr[j++];
            }
        }
        // 一顿操作猛如虎,最终会以i到达arrow值结束或j到达right+1直接,且只有一个
        // 所以需要判断一下哪一个没到达
        while (i < arrow) tmpArray[k++] = arr[i++];
        while (j <= right) tmpArray[k++] = arr[j++];

        // 最终将tmp位置的值全部放入arr[left ... right]中
        for (int m = 0; m < tmpArray.length; m++) {
            arr[left + m] = tmpArray[m];
        }
    }
}
  • 时间复杂度O(nlogn),空间复杂度O(n)

快速排序

快排核心

public class QuickSort2 {

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 2, 4, 6, 8};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }

    static void sort(int[] arr){
        // 确定一个基准,这个基准决定了前面部分的元素都小于等于这个基准,后面部分的元素都大于这个基准
        int pivot = arr[arr.length - 1];
        // 左指针指向数组的第一个位置
        int left = 0;
        // 右指针指向数组基准前面一个位置
        int right = arr.length - 2;

        // 循环开始,左指针左移到第一个大于基准的元素,右指针右移到第一个小于等于基准的元素
        while (left <= right){
            // 这里是 <= 会导致移动过程中 左指针超过右指针
            while (left <= right && arr[left] <= pivot) left++;
            while (left <= right && arr[right] > pivot) right--;

            if (left < right) swap(arr, left, right);
        }

        // 最后把轴与left值交换
        // 因为此时必然有left指向大数区第一个数组,right指向小数区最后一个数据
        swap(arr, left, arr.length - 1);
    }

    static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

迭代算法

public class QuickSort2 {

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 2, 4, 6, 8};
        midSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }

    static void midSort(int[] arr, int left, int right){
        // 首先判断是否只有一个元素,如果是,那就不用判断了
        if (left >= right) return;
        // 获取一次排序的基准轴
        int mid = sort(arr, left, right);
        // 对左半部分再次排序
        midSort(arr, left, mid - 1);
        // 对右半部分再次排序
        midSort(arr, mid + 1, right);
    }

    static int sort(int[] arr, int leftBonud, int rightBound){
        // 确定一个基准,这个基准决定了前面部分的元素都小于等于这个基准,后面部分的元素都大于这个基准
        int pivot = arr[rightBound];
        // 左指针指向需要排序的第一个位置
        int left = leftBonud;
        // 右指针指向基准前面一个位置
        int right = rightBound - 1;

        // 循环开始,左指针左移到第一个大于基准的元素,右指针右移到第一个小于等于基准的元素
        while (left <= right){
            // 这里是 <= 会导致移动过程中 左指针超过右指针
            while (left <= right && arr[left] <= pivot) left++;
            while (left <= right && arr[right] > pivot) right--;

            if (left < right) swap(arr, left, right);
        }

        // 最后把轴与left值交换
        // 因为此时必然有left指向大数区第一个数组,right指向小数区最后一个数据
        swap(arr, left, rightBound);

        return left;
    }

    static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

  • 时间复杂度 O(nlogn) 空间复杂度O(n)

小结: 快排和归并,两者区别在于。在迭代方式上,归并需要前一步处理好的数据,而快排是处理好数据给下一步

归并的核心是将排序好的数据按顺序放在一起,而快排只要将轴两边的数据放在前后就好了

原文地址:https://www.cnblogs.com/rainful/p/14854068.html