二叉树,他为啥出现呢,因为数组还不够快
0、树的常用术语
-
路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
-
根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
-
父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。
-
子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。
-
兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。
-
叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的A、E、F、G都是叶子节点。
-
子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
-
节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
-
深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
-
高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
二叉树呢,特点左节点一定会比右节点小,先这样,边刷题边总结
1、数据结构
private Node root; // root of BST
private class Node {
private Key key; // sorted by key
private Value val; // associated data
private Node left, right; // left and right subtrees
private int size; // number of nodes in subtree
public Node(Key key, Value val, int size) {
this.key = key;
this.val = val;
this.size = size;
}
}
2、遍历
- 前序遍历:根结点 —> 左子树 —> 右子树
- 中序遍历:左子树—> 根结点 —> 右子树
- 后序遍历:左子树 —> 右子树 —> 根结点
public void print(Node x){
if(x==null)return;
print(x.left);
Klog.e(x.key);
print(x.right);
}
- 先序遍历:在第一次遍历到节点时就执行操作,一般只是想遍历执行操作(或输出结果)可选用先序遍历;
- 中序遍历:对于二分搜索树,中序遍历的操作顺序(或输出结果顺序)是符合从小到大(或从大到小)顺序的,故要遍历输出排序好的结果需要使用中序遍历后序遍历:
- 后续遍历:是执行操作时,肯定已经遍历过该节点的左右子节点,故适用于要进行破坏性操作的情况,比如删除所有节点
这里我总是记忆不住具体的…
联想记忆:前序就是正规的遍历顺序 根左右 中序就是把左往前移,后序就是再把右往移
3、添加元素
要插入节点,必须先找到插入的位置。
与查找操作相似,由于二叉搜索树的特殊性,待插入的节点也需要从根节点开始进行比较,小于根节点则与根节点左子树比较
反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父节点的信息 及 待插入的位置是父节点的左子树还是右子树,才能插入到正确的位置
public void put(Key key,Value val){
if (key == null) throw new IllegalArgumentException("calls put() with a null key");
if (val == null) {
delete(key);
return;
}
//查找key,找到则更新他的值,否则为它创建一个新的节点
root = put(root, key, val);
}
private Node put(Node x, Key key, Value val) {
//如果x为空就新建节点存放key和value,如果不为空就继续对比下去,直到为空,
if (x == null) return new Node(key, val, 1);
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = put(x.left, key, val);
else if (cmp > 0) x.right = put(x.right, key, val);
else x.val = val;
x.size = 1 + size(x.left) + size(x.right);
return x;
}
这里是根据key 排序注意了
在put的时候,就已经在按照规则排序了,按照文章老规矩会说删除,但是删除是二叉树最难得部分,我们慢慢来
4、查找最大值和最小值
public Key max() {
if (isEmpty()) throw new NoSuchElementException("calls max() with empty symbol table");
return max(root).key;
}
private Node max(Node x) {
if (x.right == null) return x;
else return max(x.right);
}
public Key min() {
if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
return min(root).key;
}
/**
* 该节点下最小子节点
* @param x
* @return
*/
public Node min(Node x) {
if (x.left == null) return x;
else return min(x.left);
}
其实按照左节点一定比父节点小,右节点一定比父节点大,就可以想到 一直递归下去就行了
5、删除
5.1、热身运动:删除最大键和最小键
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMin(root);
}
/**
* 不断深入根节点的左子树,直到遇到该节点的左节点是一个空节点,然后就把指向该节点链接指向该节点右节点
* @param x
* @return
*/
private Node deleteMin(Node x) {
if (x.left == null) return x.right;
x.left = deleteMin(x.left);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
deleteMax 基本就是deleteMin 差不多 一个就是寻找右节点,一个是寻找左节点
热身结束~
5.2、核心思路:
- 将指向即将被删除的节点的链接保存为t
- 将x指向它的后继节点min(t.right)
- 将x的右链接(原本指向一颗所有节点都大于x.key的二叉树)指向deleteMin(t.right)也就是在删除后所有节点仍然大于x.key的二叉查找树
- 将x的左链接(本为空)设为t.left(其下所有键都小于被删除的节点和他的后继节点)
public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
root = delete(root, key);
}
public Node delete(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = delete(x.left, key);//如果小就继续去找他的左节点
else if (cmp > 0) x.right = delete(x.right, key);//如果大就继续去找他的右节点
else{
if(x.left==null)return x.right;
if(x.right==null)return x.left;
Node t = x;
x = min(t.right);
x.right = deleteMin(t.right);
x.left = t.left;
}
x.size = size(x.left) + size(x.right) + 1;
return x;
}
如图所示:
仔细结合图体会代码
6、二叉树的效率
从前面的大部分对树的操作来看,都需要从根节点到下一层一层的查找。
一颗满树,每层节点数大概为2n-1,那么最底层的节点个数比树的其它节点数多1,因此,查找、插入或删除节点的操作大约有一半都需要找到底层的节点,另外四分之一的节点在倒数第二层,依次类推。
总共N层共有2n-1个节点,那么时间复杂度为O(logn),底数为2。
在有1000000 个数据项的无序数组和链表中,查找数据项平均会比较500000 次,但是在有1000000个节点的二叉树中,只需要20次或更少的比较即可。
有序数组可以很快的找到数据项,但是插入数据项的平均需要移动 500000 次数据项,在 1000000 个节点的二叉树中插入数据项需要20次或更少比较,在加上很短的时间来连接数据项。
同样,从 1000000 个数据项的数组中删除一个数据项平均需要移动 500000 个数据项,而在 1000000 个节点的二叉树中删除节点只需要20次或更少的次数来找到他,然后在花一点时间来找到它的后继节点,一点时间来断开节点以及连接后继节点。
所以,树对所有常用数据结构的操作都有很高的效率。
遍历可能不如其他操作快,但是在大型数据库中,遍历是很少使用的操作,它更常用于程序中的辅助算法来解析算术或其它表达式。
7、全部代码
下面是用Java实现二叉树
public class BST<Key extends Comparable<Key>, Value> {compareTo
private Node root; // root of BST
private class Node {
private Key key; // sorted by key
private Value val; // associated data
private Node left, right; // left and right subtrees
private int size; // number of nodes in subtree
public Node(Key key, Value val, int size) {
this.key = key;
this.val = val;
this.size = size;
}
}
public BST() {
}
public Value get(Key key) {
return get(root, key);
}
public Value get(Node root, Key key) {
if (key == null) throw new IllegalArgumentException("calls get() with a null key");
if (key == null) return null;
int compareTo = key.compareTo(root.key);
// 如果指定的数小于参数返回 -1。
if (compareTo < 0) return get(root.left, key);
// 如果指定的数大于参数返回 1。
else if (compareTo > 0) return get(root.right, key);
else return root.val;
}
public void put(Key key,Value val){
if (key == null) throw new IllegalArgumentException("calls put() with a null key");
if (val == null) {
delete(key);
return;
}
root = put(root, key, val);
}
private Node put(Node x, Key key, Value val) {
if (x == null) return new Node(key, val, 1);
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = put(x.left, key, val);
else if (cmp > 0) x.right = put(x.right, key, val);
else x.val = val;
x.size = 1 + size(x.left) + size(x.right);
return x;
}
public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
root = delete(root, key);
}
public Node delete(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = delete(x.left, key);
else if (cmp > 0) x.right = delete(x.right, key);
else{
if(x.left==null)return x.right;
if(x.right==null)return x.left;
Node t = x;
x = min(t.right);
x.right = deleteMin(t.right);
x.left = t.left;
}
x.size = size(x.left) + size(x.right) + 1;
return x;
}
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMin(root);
}
public Iterable<Key> keys() {
if (isEmpty()) return new Queue<Key>() {
};
return keys(min(), max());
}
public Iterable<Key> keys(Key lo, Key hi) {
if (lo == null) throw new IllegalArgumentException("first argument to keys() is null");
if (hi == null) throw new IllegalArgumentException("second argument to keys() is null");
Queue<Key> queue = new Queue<Key>();
keys(root, queue, lo, hi);
return queue;
}
private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
if (x == null) return;
int cmplo = lo.compareTo(x.key);
int cmphi = hi.compareTo(x.key);
if (cmplo < 0) keys(x.left, queue, lo, hi);
if (cmplo <= 0 && cmphi >= 0) queue.enqueue(x.key);
if (cmphi > 0) keys(x.right, queue, lo, hi);
}
/**
* 不断深入根节点的左子树,直到遇到该节点的左节点是一个空节点,然后就把指向该节点链接指向该节点右节点
* @param x
* @return
*/
private Node deleteMin(Node x) {
if (x.left == null) return x.right;
x.left = deleteMin(x.left);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
public Key min() {
if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
return min(root).key;
}
/**
* 该节点下最小子节点
* @param x
* @return
*/
public Node min(Node x) {
if (x.left == null) return x;
else return min(x.left);
}
public boolean isEmpty() {
return size() == 0;
}
public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMax(root);
}
private Node deleteMax(Node x) {
if (x.right == null) return x.left;
x.right = deleteMax(x.right);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
public Key max() {
if (isEmpty()) throw new NoSuchElementException("calls max() with empty symbol table");
return max(root).key;
}
private Node max(Node x) {
if (x.right == null) return x;
else return max(x.right);
}
public int rank(Key key) {
if (key == null) throw new IllegalArgumentException("argument to rank() is null");
return rank(key, root);
}
public int rank(Key key, Node x) {
if (x == null) return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0) return rank(key, x.left);
else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right);
else return size(x.left);
}
public int size() {
return size(root);
}
public int size(Node x) {
if (x == null) return 0;
else return x.size;
}
public int size(TreeNode root) {
return root.N;
}
public boolean contains(Key key) {
if (key == null) throw new IllegalArgumentException("argument to contains() is null");
return get(key) != null;
}
/**
前序遍历:根结点 ---> 左子树 ---> 右子树
中序遍历:左子树---> 根结点 ---> 右子树
后序遍历:左子树 ---> 右子树 ---> 根结点
* @param x
*/
public void print(Node x){
if(x==null)return;
print(x.left);
Klog.e(x.key);
print(x.right);
}
}