排序

学习自:算法-排序

约定

待排序的元素需要实现 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 + " ");
        }
    }
}

 

posted @ 2021-04-29 15:23  +D  阅读(50)  评论(0编辑  收藏  举报