浅谈二叉树

为什么要使用二叉树

  因为二叉树结合了有序数组和链表的特点。在二叉树中查找数据的速度和在有序数组中查找一样快,并且插入数据项和删除数据项的速度也和链表一样快

查找

  从根开始进行查找,当前节点的key值与所查key值相等时返回该节点;当前节点的key值小于所查key值时,则去其左子树进行查找;当前节点的key值大于所查key值时,则去其右子树进行查找。

public Node find(int key){
	Node current = root;
	while(current.iData != key){
		if(key < current.iData){
			current = current.leftChild;
		} else {
			current = current.rightChild;
		}
		
		if(current == null){
			return null;
		}
	}
	return current;
}

插入

  二叉树的插入首先需要确定新节点插入的位置,即先找插入位置,再插入新节点

public void insert(int id, double dd){
	Node newNode = new Node();
	newNode.iData = id;
	newNode.fData = dd;
	
	if(root == null){
		root = newNode;
	} else {
		Node current = root;
		Node parent;
		
		while(true){
			parent = current;
			if(id < current.iData){
				current = current.leftChild;
				if(current == null){
					parent.leftChild = newNode;
					return;
				}
			} else {
				current = current.rightChild;
				if(current == null){
					parent.rightChild = newNode;
					return;
				}
			}
		}
	}
}

遍历

  遍历树的意思是根据一种特定的顺序访问树的每一个节点,树的遍历分为以下三种形式:

  • 前序遍历
    1.访问节点
    2.调用自身遍历该节点的左子树
    3.调用自身遍历该节点的右子树

      public void preOrder(Node localRoot){
      	if(localRoot != null){
              System.out.print(localRoot.iData + " ");
      		inOrder(localRoot.leftChild);
          	inOrder(localRoot.rightChild);
          }
      }
    
  • 中序遍历
    1.调用自身遍历该节点的左子树
    2.访问节点
    3.调用自身遍历该节点的右子树

      public void inOrder(Node localRoot){
          if(localRoot != null){
      	    inOrder(localRoot.leftChild);
      	    System.out.print(localRoot.iData + " ");
      	    inOrder(localRoot.rightChild);
          }
      }
    
  • 后序遍历
    1.调用自身遍历该节点的左子树
    2.调用自身遍历该节点的右子树
    3.访问节点

      public void preOrder(Node localRoot){
          if(localRoot != null){
      	    inOrder(localRoot.leftChild);
      	    inOrder(localRoot.rightChild);
              System.out.print(localRoot.iData + " ");
          }
      }
    

查找最大值和最小值

  查找最小值时,先走到根的左子节点处,然后接着走到那个节点的左子节点,如此类推,知道找到一个没有左子节点的节点,这个节点即为最小值所在的节点。

    public Node mininum(){
	    Node current = root;
	    Node last = null;
	    while(current != null){
		    last = current;
		    current = current.leftChild;
	    }
	    return last;
    }

  查找最大值时,先走到根的右子节点处,然后接着走到那个节点的右子节点,如此类推,知道找到一个没有右子节点的节点,这个节点即为最大值所在的节点。

    public Node maxnum(){
	    Node current = root;
	    Node last = null;
	    while(current != null){
		    last = current;
		    current = current.righttChild;
	    }
	    return last;
    }

删除节点

  删除节点是二叉树中最复杂的常用操作,删除节点的方法为先查找到所要删除的节点,找到该节点后有以下三种情况需要考虑:

  • 该节点是叶节点(没有子节点)
      要删除叶节点,只需要改变该节点的父节点的对应字段的值,由该节点改为null就可以了。要删除的节点仍然存在,但是它已经不是树的一部分了。

      if(current.leftChild == null && current.rightChild == null){
      		    if(current == root){
      			    root = null;
      		    }else if(isLeftChild){
      			    parent.leftChild = null;
      		    }else{
      			    parent.rightChild = null;
      		    }
      	    }
    
  • 该节点有一个子节点
      该节点只有两个连接:连向父节点和连向它唯一的子节点。删除这个节点的时候,需要从这个序列中“剪断”这个节点,把它的子节点直接连到它的父节点上。这个过程要求改变父节点适当的引用(左子节点还是右子节点),指向要删除节点的子节点。

      if(current.rightChild == null){
      		    if(current == root){
      			    root = current.leftChild;
      		    }else if(isLeftChild){
      			    parent.leftChild = current.leftChild;
      		    }else{
      			    parent.rightChild = current.leftChild;
      		    }
      	    }else if(current.leftChild == null){
      		    if(current == root){
      			    root =current.rightChild;
      		    }else if(isLeftChild){
      			    parent.leftChild = current.rightChild;
      		    }else{
      			    parent.rightChild = current.rightChild;
      		    }
      	    }
    
  • 该节点有两个子节点
      如果要删除的节点有两个子节点,就不能只是用它的一个子节点代替它。删除有两个子节点的节点,用它的中序后继来代替该节点。删除的过程分为以下三个步骤:
    1. 找后继节点
      首先,找到初始节点的右子节点,它的关键字值一定比初始节点大。然后转到初始节点的右子节点的左子节点那里(如果有的话),然后到这个左子节点的左子节点,以此类推,顺着左子节点的路径一直向下找。这个路径上的最后一个左子节点就是初始节点的后继。

      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;
          }    
          return successor;
      }
    

2. 后继节点是删除节点的右子节点
  如果后继节点是current节点的右子节点,只需要把以后继为根的子树移到删除的节点的位置。这个操作需要两个步骤:把current从它父节点的rightChild字段删掉(也有可能是leftChild),把这个 字段指向后继;把current的左子节点移出来,把它插到后继的leftChild字段。

    Node successor = getSuccessor(current);
    if(current == root){
	    root = successor;
    }else if(isLeftChild){
	    parent.leftChild = successor;
    }else {
	    parent.rightChild = successor;
    }
    successor.leftChild = current.leftChild;

**3. 后继节点是删除节点的左子节点 **
  如果后继节点是要删除节点右子节点的左后代,执行删除操作需要以下四个步骤:把后继父节点的leftChild字段置为后继的右节点;把后继的rightChild字段置为要删除节点的右子节点;把current从它父节点的rightChild字段移除,把这个字段置为后继;把current的左子节点从current移除,把后继的leftChild字段置为current的左子节点。

    if(successor != delNode.rightChild){
		    successorParent.leftChild = successor.rightChild;
		    successor.rightChild = delNode.rightChild;
	    }
    if(current == root){
	    root = successor;
	    }else if(isLeftChild){
		    parent.leftChild = successor;
	    }else {
		    parent.rightChild = successor;
	    }
	    successor.leftChild = current.leftChild;

删除是必要的么

  二叉树的删除是一个比较复杂的操作,实际上因为它非常复杂,一些程序言都尝试着躲开它。他们会在Node类中加一个Boolean字段,名称如isDeleted。要删除一个节点时,就把此节点的这个字段置为true。其他操作,如find(),在查找之前先判断这个节点是不是标志为已经删除了。这样删除的节点不会改变树的结构。当然,这样做存储中还保留着这种“已经删除”的节点。

posted @ 2016-04-19 09:26  编程的小蚂蚁  阅读(193)  评论(0编辑  收藏  举报