import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Java常用排序算法
 * 排序法    平均时间    最小时间        最大时间    稳定度    额外空间      备注
 * 冒泡排序    O(n2)       O(n)            O(n2)     稳定        O(1)        n小时较好
 * 选择排序    O(n2)       O(n2)           O(n2)     不稳定      O(1)        n小时较好
 * 插入排序    O(n2)       O(n)            O(n2)     稳定        O(1)        大部分已排序时较好
 * 基数排序    O(logRB)    O(n)            O(logRB)  稳定        O(n)        B是真数(0-9),R是基数(个十百)
 * Shell排序   O(nlogn)    O(ns)           1<s<2     不稳定      O(1)        s是所选分组
 * 快速排序    O(nlogn)    O(n2)           O(n2)     不稳定      O(logn)     n大时较好
 * 归并排序    O(nlogn)    O(nlogn)        O(nlogn)  稳定        O(n)        要求稳定性时较好
 * 堆排序      O(nlogn)    O(nlogn)        O(nlogn)  不稳定      O(1)        n大时较好
 */
public class SortAlgorithm {

     public static void main(String args[]) throws InvocationTargetException, IllegalAccessException {
        SortAlgorithm sortAlgorithm = new SortAlgorithm();
        int[] numList;
        Class clazz = sortAlgorithm.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        long begin;
        long end;
        for (Method method : methods) {
            if (method.getName().endsWith("Sort")) {
                numList = genegrateSourceIntArray();
                Object[] param;
                if (method.getName().equalsIgnoreCase("quickSort") || method.getName().equalsIgnoreCase("mergeSort")) {
                    param = new Object[]{numList, 0, numList.length - 1};
                } else {
                    param = new Object[]{numList};
                }
                begin = System.nanoTime();
                method.invoke(sortAlgorithm, param);
                end = System.nanoTime();
                System.out.println(method.getName() + " 排序用时为:<" + (end - begin) + " ns>");
                sortAlgorithm.display(numList);
            }
        }
    }

    /**
     * 冒泡排序:
     * 将序列中所有元素两两比较,将最大的放在最后面。
     * 将剩余序列中所有元素两两比较,将最大的放在最后面。
     * 重复第二步,直到只剩下一个数
     *
     * @param numlist
     */
    void bubbleSort(int[] numlist) {
        int temp;
        for (int j = 1; j < numlist.length; j++) {
            for (int i = 0; i < numlist.length - j; i++) {
                if (numlist[i] > numlist[i + 1]) {
                    temp = numlist[i + 1];
                    numlist[i + 1] = numlist[i];
                    numlist[i] = temp;
                }
            }
        }
    }

    /**
     * 选择排序算法
     * 遍历整个序列,将最小的数放在最前面。
     * 遍历剩下的序列,将最小的数放在最前面。
     * 重复第二步,直到只剩下一个数。
     *
     * @param numlist
     */
    void selectionSort(int[] numlist) {
        int temp;
        for (int i = 0; i < numlist.length - 1; i++) {
            for (int j = i + 1; j < numlist.length; j++) {
                if (numlist[i] > numlist[j]) {
                    temp = numlist[j];
                    numlist[j] = numlist[i];
                    numlist[i] = temp;
                }
            }
        }
    }

    /**
     * 快速排序算法
     * ①. 从数列中挑出一个元素,称为”基准”(pivot)。
     * ②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
     * ③. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
     * 递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
     *
     * @param arr,start end
     */
    void quickSort(int[] arr, int start, int end) {
        if (start < end) {
            int baseNum = arr[start];//选基准值
            int midNum;//记录中间值
            int left = start;//左指针
            int right = end;//右指针
            while (left < right) {
                while ((arr[left] < baseNum) && left < end) {
                    left++;
                }
                while ((arr[right] > baseNum) && right > start) {
                    right--;
                }
                if (left <= right) {
                    midNum = arr[left];
                    arr[left] = arr[right];
                    arr[right] = midNum;
                    left++;
                    right--;
                }
            }
            if (start < right) {
                quickSort(arr, start, right);
            }
            if (end > left) {
                quickSort(arr, left, end);
            }
        }
    }

    /**
     * 插入排序算法
     * 直接插入排序(Straight Insertion Sorting)的基本思想:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止。
     * 首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。
     * 设定插入数和得到已经排好序列的最后一个数的位数。insertNum和j=i-1。
     * 从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。
     * 将当前数放置到空着的位置,即j+1。
     *
     * @param numlist
     */
    void insertSort(int[] numlist) {
        int temp, in, out;
        for (out = 1; out < numlist.length; out++) {
            temp = numlist[out];
            in = out;
            while (in > 0 && numlist[in - 1] >= temp) {
                numlist[in] = numlist[in - 1];
                --in;
            }
            numlist[in] = temp;
        }
    }

    /**
     * 希尔排序算法
     * 希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
     *
     * @param numlist
     */
    private void shellSort(int[] numlist) {
        int n = numlist.length;
        int i, j, k, gap;
        for (gap = n / 2; gap > 0; gap /= 2) {
            for (i = 0; i < gap; i++) {
                for (j = i + gap; j < n; j += gap) {
                    int temp = numlist[j];
                    for (k = j - gap; k >= 0 && numlist[k] > temp; k -= gap) {
                        numlist[k + gap] = numlist[k];
                    }
                    numlist[k + gap] = temp;
                }
            }
        }
    }

    /**
     * 堆排序算法
     * 对简单选择排序的优化。
     * 将序列构建成大顶堆。
     * 将根节点与最后一个节点交换,然后断开最后一个节点。
     * 重复第一、二步,直到所有节点断开。
     *
     * @param a
     */
    void heapSort(int[] a) {
        int len = a.length;
        //循环建堆
        for (int i = 0; i < len - 1; i++) {
            //建堆
            buildMaxHeap(a, len - 1 - i);
            //交换堆顶和最后一个元素
            swap(a, 0, len - 1 - i);
        }
    }

    //交换方法
    private void swap(int[] data, int i, int j) {
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }

    //对data数组从0到lastIndex建大顶堆
    private void buildMaxHeap(int[] data, int lastIndex) {
        //从lastIndex处节点(最后一个节点)的父节点开始
        for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
            //k保存正在判断的节点
            int k = i;
            //如果当前k节点的子节点存在
            while (k * 2 + 1 <= lastIndex) {
                //k节点的左子节点的索引
                int biggerIndex = 2 * k + 1;
                //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
                if (biggerIndex < lastIndex) {
                    //若果右子节点的值较大
                    if (data[biggerIndex] < data[biggerIndex + 1]) {
                        //biggerIndex总是记录较大子节点的索引
                        biggerIndex++;
                    }
                }
                //如果k节点的值小于其较大的子节点的值
                if (data[k] < data[biggerIndex]) {
                    //交换他们
                    swap(data, k, biggerIndex);
                    //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
                    k = biggerIndex;
                } else {
                    break;
                }
            }
        }
    }

    /**
     * 归并排序算法
     * 速度仅次于快速排序,内存少的时候使用,可以进行并行计算的时候使用。
     * 选择相邻两个数组成一个有序序列。
     * 选择相邻的两个有序序列组成一个有序序列。
     */
    void mergeSort(int[] a, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {
            // 左边
            mergeSort(a, low, mid);
            // 右边
            mergeSort(a, mid + 1, high);
            // 左右归并
            merge(a, low, mid, high);
//            System.out.println(Arrays.toString(a));
        }

    }

    void merge(int[] a, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;// 左指针
        int j = mid + 1;// 右指针
        int k = 0;
        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (a[i] < a[j]) {
                temp[k++] = a[i++];
            } else {
                temp[k++] = a[j++];
            }
        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = a[i++];
        }
        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = a[j++];
        }
        // 把新数组中的数覆盖nums数组
        for (int k2 = 0; k2 < temp.length; k2++) {
            a[k2 + low] = temp[k2];
        }
    }

    /**
     * 基数排序算法
     * 用于大量数,很长的数进行排序时。
     * 将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
     * 将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。
     *
     * @param a
     */
    void baseSort(int[] a) {
        //首先确定排序的趟数;
        int max = a[0];
        for (int i = 1; i < a.length; i++) {
            if (a[i] > max) {
                max = a[i];
            }
        }
        int time = 0;
        //判断位数;
        while (max > 0) {
            max /= 10;
            time++;
        }
        //建立10个队列;
        List<ArrayList<Integer>> queue = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            ArrayList<Integer> queue1 = new ArrayList<>();
            queue.add(queue1);
        }
        //进行time次分配和收集;
        for (int i = 0; i < time; i++) {
            //分配数组元素;
            for (int j = 0; j < a.length; j++) {
                //得到数字的第time+1位数;
                int x = a[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
                ArrayList<Integer> queue2 = queue.get(x);
                queue2.add(a[j]);
                queue.set(x, queue2);
            }
            int count = 0;//元素计数器;
            //收集队列元素;
            for (int k = 0; k < 10; k++) {
                while (queue.get(k).size() > 0) {
                    ArrayList<Integer> queue3 = queue.get(k);
                    a[count] = queue3.get(0);
                    queue3.remove(0);
                    count++;
                }
            }
        }
    }


    /**
     * 打印出排序结果
     *
     * @param num
     */
    void display(int[] num) {
        for (int i = 0; i < num.length; i++) {
            System.out.print(num[i] + " ");
        }
        System.out.println("\n===================");
    }

    /**
     * 生成随机数组
     *
     * @return
     */
    private static int[] genegrateSourceIntArray() {
        int[] numList = new int[30];
        for (int i = 0; i < numList.length; i++) {
            numList[i] = Math.abs(new Random().nextInt() + 1) % 200;
        }
        System.out.println("随即生成的数组是:");
        for (int j = 0; j < numList.length; j++) {
            System.out.print(numList[j] + " ");
        }
        System.out.println();
        return numList;
    }

}