二叉树抽象数据类型及遍历实现

(一)树的基本定义:

树是n个结点组成的有限集合;
1.n=0时,它是一个空树
2.n>0时,所有结点中存在一个根结点,其余结点可分为m个互不相交的子集合,每个集合都是一个满足定义的树

如图即为一棵树:
在这里插入图片描述

树的基本术语:

  • 结点:树中的每一个元素都称为一个结点
  • 边:用<m,n>表示从m到n的某个连线代表m与n之间有某种关系,这种关系称为边
  • 双亲结点和孩子结点:其中m是n的双亲结点,n是m的孩子节点
  • 兄弟结点:两个结点有一个共同的双亲结点,两个结点为兄弟结点
  • 叶子结点:没有孩子结点的结点称为叶子结点
  • 分支结点:非叶子结点
  • 结点的度:该结点拥有的孩子结点的数量
  • 树的度:该树中所有结点的度的最大值
  • 结点的层数:根结点的层数为0,如图A的层数是0,E的层数的2
  • 树的深度:结点层数的最大值,如图树的深度为2
  • 树的高度:树的深度加1
  • 祖先结点和子孙结点:若m到n只有一条路径,m为n的祖先结点,n为m的子孙结点
  • 有序树:树中每个结点的子树存在确定的次序关系
  • 无序树:树中每个结点的子树不存在确定的次序关系
  • 森林:有m个互不相交的树组成

(二)二叉树的定义:

二叉树是每个节点最多有两个子树的有序树

从二叉树的定义可以看出,二叉树中不存在度大于2的结点,也就是每个结点至多有两个子树-左子树和右子树

几种特殊的二叉树:

  • 完全二叉树:一颗高度为h的二叉树,除最后一层外的所有层上的结点数均达到最大值,而最后一层的所有结点分布在该层最左边连续的位置,如图所示
    在这里插入图片描述

  • 满二叉树:如果其所有分支结点都有非空左子树和非空右子树,并且所有叶子结点都在同一层上,如图所示:
    在这里插入图片描述

  • 扩充二叉树:把原二叉树所有结点出现的空的字树位置上增加特殊的结点–空树叶,如图所示:
    在这里插入图片描述

(三)二叉树的性质:

  • 1.任何一颗二叉树,度数为0的结点比度数为2的结点多一个
  • 2.二叉树的第i层上最多有2^i个结点
  • 3.高度为h的二叉树最多有2^h-1个结点 (满二叉树时有最大)
  • 4.非空满二叉树的叶子结点的数量等于其分支结点数量加1
    • 深度为d的满二叉树中的叶节点数为:2^d
    • 深度为d的满二叉树中的分支节点数为:2^d-1
  • 5.有n个结点的完全二叉树的高度为log2 (n+1)(2为底数)
  • 6.度为m的树,第i层最多有m^i个结点

例题:

  • 1一棵完全二叉树上有1001个结点,其中叶子结点的个数是 ——
  • 2若一棵二叉树具有10个度为2的结点,5个度为1的结点,则度为0的结点个数是 —— (性质1)
  • 3具有10个叶结点的二叉树中有 —— 个度为2的结点—(性质1)

叶结点即为度为0的结点

  • 4‏一棵完全二叉树上有1001个结点,其中叶子结点的个数是 ——(性质1)
  • 5一个具有1025个结点的二叉树的高h(只有根结点时的高度为1)为——
  • 6 一棵二叉树高度为h(只有根结点时的高度为1),所有结点的度或为0,或为2,则这棵二叉树最少有 ——个结点
    解析:节点最少的情况为:除了根节点外,每层都有且仅有2个节点,所以共有2n-1个节点:第一层是一个节点,其他的层是2个节点,所以是2h-1。根节点度为2;其他层(有2个节点)一个节点的度为2,另一个节点的度为0,叶子节点的度都为0
  • 7高度为 K(只有根结点时的高度为1)的二叉树最大的结点数为—— (性质3)
  • 8一棵完全二叉树具有1000个结点则此完全二叉树有 —— 个度为2的结点 (性质1,性质3)
    解析:1000个节点的完全二叉树有10层(层数从1开始),1-9层是满二叉树,共有512-1=511个节点,说明第10层有489个节点。
    有一个公式,n2=n0-1,只需把度为0的节点数算出来就可以算出度为2的节点数。第9层有256-244-1=11个0度节点、第10层有489个0度节点,共有489+11=500个0度节点,所以这棵二叉树中度为2的节点数为
    n2=n0-1=500-1=499
  • 9一棵深度为6的满二叉树有 ——个分支结点 (性质3,4)
  • 10‏由3个结点所构成的二叉树有 —— 种形态
    解析:根-左-左;根-右-右;根-(一左一右);根-左-右;根-右-左
  • 11具有n个叶子结点的满二叉树包含 2n-1 节点

答案:1:501,2.11,3:9, 4:501, 5:11-1025,
6:2n-1,7:2^K-1,8:499,9:31,10:5

(四)二叉树的存储结构及其代码实现:

二叉树的存储结构有两种:顺序存储和链式存储

顺序存储:

二叉树的顺序存储由一个一堆数组构成,二叉树上的结点按照某种次序分别存入该数组的各个单元
在这里插入图片描述
这种存储结构可以方便的实现二叉树的各种运算

链式存储:

二叉树的链式存储操作方便,表达简明,是常见的存储方式
但由于存储大量指针,空间浪费严重

1.二叉链表:

data域称为数据域,用于存储二叉树结点的数据元素,leftChild域称为左孩子域,用于存放本结点左孩子的指针;rightChild称为右孩子域,存放指向右孩子的指针;
如图为非完全二叉树的转化:
在这里插入图片描述
如图为完全二叉树:
在这里插入图片描述

2.三叉链表:

二叉链表任意实现,但求双亲运算的实现比较麻烦,时间效率不高,三叉链表比二叉链表多了一个指针,指向父结点
在这里插入图片描述

树和结点的代码实现:

1.二叉树结点的实现:

 template<class T>
	 void BinaryTree<T>::levelOrder(BinaryTreeNode<T>*root){
	 	private : 
	 		T element;                         //结点的数据域 
 	 		BinaryTreeNode<T>*leftNode;        //结点的左孩子结点 
	 		BinaryTreeNode<T>*rightNode;       //结点的右孩子结点  
	 		public :
	 			BinaryTreeNode(){};            //构造函数 
	 			BinaryTreeNode(const T&ele):element(ele),leftChild(nullptr),rightChild(nullptr){};             
				BinaryTreeNode(const T&ele,BinaryTreeNode<T>*l,BinaryTreeNode<T>*r);    //给定数据值和左右孩子结点的构造函数 
	 			BinaryTreeNode<T> *getLeftChild()const{return leftChild};                           //返回该结点的左孩子结点 
	 			BinaryTreeNode<T> *getRightChild()const{return rightChild};                      //返回该结点的右孩子结点
				void setLeftChild(BinaryTreeNode<T> *l){leftChild=1};								 //设置该结点的左孩子结点
				void steRightChild(BinaryTreeNode<T> *r){rightChild=1};								 //设置该结点的右孩子结点
				void createLeftChild();								 //创建该结点的左孩子结点
				void createRightChild();								 //创建该结点的右孩子结点
				T getValue() const{return element;};								 //返回该结点的数据值
				void setValue(const T&val){element=val;};								 //设置该结点的数据域的值
				bool isLeaf()const;								 //判断该结点是否为叶子结点,若是,则返回true 
	 }        

2.二叉树的实现:

template <class T>
class BinaryTree{
private :
    BinaryTree<T>*root;      //二叉树根结点
public:
    BinaryTree();           //构造函数
    ~BianryTree();          //析构函数
    bool isEmpty()const;      //判断是否为空树
    BinaryTreeNode<T>*getRoot()const;        //返回二叉树的根结点
    BiaryTreeNode<T>*getParent(BinaryTreeNode<T>*current)const;  //返回结点的父节点
    BinaryTreeNode<T>*getLeftSibling(BinaryTreeNode<T>*current)const;   //返回结点的左结点
    BinaryTreeNode<T>*getRightSibling(BinaryTreeNdoe<T>*current)const;   //返回结点的有结点
    void levelOrder(BinaryTreeNode<T>*root);       //广度遍历
    void preOrder(BinaryTreeNode<T>*root);       //前序遍历
    void PreOrderWithoutRecursion(BinaryTreeNode<T>*root);    //非递归前序遍历
    void inOrder(BinaryTreeNode<T>*root);            //中序遍历
    void InOrderWithoutRecursion(BinaryTreeNode<T>*root);    //非递归中序遍历
    void postOrder(BinaryTreeNode<T>*root);     //后序遍历
    void PostOrderWithoutRecursion(BinaryTreeNode<T>*root);    //非递归后序遍历
    void deleteBinaryTree(BinaryTreeNode<T>*root);     //删除root为结点的子树
    void visit(BinaryTreeNode<T>*t);                     //访问当前结点
    void preincreatetree(BinaryTreeNode<T>*t,string pre,string in);    //前序和中序创建树
    void inpostcreatetree(BinaryTreeNode<T>*t,string in,string post);    //后序和中序创建树
}

(五)二叉树的广度优先遍历:

广度优先遍历即为按层次遍历:从最高层开始,向下逐层访问每一个结点,每一层上自左向右访问每一个结点
在访问每个结点后,它的子结点按照从左到右的顺序依次放入队列的末尾,然后访问队列头部的结点

template<class T>
void BinaryTree<T>::levelOrder(BinaryTreeNode<T>*root)
{
    queue<BinaryTreeNode<T>*>nodeQueue;
    BinaryTreeNode<T>*pointer=root;         //队列存放要访问的结点
    if(pointer)        //若根结点非空,将根结点移入队列
         nodeQueue.push(pointer);
    while(!nodeQueue.empty()){
         pointer=nodeQueue.front();   //读取队列头结点
         visit(pointer);            //访问当前结点
         nodeQueue.pop();           //将访问的结点移除队列
         if(pointer->getLeftChild())
              nodeQueue.push(pointer->getLeftChild());
         if(pointer->getRightChild())
              nodeQueue.push(pointer->getRightChild());    //将访问过的结点的左右孩子移入队尾
              }
   }

(六)二叉树的深度优先遍历:

递归实现:

二叉树的基本结构为:D为根结点,L表示左子树,R表示右子树,
若从左到右访问,则有三中遍历方式:1.前序遍历DLR;2.中序遍历LDR;3.后序遍历LRD
在这里插入图片描述

  • 若为前序遍历,其遍历顺序为:ABDEGHCFIJ
template<class T>
void BinaryTree<T>::preOrder(BinaryTreeNode<T>*root)
{
   if(root!=NULL){
       visit(root);                     //访问当前结点
       preOrder(root->getLeftChild());  //访问左子树
       preOrder(root->getRightChild());  //访问右子树
       }
  };
  • 若为中序遍历,其遍历顺序为:DBGEHACIFJ,代码实现为
template<class T>
void BianryTree<T>::inOrder(BinaryTreeNode<T>*root)
{
    if(root!=NULL){
        preOrder(root->getLeftChild());   //访问左子树
        visit(root);                      //访问当前结点
        preOrder(root->getRightChild());  //访问右子树
        }
   }
  • 若为后序遍历,则遍历顺序为:DGHEBIJFCA
template<class T>
void BinaryTree<T>::postOrder(BinaryTreeNode<T>*root)
{
     if(root!=NULL){
         postOrder(root->getLeftChild());
         postOrder(root->getRightChild());
         visit(root);
       }
  }

非递归实现:

递归算法的效率一般比非递归算法效率低

  • 前序非递归实现:
  • 在这里插入图片描述

算法思想:每遇到一个结点,先访问该结点,并把该结点的非空右子树的根结点压入栈中,然后遍历左子树,重复该过程直到当前访问的结点没有左子树停止,然后从栈顶弹出待访问的结点,继续遍历,直到栈空;代码实现:

template<class T>
void BinaryTree<T>::preOrderWithoutRecusion(BinaryTreeNode<T>*root)
{
    stack<BinaryTreeNode<T>*>nodeStack;    //存放带访问结点的栈
    BinaryTreeNode<T>*pointer=root;       //保存根结点
    while(!nodeStack.empty()||pointer)    //栈空时遍历栈
         if(pointer){                   
                   visit(pointer);         //访问当前该结点
                   if(pointer->getRightChild()!=NULL)
                        nodeStack.push(pointer->getRightChild());     //当前访问的结点的右子树的根结点入栈
                   pointer=pointer->getRightChild();//转向访问左子树
                   }else{                     //左子树读取完毕后,转向访问右子树
                   pointer=nodeStack.top();     //读取栈顶待访问的结点
                   nodeStack.pop();       //删除栈顶结点
                   }
            }
   }
  • 中序非递归实现:
    在这里插入图片描述
    算法思想:从根结点开始向左搜索,每遇到一个结点,就将其压入栈中,然后遍历其左子树,遍历完左子树后,弹出栈顶结点并访问它,然后遍历右子树:
template<class T>
void BinaryTree<T>::InOrderWithoutRecusion(BinaryTreeNode<T>*root)
{
    stack<BinaryTreeNode<T>*>nodeStack;      //存储待访问的结点
     BinaryTreeNode<T>*pointer=root;        //保存根结点
     while(!nodeStack.empty()||pointer){     //栈空时遍历结束
         if(pointer){
                 nodeStack.push(pointer);//当前结点入栈
                 pointer=pointer->getLeftChild();//转向访问其左孩子
                 }
                else{             //左子树访问完毕后,转向访问右子树
                   poiter=nodeStack.top();    //读取栈顶待访问的结点
                   visit(pointer);     //访问当前结点
                   pointer=pointer->getRightChild();   //转向其右孩子
                   nodeStack.pop();         //删除栈顶结点
                    }
             }
  }              
  • 后序非递归实现:
    在这里插入图片描述
    算法思想:从根结点访开始,向左搜索,并压入栈中,直到压入栈中的结点不再有左子树,读取栈顶结点,若其有右子树则访问其右子树,否则,访问该结点并从栈中删除
template<class T>
void BinaryTree<T>::PostWithoutRecusion(BinaryTreeNode<T>*root)
{
      stack<BianryTreeNode<T>*>nodeStack;     //存储待访问的结点
      BinaryTreeNode<T>*pointer=root;      //保存根结点
      BinaryTreeNode<T>*pre=root;        //保存第一个被访问的结点
      while(pointer){
        for(;pointer->getLeftChild()!=NULL;pointer=poiner->getLeftChild())
             nodeStack.push(pointer);     //向左搜索
             //当前结点没有右孩子或者右孩子刚被访问过,则访问该结点
        while(pointer!=NULL&&(pointer->getRightChld()==NULL||pointer->getRightChild()==pre)
       {
               visit(pointer);  
               pointer=pre;       //记录刚被访问过的结点
               if(nodeStack.empty())
                   return;
               pointer=nodeStack.top();     //取栈顶结点
               nodeStack.pop();
           }
           nodeStack.push(pointer);
           pointer=pointer->getRightChild();     //转向当前结点的右子树
          }
    }
     
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值