常用排序演算法

阿狸男朋友發表於2020-11-18

常見排序演算法:

 注:快排的空間複雜度為log2N

1.選擇排序

    //選擇排序
    public void sort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[min]) {
                    min = j;
                }
            }
            swap(arr, i, min);
        }
    }
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

2.氣泡排序

    //氣泡排序
    public void sort(int[] arr) {
        for (int i = arr.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                }
            }
        }
    }
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

3.插入排序

    //插入排序
    public void sort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j > 0; j--) {
                if (arr[j] < arr[j - 1]) {
                    swap(arr, j, j - 1);
                }
            }
        }
    }
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

樣本小且基本有序的時候效率比較高

4.希爾排序

    //希爾排序
    public void sort(int[] arr) {
        //分組
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //對每一組進行插入排序
            for (int i = gap; i < arr.length; i++) {
                for (int j = i; j >= gap; j -= gap) {
                    if (arr[j] < arr[j - gap]) {
                        swap(arr, j, j - gap);
                    }
                }
            }
        }
    }
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

圖解分析:

5.歸併排序

如果要對一個陣列進行排序,我們先把陣列從中間分成前後兩部分,然後對陣列的前後兩部分分別排序,再將排序好的兩部分合並在一起,這樣整個陣列就是有序的了

    public void sort(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        mergeSort(arr, left, right);
    }
    //分+和方法
    private void mergeSort(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = left + (right - left) / 2;
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, right, mid);
    }
    //合併的方法
    private void merge(int arr[], int left, int right, int mid) {
        int i = left;
        int j = mid + 1;
        int k = 0;
        int temp[] = new int[right - left + 1];
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        while (j <= right) {
            temp[k++] = arr[j++];
        }
        System.arraycopy(temp, 0, arr, left, temp.length);
    }

5.1 歸併排序的迭代寫法

    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        int mergeSize = 1;
        while (mergeSize < n) {
            int l = 0;
            while (l < n) {
                int m = l + mergeSize - 1;
                if (m >= n) {
                    break;
                }
                int r = Math.min(m + mergeSize, n - 1);
                merge(arr, l, m, r);
                l = r + 1;
            }
            mergeSize = mergeSize << 1;
        }
    }

    private static void merge(int[] arr, int l, int m, int r) {
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        while (p1 <= m && p2 <= r) {
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= m) {
            help[i++] = arr[p1++];
        }
        while (p2 <= r) {
            help[i++] = arr[p2++];
        }
        System.arraycopy(help, 0, arr, l, help.length);
    }

5.2 求陣列中的逆序對個數---歸併排序擴充套件

    public int reversePairs(int[] nums) {
        if (nums == null || nums.length < 2) {
            return 0;
        }
        int res = process(nums, 0, nums.length - 1);
        return res;
    }

    private int process(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        int mid = (l + r) / 2;
        return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
    }

    private int merge(int[] arr, int l, int mid, int r) {
        int[] help = new int[r - l + 1];
        int p1 = l, p2 = mid + 1, k = 0;
        int res = 0;
        while (p1 <= mid && p2 <= r) {
            if (arr[p1] <= arr[p2]) {
                help[k++] = arr[p1++];
            } else {
                res += mid - p1 + 1;
                help[k++] = arr[p2++];
            }
        }
        while (p1 <= mid) {
            help[k++] = arr[p1++];
        }
        while (p2 <= r) {
            help[k++] = arr[p2++];
        }
        System.arraycopy(help, 0, arr, l, help.length);
        return res;
    }

6.快排

    public void sort(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = partition(arr, left, right);
        sort(arr, left, mid - 1);
        sort(arr, mid + 1, right);
    }
    private int partition(int[] arr, int leftBound, int rightBound) {
        int pivot = arr[rightBound];
        int left = leftBound;
        int right = rightBound - 1;
        while (left <= right) {
            while (left <= right && arr[left] <= pivot) {
                left++;
            }
            while (left <= right && arr[right] > pivot) {
                right--;
            }
            if (left < right) {
                swap(arr, left, right);
            }
        }
        swap(arr, left, rightBound);
        return left;
    }
    private void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }

7.計數排序

    public int[] sort(int[] arr) {
        int result[] = new int[arr.length];
        int count[] = new int[10];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        int k = 0;
        for (int i = 0; i < count.length; i++) {
            while (count[i]-- > 0) {
                result[k++] = i;
            }
        }
        return result;
    }

適用於量大但是範圍比較小的情況,如年齡排序,成績排序等,上面一種實現方式不穩定

    public int[] sort(int[] arr) {
        int[] result = new int[arr.length];
        int[] count = new int[10];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        for (int i = 1; i < count.length; i++) {
            count[i] = count[i] + count[i - 1];
        }
        for (int i = arr.length - 1; i >= 0; i--) {
            result[--count[arr[i]]] = arr[i];
        }
        return result;
    }

穩定的計數排序演算法

8.基數排序

本質上是一種多關鍵字排序,有低位優先和高位優先兩種

    public int[] sort(int[] arr) {
        int result[] = new int[arr.length];
        int count[] = new int[10];
        for (int i = 0; i < 3; i++) {
            int div = (int) Math.pow(10, i);
            for (int j = 0; j < arr.length; j++) {
                int num = arr[j] / div % 10;
                count[num]++;
            }
            for (int j = 1; j < count.length; j++) {
                count[j] = count[j] + count[j - 1];
            }
            for (int j = arr.length - 1; j >= 0; j--) {
                int num = arr[j] / div % 10;
                result[--count[num]] = arr[j];
            }
            System.arraycopy(result, 0, arr, 0, arr.length);
            Arrays.fill(count, 0);
        }
        return result;
    }

物件排序 TimSort

    //物件排序
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            //使用者如果請求用遺留的LegacyMergeSort,那就用遺留的
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                //否則用TimSort,TimSort是改進的MergeSort
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }


    static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        //MIN_MERGE,分組的最小值
        if (nRemaining < MIN_MERGE) {   //private static final int MIN_MERGE = 32;
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            //二分插入,改進的插入排序
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

        TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen, c);
                runLen = force;
            }

            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);

        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

流程

如果使用者使用者需要使用遺留的LegacyMergeSort,那就使用遺留的,否則使用TimSort

在使用TimSort時,如果整個資料量小於2,那麼直接返回

如果整個資料量小於MIN_MERGE(32),那麼使用二分插入排序進行排序

否則如果大於MIN_MERGE(32),使用TimSort,使用TimSort是把資料分成很多段資料,把其中的每一小段使用二分插入排序排好順序,然後把所有的段兩兩歸併

基本資料排序  雙軸快排

    static void sort(int[] a, int left, int right,
                     int[] work, int workBase, int workLen) {
        //如果陣列的長度小於286,就使用最原始的快排
        if (right - left < QUICKSORT_THRESHOLD) {   //private static final int QUICKSORT_THRESHOLD = 286;
            sort(a, left, right, true);
            return;
        }

        //判斷是否適合使用TimSort,如果拆分後陣列個數小於67
        int[] run = new int[MAX_RUN_COUNT + 1];     //private static final int MAX_RUN_COUNT = 67;
        int count = 0; run[0] = left;


            //陣列不是高度結構化的,不適合用普通快排
            if (++count == MAX_RUN_COUNT) {
                sort(a, left, right, true);
                return;
            }


    //普通的快排
    private static void sort(int[] a, int left, int right, boolean leftmost) {
        int length = right - left + 1;

        //如果陣列長度小於47,則直接使用插入排序(插入排序為雙插入排序),否則用快排
        if (length < INSERTION_SORT_THRESHOLD) {    //private static final int INSERTION_SORT_THRESHOLD = 47;
            if (leftmost) {
                for (int i = left, j = i; i < right; j = ++i) {
                    int ai = a[i + 1];
                    while (ai < a[j]) {
                        a[j + 1] = a[j];
                        if (j-- == left) {
                            break;
                        }
                    }
                    a[j + 1] = ai;
                }
            } else {
                do {
                    if (left >= right) {
                        return;
                    }
                } while (a[++left] >= a[left - 1]);

                //雙插入排序的實現

流程

雙軸快排就是我們找兩個數,把整個陣列分成三個區域

把第一個數小的放左邊,大於等於第一個數小於等於第二個數的放中間,大於第二個數的放右邊,前提是第一個數比第二個數小

先判斷陣列的長度是否小於286,如果是,就用原始的快排

判斷是否適合使用TimSort,判斷條件是開分後的陣列個數是否小於67

如果陣列不是高度結構化的,就用普通快排

在使用普通快排的時候,如果陣列長度小於47,則使用雙插入排序

使用一切都不滿足的情況下,使用雙軸快排

9. 堆結構和堆排序

import java.util.Arrays;

public class MyMaxHeap {
    private int[] heap;
    private final int limit;
    private int heapSize;

    public MyMaxHeap(int limit) {
        this.limit = limit;
        heap = new int[limit];
        heapSize = 0;
    }

    public boolean isEmpty() {
        return heapSize == 0;
    }

    public boolean isFull() {
        return heapSize == limit;
    }

    public void push(int value) {
        if (heapSize == limit) {
            throw new RuntimeException("heap is full");
        }
        heap[heapSize] = value;
        heapInsert(heap, heapSize++);
    }

    public int pop() {
        int ans = heap[0];
        swap(heap, 0, --heapSize);
        heapify(heap, 0, heapSize);
        return ans;
    }

    private void heapInsert(int[] arr, int index) {
        //當arr[index]不比arr[index父]大了,或者index來到0位置了
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //從index位置,往下看,不斷的下沉
    private void heapify(int[] arr, int index, int heapSize) {
        int left = index * 2 + 1;
        while (left < heapSize) {
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }

    private void swap(int[] arr, int l, int r) {
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }

    //從小到大進行排序
    public void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        // O(N*log(N))
//        for (int i = 0; i < arr.length; i++) {
//            heapInsert(arr, i);
//        }
        // O(N)
        for (int i = arr.length - 1; i >= 0; i--) {
            heapify(arr, i, arr.length);
        }
        int size = arr.length;
        swap(arr, 0, --size);
        // O(N*logN)
        while (size > 0) {
            heapify(arr, 0, size);
            swap(arr, 0, --size);
        }
    }

    public static void main(String[] args) {
        int arr[] = {3, 6, 4, 8, 4, 8, 4, 9, 4, 76, 2, 5, 1};
        MyMaxHeap heap = new MyMaxHeap(arr.length);
        for (int i = 0; i < arr.length; i++) {
            heap.push(arr[i]);
        }
        for (int i = 0; i < arr.length; i++) {
            int val = heap.pop();
            System.out.print(val + " ");
        }
        System.out.println();
        heap.heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

相關文章