常见排序算法汇总学习

简介

本文包含的排序算法:冒泡排序、快速排序、堆排序、归并排序、java内置排序,使用java语言实现。
问题描述:对列表A[p..r]进行排序,使得A元素按非递减顺序排列。

1. 冒泡排序

基本思想

从起始位置p开始,相邻元素比较,如果当前元素较大,则交换到相邻元素组的靠后位置。这样,一直比较、交换到末尾,就能得出本轮比较的最大元素,也是末尾元素,剩下的无序元素-1,有序元素+1.
这样进行n-1趟排序,就能得到n-1个最大元素。也就得到有序序列了。

时间复杂度

O(n2)

实现代码

public class BubbleSort {
    private static void exchange(int[] A, int d1, int d2) {
        int temp = A[d1];
        A[d1] = A[d2];
        A[d2] = temp;
    }

    public static void bubbleSort(int []A, int p, int r) {
        for (int i = 0; i < r-p; i++) { // r-p times sort
            // A[r-i+1] has been sorted, A[p..r-i] has not been sorted
            // get A[r-i] every sort time, namely max element in non-sorted list A, as the minimum element in sorted list
            for (int j = p+1; j <= r-i; j++) {
                if (A[j-1] > A[j])
                    exchange(A, j-1, j);
            }
        }
    }
}

2. 快速排序

基本思想

每趟排序找枢轴(pivot),所有左侧元素<=pivot,所有右侧元素>=pivot,pivot所在位置pivotLoc即为枢轴位置。递归地进行划分,对划分后每段列表找枢轴,直到列表无法划分下去,即完成快速排序。
找枢轴(位置简记q)的过程,也叫一趟快速排序,这里有2种方法:
第一种,是从两边往中间进行查询,以初始元素为枢轴(pivot=A[p]),发现右侧有较大元素,就放到左边,然后查看左边如果有较大元素,就放到右边。这样交替进行,最终确认枢轴位置。
另外一种,从左至右查询,以末尾元素为枢轴(pivot=A[r]),新游标 i 作为记录<=枢轴元素的最新位置,当发现如果有元素<枢轴,立马放到位置i,并且更新i 位置。这样轮训完A[p..r]后,A[i+1]左边元素A[p..i]<=pivot,右边元素>=pivot (否则就会移动到左边)

时间复杂度

O(nlogn)

实现代码

public class QuickSort {

    /**
     * sort A[p..r] in order
     * @param A array to be sorted
     * @param p first element's index of A
     * @param r last element's index of A
     */
    public static void quickSort(int[] A, int p, int r) {

        // check index p, r
        if (p < r) {
            int q = partition(A, p, r);

            if(p < q)
                quickSort(A, p, q-1);

            if(q < r)
                quickSort(A, q+1, r);
        }
    }

    /**
     * print all data of array A
     * @param title title to be printed
     * @param A array to be printed
     */
    public static void printData(String title, int[] A) {
        System.out.println(title);
        System.out.println(A.length);
        System.out.print("[");
        for (int i = 0; i < A.length; i++) {
            if(i > 0) System.out.print(",");
            System.out.print(A[i]);
        }
        System.out.println("]");
    }

    /**
     * exchange A[d1] with A[d2]
     * @param A array
     * @param d1 first element to be exchanged
     * @param d2 second element to be exchanged
     */
    private static void exchange(int[] A, int d1, int d2) {
        int temp = A[d1];
        A[d1] = A[d2];
        A[d2] = temp;
    }

    /**
     * find out the pivot's location pivotLoc and relocate all element so that A[p..pivotLoc-1] <= A[pivotLoc] <= A[pivotLoc+1..r]
     * @param A array to be sorted
     * @param p first element's index of array A
     * @param r last element's index of array A
     * @return pivot's location pivotLoc
     * @note sweep A from p to r
     */
    public static int partition(int[] A, int p, int r) {
        int x = A[r];
        int i = p - 1;
        for (int j = p; j < r; j++) {
            if (A[j] <= x) {
                i++;
                // exchange A[i] and A[j]
                exchange(A, i, j);
            }
        }

        // exchange A[i+1] and A[r]
        exchange(A, i+1, r);
        return i+1;
    }

    /**
     * find out the pivot's location pivotLoc and relocate all element so that A[p..pivotLoc-1] <= A[pivotLoc] <= A[pivotLoc+1..r]
     * @param A array to be sorted
     * @param p first element's index of array A
     * @param r last element's index of array A
     * @return pivot's location pivotLoc
     * @note sweep A from two side to center
     */
    public static int partition_2(int[] A, int p, int r) {
        int pivot = A[p];
        int low = p;
        int high = r;

        while (low < high) {
            while (low < high && A[high] >= pivot) { high--;}
            A[low] = A[high];
            while (low < high && A[low] <= pivot) {low++;}
            A[high] = A[low];
        }
        A[low] = pivot;
        return low;
    }
}

3. 堆排序

基本思想

  1. 构建最大堆,通过从最后一个非终端结点到根节点调整,确保整个堆都符合大堆性质。
  2. 输出堆顶元素到列表末尾(堆顶元素与堆末尾元素交换),从堆顶进行调整,已经交换出来的堆顶元素就形成新的有序列表。

调整:以当前结点i为根节点,确保大堆性质。如果不满足大堆性质,就将子节点最大元素与当前根节点交换,递归的调整下去,确保交换之后也是符合大堆特性。

大堆特性:A[i] >= A[left(i), 并且A[i] >= A[right(i)] , left(i), right(i)分别表示i的左孩子和右孩子。

时间复杂度

O(nlogn)

实现代码

public class HeapSort {
    private static int heapSize;

    /**
     * get root's location in array A
     * @return root's location
     * @note store all heap elements using A[0..length-1]
     */
    public static int root() {
        return 0;
    }

    /**
     * get last node's location in array A
     * @return last node's location
     */
    public static int lastNodeOfHeap() {
        return heapSize - 1;
    }

    /**
     * get left child's location in array A
     * @param i current node's location in array A
     * @return left child's location
     */
    public static int left(int i) {
        return i * 2 + 1;
    }

    /**
     * get right child's location in array A
     * @param i current node's location in array A
     * @return right child's location
     */
    public static int right(int i) {
        return i*2 + 2;
    }

    private static void exchange(int[] A, int d1, int d2) {
        int temp = A[d1];
        A[d1] = A[d2];
        A[d2] = temp;
    }

    /**
     * adjust heap to ensure max heap feature
     * @param A
     * @param i
     */
    public static void maxHeapify(int[] A, int i) {
        int l = left(i);
        int r = right(i);
        int largest;

        if(l < heapSize && A[l] > A[i])
            largest = l;
        else largest = i;

        if(r < heapSize && A[r] > A[largest]) {
            largest = r;
        }

        if (largest != i) {
            // exchange A[largest] with A[i]
            exchange(A, largest, i);

            maxHeapify(A, largest); // dont forget adjust the child tree if adjust current node
        }
    }

    /**
     * build max heap
     * @param A
     */
    public static void buildMaxHeap(int[] A) {
        heapSize = A.length;

        // sweep from last non-leaf node to root node
        for (int i = A.length / 2 - 1; i >= 0; i--) {
            maxHeapify(A, i);
        }
    }

    /**
     * sort A[0..length-1]
     * @param A array to be sorted
     * @note store heap using A[0..length-1], so the root node is A[0], and the last node is A[heapSize-1]
     */
    public static void heapSort(int[] A) {
        // build max heap
        buildMaxHeap(A);

        // exchange root with last node, then max heapify the heap
        // output heap peek element to ordered array A from length-1 to 1. the left 0 should be the minimum element
        for (int i = A.length - 1; i >= 1; i--) {
            exchange(A, 0, i);
            heapSize--;
            maxHeapify(A, 0);
        }
    }
}

4. 归并排序

基本思想

利用分治法,将待排序列表进行递归均分,划分到最后每个列表只有一个元素,再逐层合并为有序列表。分治法一般需要对划分后子问题进行处理(问题解决),不过因为归并排序划分到最后只有每个列表1个元素,而排序是无需做特殊处理的。
本文提供2种子有序列表归并为一个大的有序列表的方法:
设当前列表A[p..r]划分成的2个子列表A[p..q], A[q+1..r]已有序,现在要将2个子列表合并回。

  1. 分别复制2个子列表A[p..q]和A[q+1..r]到L[0..n1-1]和R[0..n2-1],然后再合并回A[p..r]。代码中L[n1]和R[n2]都用到了∞(无穷大)作为哨兵;
  2. 创建出一个与A[p..r]一样大的新列表L,然后按序号从A[p..q]和A[q+1..r]中找到最小数填入L中,得到有序的L。最后将L复制回A即为有序列表。

时间复杂度

O(nlogn)

实现代码

public class MergeSort {
    /**
     * merge two ordered list A[p..q] and A[q+1..r] to ordered A[p..r]
     * @param A list to be sorted
     * @param p first element location of A
     * @param q partition element location of A
     * @param r last element location of A
     * @note use two list and sentry, then merge the two sorted list to A[p..r]
     */
    public static void merge(int[] A, int p, int q, int r) {
        int n1 = q - p + 1;
        int n2 = r - q;
        int[] L = new int[n1 + 1];
        int[] R = new int[n2 + 1];

        // copy A[p..q] to L[0..n1-1]
        for (int i = 0; i < L.length - 1; i++) {
            L[i] = A[p+i];
        }
        // copy A[q+1..r] to R[0..n2-1]
        for (int j = 0; j < R.length - 1; j++) {
            R[j] = A[q+1+j];
        }

        // set sentry using ∞ (max value)
        L[n1] = Integer.MAX_VALUE;
        R[n2] = Integer.MAX_VALUE;

        int i = 0;
        int j = 0;
        for (int k = p; k <= r; k++) { // case k==r is necessary, because r is the element with max index in A , not the length
            if (L[i] <= R[j]) { // "=" can't be absent, otherwise the sort is not stable
                A[k] = L[i];
                i++;
            }else {
                A[k] = R[j];
                j++;
            }
        }
    }

    /**
     * merge two ordered list A[p..q] and A[q+1..r] to ordered A[p..r]
     * @param A list to be sorted
     * @param p first element location of A
     * @param q partition element location of A
     * @param r last element location of A
     * @note merge two segment to new list, then re-copy back to origin list
     */
    public static void merge_2(int[] A, int p, int q, int r) {
        int[] L = new int[r - p + 1];

        /* merge A[p..q] and A[q+1..r] to L[0..length-1] by asc order */
        int i = p;
        int j = q+1;
        int k = 0;
        while (i <= q && j <= r) {
            if(A[i] <= A[j]) { // "=" can't be absent, otherwise the sort is not stable
                L[k] = A[i];
                i++;
            }
            else {
                L[k] = A[j];
                j++;
            }

            k++;
        }

        // copy left element to L
        while (i <= q) {
            L[k++] = A[i++];
        }
        while (j <= r) {
            L[k++] = A[j++];
        }

        // copy new sorted list L to origin list A
        for (int t = 0; t < L.length; t++) {
            A[p+t] = L[t];
        }
    }

    public static void mergeSort(int[] A, int p, int r) {
        if (p < r) {
            int q = (p + r) / 2;
            mergeSort(A, p, q);
            mergeSort(A, q+1, r);
            merge(A, p, q, r);
        }
    }
}

5. Java内置数组排序

基本思想

利用Java内置Arrays.sort 进行排序(底层是调用归并排序(对象类数组)或者快速排序(基础类型数组))。如果是对对象列表(Set, Map, List等)进行排序的话,需要使用Collections.sort()以及提供Comparator,或者实现Comparable的compareTo方法。

参见: Comparable接口和Comparator接口

时间复杂度

同归并排序O(nlogn)

public class ArraysSort {
    public static void arraysSort(int[] A, int p, int r) {
        if (p < r)
            Arrays.sort(A, p, r);
    }
}

C/C++ 也有类似的库函数:qsort(), 需要#include <ctype.h>

可参见:C 库函数 - qsort()

6. 计数排序

基本思想

计数排序对输入数据范围有严格要求,因为要通过计数数组索引直接映射为输入数组值,如果输入数据范围过大,会导致计数数组所需空间膨胀,但是利用率却很低。
这里简化成对输入数据A[i] ∈ [0, k], k< 50000且k∈Z, i=0,1,2..,n-1 .

通过对A[i]同样大小元素计数,运算得到<= A[i]元素个数k, A[i]的位置即为k (A从0开始存储数据,如果是从1开始A[i]位置k+1)
例如,< x的元素有17个,那么x就应该存放在位置17 (假设不存在相同元素)。对于包含相同元素情况,为了使排序稳定,可以从最后一个元素开始放起,直到最先位置的元素 。

这样,问题就主要转换成了如何求解 <= A[i]元素个数了。

由于A[i]范围有限,所以可以直接用一个数组C[0..k] (k = max{A[i]} )用来对A各元素计数。c[A[j]]表示A[j]出现次数,∑c[A[j]]表示 <= A[j] 次数

伪代码:

最初, c[i] = count(x = A[j]), i = A[j]
计算后, C[i] = ∑count(x = A[j]), i = A[j], 
也就是C[i] = count(x<=A[j]), i = A[j]

时间复杂度

O(logn) , 当k=O(logn)时

实现代码

public class CountingSort {

    /**
     * counting sort
     * @param A input list, raw data
     * @param B output sorted list
     * @param k A[i] ∈ [0, k] and k ∈ Z, i=0,1,2,...,A.length-1
     */
    public static void cSort(int[] A, int[] B, int k) {
        if (k <= 0) return;

        int[] c = new int[k + 1]; // counting array, c[0..k] stores count(A[0..length-1])

        // init counting array c[]
        for (int i = 0; i < c.length; i++) {
            c[i] = 0;
        }

        // set c[i] = count(x == A[j]), i = A[j]
        for (int j = 0; j < A.length; j++) {
            c[A[j]] ++;
        }

        // set c[i] = count(x <= A[j]), i = A[j]
        for (int i = 1; i < c.length; i++) {
            c[i] = c[i-1] + c[i];
        }

        // now, we know element i (i = A[j]) should be located in index c[i] of B
        // Provided that count(i) > 1, let j vary from A.length-1 to 0 to ensure a stable sorting
        for (int j = A.length-1; j >= 0 ; j--) {
            B[c[A[j]] - 1] = A[j]; // B[0..length-1] stores sorted result of A[0..length-1], and location should exclude count value of itself.
//            B[c[A[j]]] = A[j];
            c[A[j]] --;
        }

    }

    /**
     * interface of counting sort, sort A[p..r]
     * @param A input list, raw data
     * @param p first element's location
     * @param r last element's location
     */
    public static void countingSort(int[] A, int p, int r) {
        int max = A[p];
        int min = A[p];

        for (int i = p+1; i < r; i++) {
            if (max < A[i]) max = A[i];
            if (min > A[i]) min = A[i];
        }

        int[] B = new int[A.length];
        cSort(A, B, max);

        // copy sorted B[0..length-1] to A[0..length-1]
        for (int i = 0; i < A.length; i++) {
            A[i] = B[i];
        }
    }
}

完整Code

包括测试代码:gitee_fortunely_algorithm

参考

  1. 算法导论(第三版)
posted @ 2020-11-21 17:09  明明1109  阅读(141)  评论(0编辑  收藏  举报