二叉树
什么是树,为什么使用树
我们知道,对于数组,查找很快,有序的还可以通过二分法查找,但是插入数据却需要移动一些数据的位置。而链表的插入和删除很快,但是查找数据却需要从head开始遍历,那么有没有一种数据结构能同时具备数组查找快的优点以及链表插入和删除快的优点呢?有,就是树,Hello,树先生!
树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。一棵树是一些节点的集合。这个集合可以是空集;若不是空集,则树由根(root)的节点r以及0个或多个非空的(子)树组成,这些紫薯中每一棵的根都被来自根r的一条有向的边(edge)所连接。
树的术语
- 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”,比如从A到H的路径
- 根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点
- 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是C的父节点
- 子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点
- 兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点
- 叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的D、E、F、H都是叶子节点
- 子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中
- 节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推
- 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0
- 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0
二叉树
二叉树:树的每个节点最多只能有两个子节点,比如上图就是一个二叉树
二叉搜索树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。像下图这样
二叉搜索树的实现
节点类
package myTree;
/**
* @Author fitz.bai
* @Date 2018/9/17 21:33
*/
public class Node {
// 节点内的值
int data;
// 左子节点
Node leftChild;
// 右子节点
Node rightChild;
// 懒惰删除,可以使用逻辑删除,该节点仍留在树中,而知识标记为被删除
boolean isDelete;
public Node(int data) {
this.data = data;
}
public void value() {
System.out.println(data);
}
}
二叉树的增删改查
删除
1、删除叶子结点
2、删除只有一个子节点节点
3、删除有左右两个子节点的节点
后继节点也就是:比删除节点大的最小节点。
算法:程序找到删除节点的右节点,然后转到该右节点的左子节点,依次顺着左子节点找下去,最后一个左子节点即是后继节点;如果该右节点没有左子节点,那么该右节点便是后继节点。
然后将后继节点的值替换到待删除的节点位置,再将后继节点删除
package myTree;
/**
* @Author fitz.bai
* @Date 2018/9/17 21:35
*/
public class Tree {
public Node root;
//find
public Node find(int key) {
// 从root开始查找,如果比root大向右走,比root小向左走,依次往下循环进行
Node current = root;
while (current != null) {
if (key > current.data) {
current = current.rightChild;
} else if (key < current.data) {
current = current.leftChild;
} else {
return current;
}
}
return null;
}
public boolean put(int data) {
// 从root开始查找,如果比root大向右走,比root小向左走,依次往下循环进行,直到找到合适的位置,即找到某一节点合适子节点
Node newNode = new Node(data);
if (root == null) {
root = newNode;
return true;
} else {
Node current = root;
Node parentNode = null;
while (current != null) {
parentNode = current;
if (data > current.data) {
current = current.rightChild;
if (current == null) {
parentNode.rightChild = newNode;
return true;
}
} else {
current = current.leftChild;
if (current == null) {
parentNode.leftChild = newNode;
return true;
}
}
}
}
return false;
}
/**
* 删除比较麻烦,分为三种情况
* 1、删除叶子结点:
* 2、删除节点只有左子节点或右子节点
* 3、删除节点有左右子树
*
* @param key
* @return
*/
public boolean delete(int key) {
Node current = root;
Node parentNode = root;
boolean isLeftChild = false;
//无此key,返回false
while (current.data != key) {
parentNode = current;
if (current.data > key) {
isLeftChild = true;
current = current.leftChild;
} else {
isLeftChild = false;
current = current.rightChild;
}
if (current == null) {
return false;
}
}
//1、当前待删除节点没有子节点,叶子结点
// 判断是否为root节点,是,直接root置为null,否,如果待删除的节点是父节点的左子节点,
// 置赋节点的左子节点为null,反之,置右子节点为null
if (current.leftChild == null && current.rightChild == null) {
if (current == root) {
root = null;
} else if (isLeftChild) {
parentNode.leftChild = null;
} else {
parentNode.rightChild = null;
}
return true;
// 当前节点只有左子节点
// 判断是否为root节点,是,直接root置为当前节点的左子节点
// 若否,判断删除的节点是左还是右子节点,若是左子节点,置删除节点的父节点的左子节点为删除节点的左子节点
// 若是右子节点,置删除节点的父节点的右子节点为删除节点的左子节点
} else if (current.leftChild != null && current.rightChild == null) {
if (current == root) {
root = current.leftChild;
} else if (isLeftChild) {
parentNode.leftChild = current.leftChild;
} else {
parentNode.rightChild = current.leftChild;
}
return true;
//当前节点只有右子节点
//同上面
} else if (current.leftChild == null && current.rightChild != null) {
if (current == root) {
root = current.rightChild;
} else if (isLeftChild) {
parentNode.leftChild = current.rightChild;
} else {
parentNode.rightChild = current.rightChild;
}
return true;
} else {
//删除节点,既有左子节点,也有右子节点,详细见前面的分析
//主要做法就是,找到删除节点的后继节点,将他的值赋给要删除的节点,然后将后继节点删除
//原删除的节点的左右子树还挂在当前被改了值的节点
Node successor = getSuccessor(current);
if (current == root) {
successor = root;
} else if (isLeftChild) {
parentNode.leftChild = successor;
} else {
parentNode.rightChild = successor;
}
successor.leftChild = current.leftChild;
}
return false;
}
//获取后继节点:比删除节点大的最小节点
private Node getSuccessor(Node delNode) {
//算法:程序找到删除节点的右节点,(注意这里前提是删除节点存在左右两个子节点,
// 如果不存在则是删除情况的前面两种),然后转到该右节点的左子节点,依次顺着左
// 子节点找下去,最后一个左子节点即是后继节点;如果该右节点没有左子节点,那么
// 该右节点便是后继节点。
Node successorParent = delNode;
Node successor = delNode;
// 获取删除节点有右节点中的最小节点,也就是比删除节点大的最小节点
Node current = delNode.rightChild;
while (current != null) {
successorParent = successor;
successor = current;
current = current.leftChild;
}
if (successor != delNode.rightChild) {
successorParent.leftChild = successor.rightChild;
successor.rightChild = delNode.rightChild;
}
return successor;
}
}
二叉树的遍历
/**
* 遍历分为前序,中序,后序遍历,都是通过递归实现,
* 比如从跟节点开始前序遍历,先打印根节点,然后分别对root的
* 左右子节点在看做独立的树进行前序遍历
*
* @param current
*/
//前序遍历
public void preOrder(Node current) {
if (current != null) {
// 根节点-->左子节点-->右子节点
System.out.print(current.data + " ");
preOrder(current.leftChild);
preOrder(current.rightChild);
}
}
//中序遍历
public void infixOrder(Node current) {
if (current != null) {
// 左子节点-->根节点-->右子节点
infixOrder(current.leftChild);
System.out.print(current.data + " ");
infixOrder(current.rightChild);
}
}
//后序遍历
public void postOrder(Node current) {
if (current != null) {
// 左子节点-->右子节点-->根节点
postOrder(current.leftChild);
postOrder(current.rightChild);
System.out.print(current.data + " ");
}
}
findMax & findMin
//findMax
// 就是从根节点一直查找右子节点
public Node findMax() {
Node current = root;
Node maxNode = current;
while (current != null) {
maxNode = current;
current = current.rightChild;
}
return maxNode;
}
//findMin
// 就是从根节点一直查找左子节点
public Node findMin() {
Node current = root;
Node minNode = current;
while (current != null) {
minNode = current;
current = current.leftChild;
}
return minNode;
}
总结
网上找的有关二叉查找树的时间复杂度http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html