本文主要介绍普林斯顿算法公开课的Part_1部分的核心算法,总体分两篇文章介绍。
对于并查集相关内容请查看文章:https://blog.csdn.net/GZHarryAnonymous/article/details/80384195
第二部分:Stacks and Queues
对于这部分的内容比较简单,主要就是队列、栈的基本操作。
代码如下:
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)");
}
}
随机队列的实现也很简单!
代码如下:
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() 即树形化。
彩蛋内容非原创,摘录他人博客!