下面我们将会介绍另外一种数据结构——树。前面我们介绍数组的数据结构,我们知道对于有序数组,查找很快,并介绍可以通过二分法查找,但是想要在有序数组中插入一个数据项,就必须先找到插入数据项的位置,然后将所有插入位置后面的数据项全部向后移动一位,来给新数据腾出空间,平均来讲要移动N/2次,这是很费时的。同理,删除数据也是。
然后我们介绍了另外一种数据结构——链表,链表的插入和删除很快,我们只需要改变一些引用值就行了,但是查找数据却很慢了,因为不管我们查找什么数据,都需要从链表的第一个数据项开始,遍历到找到所需数据项为止,这个查找也是平均需要比较N/2次。
那么我们就希望一种数据结构能同时具备数组查找快的优点以及链表插入和删除快的优点,于是 树 诞生了。
1.树
树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点通过连接它们的边组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
①、节点:上图的圆圈,比如A,B,C等都是表示节点。节点一般代表一些实体,在java面向对象编程中,节点一般代表对象。
②、边:连接节点的线称为边,边表示节点的关联关系。一般从一个节点到另一个节点的唯一方法就是沿着一条顺着有边的道路前进。在Java当中通常表示引用。
树的常用术语
①、路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
②、根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
③、父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。
④、子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。
⑤、兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。
⑥、叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的A、E、F、G都是叶子节点。
⑦、子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
⑧、节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
⑨、深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
⑩、高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
2.二叉树
二叉树:树的每个节点最多只能有两个子节点
在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。
二叉搜索树要求:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
节点类
package com.ys.tree;
public interface Tree {
//查找节点
public Node find(int key);
//插入节点
public boolean insert(int data);
//中序遍历
public void midOrder(Node n);
//前序遍历
public void preOrder(Node n);
//后序遍历
public void postOrder(Node n);
//查找最大值
public Node findMax();
//查找最小值
public Node findMin();
//删除节点
public boolean delete(int key);
}
树接口
package com.ys.tree;
public interface Tree {
//查找节点
public Node find(int key);
//插入节点
public boolean insert(int data);
//中序遍历
public void midOrder(Node n);
//前序遍历
public void preOrder(Node n);
//后序遍历
public void postOrder(Node n);
//查找最大值
public Node findMax();
//查找最小值
public Node findMin();
//删除节点
public boolean delete(int key);
}
二叉树类
package com.ys.tree;
public class BinaryTree implements Tree{
//根节点
private Node root;
@Override
public Node find(int key) {
// TODO Auto-generated method stub
Node temp = root;
//先查找根节点,如果key比根节点大则查找右子节点,否则查找左子节点,如此循环,直到找到或节点为空退出
while(temp != null){
if(key == temp.data){
return temp;
}else if(key > temp.data){
temp = temp.rightChild;
}else{
temp = temp.leftChild;
}
}
return null;
}
@Override
public boolean insert(int data) {
// TODO Auto-generated method stub
Node newNode = new Node(data);
//树为空树,则把数据插入到根节点
if(root == null){
root = newNode;
return true;
}
//类似查找。先比较,找到合适的位置插入,然后在插入
Node temp = root;
while(temp != null){
if(data > temp.data){
if(temp.rightChild == null){
temp.rightChild = newNode;
return true;
}
temp = temp.rightChild;
}else if(data < temp.data){
if(temp.leftChild == null){
temp.leftChild = newNode;
return true;
}
temp = temp.leftChild;
}else{
System.out.println("插入失败:该节点已存在,无法再进行插入!");
return false;
}
}
return true;
}
@Override
public void midOrder(Node n) {
// TODO Auto-generated method stub
if(n != null){
midOrder(n.leftChild);
System.out.print(n.data+" ");
midOrder(n.rightChild);
}
}
@Override
public void preOrder(Node n) {
// TODO Auto-generated method stub
if(n != null){
System.out.print(n.data+" ");
midOrder(n.leftChild);
midOrder(n.rightChild);
}
}
@Override
public void postOrder(Node n) {
// TODO Auto-generated method stub
if(n != null){
midOrder(n.leftChild);
midOrder(n.rightChild);
System.out.print(n.data+" ");
}
}
@Override
public Node findMax() {
// TODO Auto-generated method stub
Node temp = root;
while(temp.rightChild != null){
temp = temp.rightChild;
}
return temp;
}
@Override
public Node findMin() {
// TODO Auto-generated method stub
Node temp = root;
while(temp.leftChild != null){
temp = temp.leftChild;
}
return temp;
}
@Override
public boolean delete(int key) {
// TODO Auto-generated method stub
Node temp = root;
Node parent = root;
if(find(key) == null){
System.out.println("删除失败:未找到该节点!");
return false;
}
while(temp != null){
if(temp.data > key){
parent = temp;
temp = temp.leftChild;
}else if(temp.data < key){
parent = temp;
temp = temp.rightChild;
}else{//要删除节点没有子节点
if((temp.leftChild == null)&&(temp.rightChild == null)){
if(parent == temp){
root = null;
return true;
}
if(parent.leftChild == temp){
parent.leftChild = null;
return true;
}else{
parent.rightChild = null;
return true;
}
}else if(temp.leftChild == null){//要删除的节点有右子节点
if(parent == temp){
root = temp.rightChild;
return true;
}else if(parent.leftChild == temp){
parent.leftChild = temp.rightChild;
return true;
}else{
parent.rightChild = temp.rightChild;
return true;
}
}else if(temp.rightChild == null){//要删除的节点有左子节点
if(parent == temp){
root = temp.leftChild;
return true;
}else if(parent.leftChild == temp){
parent.leftChild = temp.leftChild;
return true;
}else{
parent.rightChild = temp.leftChild;
return true;
}
}else{//要删除的节点有左右子节点
Node successor = getSuccessor(temp);
if(parent == temp){
root = successor;
}else if(parent.leftChild == temp){
parent.leftChild = successor;
}else{
parent.rightChild = successor;
}
successor.leftChild = temp.leftChild;
return true;
}
}
}
return false;
}
//寻找后继节点
public 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;
}
public static void main(String[] args) {
BinaryTree bt = new BinaryTree();
bt.insert(50);
bt.insert(20);
bt.insert(80);
bt.insert(0);
bt.insert(40);
bt.insert(60);
bt.insert(90);
bt.insert(25);
bt.insert(85);
bt.insert(100);
bt.midOrder(bt.root);
System.out.println("");
bt.preOrder(bt.root);
System.out.println("");
bt.postOrder(bt.root);
System.out.println("");
bt.delete(0);//删除没有子节点的节点
bt.delete(40);//删除有一个子节点的节点
bt.delete(80);//删除有两个子节点的节点
System.out.println(bt.findMax().data);
System.out.println(bt.findMin().data);
bt.midOrder(bt.root);
System.out.println("");
bt.preOrder(bt.root);
System.out.println("");
bt.postOrder(bt.root);
System.out.println("");
}
}