阅读722 返回首页    go 阿里云 go 技术社区[云栖]


数据结构之二叉树

一 树、二叉树和二叉查找树

1。树的概念:

递归定义:

1) 一个空结构是一个空树

2)如果t1,...,tk是分离的树,那么以t1,...,tk的根为子节点的根结构也是树

3)只有按照1,2规则产生的结构才是树

树的概念更多用于分层结构,比如数据库管理系统的分层模型。

2。二叉树(binary tree):所有节点都有两个子节点(可以为空),并且每个子节点都指明为左子节点还是右子节点

1)完全二叉数,满足第i层,有2的i-1次方个子节点此条件的二叉树

2)对于所有非空二叉树,如果它的中间子节点恰好有两个非空子女,那么叶的数目m大于中间节点的数目k,并且m=k+1

3。二叉查找树(binary search tree)

1)概念:对于树中的每个节点n,其左子节点中保存的所有数值都小于n保存的数值,右子节点保存的数值都大于n保存的数值。

2)二叉查找树可以实现更为优越的查找性能,主要实现方式有数组和链表结构,相比较而言,链表实现更为容易,因为数组实现删除和添加功能需要移动数组元素(如填补删除空位等)

 

二。二叉树的实现

首先设计一个节点(设计一个整型的二叉树,通用型通理):

此类简单明了,一个信息值,两个引用(左右子树)。

ExpandedBlockStart.gifContractedBlock.gifpublic   class  IntBSTNode  dot.gif {
InBlock.gif 
protected   int  key;
InBlock.gif
InBlock.gif 
protected  IntBSTNode left, right;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public  IntBSTNode()  dot.gif {
InBlock.gif  left 
=  right  =   null ;
ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public  IntBSTNode( int  el)  dot.gif {
InBlock.gif  
this (el,  null null );
ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public  IntBSTNode( int  el, IntBSTNode left, IntBSTNode right)  dot.gif {
InBlock.gif  key 
=  el;
InBlock.gif  left 
=  left;
InBlock.gif  right 
=  right;
ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

None.gif
None.gif


由此类而实现一个完整二叉搜索树:

ExpandedBlockStart.gifContractedBlock.gifpublic   class  IntBST  dot.gif {
InBlock.gif 
protected  IntBSTNode root;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
protected   void  visit(IntBSTNode p)  dot.gif {
InBlock.gif  System.out.print(p.key 
+   "   " );
ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public  IntBST()  dot.gif {
InBlock.gif  root 
=   null ;
ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif


第一步,实现二叉树的搜索,查找过程简单明了,对每一个节点将要查找的键与当前节点的数值比较,如果键小于该数,就进入节点的左子树继续比较,反之进入右子树继续此比较过程。

ExpandedBlockStart.gifContractedBlock.gifpublic  IntBSTNode search( int  el)  dot.gif {
InBlock.gif  
return  search(root, el);
ExpandedBlockEnd.gif }

None.gif
ExpandedBlockStart.gifContractedBlock.gif 
protected  IntBSTNode search(IntBSTNode p,  int  el)  dot.gif {
InBlock.gif  
while  (p  !=   null )
InBlock.gif   
if  (el  ==  p.key)
InBlock.gif    
return  p;
InBlock.gif   
else   if  (el  <  p.key)
InBlock.gif    p 
=  p.left;
InBlock.gif   
else
InBlock.gif    p 
=  p.right;
InBlock.gif  
return   null ;
ExpandedBlockEnd.gif }

None.gif


此查找过程最坏情况,树成为链表O(n)=(n-1)/2,最好情况:O(n)=lg(n+1)-2。经过计算可知,一般树的平均比较次数更接近于最好情况。

第二步,实现二叉树的遍历,树的遍历就是访问树的所有节点,且每个节点被访问一次。N个节点的树有N!种不同的遍历。我们只考虑两种情况的遍历

1)广度优先遍历,是从最底层(或最高层)开始逐层向上(或向下),而在同层自左向右(或者相反)访问每一个子节点,因此共有四种情况。考虑自顶向下,自左向右的情况,利用队列实现如下:

 

ExpandedBlockStart.gifContractedBlock.gifpublic   void  breadthFirst()  dot.gif {
InBlock.gif  IntBSTNode p 
=  root;
InBlock.gif  Queue queue 
=   new  Queue();
ExpandedSubBlockStart.gifContractedSubBlock.gif  
if  (p  !=   null dot.gif {
InBlock.gif   queue.enqueue(p);
ExpandedSubBlockStart.gifContractedSubBlock.gif   
while  ( ! queue.isEmpty())  dot.gif {
InBlock.gif    p 
=  (IntBSTNode) queue.dequeue();
InBlock.gif    visit(p);
InBlock.gif    
if  (p.left  !=   null )
InBlock.gif     queue.enqueue(p.left);
InBlock.gif    
if  (p.right  !=   null )
InBlock.gif     queue.enqueue(p.right);
ExpandedSubBlockEnd.gif   }

ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif }

None.gif

 

2) 深度优先遍历,此种遍历关心3个任务:

V——访问一个节点

L——遍历左子树

R——遍历右子树

因此有3!=6种此类遍历,我们关心其中3种:

VLR——先序遍历树

LVR——中序遍历树

LRV——后序遍历树

如果用递归来实现这3种遍历非常容易理解,如下:

ExpandedBlockStart.gifContractedBlock.gifpublic   void  preorder()  dot.gif {
InBlock.gif  preorder(root);
ExpandedBlockEnd.gif }

None.gif
None.gif
// 先序
None.gif

ExpandedBlockStart.gifContractedBlock.gif
protected   void  preorder(IntBSTNode p)  dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif  
if  (p  !=   null dot.gif {
InBlock.gif   visit(p);
InBlock.gif   preorder(p.left);
InBlock.gif   preorder(p.right);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif }

None.gif
ExpandedBlockStart.gifContractedBlock.gif 
public   void  inorder()  dot.gif {
InBlock.gif  inorder(root);
ExpandedBlockEnd.gif }

None.gif
None.gif
// 中序
None.gif

ExpandedBlockStart.gifContractedBlock.gif
protected   void  inorder(IntBSTNode p)  dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif  
if  (p  !=   null dot.gif {
InBlock.gif   inorder(p.left);
InBlock.gif   visit(p);
InBlock.gif   inorder(p.right);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif }

None.gif
ExpandedBlockStart.gifContractedBlock.gif 
public   void  postorder()  dot.gif {
InBlock.gif  postorder(root);
ExpandedBlockEnd.gif }

None.gif
None.gif
// 后序
None.gif

ExpandedBlockStart.gifContractedBlock.gif
protected   void  postorder(IntBSTNode p)  dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif  
if  (p  !=   null dot.gif {
InBlock.gif   postorder(p.left);
InBlock.gif   postorder(p.right);
InBlock.gif   visit(p);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif }

None.gif
None.gif


同样,我们知道,递归调用总可以被迭代方式取代,只不过不是这么容易理解了,在此不再列出。

第三步。插入操作,此算法很简单,不详细讲解,简单来讲就是找到合适的位置连接即可

ExpandedBlockStart.gifContractedBlock.gifpublic void insert(int el) dot.gif{
InBlock.gif        IntBSTNode p 
= root, prev = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
while (p != nulldot.gif// find a place for inserting new node;
InBlock.gif
            prev = p;
InBlock.gif            
if (p.key < el)
InBlock.gif                p 
= p.right;
InBlock.gif            
else
InBlock.gif                p 
= p.left;
ExpandedSubBlockEnd.gif        }

InBlock.gif        
if (root == null// tree is empty;
InBlock.gif
            root = new IntBSTNode(el);
InBlock.gif        
else if (prev.key < el)
InBlock.gif            prev.right 
= new IntBSTNode(el);
InBlock.gif        
else
InBlock.gif            prev.left 
= new IntBSTNode(el);
ExpandedBlockEnd.gif    }

None.gif
None.gif
   
第四步。节点的删除。
1)归并删除法,当被删除节点没有或者只有一个子树的时候很简单,当有两个子树存在的时候,情况稍微复杂。归并删除法就是将节点的两棵子树合并成一棵树,然后连接到节点的父节点上。归并子树的原则,寻找左子树中key最大的节点,并将其作为右子树的父节点。相反,也可以寻找右子树的key最小的节点,作为左子树的父节点。我们以第一种情况为例:

ExpandedBlockStart.gifContractedBlock.gifpublic void deleteByMerging(int el) dot.gif{
InBlock.gif        IntBSTNode tmp, node, p 
= root, prev = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
while (p != null && p.key != el) dot.gif// find the node p
InBlock.gif
            prev = p; // with element el;
InBlock.gif
            if (p.key < el)
InBlock.gif                p 
= p.right;
InBlock.gif            
else
InBlock.gif                p 
= p.left;
ExpandedSubBlockEnd.gif        }

InBlock.gif        node 
= p;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
if (p != null && p.key == el) dot.gif{
InBlock.gif            
if (node.right == null// node has no right child: its left
InBlock.gif
                node = node.left; // child (if any) is attached to its parent;
InBlock.gif
            else if (node.left == null// node has no left child: its right
InBlock.gif
                node = node.right; // child is attached to its parent;
ExpandedSubBlockStart.gifContractedSubBlock.gif
            else dot.gif// be ready for merging subtrees;
InBlock.gif
                tmp = node.left; // 1. move left
InBlock.gif
                while (tmp.right != null)
InBlock.gif                    
// 2. and then right as far as
InBlock.gif
                    tmp = tmp.right; // possible;
InBlock.gif
                tmp.right = // 3. establish the link between the
InBlock.gif
                node.right; // the rightmost node of the left
InBlock.gif                
// subtree and the right subtree;
InBlock.gif
                node = node.left; // 4.
ExpandedSubBlockEnd.gif
            }

InBlock.gif            
if (p == root)
InBlock.gif                root 
= node;
InBlock.gif            
else if (prev.left == p)
InBlock.gif                prev.left 
= node;
InBlock.gif            
else
InBlock.gif                prev.right 
= node;
ExpandedSubBlockEnd.gif        }
 else if (root != null)
InBlock.gif            System.out.println(
"key " + el + " is not in the tree");
InBlock.gif        
else
InBlock.gif            System.out.println(
"the tree is empty");
ExpandedBlockEnd.gif    }

None.gif
None.gif

2)复制删除法:归并删除法带来的问题是可能改变树的高度。另一种删除法也就是复制删除法,将待删除节点的key用它的前驱节点的key来代替,某一节点的前驱节点就是该节点左子树中key最大的节点。如下实现:
   
ExpandedBlockStart.gifContractedBlock.gif public void deleteByCopying(int el) dot.gif{
InBlock.gif        IntBSTNode node, p 
= root, prev = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
while (p != null && p.key != el) dot.gif// find the node p
InBlock.gif
            prev = p; // with element el;
InBlock.gif
            if (p.key < el)
InBlock.gif                p 
= p.right;
InBlock.gif            
else
InBlock.gif                p 
= p.left;
ExpandedSubBlockEnd.gif        }

InBlock.gif        node 
= p;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
if (p != null && p.key == el) dot.gif{
InBlock.gif            
if (node.right == null// node has no right child;
InBlock.gif
                node = node.left;
InBlock.gif            
else if (node.left == null// no left child for node;
InBlock.gif
                node = node.right;
ExpandedSubBlockStart.gifContractedSubBlock.gif            
else dot.gif{
InBlock.gif                IntBSTNode tmp 
= node.left; // node has both children;
InBlock.gif
                IntBSTNode previous = node; // 1.
ExpandedSubBlockStart.gifContractedSubBlock.gif
                while (tmp.right != nulldot.gif// 2. find the rightmost
InBlock.gif
                    previous = tmp; // position in the
InBlock.gif
                    tmp = tmp.right; // left subtree of node;
ExpandedSubBlockEnd.gif
                }

InBlock.gif                node.key 
= tmp.key; // 3. overwrite the reference
InBlock.gif
                if (previous == node) // of the key being deleted;
InBlock.gif
                    previous.left = tmp.left; // 4.
InBlock.gif
                else
InBlock.gif                    previous.right 
= tmp.left; // 5.
ExpandedSubBlockEnd.gif
            }

InBlock.gif            
if (p == root)
InBlock.gif                root 
= node;
InBlock.gif            
else if (prev.left == p)
InBlock.gif                prev.left 
= node;
InBlock.gif            
else
InBlock.gif                prev.right 
= node;
ExpandedSubBlockEnd.gif        }
 else if (root != null)
InBlock.gif            System.out.println(
"key " + el + " is not in the tree");
InBlock.gif        
else
InBlock.gif            System.out.println(
"the tree is empty");
ExpandedBlockEnd.gif    }

None.gif

4。树的平衡:如果树中任何节点的两个子树的高度差或者是0或者为1,那么这样的二叉树就是高度平衡的。如何平衡一棵树?
1)简单算法:将树中的所有数据中序遍历,放进一个数组,此数组已经排序,然后折半插入
ExpandedBlockStart.gifContractedBlock.gif public void balance(int data[], int first, int last) dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
if (first <= last) dot.gif{
InBlock.gif            
int middle = (first + last) / 2;
InBlock.gif            System.out.print(data[middle] 
+ " ");
InBlock.gif            insert(data[middle]);
InBlock.gif            balance(data, first, middle 
- 1);
InBlock.gif            balance(data, middle 
+ 1, last);
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

None.gif
文章转自庄周梦蝶  ,原文发布时间5.17
   
2)DSW算法:
基本原理是通过树的右旋转得到树的主干,然后再进行左旋转得到平衡树

最后更新:2017-05-17 11:35:36

  上一篇:go  欢迎加入阿里云云HBase技术交流群
  下一篇:go  大数据浪潮下,前端工程师眼中的完整数据链图