排序

作者: 进击的勇士 | 来源:发表于2017-03-04 18:43 被阅读0次

    初级排序算法

    1.1 选择排序

    思想

    找到数组中最小的元素,然后和数组中第一个元素进行交换。然后找到剩下数中最小的元素,和数组的第二个元素进行交换。如此往复,直到将整个数组排序。

    特点
    • 运行时间和输入无关
    • 数据移动是最少的

    1.2 插入排序

    思想

    从左向右,将每一个元素移动到左边正确的位置。左边的数组始终是有序的。

    特点
    • 运行时间取决于输入元素的初始顺序
    • 对于部分有序的数组比较高效

    1.3 希尔排序

    思想

    由于插入排序一次只能移动1位,希尔排序每次移动h位。数组中任意间隔为h的元素都是有序的。

    特点
    • 运行时间不能定量
    package edu.princeton.cs.algs4.chapter2;
    
    /**
     * 排序方法
     * Created by TXH-pc on 2017/3/4.
     */
    public class Sort {
    
        /**
         * 选择排序
         */
        public static void selectionSort(Comparable[] a) {
            int N = a.length;
            for (int i = 0; i < N; i++) {
                int min = i;
                for (int j = i+1; j< N; j++) {
                    if(less(a[j], a[min])) {
                        min = j;
                    }
                }
                exch(a, i, min);
            }
        }
    
        /**
         * 插入排序
         */
        public static void insertionSort(Comparable[] a) {
            int N = a.length;
            for (int i = 1; i < N; i++) {
                //   终止条件是到达数组的最左端或者前面的元素已经比当前元素小
                for(int j = i; j > 0 && less(a[j], a[j - 1]) ; j--) {
                    exch(a, j - 1, j);
                }
            }
        }
    
        /**
         * 希尔排序
         * @param a
         */
        public static void shell(Comparable[] a) {
            int N = a.length;
            int h = 1;
            while (h < N) {
                h = 3 * h + 1;
            }
            while (h >= 1) {
                for (int i = h; i < N; i++) {
                    for (int j = i; j >= h && less(a[j], a[j - h]); j-=h) {
                        exch(a, j - h, j);
                    }
                }
                h = h / 3;
            }
    
        }
    
        private static boolean less (Comparable v, Comparable w) {
            return v.compareTo(w) < 0;
        }
    
        private static void exch(Comparable[] a, int i, int j) {
            Comparable temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    
        private static void show(Comparable[] a) {
            for (int i = 0; i < a.length; i++) {
                System.out.print(a[i] + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            String[] a = {"t", "h", "i", "s", "i", "s", "t", "e", "s", "t"};
            Sort.selectionSort(a);
            Sort.insertionSort(a);
            Sort.shell(a);
            Sort.show(a);
        }
    }
    

    归并排序

    基本思想

    将两个有序的数组合并成一个更大的有序数组,有两种方法来实现归并排序:自顶而下的归并排序(分治法)和自底向上的归并排序。

    2.1 分治法

    1. 新建一个临时数组(成员变量),merge的时候用来存放要排序的元素。
    2. 使用递归,将原来的数组分成两半,进行排序,并将排好序的数组复制到临时数组当中,最后将排好序的数组归并成一个数组。
    3. 在归并的过程中,分别用两个指针指向两个已经排好序的数组的初始位置,通过比较,将较小的元素放到合并数组。
    分治法排序示例

    Note:

    1. sort方法的作用在于安排多次merge方法调用的正确顺序
    2. 对于小规模的数组,可能插入排序比归并排序更快
    3. 对于长度为N的任意数组,自顶向下的归并排序至少需要1/2NlgN~NlgN次比较


      归并排序时间与NlgN成正比的证明

    代码实现

    public class MergeSort {
    
        private static Comparable[] aux; // 用于辅助的排序数组
    
        public static void sort(Comparable[] a) {
            aux = new Comparable[a.length]; // 初始化辅助数组
            // 使用递归,进行排序
            sort(a, 0, a.length - 1);
        }
    
        private static void sort(Comparable[] a, int lo, int hi) {
            // 递归终止条件
            if (hi <= lo)
                return;
    
            int mid = (lo + hi) / 2;
            sort(a, lo, mid); //排序左半边
            sort(a, mid + 1, hi); //排序右半边
            merge(a, lo, mid, hi); //合并
        }
    
        private static void merge(Comparable[] a, int lo, int mid, int hi) {
            int i = lo, j = mid + 1;
    
            for (int k = lo; k <= hi; k++) {
                aux[k] = a[k]; // 将要排序的数组移动到辅助数组
            }
            // 按大小进行比较归并,如果一边比较完,直接把另一边的元素复制回去
            for (int k = lo; k <= hi; k++) {
                if (i > mid)
                    a[k] = aux[j++];
                else if (j > hi)
                    a[k] = aux[i++];
                else if (aux[i].compareTo(aux[j]) < 0)
                    a[k] = aux[i++];
                else
                    a[k] = aux[j++];
            }
        }
    
        private static void show(Comparable[] a) {
            for (int i = 0; i < a.length; i++) {
                System.out.print(a[i] + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            Integer[] a = {1, 2, 5, 6, 3, 8, 6, 9};
            MergeSort.sort(a);
            MergeSort.show(a);
        }
    
    }
    

    2.2 自底向上

    1. 按顺序对数组中每两个元素进行排序
    2. 按顺序,每次对2的n次方个元素进行排序,直到所有的元素排序完成

    代码实现

    public class MergeSort {
    
        private static Comparable[] aux; // 用于辅助的排序数组
    
        /**
         * 自底向上的实现
         * @param a
         */
        public static void sortBU(Comparable[] a) {
            aux = new Comparable[a.length];
            int N = a.length;
    
            for (int sz = 1; sz < N; sz = sz + sz) { // sz为每次排序元素(子数组)的数量
                for (int lo = 0; lo < N - sz; lo += sz) {
                    // 每次merge两个子数组,最后一个子数组的长度可能小于sz
                    merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
                }
            }
        }
    
        private static void merge(Comparable[] a, int lo, int mid, int hi) {
            int i = lo, j = mid + 1;
    
            for (int k = lo; k <= hi; k++) {
                aux[k] = a[k]; // 将要排序的数组移动到辅助数组
            }
            // 按大小进行比较归并,如果一边比较完,直接把另一边的元素复制回去
            for (int k = lo; k <= hi; k++) {
                if (i > mid)
                    a[k] = aux[j++];
                else if (j > hi)
                    a[k] = aux[i++];
                else if (aux[i].compareTo(aux[j]) < 0)
                    a[k] = aux[i++];
                else
                    a[k] = aux[j++];
            }
        }
    
        private static void show(Comparable[] a) {
            for (int i = 0; i < a.length; i++) {
                System.out.print(a[i] + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            Integer[] a = {1, 2, 5, 6, 3, 8, 6, 9};
            MergeSort.sortBU(a);
            MergeSort.show(a);
        }
    }
    
    

    排序算法的复杂度

    没有任何基于比较的算法能保证少于lg(N!) ~ NlgN次比较将长度为N的数组排序

    参考链接

    http://rason.me/2016/06/29/MergeSort/
    http://blog.acbingo.cn/2015/11/26/%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-8-%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F/
    http://www.cnblogs.com/yangecnu/p/Introduce-Merge-Sort.html

    快速排序

    基本思想

    快速排序是先将切分元素放到合适的位置,切分元素左边的值总是小于切分元素,切分元素右边的值总是大于切分元素。通过递归,将左右两边继续切分,直到排序完成。

    切分目的

    1. 对于某个j,a[j]已经确定
    2. a[lo]到a[j - 1]中的所有元素都不大于a[j]
    3. a[j + 1]到a[hi]中的所有元素都不小于a[j]
    快速排序切分示意图

    切分实现步骤

    1. 用两个指针(lo和hi),分别指向数组的头和尾
    2. 将指针指向的元素与切分元素(一般是数组的第一个元素)进行比较,如果a[lo]小于待切分的元素,则lo后移;如果a[hi]大于待切分的元素,则hi前移。
    3. 当lo指向一个大于切分元素的值时,停止移动,如果hi指向一个小于切分元素的值时,停止移动。此时,交换lo和hi所指向的值。
    4. 如此往复,直到hi<=lo,此时,交换切分元素和hi指向的值,同时返回hi

    性能分析

    1. 将长度为N的无重复数组排序,快速排序平均需要~2NlnN次比较
    2. 快速排序最多需要N*N/2次比较,但是随机打乱数组能预防这种情况
    3. 当重复元素很多时,使用三项切分快速排序能将算法的时间复杂度降到N

    代码实现

    package edu.princeton.cs.algs4.chapter2;
    
    
    /**
     * 快速排序
     * Created by TXH-pc on 2017/3/5.
     */
    public class QuickSort {
    
        public static void sort(Comparable[] a) {
            // 打乱数组 略过
            sort(a, 0, a.length - 1);
            //quick3WaySort(a, 0, a.length - 1);
        }
    
        private static void sort(Comparable[] a, int lo, int hi) {
            // 递归停止条件
            if (hi <= lo)
                return;
    
            int j = partition(a, lo, hi);
            sort(a, lo, j - 1);
            sort(a, j + 1, hi);
        }
    
    
        private static int partition(Comparable[] a, int lo, int hi) {
            int i = lo, j = hi + 1; // 左右扫描指针
            Comparable v = a[lo];
            while (true) {
                while(less(a[++i], v)) {
                    if (i == hi)
                        break;
                }
                while(less(v, a[--j])) {
                    if (j == lo)
                        break;
                }
                if (j <= i)
                    break;
                exch(a, i, j);
            }
            exch(a, lo, j); // 将v放入正确的位置
            return j;
        }
    
        /**
         * 三项切分快速排序
         * @param a
         * @param lo
         * @param hi
         */
        private static void quick3WaySort(Comparable[] a, int lo, int hi) {
            // 递归的终止条件
            if (hi <= lo)
                return;
    
            int lt = lo, i = lo + 1, gt = hi;
            Comparable v = a[lo];
            while (i <= gt) {
                int cmp = a[i].compareTo(v);
                if (cmp < 0)
                    exch(a, lt++, i++);
                else if (cmp > 0)
                    exch(a, i, gt--);
                else
                    i++;
            }
            quick3WaySort(a, lo, lt - 1);
            quick3WaySort(a, gt + 1, hi);
        }
    
        private static void exch(Comparable[] a, int i, int j) {
            Comparable temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    
        private static boolean less (Comparable v, Comparable w) {
            return v.compareTo(w) < 0;
        }
    
        private static void show(Comparable[] a) {
            for (int i = 0; i < a.length; i++) {
                System.out.print(a[i] + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            String[] a = {"R", "B", "W", "W", "R", "W", "B", "R", "R", "W", "B", "R"};
            QuickSort.sort(a);
            show(a);
        }
    }
    

    优先队列

    简介及比较

    每次出来的都是最小值/最大值,可以用来查找海量数据中最大/最小的N个数
    栈 :先进后出
    队列 :先进先出
    随机队列 : 随机出
    优先队列:每次出来的是最大值或最小值

    API介绍

    public class MaxPQ<Key extends Comparable<Key>>
    {
                MaxPQ()            // 创建一个优先队列
                MaxPQ(int max)     // 创建一个初始容量为max的优先队列
                MaxPQ(Key[] a)     // 用a[]中的元素创建一个优先队列
        void    insert(Key v)      // 向优先队列中插入一个元素
        Key     max()              // 返回最大是元素
        Key     delMax()           // 删除并返回一个最大元素
        boolean isEmpty()          // 优先队列是否为空
        int     size()             // 优先队列中的元素数量
    }
    

    优先队列实现的几种思路

    • 数组实现(无序)
      用一个数组存储数据,insert()时依次向数组中添加数据,delMax()时找出最大的元素,和边界元素进行交换,然后删除它。同时可以加入调整数组的大小的代码。
    package edu.princeton.cs.algs4.chapter2;
    
    /**
     * 无序数组实现的优先队列
     * Created by TXH-pc on 2017/3/5.
     */
    public class UnorderedMaxPQ<Key extends Comparable<Key>> {
        private Key[] pq;
        private int N;
    
        public UnorderedMaxPQ() {}           // 创建一个优先队列
    
        // 创建一个初始容量为max的优先队列
        public UnorderedMaxPQ(int max) {
            pq = (Key[]) new Comparable[max];
        }
    
        // 向优先队列中插入一个元素
        public void insert(Key v) {
            pq[N++] = v;
        }
    
        // 删除并返回一个最大元素
        public Key delMax() {
            int max = 0;
            for (int i = 0; i< N; i++) {
                if (less(max, i))
                    max = i;
            }
            exch(max, N - 1);
            return pq[--N];
        }
    
        private void exch(int max, int i) {
            Key temp = pq[max];
            pq[max] = pq[i];
            pq[i] = temp;
        }
    
        private boolean less(int max, int i) {
            if(pq[max].compareTo(pq[i]) < 0)
                return true;
            else
                return false;
        }
    
        // 优先队列是否为空
        public boolean isEmpty() {
            return N == 0;
        }
    
        // 优先队列中的元素数量
        public int size() {
            return N;
        }
    
        public static void main(String[] args) {
            UnorderedMaxPQ<String> pq = new UnorderedMaxPQ<String>(3);
            pq.insert("f");
            pq.insert("z");
            pq.insert("j");
            System.out.println(pq.delMax());
        }
    }
    
    • 数组实现(有序)
      在插入数组的时候,将较大的元素向右移动一格以保证数组有序,这样最大的元素总在数组的一边。
    • 链表表示法(有序&无序)
    • 堆实现
    优先队列的各种实现在最坏情况下运行时间的增长量级

    二叉堆实现优先队列

    • 二叉堆的每个节点都大于等于它的两个子节点
    • 使用数组表示二叉堆的时候,索引为0的位置不放置元素。那么,位置k的节点的父节点位置为k/2,两个子节点的位置为2k和2k+1
    • 由下至上的堆有序化(上浮):当某种原因导致一个节点比它的父节点更大的时候,需要交换它和它父节点的位置,循环往复直到交换后的父节点比当前节点大
    上浮过程
    • 由上至下的堆有序化(下沉):当某种原因导致一个节点比它的两个子节点更小的时候,需要将它和它的两个子节点当中较小的进行交换来恢复堆
    • 插入元素操作:将要插入的元素放置在数组的末尾,增加堆的大小然后通过上浮操作使堆恢复到有序的状态


      下沉过程
    • 删除最大元素:从数组顶端去掉最大元素,然后将数组的最后一个元素放置到顶端,减小堆的大小然后通过下沉操作恢复堆的有序状态

    基于二叉堆的优先队列的实现

    package edu.princeton.cs.algs4.chapter2;
    
    /**
     * 基于堆的优先队列
     * Created by TXH-pc on 2017/3/5.
     */
    public class MaxPQ<Key extends Comparable<Key>> {
        private Key[] pq;
        private int N = 0; // 元素存储在[1...N]当中,pq[0]不使用
    
        public MaxPQ(int maxN) {
            pq = (Key[]) new Comparable[maxN + 1];
        }
    
        public boolean isEmpty() {
            return N == 0;
        }
    
        public int size() {
            return N;
        }
    
        public void insert(Key v) {
            pq[++N] = v;
            swim(N);
        }
    
        public Key delMax() {
            Key max = pq[1];
            exch(1, N--);
            pq[N+1] = null;
            sink(1);
            return max;
        }
    
        private void swim(int k) {
            while (k > 1 && less(k/2, k)) {
                exch(k / 2, k);
                k = k / 2;
            }
        }
    
        private void sink(int k) {
            // 停止条件应该是到达底端或者子节点都比他小
            while (2*k <= N) {
                int j = 2*k;
                if (j < N && less(j, j+1))
                    j++;
                if (less(k, j))
                    break;
                exch(k, j);
                k = j;
            }
        }
    
        private boolean less(int i, int j) {
            return pq[i].compareTo(pq[j]) < 0;
        }
    
        private void exch(int i, int j) {
            Key temp = pq[i];
            pq[i] = pq[j];
            pq[j] = temp;
        }
    
        public static void main(String[] args) {
            MaxPQ<String> pq = new MaxPQ<String>(3);
            pq.insert("f");
            pq.insert("a");
            pq.insert("j");
            System.out.println(pq.delMax());
        }
    }
    
    

    堆排序

    堆排序分两个阶段:

    1. 构造堆结构

    通过从右至左用sink()构造子堆,只需要扫描数组中的一半的元素

    1. 下沉排序

    将堆中最大的元素和最后一个元素交换,然后放入堆缩小后空出的位置。然后使用sink()方法使堆有序

    package edu.princeton.cs.algs4.chapter2;
    
    /**
     * 堆排序
     * Created by TXH-pc on 2017/3/5.
     */
    public class HeapSort {
    
        public static void sort(Comparable[] a) {
            int N = a.length;
            // 堆的构造
            for (int k = N / 2; k >= 1; k--) {
                sink(a, k, N);
            }
            // 下沉排序
            while (N > 1) {
                exch(a, 1, N--);
                sink(a, 1, N);
            }
        }
    
        private static void sink(Comparable[] a, int k, int N) {
            while (2*k <= N) {
                int j = 2*k;
                if (j < N && less(a, j, j+1))
                    j++;
                if (less(a, j, k))
                    break;
                exch(a, j, k);
                k = j;
            }
    
        }
    
        // following two functions should convert from 1-base to 0 base
        private static void exch(Comparable[] a, int j, int k) {
            Comparable temp = a[j - 1];
            a[j - 1] = a[k - 1];
            a[k - 1] = temp;
        }
    
        private static boolean less(Comparable[] a, int i, int j) {
            return a[i - 1].compareTo(a[j - 1]) < 0;
        }
    
        private static void show(Comparable[] a) {
            for (int i = 0; i < a.length; i++) {
                System.out.print(a[i] + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            String[] a = {"t", "h", "i", "s", "i", "s", "t", "e", "s", "t"};
            HeapSort.sort(a);
            HeapSort.show(a);
        }
    }
    
    

    算法

    • Stable means if the two elements have the same key, they remain in the same order or positions. But that is not the case for Heap sort.
    算法比较

    相关文章

      网友评论

          本文标题:排序

          本文链接:https://www.haomeiwen.com/subject/gnljgttx.html