算法4第2章快速排序/堆排序/优先队列及习题讲解

快速排序

快速排序与归并排序类似,也是运用递归分治的思想,不过它不是从中间把数组a平分成2个子数组,而是选择一个切分元素a[lo]进行切分,切分过程中把比a[lo]小的元素都移到a[lo]的左边,把比a[lo]大的元素都移到a[lo]的右边,切分完成后根据a[lo]的最终位置j,把
数组分成两个子数组a1和a2,这样a1的元素都<=a[j],a2的元素都>=a[j],再用同样的切分方式把a1和a2切分,递归下去当a1和a2有序后,a就自然有序了,切分示意图如下:


代码如下
    public static void sort(Comparable[]  a) {
        StdRandom.shuffle(a);
        sort(a, 0, a.length - 1);
        assert isSorted(a);
    }
    
    // quicksort the subarray from a[lo] to a[hi]
    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);
        assert isSorted(a, lo , hi);
    }
    
    // partition the subarray a[lo .. hi] by returning an index j
    // so that a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
    private static int partition(Comparable[] a, int lo, int hi) {
        int i = lo;
        int 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 (i >= j) break;
            exch(a, i, j);
        }
        exch(a, lo, j);
        
        return j;
    }


切分函数是partition(),可以结合下图理解切分的过程,待排序数组是K,R,A...C,X,O,S,初始值i = 0, j = 16,一般选择数组第1个元素v=a[lo]作为切分元素,子循环while(less(a[++i], v))递增i直到找到一个大于等于v的元素a[i],子循环while(less(v, a[--j]))递减j直到找到一个小于等于v的元素a[j],然后exch(a, i, j) i和j的元素交换位置,使i左边的元素都<=v,j右边的元素都>=v,while (true)重复这个过程直到i >= j,i和j相遇时,j左边的元素都<=v,j右边的元素都>=v,最后调用exch(a, lo, j);把v放到正确的位置j,返回j结束切分的过程。切分的过程要注意i,j别越过数组的边界 2.3.11 假如在遇到和切分元素重复的元素时我们继续扫描数组而不是停下来,证明使用这种方法的快速排序在处理只有若干种元素值的数组时运行时间是平方级别的。

快速排序最好情况是每次切法都正好能将数组对半分,比较次数时间复杂度O(NlogN),可以结合前文归并排序的依赖树来理解。
快速排序最坏的情况是每次切分后,总有一个数组为空,需要N次切法,每次切法的比较次数是N,N-1,N-2,...2,1,所以总的比较次数是N+(N-1)+(N-2)+...+2+1=(N+1)N/2,时间复杂度是O(N^2),

所以快速排序开始会先调用lStdRandom.shuffle(a);把数组随机打乱,减少对输入的依赖,这样平均情况下时间复杂度O(NlogN)
# java SortCompare Quick Merge 5000 100
For 5000 random Doubles
    Quick is 1.3 times faster than Merge
For 5000 random Doubles
    Quick is 0.8 times faster than Merge
运行SortCompare可以看出快速排序和希尔排序,归并排序效率差不多,一般比希尔和归并排序快,因为快速排序内循环中移动数据次数比较少。

三向切分的快速排序
快速排序有两个问题,一是切分过程中遇到与切分元素相同的元素也会停下来交换元素,二是对元素全部重复的子数组仍然会递归进行切分,所以对于有大量重复元素的数组快速排序还有很大的改进潜力,改进算法如下,对于有大量重复元素的数组它可以把时间复杂度从线性对数基本降低到线性级别。切分示意图如下:

切分过程中把数组分成三部分,小于,等于,大于切分元素的数组,这样当子数组都是重复元素时不会继续递归下去
public class Quick3way {
    
    public static void sort(Comparable[]  a) {
        StdRandom.shuffle(a);
        sort(a, 0, a.length - 1);
        assert isSorted(a);
    }
    
    // quicksort the subarray a[lo .. hi] using 3-way partitioning
    private static void sort(Comparable[] a, int lo, int hi) {
        if (hi <= lo) return;
        int lt = lo, gt = hi;
        Comparable v = a[lo];
        int i = lo + 1;
        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++;
        }

        // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].
        sort(a, lo, lt-1);
        sort(a, gt+1, hi);
        assert isSorted(a, lo, hi);
    }

// ex2.3.5 给出一段代码将已知只有两种主键值的数组排序。
public class Sort2distinct {
    
    public static void sort(Comparable[]  a) {
        int lt = 0;
        int gt = a.length - 1;
        int i = 1;
        while(i <= gt) {
            int cmp = a[i].compareTo(a[lt]);
            if (cmp < 0) exch(a, i++, lt++);
            else if(cmp > 0) {i++; lt++;}
            else i++;
        }
    }
    
    public static void sort2(Comparable[]  a) {
        int lt = 0;
        int gt = a.length - 1;
        int i = 1;
        while(i <= gt) {
            int cmp = a[i].compareTo(a[lt]);
            if (cmp < 0) exch(a, i++, lt++);
            else if(cmp > 0) exch(a, i, gt--);
            else i++;
        }
    }
//ex 2.3.18 三取样切分,为快速排序实现正文所述的三取样切分(参见 2.3.3.2 节),运行双倍测试来确认这项改动的效果。

// ex2.3.18
public class Quick_ex2_3_18 {
    
    public static void sort(Comparable[]  a) {
        StdRandom.shuffle(a);
        sort(a, 0, a.length - 1);
        assert isSorted(a);
    }
    
    // quicksort the subarray from a[lo] to a[hi]
    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);
        assert isSorted(a, lo , hi);
    }
    
    // partition the subarray a[lo .. hi] by returning an index j
    // so that a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
    private static int partition(Comparable[] a, int lo, int hi) {
        int n = hi - lo + 1;
        int m = median3(a, lo, lo + n/2, hi);
        exch(a, m, lo); //中位数作为切分元素
        int h = less(a[lo + n/2], a[hi]) ? hi : lo + n/2;
        exch(a, h, hi);  // 较大的值放最右侧作为哨兵,可以去掉if(i== hi)和if(j== lo)的判断
        
        
        int i = lo;
        int 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 (i >= j) break;
            exch(a, i, j);
        }
        exch(a, lo, j);
        
        return j;
    }
    
    // return the index of the median element among a[i], a[j], and a[k]
    private static int median3(Comparable[] a, int i, int j, int k) {
        return (less(a[i], a[j]) ?
               (less(a[j], a[k]) ? j : less(a[i], a[k]) ? k : i) :
               (less(a[k], a[j]) ? j : less(a[k], a[i]) ? k : i));
    }

// ex 2.3.22


public class QuickBentleyMcIlroy {

    // cutoff to insertion sort, must be >= 1
    private static final int INSERTION_SORT_CUTOFF = 8;

    // cutoff to median-of-3 partitioning
    private static final int MEDIAN_OF_3_CUTOFF = 40;

    // This class should not be instantiated.
    private QuickBentleyMcIlroy() { }

    /**
     * Rearranges the array in ascending order, using the natural order.
     * @param a the array to be sorted
     */
    public static void sort(Comparable[] a) {
        sort(a, 0, a.length - 1);
    }

    private static void sort(Comparable[] a, int lo, int hi) {
        int n = hi - lo + 1;

        // cutoff to insertion sort
        if (n <= INSERTION_SORT_CUTOFF) {
            insertionSort(a, lo, hi);
            return;
        }

        // use median-of-3 as partitioning element
        else if (n <= MEDIAN_OF_3_CUTOFF) {
            int m = median3(a, lo, lo + n/2, hi);
            exch(a, m, lo);
        }

        // use Tukey ninther as partitioning element
        else  {
            int eps = n/8;
            int mid = lo + n/2;
            int m1 = median3(a, lo, lo + eps, lo + eps + eps);
            int m2 = median3(a, mid - eps, mid, mid + eps);
            int m3 = median3(a, hi - eps - eps, hi - eps, hi);
            int ninther = median3(a, m1, m2, m3);
            exch(a, ninther, lo);
        }

        // Bentley-McIlroy 3-way partitioning
        int i = lo, j = hi+1;
        int p = lo, q = 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;

            // pointers cross
            if (i == j && eq(a[i], v))
                exch(a, ++p, i);
            if (i >= j) break;

            exch(a, i, j);
            if (eq(a[i], v)) exch(a, ++p, i);
            if (eq(a[j], v)) exch(a, --q, j);
        }


        i = j + 1;
        for (int k = lo; k <= p; k++)
            exch(a, k, j--);
        for (int k = hi; k >= q; k--)
            exch(a, k, i++);

        sort(a, lo, j);
        sort(a, i, hi);
    }


    // sort from a[lo] to a[hi] using insertion sort
    private static void insertionSort(Comparable[] a, int lo, int hi) {
        for (int i = lo; i <= hi; i++)
            for (int j = i; j > lo && less(a[j], a[j-1]); j--)
                exch(a, j, j-1);
    }


    // return the index of the median element among a[i], a[j], and a[k]
    private static int median3(Comparable[] a, int i, int j, int k) {
        return (less(a[i], a[j]) ?
               (less(a[j], a[k]) ? j : less(a[i], a[k]) ? k : i) :
               (less(a[k], a[j]) ? j : less(a[k], a[i]) ? k : i));
    }

   /***************************************************************************
    *  Helper sorting functions.
    ***************************************************************************/
    
    // is v < w ?
    private static boolean less(Comparable v, Comparable w) {
        if (v == w) return false;    // optimization when reference equal
        return v.compareTo(w) < 0;
    }

    // does v == w ?
    private static boolean eq(Comparable v, Comparable w) {
        if (v == w) return true;    // optimization when reference equal
        return v.compareTo(w) == 0;
    }
        
    // exchange a[i] and a[j]
    private static void exch(Object[] a, int i, int j) {
        Object swap = a[i];
        a[i] = a[j];
        a[j] = swap;
    }


   /***************************************************************************
    *  Check if array is sorted - useful for debugging.
    ***************************************************************************/
    private static boolean isSorted(Comparable[] a) {
        for (int i = 1; i < a.length; i++)
            if (less(a[i], a[i-1])) return false;
        return true;
    }

    // print array to standard output
    private static void show(Comparable[] a) {
        for (int i = 0; i < a.length; i++) {
            StdOut.println(a[i]);
        }
    }

    /**
     * Reads in a sequence of strings from standard input; quicksorts them
     * (using an optimized version of quicksort);
     * and prints them to standard output in ascending order.
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) {
        String[] a = StdIn.readAllStrings();
        QuickBentleyMcIlroy.sort(a);
        assert isSorted(a);
        show(a);
    }

}

 

优先队列

假如文本account.txt存储了N条交易信息,格式如下,需要从中找出account最大的前M条记录,一种方法是把N条记录都读出来排序后
打印前M条,如果N很大将占用很大的存储空间,此方法不可行,另一种方法是先读取M条记录存储起来,后面每读取1条记录与这M条记录比较找出最小的删除,如果M比较大这种方法代价也比较高昂。
   name         time       account
 *  Thompson    2/27/2000  4747.08
 *  vonNeumann  2/12/1994  4732.35
 *  vonNeumann  1/11/1999  4409.74
 *  Hoare       8/18/1992  4381.21
 *  vonNeumann  3/26/2002  4121.85
使用优先队列可以很好的解决这个问题,对于size为M的优先队列MinPQ,插入和删除最小元素的时间是lg(M),对于这个问题我们可以一直从文本里读取数据,读1条插入一条到优先队列MinPQ,当队列size大于M时,删除最小的元素,当文本内容读完后,MinPQ里存的就是前M条最大记录,代码如下:
public class TopM {   

    // Print the top M lines in the input stream.
    public static void main(String[] args) {
        int M = Integer.parseInt(args[0]);
        MinPQ<Transaction> pq = new MinPQ<Transaction>(M+1);

        while (StdIn.hasNextLine()) {
            // Create an entry from the next line and put on the PQ.
            String line = StdIn.readLine();
            Transaction transaction = new Transaction(line);
            pq.insert(transaction);

            // remove minimum if M+1 entries on the PQ
            if (pq.size() > M)
                pq.delMin();
        }   // top M entries are on the PQ

        // print entries on PQ in reverse order
        Stack<Transaction> stack = new Stack<Transaction>();
        for (Transaction transaction : pq)
            stack.push(transaction);
        for (Transaction transaction : stack)
            StdOut.println(transaction);
    }
}
二叉堆可以很好的实现优先队列,当一棵二叉树的每个节点都大于等于或小于等于它的两个子节点时,它被称为堆有序,这颗二叉树也被称为二叉堆,堆的根节点要么最大,要么最小,根节点最小的二叉堆堆称为最小堆,反之叫最大堆。我们下面默认以最小堆为例。


在一个二叉堆中,对于位置k的节点,它的父节点在k/2,两个子节点在2k,2k+1.
不管怎么存储每个节点,对节点k,只要满足父节点在k/2,两个子节点在2k,2k+1并且每个节点都大于等于或小于等于它的两个子节点,这些节点组成的数据结构就是二叉堆。一般用数组实现二叉堆,代码如下:

public class MinPQ<Item> implements Iterable<Item> {
    Item[] pq;
    int N;
    
    public MinPQ(int initCapacity) {
        pq = (Item[]) new Object[initCapacity+1];
    }
    
    /**
     * Initializes an empty priority queue.
     */
    public MinPQ() {
        this(1);
    }
    
    public boolean isEmpty() { return N == 0; }
    public int size()        { return N;      }
    
    /**
     * Returns a smallest key on this priority queue.
     *
     * @return a smallest key on this priority queue
     * @throws NoSuchElementException if this priority queue is empty
     */
    public Item min() {
        if (isEmpty()) throw new NoSuchElementException("Priority queue underflow");
        return pq[1]; //不使用位置0,因为当k=0时,2k=0,无法表示子节点,从1开始更方便
    }
    
    private void resize(int capacity) {
        assert capacity >= N;
        Item[] temp = (Item[]) new Object[capacity];
        for(int i = 1; i <= N; i++)
            temp[i] = pq[i];
        pq = temp;
    }
    //插入操作首先把节点插入到叶子节点,然后上浮到合适的位置,上浮过程见函数swim(),插入过程如下图所示
    public void insert(Item x) {
        if (N == pq.length-1) resize(pq.length*2);
        // add x, and percolate it up to maintain heap invariant
        pq[++N] = x;
        swim(N);
        assert isMinHeap();
    }
    
    /**
     * Removes and returns a smallest key on this priority queue.
     *
     * @return a smallest key on this priority queue
     * @throws NoSuchElementException if this priority queue is empty
     */
//删除操作,获取根节点的元素就是最小元素,然后把最后一个叶子节点元素交换到根节点,再把根节点元素下沉到指定位置。
//下沉操作键函数sink(),删除过程如下图
    public Item delMin() {
        if (isEmpty()) throw new NoSuchElementException("Priority queue underflow");
        Item min = pq[1];
        exch(1, N--);
        sink(1);
        pq[N+1] = null;     // to avoid loiterig and help with garbage collection
        if ((N > 0) && (N == (pq.length - 1) / 4)) resize(pq.length / 2);
        assert isMinHeap();
        return min;
    }
    //与父节点比较,如果比父节点小则与父节点交换位置,直到到达根节点或者比父节点大
    private void swim(int k) {
        while(k > 1 && greater(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 && greater(j, j+1)) j++;
            if (!greater(k, j)) break;
            exch(k, j);
            k = j;
        }
    }
    
    private void exch(int i, int j) {
        Item swap = pq[i];
        pq[i] = pq[j];
        pq[j] = swap;
    }


    // ex2.4.15
    // is pq[1..N] a min heap?
    private boolean isMinHeap() {
        return isMinHeap(1);
    }

    // is subtree of pq[1..n] rooted at k a min heap?
    private boolean isMinHeap(int k) {
        if (k > N) return true;
        int left = 2*k;
        int right = 2*k + 1;
        if (left  <= N && greater(k, left))  return false;
        if (right <= N && greater(k, right)) return false;
        return isMinHeap(left) && isMinHeap(right);
    }
    
    /***************************************************************************
     * Helper functions for compares and swaps.
     ***************************************************************************/
     private boolean greater(int i, int j) {
         return ((Comparable<Item>)pq[i]).compareTo(pq[j]) > 0;
     }


}

索引优先队列

除了获取优先队列中的最小最大元素,有时候还需要引用优先队列中的其他元素,简单的方法是为每个元素增加一个索引,通过
keyOf(int i)获取指定索引对应的值。
public class IndexMinPQ<Key extends Comparable<Key>> implements Iterable<Integer> {
    private int maxN;        // maximum number of elements on PQ
    private int n;           // number of elements on PQ
    private int[] pq;        // binary heap using 1-based indexing
    private int[] qp;        // inverse of pq - qp[pq[i]] = pq[qp[i]] = i
    private Key[] keys;      // keys[i] = priority of i

    /**
     * Initializes an empty indexed priority queue with indices between {@code 0}
     * and {@code maxN - 1}.
     * @param  maxN the keys on this priority queue are index from {@code 0}
     *         {@code maxN - 1}
     * @throws IllegalArgumentException if {@code maxN < 0}
     */
    public IndexMinPQ(int maxN) {
        if (maxN < 0) throw new IllegalArgumentException();
        this.maxN = maxN;
        n = 0;
        keys = (Key[]) new Comparable[maxN + 1];    // make this of length maxN??
        pq   = new int[maxN + 1];
        qp   = new int[maxN + 1];                   // make this of length maxN??
        for (int i = 0; i <= maxN; i++)
            qp[i] = -1;
    }

    /**
     * Returns true if this priority queue is empty.
     *
     * @return {@code true} if this priority queue is empty;
     *         {@code false} otherwise
     */
    public boolean isEmpty() {
        return n == 0;
    }

    /**
     * Is {@code i} an index on this priority queue?
     *
     * @param  i an index
     * @return {@code true} if {@code i} is an index on this priority queue;
     *         {@code false} otherwise
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     */
    public boolean contains(int i) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        return qp[i] != -1;
    }

    /**
     * Returns the number of keys on this priority queue.
     *
     * @return the number of keys on this priority queue
     */
    public int size() {
        return n;
    }

    /**
     * Associates key with index {@code i}.
     *
     * @param  i an index
     * @param  key the key to associate with index {@code i}
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @throws IllegalArgumentException if there already is an item associated
     *         with index {@code i}
     */
    public void insert(int i, Key key) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        if (contains(i)) throw new IllegalArgumentException("index is already in the priority queue");
        n++;
        qp[i] = n;
        pq[n] = i;
        keys[i] = key;
        swim(n);
    }

    /**
     * Returns an index associated with a minimum key.
     *
     * @return an index associated with a minimum key
     * @throws NoSuchElementException if this priority queue is empty
     */
    public int minIndex() {
        if (n == 0) throw new NoSuchElementException("Priority queue underflow");
        return pq[1];
    }

    /**
     * Returns a minimum key.
     *
     * @return a minimum key
     * @throws NoSuchElementException if this priority queue is empty
     */
    public Key minKey() {
        if (n == 0) throw new NoSuchElementException("Priority queue underflow");
        return keys[pq[1]];
    }

    /**
     * Removes a minimum key and returns its associated index.
     * @return an index associated with a minimum key
     * @throws NoSuchElementException if this priority queue is empty
     */
    public int delMin() {
        if (n == 0) throw new NoSuchElementException("Priority queue underflow");
        int min = pq[1];
        exch(1, n--);
        sink(1);
        assert min == pq[n+1];
        qp[min] = -1;        // delete
        keys[min] = null;    // to help with garbage collection
        pq[n+1] = -1;        // not needed
        return min;
    }

    /**
     * Returns the key associated with index {@code i}.
     *
     * @param  i the index of the key to return
     * @return the key associated with index {@code i}
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @throws NoSuchElementException no key is associated with index {@code i}
     */
    public Key keyOf(int i) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
        else return keys[i];
    }

    /**
     * Change the key associated with index {@code i} to the specified value.
     *
     * @param  i the index of the key to change
     * @param  key change the key associated with index {@code i} to this key
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @throws NoSuchElementException no key is associated with index {@code i}
     */
    public void changeKey(int i, Key key) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
        keys[i] = key;
        swim(qp[i]);
        sink(qp[i]);
    }

    /**
     * Change the key associated with index {@code i} to the specified value.
     *
     * @param  i the index of the key to change
     * @param  key change the key associated with index {@code i} to this key
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @deprecated Replaced by {@code changeKey(int, Key)}.
     */
    @Deprecated
    public void change(int i, Key key) {
        changeKey(i, key);
    }

    /**
     * Decrease the key associated with index {@code i} to the specified value.
     *
     * @param  i the index of the key to decrease
     * @param  key decrease the key associated with index {@code i} to this key
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @throws IllegalArgumentException if {@code key >= keyOf(i)}
     * @throws NoSuchElementException no key is associated with index {@code i}
     */
    public void decreaseKey(int i, Key key) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
        if (keys[i].compareTo(key) <= 0)
            throw new IllegalArgumentException("Calling decreaseKey() with given argument would not strictly decrease the key");
        keys[i] = key;
        swim(qp[i]);
    }

    /**
     * Increase the key associated with index {@code i} to the specified value.
     *
     * @param  i the index of the key to increase
     * @param  key increase the key associated with index {@code i} to this key
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @throws IllegalArgumentException if {@code key <= keyOf(i)}
     * @throws NoSuchElementException no key is associated with index {@code i}
     */
    public void increaseKey(int i, Key key) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
        if (keys[i].compareTo(key) >= 0)
            throw new IllegalArgumentException("Calling increaseKey() with given argument would not strictly increase the key");
        keys[i] = key;
        sink(qp[i]);
    }

    /**
     * Remove the key associated with index {@code i}.
     *
     * @param  i the index of the key to remove
     * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     * @throws NoSuchElementException no key is associated with index {@code i}
     */
    public void delete(int i) {
        if (i < 0 || i >= maxN) throw new IllegalArgumentException();
        if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
        int index = qp[i];
        exch(index, n--);
        swim(index);
        sink(index);
        keys[i] = null;
        qp[i] = -1;
    }


   /***************************************************************************
    * General helper functions.
    ***************************************************************************/
    private boolean greater(int i, int j) {
        return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
    }

    private void exch(int i, int j) {
        int swap = pq[i];
        pq[i] = pq[j];
        pq[j] = swap;
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }


   /***************************************************************************
    * Heap helper functions.
    ***************************************************************************/
    private void swim(int k) {
        while (k > 1 && greater(k/2, k)) {
            exch(k, k/2);
            k = k/2;
        }
    }

    private void sink(int k) {
        while (2*k <= n) {
            int j = 2*k;
            if (j < n && greater(j, j+1)) j++;
            if (!greater(k, j)) break;
            exch(k, j);
            k = j;
        }
    }

索引优先队列的用例Multiway

有N个文本文件,每个文件里存储着按顺序排列的字母,读取所有文件内容,按顺序打印出来
使用索引优先队列首先读取N个文件的第1个字母,插入优先队列,索引从0到N代表N个文件的编号,值是每个文本里第1个字母
后面从队列中每打印删除一个最小元素,同时可以获取这个元素所在文件的索引编号,再从这个文件读取一个字母插入优先队列
保持队列大小为N,直到所有文件内容都读取完,对于代码如下
这个用例也可以用一般的优先队列实现,用一个对象封装两个成员index,value,这样在删除元素时可以通过这个对象的index成员获取对应的文件编号

}
/*************************************************************************
 *  Compilation:  javac Multiway.java
 *  Execution:    java Multiway input1.txt input2.txt input3.txt ...
 *  Dependencies: IndexMinPQ.java In.java StdOut.java
 *
 *  Merges together the sorted input stream given as command-line arguments
 *  into a single sorted output stream on standard output.
 *
 *  % more m1.txt
 *  A B C F G I I Z
 *
 *  % more m2.txt
 *  B D H P Q Q
 *
 *  % more m3.txt
 *  A B E F J N
 *
 *  % java Multiway m1.txt m2.txt m3.txt
 *  A A B B B C D E F F G H I I J N P Q Q Z
 *
 *************************************************************************/

public class Multiway {

    public static void merge(In[] streams) {
        int N = streams.length;
        IndexMinPQ<String> pq = new IndexMinPQ<String>(N);
        for (int i = 0; i < N; i++)
            if (!streams[i].isEmpty())
                pq.insert(i, streams[i].readString());

        // Extract and print min and read next from its stream.
        while (!pq.isEmpty()) {
            StdOut.print(pq.minKey() + " ");
            int i = pq.delMin();
            if (!streams[i].isEmpty())
                pq.insert(i, streams[i].readString());
        }
        StdOut.println();
    }


    public static void main(String[] args) {
        int N = args.length;
        In[] streams = new In[N];
        for (int i = 0; i < N; i++)
            streams[i] = new In(args[i]);
        merge(streams);
    }
}

堆排序
由优先队列衍生的的排序算法叫堆排序,思想很容易理解,把待排序的数组每个元素插入优先队列,再反复删除优先队列中每个元素回写到待排序数组。
这样需要增加额外的存储空间,可以直接在待排序数组中构造优先队列进行排序,代码如下:堆排序的时间复杂度是O(NlogN)
public class Heap {
    
    /**
     * Rearranges the array in ascending order, using the natural order.
     * @param pq the array to be sorted
     */
    public static void sort(Comparable[] pq) {
        int n = pq.length;
        //从k=n/2开始构造最大堆,这样从k到1都是有子节点的父节点,通过下沉操作,让k和它的子节点构成的子树堆有序,再让k-1和它的子节点构成的子树堆有序
        //最后让1和它的子节点构成的树堆有序,这样就把整个数组构成了一个最大堆,示意图如下:
        for (int k = n/2; k >= 1; k--)
            sink(pq, k, n);
        //最大堆构造完成后模拟删除操作,反复删除队列中最大元素依次放到数组n到1的位置,这样数组就有序了,示意图如下:
        while (n > 1) {
            exch(pq, 1, n--);
            sink(pq, 1, n);
        }
    }

   /***************************************************************************
    * Helper functions to restore the heap invariant.
    ***************************************************************************/

    private static void sink(Comparable[] pq, int k, int n) {
        while (2*k <= n) {
            int j = 2*k;
            if (j < n && less(pq, j, j+1)) j++;
            if (!less(pq, k, j)) break;
            exch(pq, k, j);
            k = j;
        }
    }
    
    /***************************************************************************
     * Helper functions for comparisons and swaps.
     * Indices are "off-by-one" to support 1-based indexing.
     ***************************************************************************/
    //这里二叉堆中节点k的元素是存储在数组k-1的位置的,前面说过不管节点的元素存储在哪里,只要通过k,k/2,2k,2k+1可以找到相应的父子节点,并且k/2>=k>=2k,2k+1,这些节点构成的我就是二叉堆
     private static boolean less(Comparable[] pq, int i, int j) {
         return pq[i-1].compareTo(pq[j-1]) < 0;
     }

     private static void exch(Object[] pq, int i, int j) {
         Object swap = pq[i-1];
         pq[i-1] = pq[j-1];
         pq[j-1] = swap;
     }
    
    private static void show(Comparable[] a) {
        for (int i = 0; i < a.length; i++) {
            StdOut.println(a[i]);
        }
    }

    public static void main(String[] args) {
        Integer[] arr = {7,1,3,78,23,45,2,4,9,99,16};
        Heap.sort(arr);
        show(arr);
    }

}

/*
 * ex 2.4.25
 *
 * 计算数论。
 * 编写程序 CubeSum.java,
 * 在不使用额外空间的条件下,
 * 按大小顺序打印所有 a^3+b^3 的结果,
 * 其中 a 和 b 为 0 至 N 之间所有的整数。
 * 思路:这个问题可以用类似Mutiway问题的思路来理解,当a=0时,b可以取0~N并且值是递增的,当a=1时,b可以取1~N,当a=N时,b可以取N~N
 * 把a=0时的所有情况的值归为1个文本中,把a=1时的所有情况的值归为1个文本中...把a=N时的所有情况的值归为1个文本中,可以归为N+1个文本,
 * 跟Mutiway类似先把每个文本的第1个值插入优先队列,就是a=b=0,a=b=1到a=b=N.然后每从队列删除打印一个值就从对应文本读一个值再插入到优先队列
 * 保持队列大小为N+1,直到所有文本读完。
 * 用这段程序找出 0 到 10^6 之间
 * 所有满足 a^3+b^3 = c^3+d^3 的不同整数 a, b, c, d。
 * 当a^3+b^3 = c^3+d^3时,a,b,c,d会在相邻位置打印,打印时记住上次打印的值对比下就找到了,如下列打印结果的1729
 * 1728 = 0^3 + 12^3
 * 1729 = 1^3 + 12^3
 * 1729 = 9^3 + 10^3
 * 1736 = 2^3 + 12^3
 * 1755 = 3^3 + 12^3
/******************************************************************************
 *  Compilation:  javac CubeSum.java
 *  Execution:    java CubeSum n
 *  Dependencies: MinPQ.java
 *
 *  Print out integers of the form a^3 + b^3 in sorted order, where
 *  0 <= a <= b <= n.
 *
 *  % java CubeSum 10
 *  0 = 0^3 + 0^3
 *  1 = 0^3 + 1^3
 *  2 = 1^3 + 1^3
 *  8 = 0^3 + 2^3
 *  9 = 1^3 + 2^3
 *  ...
 *  1729 = 9^3 + 10^3
 *  1729 = 1^3 + 12^3
 *  ...
 *  3456 = 12^3 + 12^3
 *
 *  Remarks
 *  -------
 *   - Easily extends to handle sums of the form f(a) + g(b)
 *   - Prints out a sum more than once if it can be obtained
 *     in more than one way, e.g., 1729 = 9^3 + 10^3 = 1^3 + 12^3
 *
 ******************************************************************************/

public class CubeSum implements Comparable<CubeSum> {
    private final int sum;
    private final int i;
    private final int j;

    public CubeSum(int i, int j) {
        this.sum = i*i*i + j*j*j;
        this.i = i;
        this.j = j;
    }

    public int compareTo(CubeSum that) {
        if (this.sum < that.sum) return -1;
        if (this.sum > that.sum) return +1;
        return 0;
    }

    public String toString() {
        return sum + " = " + i + "^3" + " + " + j + "^3";
    }


    public static void main(String[] args) {

        int n = Integer.parseInt(args[0]);

        // initialize priority queue
        MinPQ<CubeSum> pq = new MinPQ<CubeSum>();
        for (int i = 0; i <= n; i++) {
            pq.insert(new CubeSum(i, i));
        }

        // find smallest sum, print it out, and update
        while (!pq.isEmpty()) {
            CubeSum s = pq.delMin();
            StdOut.println(s);
            if (s.j < n)
                pq.insert(new CubeSum(s.i, s.j + 1));
        }
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《算法笔记》是由胡凡编写的一本算法学习的教材,它最初以C/C++为主要编程语言。这本教材主要面向想要学习算法的初学者,以及希望进一步提升算法能力的人群。 《算法笔记》的内容广泛而且深入,涵盖了很多算法的基础知识和主要思想,如递归、排序、查找、图论、动态规划等。通过学习这本教材,读者可以掌握这些算法的基本原理和实现方法,提高解决实际问题的能力。 该教材有几个特点:首先,它提供了很多例子和习题,帮助读者巩固所学的知识;其次,它介绍了很多常用的数据结构,如数组、链表、栈、队列等,读者可以通过学习这些数据结构更好地理解和应用算法;最后,它还介绍了一些高级主题,如高级数据结构、算法优化等,这些内容对于进一步提升算法水平非常有帮助。 《算法笔记》是一本入门级的教材,因此在阅读时需要一些基本的编程知识和逻辑思维能力。该教材的语言简洁明快,适合作为学习参考书,同时也可以作为算法竞赛的辅助教材。 总而言之,《算法笔记》是一本很好的算法学习教材,它以C/C++为编程语言,全面介绍了算法的基本知识和常用方法,适合想要学习算法的初学者。无论是学术研究还是实际应用,阅读《算法笔记》都能提升算法能力,并为进一步学习和应用算法打下坚实的基础。 ### 回答2: 《算法笔记 胡凡 c/c 快速入门pdf》是一本介绍算法和C/C++编程语言的入门书籍。该书的作者是胡凡,它主要规划了算法的学习路径以及基本的C/C++编程语言。这本书适合初学者快速入门,对于想要系统学习算法和C/C++编程的人来说是一本不错的选择。 这本书的内容非常系统和全面。它以算法和数据结构为基础,包括基本排序、查找、图论等算法的介绍和实践,让读者能够快速掌握这些算法的原理和实现方法。同时,它还介绍了C/C++语言的基础知识和编程技巧,帮助读者理解和运用这些知识。 书中每都有一些练习题,帮助读者巩固所学的知识。同时,每个节末尾还提供了一些进阶的题目和参考答案,供读者深入学习和自我检测。这样的设计能够帮助读者更好地理解和掌握所学的内容。 总的来说,《算法笔记 胡凡 c/c 快速入门pdf》是一本很好的算法和C/C++入门书籍。它能够帮助读者快速了解算法和数据结构的基础知识,并学会使用C/C++语言进行编程。无论是对于想要入门算法和C/C++编程的初学者,还是已经有一定基础的读者,这本书都是一个很好的选择。 ### 回答3: 《算法笔记:胡凡C/C++快速入门PDF》是一本很棒的入门算法书籍。这本书主要介绍了常用的数据结构与算法,并通过C/C++语言来实现这些算法。 首先,这本书非常适合算法初学者。它从基础的数据结构开始讲解,如数组、链表、栈和队列,然后逐渐引入更复杂的数据结构,如二叉树、图和堆。此外,书中还介绍了常用的排序和查找算法,如冒泡排序、快速排序、二分查找等。每个算法都配有具体的代码实现和详细的解释,帮助读者理解算法的原理和应用。 其次,这本书的学习资料丰富。书中提供了很多例题和习题,读者可以通过实践来巩固所学的知识。此外,书中还介绍了一些常见的算法优化技巧和设计思路,提供了一些高效解决问题的方法和思考方式。 最后,这本书的编写风格简明易懂。作者通过清晰的语言和简洁的代码,将复杂的算法问题简化为易于理解的部分。不论是对于算法初学者还是对于有一定编程基础的读者,这本书都是一本很好的学习资料。 总而言之,《算法笔记:胡凡C/C++快速入门PDF》是一本很好的入门算法书籍。它适合初学者学习和理解常用的数据结构与算法,通过具体的代码实现和详细的解释帮助读者掌握算法的原理和应用。无论是编程初学者还是有一定基础的读者,都可以从这本书中获得丰富的知识和实践经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值