数据结构之二叉树

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

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

 

二。二叉树的实现

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

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

public   class  IntBSTNode  {
  protected   int  key;

  protected  IntBSTNode left, right;

  public  IntBSTNode()  {
  left  =  right  =   null ;
 }

  public  IntBSTNode( int  el)  {
   this (el,  null ,  null );
 }

  public  IntBSTNode( int  el, IntBSTNode left, IntBSTNode right)  {
  key  =  el;
  left  =  left;
  right  =  right;
 }
}

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

public   class  IntBST  {
  protected  IntBSTNode root;

  protected   void  visit(IntBSTNode p)  {
  System.out.print(p.key  +   "   " );
 }

  public  IntBST()  {
  root  =   null ;
 }

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

 

public  IntBSTNode search( int  el)  {
   return  search(root, el);
 }

  protected  IntBSTNode search(IntBSTNode p,  int  el)  {
   while  (p  !=   null )
    if  (el  ==  p.key)
     return  p;
    else   if  (el  <  p.key)
    p  =  p.left;
    else
    p  =  p.right;
   return   null ;
 }

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

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

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

 

public   void  breadthFirst()  {
  IntBSTNode p  =  root;
  Queue queue  =   new  Queue();
   if  (p  !=   null )  {
   queue.enqueue(p);
    while  ( ! queue.isEmpty())  {
    p  =  (IntBSTNode) queue.dequeue();
    visit(p);
     if  (p.left  !=   null )
     queue.enqueue(p.left);
     if  (p.right  !=   null )
     queue.enqueue(p.right);
   }
  }
 }

 

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

V——访问一个节点

L——遍历左子树

R——遍历右子树

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

VLR——先序遍历树

LVR——中序遍历树

LRV——后序遍历树

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

public   void  preorder()  {
  preorder(root);
 }

// 先序

protected   void  preorder(IntBSTNode p)  {
   if  (p  !=   null )  {
   visit(p);
   preorder(p.left);
   preorder(p.right);
  }
 }

  public   void  inorder()  {
  inorder(root);
 }

// 中序

protected   void  inorder(IntBSTNode p)  {
   if  (p  !=   null )  {
   inorder(p.left);
   visit(p);
   inorder(p.right);
  }
 }

  public   void  postorder()  {
  postorder(root);
 }

// 后序

protected   void  postorder(IntBSTNode p)  {
   if  (p  !=   null )  {
   postorder(p.left);
   postorder(p.right);
   visit(p);
  }
 }

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

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

public void insert(int el) {
        IntBSTNode p = root, prev = null;
        while (p != null) { // find a place for inserting new node;
            prev = p;
            if (p.key < el)
                p = p.right;
            else
                p = p.left;
        }
        if (root == null) // tree is empty;
            root = new IntBSTNode(el);
        else if (prev.key < el)
            prev.right = new IntBSTNode(el);
        else
            prev.left = new IntBSTNode(el);
    }



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

public void deleteByMerging(int el) {
        IntBSTNode tmp, node, p = root, prev = null;
        while (p != null && p.key != el) { // find the node p
            prev = p; // with element el;
            if (p.key < el)
                p = p.right;
            else
                p = p.left;
        }
        node = p;
        if (p != null && p.key == el) {
            if (node.right == null) // node has no right child: its left
                node = node.left; // child (if any) is attached to its parent;
            else if (node.left == null) // node has no left child: its right
                node = node.right; // child is attached to its parent;
            else { // be ready for merging subtrees;
                tmp = node.left; // 1. move left
                while (tmp.right != null)
                    // 2. and then right as far as
                    tmp = tmp.right; // possible;
                tmp.right = // 3. establish the link between the
                node.right; // the rightmost node of the left
                // subtree and the right subtree;
                node = node.left; // 4.
            }
            if (p == root)
                root = node;
            else if (prev.left == p)
                prev.left = node;
            else
                prev.right = node;
        } else if (root != null)
            System.out.println("key " + el + " is not in the tree");
        else
            System.out.println("the tree is empty");
    }

2)复制删除法:归并删除法带来的问题是可能改变树的高度。另一种删除法也就是复制删除法,将待删除节点的key用它的前驱节点的key来代替,某一节点的前驱节点就是该节点左子树中key最大的节点。如下实现:
   

 public void deleteByCopying(int el) {
        IntBSTNode node, p = root, prev = null;
        while (p != null && p.key != el) { // find the node p
            prev = p; // with element el;
            if (p.key < el)
                p = p.right;
            else
                p = p.left;
        }
        node = p;
        if (p != null && p.key == el) {
            if (node.right == null) // node has no right child;
                node = node.left;
            else if (node.left == null) // no left child for node;
                node = node.right;
            else {
                IntBSTNode tmp = node.left; // node has both children;
                IntBSTNode previous = node; // 1.
                while (tmp.right != null) { // 2. find the rightmost
                    previous = tmp; // position in the
                    tmp = tmp.right; // left subtree of node;
                }
                node.key = tmp.key; // 3. overwrite the reference
                if (previous == node) // of the key being deleted;
                    previous.left = tmp.left; // 4.
                else
                    previous.right = tmp.left; // 5.
            }
            if (p == root)
                root = node;
            else if (prev.left == p)
                prev.left = node;
            else
                prev.right = node;
        } else if (root != null)
            System.out.println("key " + el + " is not in the tree");
        else
            System.out.println("the tree is empty");
    }

4。树的平衡:如果树中任何节点的两个子树的高度差或者是0或者为1,那么这样的二叉树就是高度平衡的。如何平衡一棵树?
1)简单算法:将树中的所有数据中序遍历,放进一个数组,此数组已经排序,然后折半插入

 public void balance(int data[], int first, int last) {
        if (first <= last) {
            int middle = (first + last) / 2;
            System.out.print(data[middle] + " ");
            insert(data[middle]);
            balance(data, first, middle - 1);
            balance(data, middle + 1, last);
        }
    }

文章转自庄周梦蝶  ,原文发布时间5.17

   
2)DSW算法:
基本原理是通过树的右旋转得到树的主干,然后再进行左旋转得到平衡树

时间: 2024-08-31 21:56:22

数据结构之二叉树的相关文章

数据结构有关二叉树问题

问题描述 数据结构有关二叉树问题 一颗完全二叉树有700个结点,则共有几个叶子结点. 答案是350个,求详细计算过程 解决方案 ?? #define null 0???#include "stdio.h" ???typedef char datatype;?? typedef struct tn? {datatype data;?? struct tn *lc,*rc;??......答案就在这里:数据结构-二叉树 问题---------------------- 解决方案二: n0=

二叉树 层序遍历-C++ 数据结构、二叉树、层序遍历问题

问题描述 C++ 数据结构.二叉树.层序遍历问题 代码结构如下: template class CirQueue... // 栈类: template struct BiNode{ // 节点类: T data; BiNode *lchild, * rchild; }; template class BiTree.... // 二叉树类: ? template void BiTree::leverOrder( ) { // 层序遍历: if( root == NULL ) { cout<<&q

数据结构关于二叉树遍历的一道题 在线等~

问题描述 数据结构关于二叉树遍历的一道题 在线等~ 利用栈的基本操作写出先序遍历二叉树的非递归算法 要求进栈的元素最少, 并指出下列二叉树中需进栈的元素. 这是答案: 根据上述代码, (1)左子树lchild不需要入栈吗? (2)入栈顺序是什么? (3)最后一行代码 if (top>0) bt=s[top--] 是什么意思? (4)如果是中序或后序,入栈顺序又是什么? 谢谢大神们啦~~ O(∩_∩)O 解决方案 3 if (top>0) bt=s[top--] 出栈.取出top位置的元素,并且

数据结构之---二叉树C实现

学过数据结构的都知道树,那么什么是树? 树(tree)是包含n(n>0)个结点的有穷集,其中: (1)每个元素称为结点(node): (2)有一个特定的结点被称为根结点或树根(root). (3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,--Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree). 树也可以这样定义:树是由根结点和若干颗子树构成的.树是由一个集合以及在该集合上定义的一种关系构成的.集合中的元素称为树的

数据结构例程——二叉树的构造

本文是数据结构基础系列(6):树和二叉树中第13课时二叉树的构造的例程. 1.由先序序列和中序序列构造二叉树 定理:任何n(n≥0)个不同节点的二叉树,都可由它的中序序列和先序序列唯一地确定. 证明(数学归纳法) 基础:当n=0时,二叉树为空,结论正确. 假设:设节点数小于n的任何二叉树,都可以由其先序序列和中序序列唯一地确定. 归纳:已知某棵二叉树具有n(n>0)个不同节点,其先序序列是a0a1-an−1:中序序列是b0b1-bk−1bkbk+1-bn−1. 先序遍历"根-左-右&quo

数据结构例程——二叉树遍历的非递归算法

本文是数据结构基础系列(6):树和二叉树中第11课时二叉树遍历非递归算法的例程. [二叉树遍历的非递归算法] 实现二叉树的先序.中序.后序遍历的非递归算法,并对用"A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))"创建的二叉树进行测试. 请利用二叉树算法库. [参考解答](btreee.h见算法库) #include <stdio.h> #include "btree.h" void PreOrder1(BTNode *b) {

数据结构例程——二叉树遍历的递归算法

本文是数据结构基础系列(6):树和二叉树中第10课时二叉树的遍历的例程. [二叉树遍历的递归算法] 实现二叉树的先序.中序.后序遍历的递归算法,并对用"A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))"创建的二叉树进行测试. 请利用二叉树算法库. [参考解答](btreee.h见算法库) #include <stdio.h> #include "btree.h" void PreOrder(BTNode *b) //先序遍历的递

数据结构例程——二叉树的层次遍历算法

本文是数据结构基础系列(6):树和二叉树中第12课时层次遍历算法的例程. [二叉树的层次遍历算法] 实现二叉树的层次遍历算法,并对用"A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))"创建的二叉树进行测试. 请利用二叉树算法库. [参考解答](btreee.h见算法库) #include <stdio.h> #include "btree.h" void LevelOrder(BTNode *b) { BTNode *p; BT

数据结构 排序二叉树(BST) 插入删除查询 中序遍历 销毁(后序遍历)

结构概念如下: 二叉排序树(binary sort tree): 1.也叫做二叉查找树 2.如果他的左子树不为空,则左子树上所有结点的 值均小于他的根结构的值 3.如果他的右子树不为空,则右子树上所有结点的 值均大于他的根结构的值 4.他的左右子树分别为二叉排序树 5.按照二叉树中序遍历其返回结果树有序的 下面就是一颗典型的二叉树,只是是字母,可以将字母换位字母的顺序进行审视,但是它不是平衡二叉树 排序二叉树(BST)的优点在于进行数据查找比较方便,因为他是按照数据来到的顺序进行如上的规则进行的

采用栈数据结构的二叉树非递归遍历

[前言]树的遍历,根据访问自身和其子节点之间的顺序关系,分为前序,后序遍历.对于二叉树,每个节点至多有两个子节点(特别的称为左,右子节点),又有中序遍历.由于树自身具有的递归性,这些遍历函数使用递归函数很容易实现,代码也非常简洁.借助于数据结构中的栈,可以把树遍历的递归函数改写为非递归函数.   在这里我思考的问题是,很显然,循环可以改写为递归函数.递归函数是否借助栈这种数据结构改写为循环呢.因为函数调用中,call procedure stack 中存储了流程的 context,调用和返回相当