排序算法总结

排序需要两个基本方法:判断两个元素大小,交换两个元素的位置,把这两种方法写在模板类中

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 temp = a[i];
        a[i] = a[j];
        a[j] = temp;

    }
}

1.选择排序:从数组第0位开始,逐个和后边的元素比较,如果后边的元素比较小,就把两者替换,直到遍历完数组,可以找到数组中的最小元素。再从第1位开始,重复这个过程,直到数组最后一位。时间复杂度:log(N2)

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

2.冒泡排序:数组中相邻的两个元素比较,把较大元素移到后边,数组中最大的元素就像气泡一位一位地上浮到最后边。选择排序是先找到最小值并放在前边,冒泡排序是先找到最大值并放在后边。时间复杂度:log(N2)

public class BubbleSort<T extends Comparable<T>>extends Sort<T> {
    @Override
    public void sort(T[] nums) {
        int n = nums.length;
        boolean isSorted = false; //表示一次遍历中是否发生过交换,如果没有,说明已经排好序了,没必要继续往后算,否则说明数组还没排好序
        for(int i = n-1; i>=0 && !isSorted; i--){
            isSorted = true;    
            for(int j = 0; j<i; j++){
                if(less(nums[j+1],nums[j]))
                    isSorted = false;
                    swap(nums, j, j+1);
            }
        }

    }
}

3.插入排序:可以理解为倒过来的冒泡排序,把数组分成左右两部分,左数组的最后一位与前一位比较,如果比前面小就交换位置,使最小值一位一位的被移动到前边。一轮比较完成后,左数组向右扩大一位,直到左数组大小等于原始数组。但是插入排序与冒泡排序不同的是,冒泡排序没有利用之前已经取得的成果,也就是说最大值上浮到右边之后就再也没动作,但是插入排序的左数组一直在参与排序,并且在一轮比较完成后,左数组一定是有序的,所以下一轮开始前,如果新的左数组最后一位比前一位大,说明它已经是最大值了,不需要继续比较,所以减小了时间复杂度。最坏的情况是数组是倒序的,需要 ~N2/2 比较以及 ~N2/2 次交换。最好的情况就是数组已经有序了,需要 N-1 次比较和 0 次交换。

public class Insertion<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        for (int i = 1; i < N; i++) {
            for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) {
                swap(nums, j, j - 1);
            }
        }
    }
}

4.希尔排序:插入排序的改进。排序的本质是使数组中的逆序对数目减小到0,插入排序每次交换只能使逆序对数目减少1,因为交换的两个数的位置是相邻的,希尔排序增大了要交换的两个数之间的距离,从而增加了每次减少的逆序对数目。

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; // 1, 4, 13, 40, ...
        }

        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;
        }
    }
}

5.快速排序

快速排序的思想是,把数组的第一个值放到正确的位置,使这个值左边的数字都比它小,右边的数字都比它大,因此把数组分成了两个部分,再用递归对这两个部分排序。

那么如何把第一个值放到正确的位置呢?需要用双指针 i 和 j ,i 始于待比较数字的下一个位置, j 始于数组尾部,对数组nums[] 来说,如果nums[i] > nums[0] 且 nums[j] < nums[0], 那么nums[i] 和 nums[j] 交换位置,直到i >= j。

public class QuickSort<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        shuffle(nums);        // 打乱数组,因为如果数组本来是倒序的话,时间复杂度会用O(NlogN)增加为O(N2)                   
        sort(nums, 0, nums.length - 1);
    }

    private void sort(T[] nums, int l, int h) {
        if (h <= l)
            return;
        int j = partition(nums, l, h);
        sort(nums, l, j - 1);
        sort(nums, j + 1, h);
    }

    private void shuffle(T[] nums) {
        List<Comparable> list = Arrays.asList(nums);
        Collections.shuffle(list);
        list.toArray(nums);
    }
    private int partition(T[] nums, int l, int h) {
    int i = l, j = h + 1;
    T v = nums[l];
    while (true) {
        while (less(nums[++i], v) && i != h) ;
        while (less(v, nums[--j]) && j != l) ;
        if (i >= j)
            break;
        swap(nums, i, j);
    }
    swap(nums, l, j);
    return j;
}
    
}

6.归并排序

快速排序是先排序再切分,归并排序是先切分再排序,归并的意思是,对于两个已经排序的数组,如果要把这两个数组放在一起排序,只需要从两个数组起始位置开始比较,把较小的元素放进新数组,在把对应的指针后移,重复这个过程。那么怎么得到这两个排序的数组呢?首先把数组从中间分开,得到两个数组,然后分别对它们俩排序,所以最后问题从对大数组排序变成了对小数组排序,很明显是个递归问题。

public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {
    private int[] aux; // 辅助数组
    @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);
    }
protected void merge(T[] nums, int l, int m, int h) {

        int i = l, 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++];
            }
        }
    }
}

 

posted @ 2020-07-03 13:41  嫩西瓜  阅读(159)  评论(0)    收藏  举报