普林斯顿大学_算法公开课:Part_1_第一部分: Deques and Randomized Queues\Collinear Points\8 Puzzle\kd-Trees

本文主要介绍普林斯顿算法公开课的Part_1部分的核心算法,整体分两篇文章介绍。java

对于并查集相关内容请查看文章:https://blog.csdn.net/GZHarryAnonymous/article/details/80384195node

第二部分:Stacks and Queuesredis

对于这部分的内容比较简单,主要就是队列、栈的基本操做。算法

代码以下:数组

package stackQueue;


import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import java.util.Iterator;
import java.util.NoSuchElementException;


public class Deque<Item> implements Iterable<Item> {//java泛型应用。迭代器实现。
    private int N;//节点总数。
    private Node first, last;//因为是双端队列,因此有两个队首指针。


    private class Node {//节点类定义。
        private Item item;//数据域。
        private Node previous, next;//节点先后双指针域。
    }


    public Deque() {//双端队列构造函数初始化。
        N = 0;
        first = null;
        last = first;
    }


    public boolean isEmpty() {//判队空。
        return N == 0;
    }


    public int size() {//当前队列大小。
        return N;
    }


    public void addFirst(Item item) {//first端入队。
        if (item == null) {
            throw new IllegalArgumentException();
        }
        Node node = new Node();
        node.item = item;
        if (!isEmpty()) {//若是队列不为空,在first端添加新节点。
            node.previous = null;//最终first指向的节点的左手空空。
            node.next = first;//链接,新节点右手拉原first侧端节点。
            first.previous = node;//链接,原first侧端节点左手拉新节点。
            first = node;//first指向的是一端的节点指针。
        } else {//添加的是第一个节点。注意:这里双队列指针同时指向的是惟一的一个节点。
            node.previous = null;
            node.next = null;
            first = node;
            last = first;
        }
        N++;//节点个数加一。
    }


    public void addLast(Item item) {//last端入队。与first端入队是彻底等价的。本身体会吧!
        if (item == null) {
            throw new IllegalArgumentException();
        }
        Node node = new Node();
        node.item = item;
        if (!isEmpty()) {
            node.next = null;
            node.previous = last;//其实,这个语句是能够和下一条语句换位置的。
            last.next = node;
            last = node;
        } else {
            node.previous = null;
            node.next = null;
            last = node;
            first = last;
        }
        N++;
    }


    public Item removeFirst() {//first端出队。
        if (isEmpty()) {
            throw new NoSuchElementException();
        }
        Item item = first.item;
        if (N == 1) {
            first = null;
            last = null;
        } else {
            first = first.next;
            first.previous = null;
        }
        N--;
        return item;
    }


    public Item removeLast() {//last端出队。建议你们把整个过程想象成一群小朋友手拉手。
        if (isEmpty()) {
            throw new NoSuchElementException();
        }
        Item item = last.item;
        if (N == 1) {
            first = null;
            last = null;
        } else {
            last = last.previous;
            last.next = null;
        }
        N--;
        return item;
    }


    public Iterator<Item> iterator() {
        return new ListIterator(first);
    }


    private class ListIterator implements Iterator<Item> {
        private Node current = first;//迭代过程是first端开始单项的。


        public ListIterator(Node first) {
            current = first;
        }


        public boolean hasNext() {
            return current != null;
        }


        public void remove() {
            throw new UnsupportedOperationException();
        }


        public Item next() {
            if (!hasNext())
                throw new NoSuchElementException();


            Item item = current.item;
            current = current.next;
            return item;
        }
    }


    public static void main(String[] args) {
        Deque<String> q = new Deque<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-") && !item.equals("#")) {
                q.addLast(item);
            } else if (item.equals("-")) {
                StdOut.print(q.removeFirst() + " ");
            } else if (item.equals("#")) {
                StdOut.print(q.removeLast() + " ");
            }
        }
        StdOut.println("(" + q.size() + " left on queue)");
    }


app

}less

随机队列的实现也很简单!dom

代码以下:ide

package stackQueue;


import java.util.Iterator;
import java.util.NoSuchElementException;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;


public class RandomizedQueue<Item> implements Iterable<Item> {
    private Item[] q;
    private int N = 0;


    public RandomizedQueue() {
        q = (Item[]) new Object[1];//向下转型。
    }


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


    public int size() {
        return N;
    }


    private void resize(int max) {
        Item[] temp = (Item[]) new Object[max];
        for (int i = 0; i < N; i++) {
            temp[i] = q[i];
        }
        q = temp;
    }


    public void enqueue(Item item) {
        if (item == null)
            throw new IllegalArgumentException();
        if (N == q.length)
            resize(2 * q.length);//队满,则自动乘以2倍。扩容。
        q[N++] = item;
    }


    public Item dequeue() {
        if (isEmpty())
            throw new NoSuchElementException();
        int offset = StdRandom.uniform(N);
        Item item = q[offset];
        if (offset != N - 1)
            q[offset] = q[N - 1];
        q[N - 1] = null;
        N--;
        if (N > 0 && N == q.length / 4)//队长只到1/4,容量减半。
            resize(q.length / 2);
        return item;
    }


    public Item sample() {
        if (isEmpty())
            throw new NoSuchElementException();
        int offset = StdRandom.uniform(N);
        return q[offset];
    }


    public Iterator<Item> iterator() {
        return new ArrayIterator();
    }


    private class ArrayIterator implements Iterator<Item> {
        private Item[] copyArray = (Item[]) new Object[q.length];
        private int copyN = N;


        public ArrayIterator() {
            for (int i = 0; i < q.length; i++) {
                copyArray[i] = q[i];
            }
        }


        public boolean hasNext() {
            return copyN != 0;
        }


        public void remove() {
            throw new UnsupportedOperationException();
        }


        public Item next() {// 随机读取。
            if (!hasNext())
                throw new NoSuchElementException();
            int offset = StdRandom.uniform(copyN);
            Item item = copyArray[offset];
            if (offset != copyN - 1) {
                copyArray[offset] = copyArray[copyN - 1];
            }
            copyArray[copyN - 1] = null;
            copyN--;
            return item;
        }
    }


    public static void main(String[] args) {
        RandomizedQueue<String> q = new RandomizedQueue<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-"))
                q.enqueue(item);
            else if (!q.isEmpty())
                StdOut.print(q.dequeue() + " ");
        }
        StdOut.println("(" + q.size() + " left on queue)");
    }
函数

}

第三部分:MergeSort

问题描述:随机的生成一些点,请找出全部的4点或多余4点同线的状况。

主体代码对比分析:

方法一,暴力破解,没啥说的,随着问题规模的增长,时间复杂度没法接受。

public BruteCollinearPoints(Point[] points) {
        if (points == null)
            throw new java.lang.IllegalArgumentException("the points is null!");
        Arrays.sort(points);
        for (int i = 0; i < points.length; i++) {
            if (points[i] == null)
                throw new java.lang.IllegalArgumentException("the points[i] is null!");
            if (i < points.length - 1 && points[i].compareTo(points[i + 1]) == 0)
                throw new java.lang.IllegalArgumentException("there exists repeated points!");
        }
        ls = new ArrayList<LineSegment>();
        for (int i = 0; i < points.length - 3; i++) {
            for (int j = i + 1; j < points.length - 2; j++) {
                // double curSlope = points[i].slopeTo(points[j]);
                for (int k = j + 1; k < points.length - 1; k++) {
                    for (int m = k + 1; m < points.length; m++) {
                        if (points[i].slopeTo(points[j]) == points[i].slopeTo(points[k])
                                && points[i].slopeTo(points[j]) == points[i].slopeTo(points[m])) {
                            LineSegment cur = new LineSegment(points[i], points[m]);
                            ls.add(cur);
                        }
                    }
                }
            }
        }
    } // finds all line segments containing 4 points

方法二,应用归并排序。

将全部斜率排序,实现逻辑较为复杂。

public FastCollinearPoints(Point[] points) {
        if (points == null)
            throw new java.lang.IllegalArgumentException("the points is null!");
        Arrays.sort(points);//左上到右下的规则,排序点按照位置。
        for (int i = 0; i < points.length; i++) {
            if (points[i] == null)
                throw new java.lang.IllegalArgumentException("the points[i] is null!");
            if (i < points.length - 1 && points[i].compareTo(points[i + 1]) == 0)
                throw new java.lang.IllegalArgumentException("there exists repeated points!");
        }
        ls = new ArrayList<LineSegment>();
        ArrayList<Point> tmp = new ArrayList<Point>();
        for (int i = 0; i < points.length; i++) {//遍历全部斜率。
            for (int j = 0; j < points.length; j++) {
                if (i != j)
                    tmp.add(points[j]);// 当前节点是i,和其余节点进行比较。
            }
            Collections.sort(tmp, points[i].slopeOrder());// 斜率排序。


            int curIndex = 0;
            int minTag = curIndex;
            int maxTag = curIndex;
            while (curIndex + 2 < tmp.size()) {// 保证至少有4个点。
                // double curSlope = points[i].slopeTo(tmp.get(curIndex));


                if (points[i].slopeTo(tmp.get(curIndex)) == points[i].slopeTo(tmp.get(curIndex + 2))) {

//这里勉强算是有一个技巧,因为斜率按大小排序后,只比较1,2的斜率与1,4的斜率就能够。

//但因为点是无序的,归并排序斜率的代价是点变得无序。因此接下来要肯定4点同线的端点。

                    if (tmp.get(maxTag).compareTo(tmp.get(curIndex + 1)) == -1) {
                        maxTag = curIndex + 1;
                    } else {
                        minTag = curIndex + 1;//线段最小段不包括i,由于i是有序的遍历,确定为更小。在后面有更为巧妙的应用。
                    }
                    if (tmp.get(maxTag).compareTo(tmp.get(curIndex + 2)) == -1) {
                        maxTag = curIndex + 2;
                    } else if (tmp.get(minTag).compareTo(tmp.get(curIndex + 2)) == 1) {
                        minTag = curIndex + 2;
                    }
                    int maxIndex = curIndex + 2;
                    while (maxIndex + 1 < tmp.size()
                            && points[i].slopeTo(tmp.get(curIndex)) == points[i].slopeTo(tmp.get(maxIndex + 1))) {
                        maxIndex++;//考虑多余4点同线的状况。
                        if (tmp.get(maxTag).compareTo(tmp.get(maxIndex)) == -1) {
                            maxTag = maxIndex;//从新确立端点。
                        } else if (tmp.get(minTag).compareTo(tmp.get(maxIndex)) == 1) {
                            minTag = maxIndex;
                        }
                    } /*
                       * if (maxIndex + 1 < tmp.size()) { curIndex = maxIndex + 1;
                       * 
                       * } else { curIndex = maxIndex;
                       * 
                       * }
                       */
                    // System.out.println(points[minTag] + " " + points[maxTag]);
                    curIndex = maxIndex + 1;
                    if (points[i].compareTo(tmp.get(minTag)) == -1) {//关键点:避免了重复,仅添加最长的那条线段。应用巧妙。
                        LineSegment cur = new LineSegment(points[i], tmp.get(maxTag));
                        ls.add(cur);
                    }
                    minTag = curIndex;
                    maxTag = curIndex;


                } else {
                    curIndex += 1;
                    minTag = curIndex;
                    maxTag = curIndex;
                }


            }
            tmp.clear();
        } // finds all line segments containing 4 or more points
    }

第三部分:Priority Queues

问题描述:8-puzzle

可能更多的人知道华容道,8-puzzle相似,一个九宫格的面板,8个方块数字,还有一个方块区域空白,经过移动面板将8个方块数字的顺序复原。

思路分析:经过二叉小根堆实现优先级队列,启发式算法追寻最优方案。启发式算法属于A*(A-star算法的一类),百度百科的定义为——A*算法,A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。

我的理解所谓的启发式算法就是在(有限多)无限多的可能中优先寻找那些最像是结果的方案。具体实现过程当中,关键在于如何分辨哪些方案更接近最佳目标?这就引入了一个判断的依据:估价函数。在本例中,估价函数就是哈夫曼距离或者是海明距离。

若是咱们用公式来描述启发式算法,则有f(n)=g(n)+h(n),其中 f(n) 是从初始状态经由状态n到目标状态的代价估计,g(n) 是在状态空间中从初始状态到状态n的实际代价,h(n) 是从状态n到目标状态的最佳路径的估计代价。(对于路径搜索问题,状态就是图中的节点,代价就是距离)

我的理解g(n)就是初始状态经由当前状态到达实际的最优状态的代价,判别实际最优状态与否总要有一个标准,本例中的标准就是移动的数字格子的总次数最少,即代价最小复原面板顺序。注意哦,f(n)是初始状态到达实际的最优状态的估计,而“经由当前状态”意味着该算法是一个不断迭代的过程,且初始状态下,f(0)=g(0)+h(0)=h(0).而最终呢?咱们想要的且应该获得的结果是:f(n)=g(n)+h(n)=g(n).咱们试想一下,该算法的不断迭代过程,同时也是h(0)不断减少而g(n)不断增大的过程。那么,咱们如何肯定这个h(0)呢?这个h(0)与g(n)又有什么关系呢?假设某个函数能恰好肯定最少的移动次数,也就是h(0)=g(n),那么在这种理想状态下,直接一步到位,ok!那若是h(0)<g(n)呢?从直觉上讲,若是你一开始估计的代价比最优的实际代价还要优,这每每也意味着你这个估计是不太合理的。最开始的疏忽固然也意味着后期费力的弥补。实际状况是,搜索的点数多,范围大,效率低,但皇天不负有心人,你最终必定能找到那个最优解,最坏的打算也就是近乎遍历全部的可能吧!但一般状况下,咱们更加指望的是可以更快的寻找到最优的状态,咱们没法直接找到最优,但能够大体判断何为更优。在本例中,哈夫曼距离和海明距离就是迭代的一步步判断何为更优,并朝着更优的方向去前进,摸索前行。固然,其h(0)>g(n),其描述更合理贴切,效率更高,更具启发意义。

备注:海明距离必定对应h(0)>g(n)的状况吗?反过来想想,每移动一次数字格子,最多只能使得海明距离加一,不是吗?!

代码详解:

package puzzle8;


import java.util.ArrayList;


public class Board {
    private static final int BLANK = 0;// 静态最终变量,也称做编译器常量。
    // 其值是在编译期间肯定的,运行期间不可改变。固然,若想改变你能够从新编译。
    private final int n;// 面板边长。
    private int[][] blocks;// 二维数组模拟方形面板。


    public Board(int[][] inBlocks) {
        // construct a board from an n-by-n array of blocks
        // (where blocks[i][j] = block in row i, column j)
        n = inBlocks.length;
        blocks = new int[n][n];
        copy(blocks, inBlocks);
    }


    private void copy(int[][] toBlocks, int[][] fromBlocks) {
        for (int row = 0; row < n; row++)
            for (int col = 0; col < n; col++)
                toBlocks[row][col] = fromBlocks[row][col];
    }


    public int dimension() {
        // board dimension n
        return n;
    }


    private int getRow(int value) {
        return (value - 1) / n;
    }


    private int getCol(int value) {
        return (value - 1) % n;
    }


    private int getValue(int row, int col) {
        return row * n + col + 1;
    }


    public int hamming() {// 海明距离的计算:若是当前位置对应数字与实际数字编号不符,则距离加一。
        // number of blocks out of place
        int hamming = 0;
        for (int row = 0; row < n; row++)
            for (int col = 0; col < n; col++)
                if (blocks[row][col] != BLANK && blocks[row][col] != getValue(row, col))
                    hamming++;
        return hamming;
    }


    public int manhattan() {// 曼哈顿距离的计算:若是当前位置对应的数字与实际的数字编号不符,则距离加上其归位所需的最小步数。
        // sum of Manhattan distances between blocks and goal
        int manhattan = 0;
        for (int row = 0; row < n; row++)
            for (int col = 0; col < n; col++)
                if (blocks[row][col] != BLANK && blocks[row][col] != getValue(row, col))
                    manhattan += Math.abs(getRow(blocks[row][col]) - row) + Math.abs(getCol(blocks[row][col]) - col);
        return manhattan;
    }


    public boolean isGoal() {// 判别是否都已归位。
        // is this board the goal board?
        for (int row = 0; row < n; row++)
            for (int col = 0; col < n; col++)
                if (blocks[row][col] != BLANK && blocks[row][col] != getValue(row, col))
                    return false;
        return true;
    }


    public Board twin() {// 某个经过原始序列面板单次转换获得的异常面板,之因此称之为异常是因为其正常调试移动没法复原。
        // 具体是图论的一些知识,能够类比没法复原的模仿哈!
        // a board that is obtained by exchanging any pair of blocks
        Board twinBoard = new Board(blocks);
        int firRow = 0;
        int firCol = 0;
        if (blocks[firRow][firCol] == BLANK)
            firCol++;
        for (int row = 0; row < n; row++) {
            for (int col = 0; col < n; col++) {
                if (blocks[row][col] != blocks[firRow][firCol] && blocks[row][col] != BLANK) {
                    twinBoard.swap(firRow, firCol, row, col);
                    return twinBoard;
                }
            }
        }
        return twinBoard;
    }


    private void swap(int vRow, int vCol, int wRow, int wCol) {// 交换位置。
        int t = blocks[vRow][vCol];
        blocks[vRow][vCol] = blocks[wRow][wCol];
        blocks[wRow][wCol] = t;
    }


    public boolean equals(Object y) {// 面板相同与否比较。
        // does this board equal y?
        if (y == null)
            return false;
        if (y == this)
            return true;
        if (y.getClass().isInstance(this)) {
            Board yb = (Board) y;
            if (yb.n != this.n)
                return false;
            else {
                for (int row = 0; row < n; row++)
                    for (int col = 0; col < n; col++)
                        if (yb.blocks[row][col] != blocks[row][col])
                            return false;
                return true;
            }
        } else
            return false;
    }


    public Iterable<Board> neighbors() {// 当前面板移动一步的全部可能状况,放到可迭代数组链表中。
        // all neighboring boards
        ArrayList<Board> neighbors = new ArrayList<Board>();
        for (int row = 0; row < n; row++) {
            for (int col = 0; col < n; col++) {
                if (blocks[row][col] == BLANK) {


                    if (row > 0) {// 上移
                        Board neighborT = new Board(blocks);
                        neighborT.swap(row, col, row - 1, col);
                        neighbors.add(neighborT);
                    }


                    if (row < n - 1) {// 下移
                        Board neighborB = new Board(blocks);
                        neighborB.swap(row, col, row + 1, col);
                        neighbors.add(neighborB);
                    }


                    if (col > 0) {// 左移
                        Board neighborL = new Board(blocks);
                        neighborL.swap(row, col, row, col - 1);
                        neighbors.add(neighborL);
                    }


                    if (col < n - 1) {// 右移
                        Board neighborR = new Board(blocks);
                        neighborR.swap(row, col, row, col + 1);
                        neighbors.add(neighborR);
                    }
                }
            }
        }
        return neighbors;
    }


    public String toString() {// 面板输出
        // string representation of this board (in the output format specified
        // below)
        StringBuilder sb = new StringBuilder();
        sb.append(n + "\n");
        for (int row = 0; row < n; row++) {
            for (int col = 0; col < n; col++) {


                sb.append(String.format("%2d ", blocks[row][col]));
            }
            sb.append("\n");
        }
        return sb.toString();
    }


    public static void main(String[] args) {
        // unit tests (not graded)
        int[][] test = { { 0, 1 }, { 2, 3 } };
        Board b = new Board(test);
        System.out.println(b);
        System.out.println(b.hamming());
        System.out.println(b.manhattan());
    }
}

package puzzle8;


import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.Stack;
import edu.princeton.cs.algs4.StdOut;


public class Solver {
    private SearchNode currentNode;// 当前面板。
    // private SearchNode twincurrentNode;
    private Stack<Board> solution;// 以面板做为Item的堆栈,用于刻画描述启发式算法流程。


    private class SearchNode implements Comparable<SearchNode> {
        public Board board;
        public int moves;
        public SearchNode preSearchNode;// 变换到当前面板的前一个面板。


        public final int priority;// 权重即系数,也就是当前方案累计的曼哈顿距离。


        public SearchNode(Board inboard, SearchNode inPreSearchNode) {
            board = inboard;
            preSearchNode = inPreSearchNode;
            if (inPreSearchNode == null)
                moves = 0;
            else
                moves = inPreSearchNode.moves + 1;
            priority = moves + board.manhattan();// 当前采用曼哈顿距离,也能够采用海明距离。
        }


        @Override
        public int compareTo(SearchNode o) {// 面板的大小比较的依据是priority,依据是曼哈顿距离。
            return Integer.compare(this.priority, o.priority);
        }
    }


    public Solver(Board initial) {
        // find a solution to the initial board (using the A* algorithm)
        if (initial == null)
            throw new IllegalArgumentException("Constructor argument Board is null!");
        currentNode = new SearchNode(initial, null);
        // twincurrentNode = new SearchNode(initial.twin(),null);
        MinPQ<SearchNode> priorityQueue = new MinPQ<SearchNode>();
        // MinPQ<SearchNode> twinPriorityQueue = new MinPQ<SearchNode>();
        priorityQueue.insert(currentNode);
        // twinPriorityQueue.insert(twincurrentNode);
        while (true) {
            currentNode = priorityQueue.delMin();
            if (currentNode.board.isGoal())
                break;
            putNeighBorsIntoPQ(currentNode, priorityQueue);


            // twincurrentNode = twinPriorityQueue.delMin();
            // if(twincurrentNode.board.isGoal()) break;
            // putNeighBorsIntoPQ(twincurrentNode,twinPriorityQueue);
        }
    }


    private void putNeighBorsIntoPQ(SearchNode searchNode, MinPQ<SearchNode> pq) {
        Iterable<Board> neighbors = searchNode.board.neighbors();
        for (Board neighbor : neighbors) {


            if (searchNode.preSearchNode == null || !neighbor.equals(searchNode.preSearchNode.board))
                pq.insert(new SearchNode(neighbor, searchNode));
        }
    }


    public boolean isSolvable() {// 是否都已归位。
        // is the initial board solvable?
        return currentNode.board.isGoal();
    }


    public int moves() {// 返回移动次数。
        // min number of moves to solve initial board; -1 if unsolvable
        if (currentNode.board.isGoal())
            return currentNode.moves;
        else
            return -1;
    }


    public Iterable<Board> solution() {// 最优路径的反向存取,正向打印。应用栈。
        // sequence of boards in a shortest solution; null if unsolvable
        if (currentNode.board.isGoal()) {
            solution = new Stack<Board>();
            SearchNode node = currentNode;
            while (node != null) {
                solution.push(node.board);
                node = node.preSearchNode;
            }
            return solution;
        } else
            return null;
    }


    public static void main(String[] args) {
        // solve a slider puzzle (given below)
        // create initial board from file
        // In in = new In(args[0]);
        In in = new In("puzzle00.txt");
        int n = in.readInt();
        int[][] blocks = new int[n][n];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                blocks[i][j] = in.readInt();
        Board initial = new Board(blocks);


        // solve the puzzle
        Solver solver = new Solver(initial);


        // print solution to standard output
        if (!solver.isSolvable())
            StdOut.println("No solution possible");
        else {
            StdOut.println("Minimum number of moves = " + solver.moves());
            for (Board board : solver.solution())
                StdOut.println(board);
        }
    }

}

下面是coursera上提供的MinPQ库源码:

/******************************************************************************
 *  Compilation:  javac MinPQ.java
 *  Execution:    java MinPQ < input.txt
 *  Dependencies: StdIn.java StdOut.java
 *  Data files:   https://algs4.cs.princeton.edu/24pq/tinyPQ.txt
 *  
 *  Generic min priority queue implementation with a binary heap.
 *  Can be used with a comparator instead of the natural order.
 *
 *  % java MinPQ < tinyPQ.txt
 *  E A E (6 left on pq)
 *
 *  We use a one-based array to simplify parent and child calculations.
 *
 *  Can be optimized by replacing full exchanges with half exchanges
 *  (ala insertion sort).
 *
 ******************************************************************************/


package edu.princeton.cs.algs4;


import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;


/**
 *  The {@code MinPQ} class represents a priority queue of generic keys.
 *  It supports the usual <em>insert</em> and <em>delete-the-minimum</em>
 *  operations, along with methods for peeking at the minimum key,
 *  testing if the priority queue is empty, and iterating through
 *  the keys.
 *  <p>
 *  This implementation uses a binary heap.
 *  The <em>insert</em> and <em>delete-the-minimum</em> operations take
 *  logarithmic amortized time.
 *  The <em>min</em>, <em>size</em>, and <em>is-empty</em> operations take constant time.
 *  Construction takes time proportional to the specified capacity or the number of
 *  items used to initialize the data structure.
 *  <p>
 *  For additional documentation, see <a href="https://algs4.cs.princeton.edu/24pq">Section 2.4</a> of
 *  <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne.
 *
 *  @author Robert Sedgewick
 *  @author Kevin Wayne
 *
 *  @param <Key> the generic type of key on this priority queue
 */
public class MinPQ<Key> implements Iterable<Key> {
    private Key[] pq;                    // store items at indices 1 to n
    private int n;                       // number of items on priority queue
    private Comparator<Key> comparator;  // optional comparator


    /**
     * Initializes an empty priority queue with the given initial capacity.
     *
     * @param  initCapacity the initial capacity of this priority queue
     */
    public MinPQ(int initCapacity) {
        pq = (Key[]) new Object[initCapacity + 1];
        n = 0;
    }


    /**
     * Initializes an empty priority queue.
     */
    public MinPQ() {
        this(1);
    }


    /**
     * Initializes an empty priority queue with the given initial capacity,
     * using the given comparator.
     *
     * @param  initCapacity the initial capacity of this priority queue
     * @param  comparator the order in which to compare the keys
     */
    public MinPQ(int initCapacity, Comparator<Key> comparator) {
        this.comparator = comparator;
        pq = (Key[]) new Object[initCapacity + 1];
        n = 0;
    }


    /**
     * Initializes an empty priority queue using the given comparator.
     *
     * @param  comparator the order in which to compare the keys
     */
    public MinPQ(Comparator<Key> comparator) {
        this(1, comparator);
    }


    /**
     * Initializes a priority queue from the array of keys.
     * <p>
     * Takes time proportional to the number of keys, using sink-based heap construction.
     *
     * @param  keys the array of keys
     */
    public MinPQ(Key[] keys) {
        n = keys.length;
        pq = (Key[]) new Object[keys.length + 1];
        for (int i = 0; i < n; i++)
            pq[i+1] = keys[i];
        for (int k = n/2; k >= 1; k--)
            sink(k);
        assert isMinHeap();
    }


    /**
     * 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;
    }


    /**
     * Returns the number of keys on this priority queue.
     *
     * @return the number of keys on this priority queue
     */
    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 Key min() {
        if (isEmpty()) throw new NoSuchElementException("Priority queue underflow");
        return pq[1];
    }


    // helper function to double the size of the heap array
    private void resize(int capacity) {
        assert capacity > n;
        Key[] temp = (Key[]) new Object[capacity];
        for (int i = 1; i <= n; i++) {
            temp[i] = pq[i];
        }
        pq = temp;
    }


    /**
     * Adds a new key to this priority queue.
     *
     * @param  x the key to add to this priority queue
     */
    public void insert(Key x) {
        // double size of array if necessary
        if (n == pq.length - 1) resize(2 * pq.length);


        // 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
     */
    public Key delMin() {
        if (isEmpty()) throw new NoSuchElementException("Priority queue underflow");
        Key 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;
    }




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


    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;
        }
    }


   /***************************************************************************
    * Helper functions for compares and swaps.
    ***************************************************************************/
    private boolean greater(int i, int j) {
        if (comparator == null) {
            return ((Comparable<Key>) pq[i]).compareTo(pq[j]) > 0;
        }
        else {
            return comparator.compare(pq[i], pq[j]) > 0;
        }
    }


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


    // 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);
    }




    /**
     * Returns an iterator that iterates over the keys on this priority queue
     * in ascending order.
     * <p>
     * The iterator doesn't implement {@code remove()} since it's optional.
     *
     * @return an iterator that iterates over the keys in ascending order
     */
    public Iterator<Key> iterator() {
        return new HeapIterator();
    }


    private class HeapIterator implements Iterator<Key> {
        // create a new pq
        private MinPQ<Key> copy;


        // add all items to copy of heap
        // takes linear time since already in heap order so no keys move
        public HeapIterator() {
            if (comparator == null) copy = new MinPQ<Key>(size());
            else                    copy = new MinPQ<Key>(size(), comparator);
            for (int i = 1; i <= n; i++)
                copy.insert(pq[i]);
        }


        public boolean hasNext()  { return !copy.isEmpty();                     }
        public void remove()      { throw new UnsupportedOperationException();  }


        public Key next() {
            if (!hasNext()) throw new NoSuchElementException();
            return copy.delMin();
        }
    }


    /**
     * Unit tests the {@code MinPQ} data type.
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) {
        MinPQ<String> pq = new MinPQ<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-")) pq.insert(item);
            else if (!pq.isEmpty()) StdOut.print(pq.delMin() + " ");
        }
        StdOut.println("(" + pq.size() + " left on pq)");
    }


}


/******************************************************************************
 *  Copyright 2002-2016, Robert Sedgewick and Kevin Wayne.
 *
 *  This file is part of algs4.jar, which accompanies the textbook
 *
 *      Algorithms, 4th edition by Robert Sedgewick and Kevin Wayne,
 *      Addison-Wesley Professional, 2011, ISBN 0-321-57351-X.
 *      http://algs4.cs.princeton.edu
 *
 *
 *  algs4.jar is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  algs4.jar is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with algs4.jar.  If not, see http://www.gnu.org/licenses.
 ******************************************************************************/

第四部分:

二叉树,二叉搜索树(二叉平衡树),2-3 搜索树,红黑树(BST),B树等等。

这里给出几个参考连接,固然你也能够经过其余途径去了解它们。

https://www.coursera.org/learn/algorithms-part1/lecture/wIUNW/2-3-search-trees

https://www.coursera.org/learn/algorithms-part1/lecture/GZe13/red-black-bsts

https://d3c33hcgiwev3.cloudfront.net/_806d02702c594a8d442f4e96711a454c_33BalancedSearchTrees.pdf?Expires=1527552000&Signature=NONFDAxrZKZjqakTr~8zHhYuuz1YcWEW~In9De2b8j9soVfKQcO8LjJzCM~dqNcXbV2POppg4EBCwxcTjzC45HBRlyrIIZUmueoecgJdUnui1olsak2uJ1cAcDQ5WUHwfZKrlyO~2yJeUK~0LZCBbcjcPgggfJxDdhFCIqlh-Mc_&Key-Pair-Id=APKAJLTNE6QMUY6HBC5A

上述资源的访问连接国内有时访问状态不稳定,若有须要能够在此连接下载文档:

https://download.csdn.net/download/gzharryanonymous/10456354

代码详解:

package Left_BST_red_black_tree_imple;
import java.util.Scanner;
/*
 * 实现红黑树!
 * Represent 2-3 tree as a BST.
 * Use "internal" left-leaning links as "glue" for 3-nodes.
 * */
//private static final boolean RED=true;
//private static final boolean BLACK=false;
class Node {//节点类。
    int key;//键,用于数据逻辑化。
    String val;//值,真实的存储的数据。
    Node left, right;//左右指针域。
    boolean color; //color of parent link 标记

    public Node() {}//空构造函数。

    public Node(int key, String val, boolean color) {//带参数构造函数。
        this.key = key;
        this.val = val;
        this.left = null;
        this.right = null;
        this.color = color;
    }
}

public class RBT{//主类
    static Node root = null;//全局变量
    public static int compareTo(int key1,int key2) {
        if(key1==key2)return 0;
        if(key1>key2)return 1;
        if(key1<key2)return -1;
        return (Integer) null;
    }
    private static boolean isRed(Node node) {//判别标记    
        if (node == null) return false;//the root Node's link is black.    
        return node.color == true; }
    public String get(int key){//根据键,获得值。
        Node x=root;
        while(x!=null){
            int cmp=compareTo(key,x.key);
            if(cmp<0)x=x.left;
            else if(cmp>0)x=x.right;
            else if(cmp==0)return x.val;
            }
            return null;}
    private static Node rotateLeft(Node h) {//左旋
        assert isRed(h.right);    
        Node x = h.right;    
        h.right = x.left;    
        x.left = h;    
        x.color = h.color;    
        h.color = true;    
        return x; }
    private static Node rotateRight(Node h) {//右旋
        assert isRed(h.left);    
        Node x = h.left;    
        h.left = x.right;    
        x.right = h;    
        x.color = h.color;    
        h.color = true;    
        return x; }
    private static void flipColors(Node h) {//翻转 
        assert !isRed(h);    
        assert isRed(h.left);    
        assert isRed(h.right);    
        h.color = true;    
        h.left.color = false;    
        h.right.color = false; }
    //Insertion in a LLRB tree:  Java implementation
    private static Node put(Node h, int key, String val) {    
        if (h == null) return new Node(key, val, true);//根节点默认为红标记。
        int cmp = compareTo(key,h.key);    
        if(cmp  < 0) h.left  = put(h.left,  key, val);    
        else if (cmp  > 0) h.right = put(h.right, key, val);    
        else if (cmp == 0) h.val = val;//键值相同,更新值域。
        if (isRed(h.right) && !isRed(h.left))     
            h = rotateLeft(h);    
        if (isRed(h.left)&&isRed(h.left.left)) 
            h = rotateRight(h);    
        if (isRed(h.left)&&isRed(h.right))     
            flipColors(h);       
            return h; }
    
    private static void pre_order(Node node)
    {
        if(node != null)
        {
            System.out.print(" "+node.val);
            pre_order(node.left);
            pre_order(node.right);
        }
    }
    
    private static void in_order(Node node) {
        if (node != null) {
            in_order(node.left);
            System.out.print(" " + node.val);
            in_order(node.right);
        }
    }
    
    private static void post_order(Node node)
    {
        if(node != null)
        {
            post_order(node.left);
            post_order(node.right);
            System.out.print(" "+node.val);
        }
    }
    public static void main(String args[]) {
        System.out.print("Please input the numbers of Node:");
        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();
        int tmp = num;
        while(tmp!=0) {
            int count = num-tmp+1;
            System.out.println("Please input the Node "+count+":");
            int key = sc.nextInt();
            String val = sc.nextLine();
            System.out.println(key);
            System.out.println(val);
            root = put(root,key,val);
            tmp=tmp-1;
        }
        pre_order(root);
        System.out.println();
        in_order(root);
        System.out.println();
        post_order(root);
        System.out.println();
    }
}

上述代码为RBT的实现,程序运行结果为:

Please input the numbers of Node:6
Please input the Node 1:
1 a
1
 a
Please input the Node 2:
6 b
6
 b
Please input the Node 3:
4 c
4
 c
Please input the Node 4:
5 d
5
 d
Please input the Node 5:
3 e
3
 e
Please input the Node 6:
2 f
2
 f
  c  f  a  e  b  d
  a  f  e  c  d  b
  a  e  f  d  b  c

Kd-Trees的应用:

package KdTrees;

import edu.princeton.cs.algs4.Point2D;
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.RectHV;
import edu.princeton.cs.algs4.StdDraw;

public class KdTree {//定义红黑树。
    private Node root;//根节点。
    private int N;//节点个数。
    private static class Node {//静态内部类:
        //静态内部类非静态内部类在编译完成以后会隐含地保存着一个引用,该引用是指向建立它的外围类,可是静态内部类却没有。
        //没有这个引用就意味着:
        //它的建立是不须要依赖于外围类的。
        //它不能使用任何外围类的非static成员变量和方法。
        private Point2D p; // the point:就是二维平面上的一个点。
        // the axis-aligned rectangle corresponding to this node
        // the max rectangle include this node, aabb
        private RectHV rect; //一个二维平面上的矩形。对应当前点所分的矩形。
        private Node lb; // the left/bottom subtree
        private Node rt; // the right/top subtree
        public Node(Point2D p, RectHV rect) {
            this.p = p;
            this.rect = rect;
            lb = null;
            rt = null;
        }
    }
    
    private final RectHV CANVAS = new RectHV(0, 0, 1, 1);
    // construct an empty set of points
    public KdTree() {
        root = null;
        N = 0;
    }
    
    // is the set empty?
    public boolean isEmpty() {
        return N == 0;
    }                      
   
    // number of points in the set
    public int size() {
        return N;
    }
    
    /**************************************
      * less
      * compare two Point2D with orientation
      *************************************/
    private int compareTo(Point2D v, Point2D w, int ori) {//果不其然!!!
        if (v.equals(w)) return 0; // same point
        else {
            if (ori == 0) { 
                // vertical line:ori是标记,若是为0,则垂直的。
                if (v.x() < w.x()) return -1;
                else return 1;
            } else {
                // horizontal line 若是为1,则水平的。
                if (v.y() < w.y()) return -1;
                else return 1;
            }
        }
    }
    
    /***********************************************
     * Insert
     **********************************************/
    //构造函数。
    private Node insert(Node x, Point2D p, 
                        double xmin, double ymin, double xmax, double ymax, 
                        int ori) {
        if (x == null) {
            N++;//根节点的划分是没有比较的。
            return new Node(p, new RectHV(xmin, ymin, xmax, ymax));//一布一点,过点垂直线段。
        }
        int cmp = compareTo(p, x.p, ori);//ori等于0,垂直去插入,则比较的是x。
        //好比根节点将其画布垂直一分为2,若是接下来的节点,好比其左右孩子,那么左孩子应该去分左半部分,右孩子应该去分右半部分。
        //那么应该判别其在第一条垂直线段的哪一边,比较的是x。
        //相应的ori等于1的话,水平插入,则比较的是y。
        double x0 = xmin, y0 = ymin, x1 = xmax, y1 = ymax;
        if (cmp < 0) {
            if (ori == 0) x1 = x.p.x();//以过根节点p的直线为当前节点对应矩形的右边。
            else y1 = x.p.y();//以过根节点的水平直线为当前节点对应矩形的下边。
            x.lb = insert(x.lb, p, x0, y0, x1, y1, 1-ori);
        }
        else if (cmp > 0) {
            if (ori == 0) x0 = x.p.x();//同理
            else y0 = x.p.y();
            x.rt = insert(x.rt, p, x0, y0, x1, y1, 1-ori);
        }
        return x;
    }
    
    // add the point p to the set (if it is not already in the set)
    public void insert(Point2D p) {
        // 0 for vertical, 1 for horizontal
        root = insert(root, p, 
                      CANVAS.xmin(), CANVAS.ymin(),
                      CANVAS.xmax(), CANVAS.ymax(), 0);
    }
    
    /*******************************************
      * contains
      *****************************************/
    private boolean get(Node x, Point2D p, int ori) {//x与ori是对应的。递归实现。
        if (x == null) return false;
        int cmp = compareTo(p, x.p, ori);
        if (cmp < 0) return get(x.lb, p, 1-ori);
        else if (cmp > 0) return get(x.rt, p, 1-ori);
        return true;
    }
    
    // does the set contain the point p?
    public boolean contains(Point2D p) {
        // 0 for vertical, 1 for horizontal
        return get(root, p, 0);
    }
    
    /***************************************
      * Draw()
      *************************************/
    private void draw(Node x, int ori) {
        if (x == null) return;
        // draw point
        StdDraw.setPenColor(StdDraw.BLACK);
        StdDraw.setPenRadius(.01);
        StdDraw.point(x.p.x(), x.p.y());
        // draw line
        if (ori == 0) {
            // vertical
             StdDraw.setPenColor(StdDraw.RED);
             StdDraw.setPenRadius();
             StdDraw.line(x.p.x(), x.rect.ymin(), x.p.x(), x.rect.ymax());
        } else {
            // horizontal
            StdDraw.setPenColor(StdDraw.BLUE);
            StdDraw.setPenRadius();
            StdDraw.line(x.rect.xmin(), x.p.y(), x.rect.xmax(), x.p.y());
        }
        draw(x.lb, 1-ori);
        draw(x.rt, 1-ori);
    }
    
    // draw all of the points to standard draw
    public void draw() {
        StdDraw.setScale(0, 1);  
        StdDraw.setPenColor(StdDraw.BLACK);
        StdDraw.setPenRadius();
        CANVAS.draw();
        draw(root, 0);
    }                              
    
    // all points in the set that are inside the rectangle
    public Iterable<Point2D> range(RectHV rect) {
        Queue<Point2D> points = new Queue<Point2D>();
        Queue<Node> queue = new Queue<Node>();
        if (root == null) return points;
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            Node x = queue.dequeue();
            if (x == null) continue;
            if (rect.contains(x.p)) points.enqueue(x.p);
            if (x.lb != null && rect.intersects(x.lb.rect)) queue.enqueue(x.lb);//若是与点对应的矩形没有交集,则确定不存在包含。
            if (x.rt != null && rect.intersects(x.rt.rect)) queue.enqueue(x.rt);
        }
        return points;
    }
    
    // a nearest neighbor in the set to p; null if set is empty
    public Point2D nearest(Point2D p) {
        if (root == null) return null;
        Point2D retp = null;
        double mindis = Double.MAX_VALUE;
        Queue<Node> queue = new Queue<Node>();
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            Node x = queue.dequeue();
            double dis = p.distanceSquaredTo(x.p);
            if (dis < mindis) {
                retp = x.p;
                mindis = dis; 
            }
            if (x.lb != null && x.lb.rect.distanceSquaredTo(p) < mindis) //若是与点对应的矩形到达当前点的距离大于半径,则确定没戏。
                queue.enqueue(x.lb);
            if (x.rt != null && x.rt.rect.distanceSquaredTo(p) < mindis) 
                queue.enqueue(x.rt);
        }
        return retp;
    }
}

文章写到告终尾,课程告一段落,但路漫漫,知识是没有尽头的,因此积极进取,不急不躁的心态很重要!

一望无际的未知,同时也蕴含着无穷无尽的乐趣!珍惜时间,活在当下。与君共勉!


彩蛋:

JDK 1.8 之前 HashMap 的实现是 数组+链表,即便哈希函数取得再好,也很难达到元素百分百均匀分布。

当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就至关于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),彻底失去了它的优点。

针对这种状况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

在Java 8 中,若是一个桶中的元素个数超过 TREEIFY_THRESHOLD(默认是 8 ),就使用红黑树来替换链表,从而提升速度。

这个替换的方法叫 treeifyBin() 即树形化。

彩蛋内容非原创,摘录他人博客!