一、时间复杂度为O(n^2)的排序(冒泡排序、选择排序、插入排序)

1.冒泡排序

   快速回忆过程:
   1.选择区间[0 ~ n-1],遍历,每次与下一个元素比较,若大于下一个元素,则交换。一轮遍历过后,确保当前子数组的最大元素为最后一个元素
   2.依次选择区间[0 ~ n-2]...[0 ~ 1],执行step1即可。

import java.util.*;

public class BubbleSort {
    public int[] bubbleSort(int[] A, int n) {
        // 双重循环实现
        for(int i = 0; i < n-1; i++){
            for(int j = 0; j < n-i-1; j++){
                if(A[j] > A[j+1]){
                    swap(A, j , j+1);
                }
            }
        }     
        return A;
    }

    private void swap(int[] array, int index1, int index2){
        //交换数组index1和index2位置的元素
        int temp = array[index1];
        array[index1] = array[index2];
        array[index2] = temp;
    }
}

2.选择排序

   快速回忆过程:
   1.选择区间[0 ~ n-1],遍历,每次找出该区间的最小元素放在该区间的第一个位置。一轮遍历过后,确保当前子数组的最小元素为第一个元素
   2.依次选择区间[1 ~ n-1]...[n-2 ~ n-1],执行step1即可。

import java.util.*;

public class SelectionSort {
    public int[] selectionSort(int[] A, int n) {
        // write code here
        int currentMin;        //当前遍历区间的最小值
        int currentMinIndex;    //当前遍历区间最小值的索引

        for(int i = 0; i < n-1; i++){
            //每次遍历区间前,将第一个元素暂视为最小元素
            currentMin = A[i];
            currentMinIndex = i;

            for(int j = i+1; j < n; j++){
                if(currentMin > A[j]){
                    currentMin = A[j];
                    currentMinIndex = j;
                }
            }

            swap(A, i, currentMinIndex);    //区间遍历完成后,交换最小元素和区间的第一个元素
        }

        return A;
    }

    private void swap(int[] A, int index1, int index2){
        int temp = A[index1];
        A[index1] = A[index2];
        A[index2] = temp;
    }
}

3.插入排序

   快速回忆过程:
   1.选择元素array[1],与array[0]比较,若array[1]更小,则与array[0]交换。
   2.一般过程:选择元素array[n],与array[n-1]比较,若array[n]更小,则交换,然后让array[n-1]再与array[n-2]比较,依次类推,直到遍历到array[0]或比较中出现大于关系array[i]>array[i-1]。(也就是说,一次遍历后,子区间一定是递增的

import java.util.*;

public class InsertionSort {
    public int[] insertionSort(int[] A, int n) {
        // write code here
        int insertIndex;    //最终插入的索引
        int temp;            //暂存当前区间最后一个元素,最终插入到insertIndex的位置。

        for(int i = 1; i < n; i++){
            //初始化
            insertIndex = i;
            temp = A[i];

            for(int j = i-1; j>=0; j--){
                if(temp < A[j]){
                    A[j+1] = A[j];        //向后移动元素
                    insertIndex = j;    //记录插入位置
                }
                else{
                    break;
                }
            } 
            A[insertIndex] = temp;    //插入
        }

        return A;
    }
}

二、时间复杂度为O(n*log n)的排序(归并排序,快速排序,堆排序、希尔排序)

1.归并排序

   快速回忆过程:
   1.将每个元素变为单独的有序区间,然后依次两两归并成新的有序区间即可。

import java.util.*;

public class MergeSort {
    public int[] mergeSort(int[] A, int n) {
        // 元素个数小于等于1,返回
        if(n <= 1 || A == null)
            return A;

        int left;    //用于记录当前归并集的第一个数组的第一个元素的位置
        int mid;    //用于记录当前归并集的第二个数组的第一个元素的位置
        int right;    //用于记录当前归并集的第二个数组的最后一个元素的位置
        for(int interval = 1; interval < n; interval = interval*2){      //每轮归并后,归并集大小提升2倍。
            //根据当前归并集大小初始化
            left = 0; 
            mid = interval;
            right = 2*interval-1;
            while(true){
                /*这里一共有三种情况:
                    【1】mid>n-1,说明数组剩余元素数只够一个归并集,那么不用归并,并跳出循环,开始下一轮归并。
                    【2】mid<=n-1 && right>=n-1,说明数组剩余元素数不能填满第二个归并集,那么进行最后一次归并,并跳出循环,开始下一轮归并。
                    【3】mid<=n-1 && right<n-1,说明数组剩余元素数完全够2个归并集,那么归并,并进行本轮下一次归并。
                */
                if(mid > n-1)    break;
                if(mid <= n-1 && right >= n-1){
                    merge(A, left, mid, n-1);
                    break;
                }
                else{
                    merge(A, left, mid, right);
                    left = left + 2*interval;
                    mid = mid + 2*interval;
                    right = right + 2*interval;
                }

            }
        }

        return A;
    }

    private void merge(int[] A, int left, int mid, int right){
        //归并函数,相当于已知两个有序数组求它们合并后的有序数组。
        int[] temp = new int[right - left + 1];
        int i = left;
        int j = mid;
        int index = 0;

        while(i <= mid-1 && j <= right){
            if(A[i] <= A[j])
                temp[index++] = A[i++];
            else
                temp[index++] = A[j++];
        }

        while(i <= mid-1)
            temp[index++] = A[i++];

        while(j <= right)
            temp[index++] = A[j++];

        index = left;
        for(int element : temp)
            A[index++] = element;
    }

}

2.快速排序

   快速回忆过程:
   1.随机选择一个元素,进行依次Patition过程,使得它左边的元素小于它,右边的元素大于它。
   2.对左右区间递归调用快速排序即可。

   Patition过程:
   【1】先将划分元素与最后一个元素交换,并定义一个小于等于区间,表示小于等于划分值的区间,初始时长度为0
   【2】从左到右遍历所有元素,若大于划分值,则继续遍历,若小于等于划分值,则将小于等于区间长度位置元素与当前位置元素交换,并且小于等于区间向右扩一个位置(例如小于等于区间为空,并且array[5] < 划分值,则array[5]与array[0]交换。小于等于区间变为{array[0]})
   【3】遍历完成后,将划分值与小于等于区间的下一个元素交换。

import java.util.*;

public class QuickSort {
    public int[] quickSort(int[] A, int n) {
        // write code here
        if(A == null || n < 2)
            return A;

        process(A, 0, n-1);    //开始排序

        return A;
    }

    private void process(int[] A, int left, int right){
        if(left >= right)
            return;
        int randomIndex = left + (int)(Math.random()*(right - left +1));        //随机划分位置
        int mid = patition(A, randomIndex, left, right);        //进行一次划分过程,并生成最终划分值的位置
        process(A, left, mid-1);                //向左递归调用
        process(A, mid+1, right);                //向右递归调用
    }

    private int patition(int[] A, int randomIndex, int left, int right){
        swap(A, randomIndex, right);        //先交换划分值和最后一个元素
        int lessEqualSection = left;        //指定小于等于区间的下一个位置

        for(int i = left; i < right; i++){
            if(A[i] <= A[right]){        //元素小于划分值,则小于等于区间下一个位置元素与当前元素交换,小于等于区间增加1
                swap(A, lessEqualSection++, i);
            }
        }

        swap(A, lessEqualSection, right);        //最终的小于等于区间下一个元素与划分值交换

        return lessEqualSection;        //返回小于等于区间的下一个元素的位置,即划分值的位置

    }

    private void swap(int[] A, int index1, int index2){
        int temp = A[index1];
        A[index1] = A[index2];
        A[index2] = temp;
    }
}

3.堆排序

   快速回忆过程:
   1.构建大根堆。
   2.将大根堆的堆顶元素与最后一个元素交换并将最后一个元素移出堆,作为结果数组的最后一个元素。
   3.重新调整堆为大根堆
   4.重复step1-3,直到堆中只剩一个元素为止

   点击这里查看堆建立的解析

import java.util.*;

public class HeapSort {
    public int[] heapSort(int[] A, int n) {
        // write code here
        if(A == null)
            return A;

        for(int i = n/2-1; i >= 0; i--){        //建立大顶堆
            adjustHeap(A, i, n);
        }

        for(int i = n-1; i > 0; i--){        //堆排序过程
            swap(A, 0, i);        //交换首尾元素
            adjustHeap(A, 0, i);        //重新调整堆为大顶堆
        }

        return A;
    }

    private void adjustHeap(int[] A, int current, int length){
        int temp = A[current];        //记录堆顶元素最后赋值,可以减少交换次数
        for(int i = 2*current+1; i < length; i = i*2+1){        //从当前节点开始向下调整
            if(i+1 < length && A[i+1] > A[i]){        //右孩子大于左孩子,则改为比较右孩子
                i++;
            }

            if(A[i] > temp){        //如果父节点小于子节点,则交换(这里赋值,最后再交换,因此每次用temp比较)
                A[current] = A[i];
                current = i;
            }
            else{            //如果父节点大于子节点,则不用调整,退出循环
                break;
            }
        }

        A[current] = temp;        
    }

    private void swap(int A[], int index1, int index2){
        int temp = A[index1];
        A[index1] = A[index2];
        A[index2] = temp;

    }
}

4.希尔排序

   快速回忆过程:
   希尔排序相当于插入排序的一般化,插入排序的步长为1,而希尔排序的步长逐渐由大到小最后变为1。
   希尔排序的时间复杂度是不确定的,当选择合适的步长时,最优可以达到O(n*log n),最差则为O(n^2)。

import java.util.*;

public class ShellSort {
    public int[] shellSort(int[] A, int n) {
        // write code here
        if(A == null || n < 2)
            return A;

        int temp;
        int index;

        for(int feet = n/2; feet > 0; feet/=2){        //初始步长为长度的一般,步长不断除以2

            for(int i = feet; i < n; i++){        //执行一次插入排序
                temp = A[i];                        //保存待插入的元素,可以避免交换
                index = i;                            //插入位置
                while(index - feet >= 0){
                    if(temp < A[index - feet]){
                        A[index] = A[index - feet];
                        index -= feet;
                    }
                    else
                        break;
                }

                A[index] = temp;

            }
        }

        return A;
    }
}