General Tree
又向,联通,无环
Copy class TreeNode {
int key;
List < TreeNode > children;
public TreeNode ( int key) {
this . key = key;
children = new ArrayList < TreeNode >();
}
}
root //拎出来root就找到了整个树
General Graph
Copy class GraphNode {
int key;
List < TreeNode > neighbors;
public GraphNode ( int key) {
this . key = key;
neighbors = new ArrayList < GraphNode >();
}
}
List< GraphNode > //因为这个时候不能用一个root拎出来整张图了,需要一个list of node
//or adjacency list
//or adjacency matrix
A的neighbor里有B说明A到B有边,但是只有查了B才能知道B到A有没有边(对于有向图)
G = V + E
Given V, Dense Graph的E是O( V 2 V^2 V 2 ), Sparse Graph的E是O(v). 矩阵只适合node少的情况(sparse graph),而list适合node多的情况。
如果简化adj list,用list of list,这个list可以是array list, 也可以是linked list. 把vertex的index存下来,那么就可以用graph.get(i)拿到第i个node的 neighbor index 做算法题的时候,都是下面这种简化的。在实际工作中其实还是哟搞得上面的general graph,因为需要有value在
Copy List<List< Integer >> graph //也可以 Array<Array<Integer>> 如果知道长度
graph . get ( 0 ) //里的list的长度就是0的neighbor的个数
graph . get ( 1 )
for ( Integer i : graph . get ( 1 )) {
// i is a neighbor of node 1
}
外层list的顺序重要,里面的list的数量无所谓
Iterative Traversal of Binary Tree
why iterative?
Pre-Order
root进,pop栈顶并打印,右进,左进,pop栈顶并打印... 直到栈空了
因为stack是先进后出,所以进stack时先进右后进左
Time: O(n)
Space: heap上的那堆右节点 O(height)
In-Order
关键是要知道什么时候左子树被打印完了 用一个helper,when helper is null, it means the left subtree of the root is finished, the root is the top element in the stack 当helper是null的时候,当前的左都打完了,这个时候可以放右边的node进helper;最后栈空了,但是helper不是null,这个时候就可以pop打印了。
helper的意义:下一个要看的节点 如果不是null就入栈 如果是null就证明当前栈的top(左边)被打印完了,可以pop了,pop完helper里是刚才pop的节点的 right child 直到left和right都是空的时候停止
Post-Order
奇技淫巧 反过来pre-order
Time: O(2n)
Space: O(n)
正派做法 记录从哪里来的
如果prev是current的parent,这是第一次看到它;如果prev是current的left child,这是第二次看到它;如果prev是curr的right child,可以打印了
if previous is null --> going down (left subtree)
if previous is current's parent --> going down (left subtree)
go down: 有左,优先左;没有左,打印右; 都没有,打印自己并弹栈
from left: 右或者打印自己并弹栈
from right: 打印自己并弹栈
偷看栈顶 从上下来,有左压左,有右压右,左右都无,打印自己
从左上来,有右压右,无右打自己
从右上来,打印自己
Binary Search Tree
对任何一个节点,左边的所有数都比它小,右边所有都比它大 优势是search时每一步砍掉一半的子树,缩小搜索空间。
search() worst case O(n) average(logn)
insert() worst case O(n) average(logn)
remove() worst case O(n) average(logn)
Self-Balancing Binary Search Tree
为了解决BST不总是平衡的问题,有了两种特殊的树,自己维持self-balancing的状态,以保证以上三种操作的时间复杂度都是logn
Red-Black Tree
in Java: TreeMap / TreeSet
问题来了,重复node?那么就在Map里存一个value,也就是节点的数量。
Search in BST
Copy class TreeNode {
int key;
TreeNode left;
TreeNode right;
public TreeNode ( int key) {
this . key = key;
}
}
//recursion 尾递归 最后一步调用自身
public TreeNode searchInBST( TreeNode root , int value) {
if (root == null || root . key == target) { //没找到或者找到了的时候停止
return root;
}
if ( root . key < value) {
return searchInBST( root . right , value) ;
} else {
return searchInBST( root . left , value) ;
}
}
//recursion 三目
public TreeNode biSearch( TreeNode root , int target) {
if (root == null || root . key == target) {
return root;
}
return biSearch( root . key > target ? root . left : root . right , target) ;
//iteration
public TreeNode searchInBST( TreeNode root , Integer value) {
while (root != null && root . key != target) { //root==null || root.key==target
if ( root . key < value) {
root = root . right ;
} else {
root = root . left ;
}
return root
}
Tail Recursion
最后一步且仅在最后一步 调用自身 这种递归很容易 写成iterative 这个写法的好处是先前存的所有stack都弹栈了。如果写成tail recursion的话 建议改写成iteration。
下面这个不是tail recursion,因为line 6这一行还在栈里。
Copy public static void preOrder( TreeNode root , List< Integer > result) {
if (root == null ) {
return ;
}
result . add ( root . val );
preorder( root . left , result) ;
preorder( root . right , result) ; //其实是为了存储
}
return isBST()&&isBST()
也不是tail recursion 因为最后一步是&&
另一个例子是求阶乘
Copy int factorial( int n) {
if (n == 1 ){
return 1 ;
}
return n * factorial(n - 1 ) ;
}
Copy int tailFactorial( int n , int total) {
if (n == 1 ) {
return total;
}
return tailFactorial(n - 1 , n * total) ;
}
Insert in BST
应该return改了的tree的root node
Copy //recursion
TreeNode insertBST( TreeNode root , int target) {
if (root == null ) {
return new TreeNode(target) ; //只需要在base case里new出来一个就行,不要放在if外,不然每层都有一个
}
if (target < root . key ) { //1. 怎么往下走 问左右孩子要什么: 以它为root的subtree
root . left = insertBST( root . left , target) ; //2. 当前层做什么:挂
} else if (target > root . key ) { //1. 怎么往下走 问左右孩子要什么: 以它为root的subtree
root . right = insertBST( root . right , target) ; //2. 当前层做什么:挂
}
return root; //3. 给上层返回什么
} //超级重点: root.left = , root.right=
//iteration method 1 存下pre然后post-processing
TreeNode insertBST( TreeNode root , int target) {
if (root == null ) { // root给的null,return这个新的node
return new TreeNode(target) ;
}
TreeNode returnRoot = root;
TreeNode pre = null ;
while (root != null ) {
pre = root;
if ( root . key < target) {
root = root . right ;
} else if ( root . key > target){
root = root . left ;
} else {
return returnRoot;
}
} //root==null
if ( pre . key < target){
pre . right = new TreeNode(target) ;
} else if ( pre . key > target){
pre . left = new TreeNode(target) ;
}
return returnRoot;
}
//iteration method 2 往下看一眼
TreeNode insertBST( TreeNode root , int target) {
if (root == null ) { // root给的null,return这个新的node
return new TreeNode(target) ;
}
TreeNode returnRoot = root;
while ( root . key != target) {
if ( root . key < target) {
if ( root . right == null ){
root . right = new TreeNode(target) ;
return returnRoot;
}
root = root . right ;
} else {
if ( root . left == null ) {
root . left = new TreeNode(target) ;
return returnRoot;
}
root = root . left ;
}
}
return returnRoot;
}
//tail recursion, remove redundant operation
public static TreeNode insert( TreeNode root , int target) {
if (root == null ) { // root给的null,return这个新的node
return new TreeNode(target) ;
}
helper(root , target) ;
return root;
}
public static void helper( TreeNode root , int target) {
if (target == root . key ) {
return ;
} else if (target < root . key ) {
if ( root . left == null ) {
root . left = new TreeNode(target) ;
} else {
helper( root . left , target) ;
}
} else {
if ( root . right == null ) {
root . right = new TreeNode(target) ;
} else {
helper( root . right , target) ;
}
}
}
Delete in BST
only has child on one sight: 3.right = 3.right.right / 3.right = 3.right.left
has children on both sides: 调右子树的最小值
如果右节点没有children 它自己就是最小的
然后把该继承的(原来的left)都继承好
如果右节点还有左子树 一直往左走,直到没有left为止
把最小的替换了
把最小的右子树接到原来的最小值位置
Time: O(height)
Space: O(height)
Copy def __deleteMin ( self , root ):
if not root . left :
return root . right
root . left = self . __deleteMin (root.left)
return root
def __delete ( self , root , key ):
if not root :
return None
if key < root . key :
root . left = self . __delete (root.left, key)
elif key > root . key :
root . right = self . __delete (root.right, key)
else : # root!=null and found it
if not root . right :
return root . left #report给了line 11或者line13,把return的value挂回去
if not root . left :
return root . right
t = root
root = self . __min (t.right)
root . right = self . __deleteMin (t.right)
root . left = t . left
return root
def delete ( self , key ):
self . __root = self . __delete (self.__root, key)