java数据结构和算法(一):二叉树

  有序数组的查找速度很快,但是插入一个数据项时,就必须先找到插入数据项的位置,然后将后面所有的数据项后移一位,平均要移动N/2次,这是很费时的,删除数据也是。

  链表的插入和删除很快,只需要改变引用值就行了,但是查找数据却很慢,需要从链表的第一个节点查找到所需要的数据项为止,平均需要比较N/2次。

  树这种数据结构就具有了两者的优点,数组查找快以及链表插入、删除快。

1.树的定义:

  树是n个有限个数据点通过连接他们的边组成的具有层次关系的集合。

  节点高度:从叶节点向上数,连接该节点的边数就是节点的高度

  节点深度:从根节点往下数,该节点所在的层数就是节点的深度。

  树中节点高度和深度的最大值就是树的高度和深度。注意:有两种说法,一种是根的深度为0,一种根的深度为1,所以树的深度取决于根节点所在的层数是0还是1。

2.二叉树的性质:

  (1)两种特殊的树:满二叉树和完全二叉树。在一棵二叉树中,如果所有分支节点都存在左子树和右子树,并且所有的叶节点都在同一层上称为满二叉树;对于一颗具有n个节点的二叉树按层序编号,如果编号为i(0<i<=n)的节点与同样深度的满二叉树中编号为i的节点在二叉树中位置完全相同,则称完全二叉树。

  (2)在二叉树中的第i层上至多有2^(i-1)个节点(i>=1)

  (3)深度为k的二叉树至多有2^k-1个节点(k>=1)

    (4)  对于任何一颗二叉树T,如果其叶子节点数为n0,度为2的节点数为n2,则n0 = n2+1

  (5)具有n个节点的完全二叉树的深度为|log2n|+1(|log2n|为不大于它自身的最大整数)。

3.二叉树的插入、查找和删除

  这里主要讲二叉搜索树的操作。首先谈二叉搜索树的简单定义:如果节点不为空,有右子节点》根节点》左子节点,满足这样条件的树称为二叉搜索树。

插入:

public void insert(int data){
if(root==null){
root = new Node(data);
return;
}
Node temp = root;
Node pre = new Node();//获取待插入节点的父节点
while(temp!=null){
pre = temp;
if(temp.data==data){
return;
}else if(temp.data>data){
temp = temp.leftnode;
}else{
temp = temp.rightnode;
}
}
if(pre.data>data){
pre.leftnode = new Node(data);
}else{
pre.rightnode = new Node(data);
}

}

 

查找:

public Node search(int data){
Node temp = root;
while(temp!=null){
if(temp.data==data){
return temp;
}else if(temp.data>data){
temp = temp.leftnode;
}else{
temp = temp.rightnode;
}
}
return new Node();
}

 

删除:

分情况讨论: 

 (1)删除的节点如果没有右节点则直接用左子节点替换。

 (2)否则,如果删除节点的右节点不存在左子树,则直接用该右子节点替换。

 (3)否则,找到右节点的左子树中最左的节点替换被删除的节点,然后将替换节点的右子节点放到替换节点的位置。

 (1)第一种情况         (2)第二种情况

 (3)第三种情况

 1 public boolean delete(int data){
 2         Node temp = root;
 3         Node pre = new Node();
 4         pre.leftnode = root;
 5         byte state = 0;               // 0代表左子节点,1代表右子节点
 6         while(temp!=null&&temp.data!=data){    //找到待删除的节点
 7             pre = temp;
 8             if(temp.data>data){
 9                 temp = temp.leftnode;
10                 state = 0;
11             }else{
12                 temp = temp.rightnode;
13                 state = 1;
14             }
15         }
16         if(temp==null) return false;
17 
18         Node templ = temp.leftnode;      //保存待删除节点的左子树和右子树
19         Node tempr = temp.rightnode;
20 
21         if(tempr==null){            //第一种情况
22             connectNode(pre,templ,state);
23         }else if(tempr.leftnode==null){   //第二种情况
24             connectNode(pre,tempr,state);
25             tempr.leftnode = templ;
26         }else{                  //第三种情况
27             Node pre2 = tempr;
28             temp = pre2.leftnode;
29             while(temp.leftnode!=null){
30                 pre2 = temp;
31                 temp = temp.leftnode;
32             }
33             pre2.leftnode = temp.rightnode;
34 
35             temp.leftnode = templ;
36             temp.rightnode = tempr;
37             if(pre.leftnode==root) root = temp;
38             connectNode(pre,temp,state);
39         }
40         return true;
41     }

private void connectNode(Node pre, Node node, byte state) {
    if(state==0){
pre.leftnode = node;
}else{
pre.rightnode = node;
}
}

 

  通过上面的删除分类讨论,我们发现删除其实是挺复杂的,那么其实我们可以不用真正的删除该节点,只需要在Node类中增加一个标识字段isDelete,当该字段为true时,表示该节点已经删除,反正没有删除。那么我们在做比如find()等操作的时候,要先判断isDelete字段是否为true。这样删除的节点并不会改变树的结构。

4.遍历二叉树

(1)前序遍历:父节点=》左子节点=》右子节点

递归算法:

1 public void beforeOrder(Node node){
2         if(node==null){
3             return;
4         }
5         System.out.println(node.data);
6         beforeOrder(node.leftnode);
7         beforeOrder(node.rightnode);
8     }

 

非递归算法:

 1     public void beforeOrder2(Node node){
 2         Stack<Node> stack = new Stack<>();
 3         stack.push(node);
 4         while(!stack.isEmpty()){
 5             Node pop = stack.pop();
 6             if (pop==null) continue;
 7             System.out.println(pop.data);
 8             stack.push(pop.rightnode);
 9             stack.push(pop.leftnode);
10         }
11 
12     }

 

(2)中序遍历:左子节点=》父节点=》右子节点

递归算法:

1 public void inOrder(Node node){
2     if(node==null){
3         return;
4     }
5     inOrder(node.leftnode);
6     System.out.println(node.data);
7     inOrder(node.rightnode);
8 }

 

非递归算法:

 1 public void inOrder2(Node node){
 2     Stack<Node> stack = new Stack<>();
 3     Node cur = node;
 4     while(!stack.isEmpty()||cur!=null){
 5         while(cur!=null){
 6             stack.push(cur);
 7             cur = cur.leftnode;
 8         }
 9         Node pop = stack.pop();
10         System.out.println(pop.data);
11         if(pop.rightnode==null) continue;
12         cur = pop.rightnode;
13     }
14 }

 

(3)后序遍历:左子节点=》右字节点=》父节点

递归算法:

1 public void afterOrder(Node node){
2     if(node==null){
3         return;
4     }
5     afterOrder(node.leftnode);
6     afterOrder(node.rightnode);
7     System.out.println(node.data);
8 }

 

非递归算法:

 1 public void afterOrder2(Node node){
 2     Stack<Node> stack = new Stack<>();
 3     stack.push(node);
 4     List<Node> mylist = new ArrayList<>();
 5     while(!stack.isEmpty()){
 6         Node pop = stack.pop();
 7         if (pop==null) continue;
 8         mylist.add(pop);
 9         stack.push(pop.leftnode);
10         stack.push(pop.rightnode);
11     }
12     Collections.reverse(mylist);
13     for (Node node1 : mylist) {
14         System.out.println(node1.data);
15     }
16 }

(4)层次遍历

 1 public void levelOrder(Node node){
 2     Queue<Node> queue = new LinkedList<>();
 3     queue.offer(node);
 4     while(!queue.isEmpty()){
 5         int size = queue.size();
 6         while(size-->0){
 7             Node node1 = queue.poll();
 8             if(node1==null){
 9                 continue;
10             }
11             System.out.println(node1.data);
12             queue.offer(node1.leftnode);
13             queue.offer(node1.rightnode);
14         }
15     }
16 }

 

5.效率分析

  在插入的数据是无序的情况下,插入的效率是logN;在二叉树是平衡树的情况下,删除、查找的效率都为logN;但是树的遍历效率比较低。

posted @ 2020-04-10 20:27  J-mo-太阳  阅读(223)  评论(0编辑  收藏  举报