zoukankan      html  css  js  c++  java
  • 常见排序算法汇总学习

    简介

    本文包含的排序算法:冒泡排序、快速排序、堆排序、归并排序、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. 算法导论(第三版)
  • 相关阅读:
    Java基础知识强化之集合框架笔记20:数据结构之 栈 和 队列
    Java基础知识强化之集合框架笔记19:List集合迭代器使用之 并发修改异常的产生原因 以及 解决方案
    模块已加载,但对dllregisterServer的调用失败
    sql回滚
    BAT 批处理脚本 教程
    shell脚本小技巧
    shell if
    REDHAT4.8安装yum
    Linux中文显示乱码解决
    Nginx配置文件详细说明
  • 原文地址:https://www.cnblogs.com/fortunely/p/14016332.html
Copyright © 2011-2022 走看看