package com.acker.android;

import java.util.Arrays;

/*各种排序算法,默认从小到大排序*/
public class Sort {
    public static void main(String[] args) {
        int[] test = {12, 3, 6, 19, 11, 17, 23, 16, 25, 2, 0, 1, 45, 33, 7, 10};
        System.out.println("原始:" + Arrays.toString(test));
        bubbleSort(test);
        System.out.println("冒泡:" + Arrays.toString(test));
        selectSort(test);
        System.out.println("选择:" + Arrays.toString(test));
        insertSort(test);
        System.out.println("插入:" + Arrays.toString(test));
        shellSort(test);
        System.out.println("希尔:" + Arrays.toString(test));
        quickSort(test, 0, test.length - 1);
        System.out.println("快速:" + Arrays.toString(test));
        mergeSort(test, 0, test.length - 1);
        System.out.println("归并:" + Arrays.toString(test));
    }

    private static void swap(int[] source, int i, int j) {
        int temp = source[i];
        source[i] = source[j];
        source[j] = temp;
    }

    // 冒泡排序:让更大的数一直往上漂,漂到数组尾部,成为已排序部分
    // 稳定,假如一次扫描过程中没有交换就认为排序完成,则最好时间复杂度O(n),否则最好时间复杂度O(n^2),
    // 最坏时间复杂度O(n^2),平均时间复杂度O(n^2),空间复杂度O(1)
    public static void bubbleSort(int[] source) {
        int i, j;
        int n = source.length;
        for (i = 0; i < n; i++) {
            for (j = 0; j < n - i - 1; j++) {
                if (source[j] > source[j + 1]) {
                    swap(source, j, j + 1);
                }
            }
        }
    }

    // 选择排序:每次在未排序的部分中选择一个最小的,放到已排序部分的末尾
    // 不稳定(选择完成,交换时可能造成顺序改变),时间复杂度都是O(n^2),空间复杂度O(1)
    public static void selectSort(int[] source) {
        int i, j, min;
        int n = source.length;
        for (i = 0; i < n - 1; i++) {
            min = i;
            for (j = i + 1; j < n; j++) {
                if (source[min] > source[j]) {
                    min = j;
                }
            }
            if (min != i) {
                swap(source, i, min);
            }
        }
    }

    // 插入排序:每次从未排序区取第一个数据,然后往前插入到排序区的合适位置
    // 稳定,最好时间复杂度O(n),最坏时间复杂度O(n^2),平均时间复杂度O(n^2),空间复杂度O(1)
    public static void insertSort(int[] source) {
        int i, j, temp;
        int n = source.length;
        for (i = 1; i < n; i++) {
            temp = source[i];
            j = i - 1;
            while (j >= 0 && temp < source[j]) {
                source[j + 1] = source[j];
                j--;
            }
            source[j + 1] = temp;
        }
    }

    // Shell排序:改进的插入排序方法,对插入排序再加一个步长的循环就是希尔排序,适用于n较大时
    // 步长序列应该1.保证最终步长为1。2.尽量避免序列中的值互为倍数(尤其是相邻的值)
    // 利用了:1.n值较小时,直接插入排序的最好最坏时间复杂度相差不大。2.当数据基本有序时,直接插入排序的时间复杂度较小
    // 不稳定,时间复杂度取决于步长序列,空间复杂度O(1)
    public static void shellSort(int[] source) {
        int[] stepSeq = {5, 3, 2, 1};
        int i, j, k, temp, step;
        int n = source.length;
        for (k = 0; k < stepSeq.length; k++) {
            step = stepSeq[k];
            for (i = step; i < n; i++) {
                temp = source[i];
                j = i - step;
                while (j >= 0 && temp < source[j]) {
                    source[j + step] = source[j];
                    j = j - step;
                }
                source[j + step] = temp;
            }
        }
    }

    // 快速排序:分治法递归求解
    // 每次要找到一个中间位置,使得该点左边所有数值均比其小,右边均比其大,然后左右区间重复上述过程,直到所有子集只剩下一个元素为止
    // 不稳定,最坏时间复杂度O(n^2)(发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候)
    // 最好时间复杂度O(nlogn)(每次区间都是等分),平均时间复杂度O(nlogn)
    // 空间复杂度,主要是由于递归造成了栈空间的使用,一般而言,最好和平均为O(logn),最差为O(n)
    public static void quickSort(int[] source, int low, int high) {
        int middle;
        if (low < high) {
            middle = partition(source, low, high);
            quickSort(source, low, middle - 1);
            quickSort(source, middle + 1, high);
        }
    }

    // 快速排序划分算法:区间第一个记录作为基准,从区间两端交替向中间扫描,直至i=j为止
    public static int partition(int[] source, int i, int j) {
        int point = source[i];
        while (i < j) {
            while (i < j && source[j] >= point) {
                j--;
            }
            if (i < j) {
                swap(source, i++, j);
            }
            while (i < j && source[i] <= point) {
                i++;
            }
            if (i < j) {
                swap(source, i, j--);
            }
        }
        return i;
    }

    // 归并排序:分治法递归求解,分为分割和合并两个部分
    // 将序列递归分割(二分或四分或。。)直至每个子序列只有一个值,然后相邻的子序列再递归合并,
    // 在合并的过程中完成对每个子序列的排序,即排好序的相邻子序列再次进行合并排序
    // 稳定,最好,最坏,平均时间复杂度都是O(nlogn)
    // 空间复杂度:使用了额外的空间,O(n)
    public static void mergeSort(int[] source, int low, int high) {
        if (low < high) {
            int mid = (high + low) / 2;
            // 两路归并,合并一次;如果是多路归并,同样是按照分割为几组,便合并几减一次。
            mergeSort(source, low, mid);
            mergeSort(source, mid + 1, high);
            merge(source, low, mid, mid + 1, high);
        }
    }

    public static void merge(int[] source, int lowA, int highA, int lowB,
                             int highB) {
        int i = lowA;
        int j = lowB;
        // 需要一个数组来存放merge结果
        int k = 0;
        int[] temp = new int[highB - lowA + 1];
        for (; i <= highA; i++) {
            for (; j <= highB; j++) {
                if (source[j] < source[i]) {
                    temp[k++] = source[j];
                } else {
                    break;
                }
            }
            temp[k++] = source[i];
        }
        // 只复制k个值,若第二段存在比第一段所有数都大的数,要将其及其后面部分在source中位置不变
        System.arraycopy(temp, 0, source, lowA, k);
    }
}