当前位置:   article > 正文

二叉树的创建和基本操作(详解)

二叉树的创建

文章目录

二叉树的创建(使用先序遍历)

递归实现二叉树的遍历:

先序遍历:

中序遍历:

后续遍历:

一些二叉树基本操作:

求树的深度:

求树的结点个数:

查找特定值的结点:

查找特定值结点的父结点:

将树拷贝到新树上(二叉树的复制):

非递归实现遍历:

层次遍历:

前序遍历:

中序遍历:

后续遍历:

本文主要讲解递归实现二叉树的代码,对于树,二叉树的性质概念不多赘述

我们以'#'代表空结点(为已有结点下的左右空孩子),方便创建二叉树,我们以下图的二叉树作为讲解

二叉树的创建(使用先序遍历)

以下我会用递归的方法实现创建和基本操作,最后也会使用非递归的思想取实现二叉树的几种遍历方法

  1. class Node {//二叉树结点
  2. public:
  3. char m_value;//结点值
  4. Node* m_left;//左子树
  5. Node* m_right;//右子树
  6. Node():m_left(nullptr),m_right(nullptr){}
  7. Node(char val):m_value(val),m_left(nullptr),m_right(nullptr){}
  8. ~Node(){}
  9. };
  10. class Tree {//创建二叉树
  11. public:
  12. Node* m_root;//根节点
  13. Tree():m_root(nullptr){}
  14. Tree(Node* t):m_root {t} {}
  15. ~Tree(){}
  16. //传递节点指针的指针!!!const char*& str
  17. //用#表示空结点
  18. Node* Create(const char*& str);//先序遍历创建二叉树
  19. };
  1. Node* Tree::Create(const char*& str)//创建二叉树然后把树的根结点返回去
  2. {
  3. if (*str == '#')//没有树
  4. {
  5. return nullptr;
  6. }
  7. else
  8. {
  9. Node* root = new Node(*str);//新创建根结点
  10. root->m_left = Create(++str);//创建左孩子
  11. root->m_right = Create(++str);//创建右孩子
  12. return root;
  13. }
  14. }

递归实现二叉树的遍历:

先序遍历

当根节点不空时,将此时根节点值输出,开始递归遍历左子树,不空输出值若为空,则跳出这一层函数,去遍历此时结点处是否有右子树,当为空,则退出返回上一层。一定要理解递归的含义,照着代码照着图自己走一遍,就会理解,后续递归实现遍历和一些操作也都大同小异

  1. void Tree::PreOrderTree(Node* t)//根左右,t为传入根节点
  2. {
  3. if (t == nullptr)return;
  4. else {
  5. //先遍历根,及输出根的值
  6. cout << t->m_value <<" ";
  7. PreOrderTree(t->m_left);
  8. PreOrderTree(t->m_right);
  9. }
  10. }

中序遍历

  1. void Tree::InOderTree(Node* t)//左根右
  2. {
  3. if (t == nullptr)return;
  4. else {
  5. //如果根不空了先遍历他的左子树
  6. InOderTree(t->m_left);
  7. cout << t->m_value << " ";//左子树完了之后,根,其实就是把根的值输出一下
  8. InOderTree(t->m_right);//完了再遍历他的右子树
  9. }
  10. }

后续遍历:

  1. void Tree::PostOrderTree(Node* t)//左右根
  2. {
  3. if (t == nullptr)return;
  4. else {
  5. PostOrderTree(t->m_left);
  6. PostOrderTree(t->m_right);
  7. cout << t->m_value << " ";
  8. }
  9. }

一些二叉树基本操作:

求树的深度:

依旧用递归来写,求树的深度其实就是求左右子树的深度较大值+1,如果不能理解可以先看看求树的结点个数,我粗略的举个例子,描述一些递归思想,懂了之后,这些基本操作其实递归起来都大差不差。

  1. int Tree::Height(Node* t)
  2. {
  3. if (t== nullptr)
  4. {
  5. return 0;
  6. }
  7. else
  8. {
  9. int h1 = Height(t->m_left);
  10. int h2 = Height(t->m_right);
  11. return h1 > h2 ? h1 + 1 : h2 + 1;
  12. }
  13. }

求树的结点个数:

先看代码:看不懂了照着下面的解释和代码一起看

  1. int Tree::Size(Node* t)//结点个数
  2. {
  3. if (t == nullptr)//如果根是空的,没有结点,返回0
  4. {
  5. return 0;
  6. }
  7. else
  8. {//结点个数就是左子树结点+右子树结点+根结点个数(1)
  9. //左子树一路递归+右子树一路递归+1
  10. return Size(t->m_left) + Size(t->m_right) +1 ;
  11. }
  12. }

    *    A
    *  B   C  举例 
    * t等于A不空,所以走到 Size(t->m_left)这一句
    * t->m_left就是B
    * 此时进入t等于B的Size函数,t不空再走到Size(t->m_left)
    * 即新t要等于B的左子树
    * 而B的左子树是空,return 0返回到t=B的这一层
    * 在t=B的这一层Size(t->m_left) + Size(t->m_right) +1中
    * Size(t->m_left)就是0了,等于开始走Size(t->m_right)
    * 同上面的思路,B的右孩子是空,返回0,返回到B这一层
    * 此时 Size(t->m_right)也是0
    * 此时t等于B这一层函数就成了0+0+1,等于1
    * 相当于return 1,返回到了t等于A这一层函数
    * 此时在A层的函数中,经过前面左子树递归
    * 得到1+Size(t->m_right) +1,这下开始从A开始走Size(t->m_right)函数
    * 和遍历左子树一个逻辑,最终也会返回一个1
    * 最后A这层函数就是1+1+1
    * 最终结果就return 3;

    

查找特定值的结点:

  1. Node* Tree::Search(Node* t, char v)//查找特定值的结点,找到返回结点,没找到返回空
  2. {
  3. if (t == nullptr){
  4. return nullptr;
  5. }
  6. if (t->m_value == v) {
  7. return t;
  8. }
  9. //如果不空,也不是根,那么开始往左子树查找
  10. Node* p = Search(t->m_left, v);
  11. if (p == nullptr)//如果是空,往右边找
  12. return Search(t->m_right, v);
  13. }

查找特定值结点的父结点:

  1. Node* Tree::Search_Parent(Node* t, char v)//特点值结点的父结点
  2. {
  3. if (t == nullptr)
  4. {
  5. return nullptr;
  6. }
  7. if (t->m_left != nullptr && t->m_left->m_value == v)
  8. {
  9. return t;//如果t结点左孩子不空,左孩子值是v,t就是特定结点的父结点
  10. }
  11. if (t->m_right != nullptr && t->m_right->m_value == v)
  12. {
  13. return t;
  14. }
  15. if (Search_Parent(t->m_left, v) != nullptr)//如果左边找到且不空,返回
  16. return Search_Parent(t->m_left, v);
  17. else
  18. return Search_Parent(t->m_right, v);
  19. }

将树拷贝到新树上(二叉树的复制):

  1. void Tree::Copy(Node* &t1, Node* &t)//将t树拷贝到t1上
  2. {
  3. //先拷根,再拷左子树,再拷右子树
  4. if (t == nullptr)
  5. {
  6. t1 = nullptr;
  7. }
  8. else
  9. {
  10. t1=new Node(t->m_value);//申请空间,把根值拷贝
  11. Copy(t1->m_left, t->m_left);//递归把左子树拷过去
  12. Copy(t1->m_right, t->m_right);
  13. }
  14. }

非递归实现遍历:

层次遍历:

对于非递归实现遍历,无非就是模拟如何遍历的过程,这其中我们需要借助栈或者队列去实现。

对于层次遍历而言我们需要借助队列去完成,其他三种需要借助栈去实现。

大致思路为:

  1.     如果说层次A B C
  2.     先把A入队,用p保存A的值,然后出队A,然后看p有没有左右孩子
  3.     如果有入,他的左右孩子B C,然后出队B,p指向B,在看B有没有左右孩子
  4.     依次类推

注意:一定要画图照着代码去模拟一遍过程,其他三种遍历也是如此。

  1. void Tree::LevelOrder(Node* t)//非递归实现层次遍历
  2. {
  3. queue<Node*>q; //借助队列实现
  4. Node* ff = nullptr;//指向对头的指针,用来后面判断有没有左右孩子,需要入队
  5. if (t == nullptr) return;
  6. else
  7. {
  8. q.push(t); //如果有根,入队
  9. while (!q.empty()) //看队列空不空
  10. {
  11. ff=q.front();//获取对头元素,为了保存下一层的孩子,如果有孩子就需要入队
  12. q.pop();
  13. cout << ff->m_value << " ";
  14. if(ff->m_left!=nullptr)
  15. {
  16. q.push(ff->m_left);
  17. }
  18. if (ff->m_right != nullptr)
  19. {
  20. q.push(ff->m_right);
  21. }
  22. }
  23. }
  24. }

前序遍历:

  1. void Tree::PreOrder_YTree(Node* t)//先序遍历非递归
  2. {
  3. stack<Node*> ss;//用栈
  4. Node* p = nullptr;//用来保存栈顶
  5. //根左右,根入了之后保存栈顶,要按照先入右边再入左边,为的是把右子树保存到栈,左子树访问完了开始访问右子树
  6. //因为栈是先进后出
  7. if (t == nullptr)return;
  8. else
  9. {
  10. ss.push(t);//根入栈
  11. while (!ss.empty())
  12. {
  13. p = ss.top();//保存栈顶元素
  14. ss.pop();
  15. cout << p->m_value << " ";
  16. if (p->m_right != nullptr)
  17. {
  18. ss.push(p->m_right);
  19. }
  20. if (p->m_left != nullptr)
  21. {
  22. ss.push(p->m_left);
  23. }
  24. }
  25. }
  26. }

中序遍历:

大致思路:

  1.  左根右,访问完最左边孩子,接着要访问他的父结点,所以得保存父结点,自然就能找到右节点
  2.    先将根以及根以下左子树全部入栈(把路径上的父子结点都保存着)
  3.    判断栈空不空,不空获取栈顶并出栈
  4.    并用p记住栈顶的右子树
  5.    查看当前结点p有没有左子树,如果有则继续压栈左边,如果没有或者为空则返回栈顶(父亲),找栈顶p的右孩子
  6.     从右孩子这边继续找左,没有,返回这个p,再找他的右子树...
  1. void Tree::InOder_YTree(Node* t)//中序遍历非递归
  2. {
  3. stack<Node*>ss;
  4. Node* p = t;
  5. Node* tmp=nullptr;
  6. if (t == nullptr)return;
  7. else
  8. {
  9. while (p!=nullptr || !ss.empty())
  10. {
  11. while (p!=nullptr)
  12. {
  13. ss.push(p);
  14. p = p->m_left;
  15. }
  16. if (!ss.empty())
  17. {
  18. tmp = ss.top();
  19. ss.pop();
  20. cout << tmp->m_value << " ";
  21. p = tmp->m_right;//当前结点遍历完,记录右子树
  22. }
  23. }
  24. }
  25. }

后续遍历:

  1.     左右根--> 入的顺序应该根右左
  2.     注意:需要一个指针记住已经遍历过得结点(刚刚出去的上个结点)
  3.     原因:入栈:如果当前结点有左右孩子,则按照右左顺序入栈
  4.     出栈:如果当前结点没有左右孩子,当前结点有孩子,但是孩子已经遍历过了,则出栈
  1. void Tree::PostOrder_YTree(Node* t)//后序遍历非递归
  2. {
  3. if (t != nullptr)
  4. {
  5. stack<Node*> ss;
  6. Node* top = nullptr, * pre = nullptr;
  7. ss.push(t);
  8. while (!ss.empty())
  9. {
  10. top = ss.top();
  11. if (top->m_left == nullptr && top->m_right == nullptr ||
  12. pre != nullptr && top->m_left == pre || pre != nullptr && top->m_right == pre)
  13. {
  14. ss.pop();
  15. cout << top->m_value << " ";
  16. pre = top;
  17. }
  18. else
  19. {
  20. if (top->m_right != nullptr)
  21. ss.push(top->m_right);
  22. if (top->m_left != nullptr)
  23. ss.push(top->m_left);
  24. }
  25. }
  26. }
  27. }

我们在主函数测试一下:

  1. int main()
  2. {
  3. Tree t;
  4. const char* str = "ABDG##HI####CE#J##F##";
  5. t.m_root = t.Create(str);//创建二叉树,返回他的根结点
  6. cout << "先序遍历:" ;
  7. t.PreOrderTree(t.m_root);
  8. cout <<endl;
  9. cout << "中序遍历:";
  10. t.InOderTree(t.m_root);
  11. cout << endl;
  12. cout << "后序遍历:";
  13. t.PostOrderTree(t.m_root);
  14. cout << endl;
  15. cout << "Size为:"<< t.Size(t.m_root)<<endl;
  16. cout << "Height为:" << t.Height(t.m_root) << endl;
  17. Node* p = t.Search(t.m_root, 'E');
  18. if (p== nullptr)
  19. {
  20. cout << "没有找到" << endl;
  21. }
  22. else
  23. cout <<"找到了:"<< p->m_value << endl;
  24. Node* pp = t.Search_Parent(t.m_root, 'B');
  25. if (p == nullptr)
  26. {
  27. cout << "没有找到" << endl;
  28. }
  29. else
  30. {
  31. cout << "找到了:" << pp->m_value << endl;
  32. }
  33. cout << "非递归层次遍历:";
  34. t.LevelOrder(t.m_root);
  35. cout << endl;
  36. cout << "非递归先序遍历:";
  37. t.PreOrder_YTree(t.m_root);
  38. cout << endl;
  39. cout << "非递归中序遍历:";
  40. t.InOder_YTree(t.m_root);
  41. cout << endl;
  42. cout << "非递归后序遍历:";
  43. t.PostOrder_YTree(t.m_root);
  44. cout << endl;
  45. Tree t1;
  46. t1.Copy(t1.m_root, t.m_root);
  47. cout << "拷贝成功后,先序遍历:";
  48. t1.PreOrderTree(t1.m_root);
  49. return 0;
  50. }

本篇旨在讲解二叉树的代码方便查阅

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/514508
推荐阅读
相关标签
  

闽ICP备14008679号