• 排序


    学习自:算法-排序

    约定

    待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。

    使用辅助函数 less() 和 swap() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。

    排序算法的成本模型是比较和交换的次数。

    package allSort;
    
    public abstract class Sort<T extends Comparable<T>> {
    
        public abstract void sort(T[] nums);
    
        protected boolean less(T v, T w) {
            return v.compareTo(w) < 0;
        }
    
        protected void swap(T[] a, int i, int j) {
            T t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }

    选择排序

    从数组中选择最小的元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作。直至将真个数组排序。

    package allSort;
    
    public class Selection<T extends Comparable<T>> extends Sort<T> {
    
        @Override
        public void sort(T[] nums) {
            int l = nums.length;
            for (int i = 0; i < l - 1; i++) {
                int min = i;
                for (int j = i + 1; j < l; j++) {
                    if (less(nums[j], nums[min])) {
                        min = j;
                    }
                }
                swap(nums, i, min);
            }
        }
    }

    冒泡排序

    从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。

    在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。

    package allSort;
    
    public class Bubble<T extends Comparable<T>> extends Sort<T> {
    
        @Override
        public void sort(T[] nums) {
            boolean flag = true;
            int l = nums.length;
            for (int i = 0; i < l - 1 && flag; i++) {
                flag = false;
                for (int j = 0; j < l - 1; j++) {
                    if (less(nums[j + 1], nums[j])) {
                        flag = true;
                        swap(nums, j + 1, j);
                    }
                }
            }
        }
    }

    插入排序

    每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序。

    对于数组 {3, 5, 2, 4, 1},它具有以下逆序:(3, 2), (3, 1), (5, 2), (5, 4), (5, 1), (2, 1), (4, 1),插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

    插入排序的时间复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么逆序较少,需要的交换次数也就较少,时间复杂度较低。

    package allSort;
    
    public class Insertion<T extends Comparable<T>> extends Sort<T> {
        @Override
        public void sort(T[] nums) {
            int l = nums.length;
            for (int i = 1; i < l; i++) {
                for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) {
                    swap(nums, j, j - 1);
                }
            }
        }
    }

    希尔排序

    对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。希尔排序的出现就是为了解决插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。

    希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的(就用增量来将数组进行分隔,直到增量为1。底层干的还是插入排序干的活~)。

    (注意增量h的确定)

    package allSort;
    
    public class Shell<T extends Comparable<T>> extends Sort<T> {
        @Override
        public void sort(T[] nums) {
            int N = nums.length;
            int h = 1;
            while (h < N / 3) {
                h = 3 * h + 1;
            }
    
            while (h >= 1) {
                for (int i = h; i < N; i++) {
                    for (int j = i; j >= h && less(nums[j], nums[j - h]); j -= h) {
                        swap(nums, j, j - h);
                    }
                }
                h = h / 3;
            }
    
        }
    }

    归并排序

    归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。采用递归。

    归并方法将数组中两个已经排序的部分归并成一个

    package allSort;
    
    public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> {
        protected T[] aux;
    
        /**
         * 合并两个有序数组
         * @param nums  有序数组
         * @param l     起始下标
         * @param m     分割下标
         * @param h     最后的下标
         */
        protected void merge(T[] nums, int l, int m, int h) {
            int i = l;
            int j = m + 1;
    
            for (int k = l; k <= h; k++) {
                aux[k] = nums[k];
            }
            for (int k = l; k <= h; k++) {
                if (i > m) {
                    nums[k] = aux[j++];
                } else if (j > h) {
                    nums[k] = aux[i++];
                } else if (aux[i].compareTo(aux[j]) <= 0) {
                    nums[k] = aux[i++];
                } else {
                    nums[k] = aux[j++];
                }
            }
        }
    }

    自顶向下归并排序:

    将一个大数组分成两个小数组去求解。

    因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN)。

    package allSort;
    
    // 自顶向下归并排序
    public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {
        @Override
        public void sort(T[] nums) {
            aux = (T[]) new Comparable[nums.length];
            sort(nums, 0, nums.length - 1);
        }
    
        private void sort(T[] nums, int l, int h) {
            if (h <= l) {
                return;
            }
            int mid = l + (h - l) / 2;
            sort(nums, l, mid);
            sort(nums,mid + 1, h);
            merge(nums, l, mid, h);
        }
    
    }

    自底向上归并排序:

    快速排序

    学习自:排序-快速排序

    快排运用了二分的思想,首先选择一个基准,定义左右两端指针,先从左到右进行扫描直到,R[hi] < temp,将R[hi]移动至lo所在位置 [公式] 从右往左进行扫描,直到R[lo] > temp,将R[lo]移动到hi所在位置上,左右端指针在排序过程中从数组的两端往中间进行靠近,直到hi == lo。而快速排序则要进行多次快排过程,直到划分的区间最后长度仅为1。

    package allSort;
    
    public class QuickSort<T extends Comparable<T>> extends Sort<T>{
        @Override
        public void sort(T[] nums) {
            int i = 0, j = nums.length-1;
            QuickSort1(nums, i, j);
        }
    
        void QuickSort1(T[] nums, int l, int h){
            int i = l, j = h;
            T temp;
            if(i<j){
                temp=nums[i];
                while(i!=j){
                    while (j>i && less(temp, nums[j])){
                        j--;
                    }
                    nums[i] = nums[j];
                    while (i<j && less(nums[i], temp)){
                        i++;
                    }
                    nums[j] = nums[i];
                }
                nums[i] = temp;
                QuickSort1(nums, l,i-1);
                QuickSort1(nums,i+1, h);
            }
        }
    
    }

    开头学习资源的实现(QuickSort2,思路与上面一摸一样):

    package allSort;
    
    public class QuickSort<T extends Comparable<T>> extends Sort<T>{
        @Override
        public void sort(T[] nums) {
            int i = 0, j = nums.length-1;
    //        QuickSort1(nums, i, j);
            QuickSort2(nums, i, j);
        }
    
        void QuickSort1(T[] nums, int l, int h){
            int i = l, j = h;
            T temp;
            if(i<j){
                temp=nums[i];
                while(i!=j){
                    while (j>i && less(temp, nums[j])){
                        j--;
                    }
                    nums[i] = nums[j];
                    while (i<j && less(nums[i], temp)){
                        i++;
                    }
                    nums[j] = nums[i];
                }
                nums[i] = temp;
                QuickSort1(nums, l,i-1);
                QuickSort1(nums,i+1, h);
            }
        }
    
        private void QuickSort2(T[] nums, int l, int h){
            if(h<=l){
                return;
            }
            int j = partition(nums, l, h);
            QuickSort2(nums, l, j-1);
            QuickSort2(nums, j+1, h);
        }
        private int partition(T[] nums, int l, int h){
            int i = l, j = h+1;
            T temp = nums[l];
            if(l<h){
                while(less(nums[++i], temp) && i!=h);
                while(less(temp, nums[--j]));
                if(i<=j){
                    swap(nums, i, j);
                }
            }
            swap(nums,l,j);
            return j;
        }
    
    }

    堆排序

    视频讲解

    开头学习资源讲解:

    堆中某个节点的值总是大于等于或小于等于其子节点的值,并且堆是一颗完全二叉树。

    堆可以用数组来表示,这是因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1数组索引从1开始,这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。

    上浮:在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作,把这种操作称为上浮。

    下沉:类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那个节点进行交换。

    堆排序:

          构建堆:无序数组建立堆最直接的方法是从左到右遍历数组进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。

          交换堆顶元素与最后一个元素:交换之后需要进行下沉操作维持堆的有序状态。

    package allSort;
    
    public class HeapSort<T extends Comparable<T>> extends Sort<T> {
        @Override
        public void sort(T[] nums) {
            int N = nums.length - 1;
            for (int k = N / 2; k >= 1; k--) {
                sink(nums, k, N);
            }
            while (N > 1) {
                swap(nums, 1, N--);
                sink(nums, 1, N);
            }
        }
    
        private void sink(T[] nums, int k, int N) {
            while (2 * k <= N) {
                int j = 2 * k;
                if (j < N && less(nums, j, j + 1)) {
                    j++;
                }
                if (!less(nums, k, j)) {
                    break;
                }
                swap(nums, k, j);
                k = j;
            }
        }
    
        private boolean less(T[] nums, int i, int j) {
            return nums[i].compareTo(nums[j]) < 0;
        }
    }

    测试代码:

    由于上面堆排序的下标从1开始,因此测试堆排序时需要对应处理一下数组。

    package allSort;
    
    public class Test {
        public static void main(String[] args) {
            Integer[] a = new Integer[]{12, 3, 6, 2, 7, 5};
    //        Selection s1 = new Selection();
    //        s1.sort(a);
    
    //        Bubble b1 = new Bubble();
    //        b1.sort(a);
    
    //        Insertion i1 = new Insertion();
    //        i1.sort(a);
    
    //        Shell s1 = new Shell();
    //        s1.sort(a);
    
    //        Up2DownMergeSort u1 = new Up2DownMergeSort();
    //        u1.sort(a);
    //        Down2UpMergeSort d1 = new Down2UpMergeSort();
    //        d1.sort(a);
    
    //        QuickSort q1 = new QuickSort();
    //        q1.sort(a);
    
    //        HeapSort h1 = new HeapSort();
    //        h1.sort(a);
            for (Integer s : a) {
                System.out.print(s + " ");
            }
        }
    }
  • 相关阅读:
    超级有用的15个mysqlbinlog命令
    表迁移工具的选型-复制ibd的方法-传输表空间
    误删mysql表物理文件的解决方法(不涉及恢复数据)
    美团SQL优化工具SQLAdvisor
    Linux网络状态工具ss命令使用详解
    MySQL应用架构优化-实时数据处理
    运维利器万能的 strace
    ngrep命令用法
    [Linux|DBA]运维三十六计
    sysbench使用
  • 原文地址:https://www.cnblogs.com/dong973711/p/14710779.html
Copyright © 2020-2023  润新知