数据结构面试之六——二叉树的常见操作2(非递归遍历&二叉排序树)

简介: 六、二叉树的基本操作(非递归遍历)&二叉排序树的操作 接上一节第五部分,主要分析二叉树的非递归遍历和二叉排序树的操作。

数据结构面试之六——二叉树的常见操作2(非递归遍历&二叉排序树)

题注:《面试宝典》有相关习题,但思路相对不清晰,排版有错误,作者对此参考相关书籍和自己观点进行了重写,供大家参考。


六、二叉树的基本操作(非递归遍历)&二叉排序树的操作


      接上一节第五部分,主要分析二叉树的非递归遍历和二叉排序树的操作。


1.      非递归中序遍历


//1.依次将根节点root的左子树入栈,直到lchild=NULL,执行2


//2.将栈的元素出栈、访问;将当前指针指向节点的rchild,循环遍历。直到栈空为止!


   


template<typenameelemType>

      voidbinaryTreeType<elemType>::noRecursionInorderTraversal()                      //非递归中序遍历

      {

             cout<< "noRecursionInorderTraversal--------------------------->"<< endl;

             linkedStackType<nodeType<elemType>* > stack;

             nodeType<elemType>*current = root;

             while(current!= NULL || !stack.isEmptyStack())  //或者||

             {

                    if(current!= NULL)

                    {

                           stack.push(current);

                           current= current->llink;

                    }

                    else

                    {

                           stack.pop(current);

                           cout<< current->info << "\t"; //出栈的时候访问节点

                           current= current->rlink;

                    }

             }

             cout<< endl;

             cout<< "<------------------------noRecursionInorderTraversal"<< endl;

      }

2.      非递归先序遍历


      //在中序遍历的基础上,访问次序发生变化;


      //先序遍历,需要先逐个遍历根节点,然后依次处理其左、右孩子节点。


 


 template<typenameelemType>

      voidbinaryTreeType<elemType>::noRecursionPreorderTraversal()                     //非递归前序遍历

      {

             cout<<"noRecursionPreorderTraversal--------------------------->"<< endl;

             linkedStackType<nodeType<elemType>* > stack;

             nodeType<elemType>*current = root;

             while(current!= NULL || !stack.isEmptyStack())  //或者||

             {

                    if(current!= NULL)

                    {

                           cout<< current->info << "\t";   //先访问节点后入栈

                           stack.push(current);

                           current= current->llink;

                    }

                    else

                    {

                           stack.pop(current);

                           current= current->rlink;

                    }

             }

             cout<< endl;

             cout<< "<------------------------noRecursionPreorderTraversal"<< endl;

      }

3.      非递归后序遍历


由于访问的顺序为先左子树、然后右子树,最后根节点。并且对于每一个节点都是上述操作,所以,对于遍历来讲,需要识别当前节点类型是根(相对)、左孩子节点 、右孩子节点。故,我们设定了flag标记变量,flag=0初始标记,节点尚未入栈;在访问左孩子之前将flag置为1;在访问右孩子之前将flag置为2;并且在访问右孩子之后,将flag置为0。


      //后序非递归遍历比较复杂..


 


 template<typenameelemType>

voidbinaryTreeType<elemType>::noRecursionPostorderTraversal()                    //非递归后序遍历

      {

             cout<<"noRecursionPostorderTraversal--------------------------->"<< endl;

             linkedStackType<nodeType<elemType>* > stack;

             linkedStackType<int>intStack;                       //标记位同步栈.

             nodeType<elemType>*current = root;

             intnflag = 0;                                      //初始标记为0.

             if(current== NULL)

             {

                    cout<< "The Stack is Empty!" << endl;

             }

             else

             {

                    //1.将头节点先入栈,

                    stack.push(current);

                    intStack.push(1);

              current = current->llink;        //注意此处需要调整指向******

                    while(!stack.isEmptyStack()&& !intStack.isEmptyStack())        

                    {

                           if(current!= NULL && nflag == 0)                                    

                           {

                              stack.push(current);

                                  intStack.push(1);   //标记位为1,[在访问左孩子之前,将其值置为1]。

                              current = current->llink;

                           }

                           else

                           {

                                  stack.pop(current);

                                  intStack.pop(nflag);    //此时的标记位为返回值,需要根据其做判断

                                  if(nflag== 1)         //说明下一步需要入栈的为右孩子.

                                  {

                                         stack.push(current);   //继续将该节点入栈,                                                              

                                        intStack.push(2);      //但[在访问右孩子之前,将其置为2]。

                                         current= current->rlink;           //访问右节点,

                                         nflag= 0;                                  //置标记位为0

                                  }

                                  else

                                  {

                                         cout<< current->info << " ";  //待左右子树都为空再访问节点。

                                  }

                           }

                    }

                    cout<< endl;

                    cout<< "<------------------------noRecursionPostorderTraversal"<< endl;

             }    

      }



4.      二叉排序树的搜索操作


明确概念,国内、国外的著作里提及的下三个概念等价,二叉搜索树=二叉查找树=二叉排序树。


//二叉排序树的查找存在以下几种情况:


//1.链表为空,提示并返回;


//2.链表非空,需要循环查找直到指针为空,若存在,则bfound=true;否则查找至最后bfound=缺省false。



template <class elemType>

boolbSearchTreeType<elemType>::search(const elemType& searchItem)

{

      nodeType<elemType>*current = new nodeType<elemType>;

      boolbFound = false;

      if(root== NULL)

      {

             cout<< "The bSearchTree is NULL\n";       //case1: 链表为空!

             returnfalse;

      }

      else

      {

             current= root;

             while(current!= NULL && !bFound) //case2:在链表中查找,根据大小锁定左、右子树.

             {

                    if(current->info== searchItem)

                    {

                           bFound= true;

                    }

                    elseif(current->info > searchItem)

                    {

                           current= current->llink;              //左子树

                    }

                    elseif(current->info < searchItem)

                    {

                           current= current->rlink;             //右子树

                    }

             }

      }

      returnbFound;

}

5.      二叉排序树的插入存在以下几种情况:


//1.链表为空,插入元素即为根节点;


//2.链表非空,需要寻找插入位置后插入。


//2.1插入元素已经存在,则提示出错。


//2.2总能找到大于或小于某节点的位置,记录trailcurrent完成插入操作。



template <class elemType>

voidbSearchTreeType<elemType>::insert(const elemType& insertItem)

{

      nodeType<elemType>*newNode = new nodeType<elemType>;

      nodeType<elemType>*current;

      nodeType<elemType>*trailCurrent;

      newNode->info= insertItem;

      newNode->llink= NULL;

      newNode->rlink= NULL;

      if(root== NULL)

      {

             root= newNode;                                //case1:树为空.

      }

      else

      {

             current= root;

             while(current!= NULL)                          //case2,3,4搜索才知道!

             {

                    trailCurrent= current;

                    if(current->info== insertItem)

                    {

                           cout<< "the elem is already exist!\n";  //case2:元素已经存在

                           return;

                    }

                    else

                    {

                           if(current->info> insertItem)

                           {

                                  current= current->llink;           //case3:锁定左侧位置...

                           }

                           else

                           {

                                  current= current->rlink;           //case4:锁定右侧位置...

                           }

                    }

             }//endwhile

             //case3,4根据大小进行链接

             if(trailCurrent->info< insertItem)          

             {

                    trailCurrent->rlink= newNode;

             }

             else

             {

                    trailCurrent->llink= newNode;

             }

      }//end else

}


6.       二叉排序树的删除存在以下几种情况【此处可能复杂些】:

//删除一个节点,要首先判断元素值在二叉排序树中是否存在,


//若不存在则返回;


//若存在则需要锁定其对应位置为1根节点;2叶节点;3其余节点。


//根据要删除的节点是否含有左右子树的不同,分为4种情况考虑,


//见deleteFromTree()函数。



template <class elemType>

voidbSearchTreeType<elemType>::deleteNode(const elemType& deleteItem)

{

      //1.查找节点

      //2.1找不到,不存在;

      //2.2找到,删除,调用函数

      nodeType<elemType>*current;

      nodeType<elemType>*trailCurrent;

      boolbFound = false;

      if(root== NULL)

      {

             cout<< "Can't delete an Empty BST" << endl;

             return;

      }

      else

      {

             current= root;

             trailCurrent= root;

             while(current != NULL && !bFound)

             {

                    if(current->info== deleteItem)

                    {

                           bFound= true;

                    }

                    elseif(current->info > deleteItem)

                    {

                           trailCurrent= current;

                           current= current->llink;    //左

                    }

                    else

                    {

                           trailCurrent= current;

                           current= current->rlink;   //右

                    }

             }//endwhile

             if(current== NULL)

             {

                    cout<< deleteItem << " is not Exist in the BST!\n" <<endl;

             }

             elseif(bFound)

             {

                    if(current== root)

                    {

                           deleteFromTree(root);                  //可能是根节点

                    }

                    elseif(trailCurrent->info > deleteItem)

                    {

                           deleteFromTree(trailCurrent->llink);//左半分支,调整trailCurrent的指向

                    }

                    elseif(trailCurrent->info < deleteItem)

                    {

                           deleteFromTree(trailCurrent->rlink);  //右半分支,调整trailCurrent的指向

                    }

             }//endif bFound

        }//end else

}



//[原理]:某节点的前驱是该节点左子树的最右端的节点(中序遍历的结果)



template <class elemType>

voidbSearchTreeType<elemType>::deleteFromTree(nodeType<elemType>*&p)

{

      nodeType<elemType>*temp;

      nodeType<elemType>*current;

      nodeType<elemType>*trailCurrent;

      if(p== NULL)

      {

             cout<< "The BST is NULL!" << endl;

             return;

      }

      if(p->llink== NULL && p->rlink == NULL)      //情况1,左右节点都为空(叶节点)

      {

             temp= p;

             p= NULL;

             deletetemp;

      }

      elseif( p->rlink == NULL)                     //情况2,右子树为空,左非空

      {

             temp= p;

             p= temp->llink;

             deletetemp;

      }

      elseif(p->llink == NULL)                      //情况3,左子树为空,右非空

      {

             temp= p;

             p= temp->rlink;

             deletetemp;

      }

      else                           //情况4,左右都非空[用中序遍历的前一个节点替换]

      {

             current= p->llink;

             trailCurrent= NULL;

             while(current->rlink!= NULL)

             {

                    trailCurrent= current;   //trailCurrent最终指向准备删除节点的前一个节点

                    current= current->rlink;

             }

             p->info= current->info;                //信息赋值

             if(trailCurrent== NULL)              //仅一个左孩子节点

             {

                    p->rlink = current->llink;        

             }

             else

             {

                    trailCurrent->rlink= current->llink; //给删除前点的前面一个节点调整指针指向

             }

             deletecurrent;

      }

}


相关文章
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
32 1
|
29天前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
81 4
|
1月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
129 8
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
31 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
2月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
174 9
|
22天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
43 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。