二叉树----第一篇(构造,遍历,求节点,求叶子)

来源:互联网 发布:健身房锻炼软件 编辑:程序博客网 时间:2024/06/05 18:30

一:二叉树结构

#include<iostream>#include<stack>#include<queue>using namespace std;struct BiData//节点结构{char data;BiData *lChild, *rChild;};class BiTree{public:BiData *root;//根节点int leafNum = 0;//叶子数,如果此处报错,请将0去掉,并将leafNum的初始化移到构造函数中,此程序在VisualStudio2015编译运行通过BiTree();//构造函数,构造一个二叉树BiData * Create();void PreOrder(BiData * _root);//前序遍历递归void PreOrderNonRec(BiData * _root);//前序遍历非递归void InOrder(BiData * _root);//中序遍历递归void InOrderNonRec(BiData * _root);// 中序遍历非递归void PostOrder(BiData * _root);//后序遍历递归void PostOrderNonRec(BiData * _root);//后序遍历非递归void LevelOrder(BiData * _root);//层次遍历int Count(BiData * _root);//计算节点int CountLeaf(BiData * _root);//计算叶子的个数(叶子就是度为0的节点)};int main(){BiTree myTree;//1.cout << "前序遍历递归与非递归\n";myTree.PreOrder(myTree.root); cout << endl;myTree.PreOrderNonRec(myTree.root); cout << endl<<endl;//2.cout << "中序遍历递归与非递归\n";myTree.InOrder(myTree.root); cout << endl;myTree.InOrderNonRec(myTree.root); cout << endl<<endl;//3.cout << "后序遍历递归与非递归\n";myTree.PostOrder(myTree.root); cout << endl;myTree.PostOrderNonRec(myTree.root); cout << endl << endl;;//4.cout << "层次遍历\n";myTree.LevelOrder(myTree.root); cout << endl << endl;;//5.cout << "该二叉树的节点有";cout << myTree.Count(myTree.root);cout << "个\n\n";//6.cout << "该二叉树的叶子有";cout << myTree.CountLeaf(myTree.root);cout << "个\n\n";return 0;}

上面的代码实现如下图:




上图myTree变量二叉树如下图




二:具体实现

1.二叉树的构造

        对一个二叉树的构造,我使用递归的方法。二叉树的构造和遍历是比较考验对递归的理解的。如果你对递归的理解不到位,那么下面的二叉树三种非递归可能理解会吃力。

BiData * BiTree::Create(){char x;cin >> x;BiData *p;if (x == '0')//输入0表示下一个结点结束p = nullptr;else{p = new BiData;p->data = x;//分别构造左子树和右子树p->lChild = Create();p->rChild = Create();}return p;}BiTree::BiTree()//构造二叉树{root = Create();}

2.二叉树遍历-----前序遍历递归与非递归

      递归版本的不用多讲了,就是按照“根节点---左孩子----右孩子”的顺序进行遍历。

     主要是非递归, 对于任一结点P:
     1)访问结点P,并将结点P入栈;
     2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
     3)直到P为nullptr并且栈为空,则遍历结束。

     模拟递归,借助容器stack来实现。优先访问根节点,然后接着左子树和右子树。其实对于每一个节点都可以看作根节点。对于上图的myTree,首先访问和输出根节点(也就是1),然后,左子树(也就是2---4---6----7),接着右子树(也就是3----5)。当访问和输出1后,接着访问和输出左孩子2,因为每一个节点都可以看作根节点,因此此时的2变为一个根节点,接着访问和输出2的左孩子(如果2有左孩子的话,如图来看,2是有左孩子的),依次访问到4,此时没有左孩子了,那就要看右孩子6了,访问6的左孩子,发现没有,接着6的右孩子7,此时7已经是结尾了,既没有左孩子也没有右孩子,至此2的左子树(就是4---6--7)已经遍历完毕了,要开始遍历2的右子树,发现2是没有右子树的,那就结束,自此,1的左子树也遍历完毕。好,接下来就是1的右子树了,依次推,3---5,结束。

void BiTree::PreOrder(BiData * _root)//前序遍历递归{if (_root == nullptr)return;else{cout << _root->data << " ";PreOrder(_root->lChild);PreOrder(_root->rChild);}}


void BiTree::PreOrderNonRec(BiData * _root)//前序遍历非递归{stack<BiData*> s;//模拟一个栈while (_root != nullptr || !s.empty()){while (_root != nullptr){cout << _root->data << " ";s.push(_root);_root = _root->lChild;}if (!s.empty()){_root = s.top()->rChild;s.pop();}}}

3.二叉树遍历----中序遍历递归与非递归

        非递归,对于任一结点P,
       1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
       2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
       3)直到P为nullptr并且栈为空则遍历结束。

       中序非递归版本其实就是需要遍历完一个节点的左子树,然后输出根节点,接着右子树。对于myTree,先遍历1的左子树,访问到4时,4没有左子树,输出4,再看其右孩子6.再开始遍历6的左子树,6没有左子树,那就遍历右子树7,至此,2的左子树遍历完成,开始2的右子树,发现为空,此时1的左子树遍历完成,输出1,再开始1的右子树。          其实只要记住先左遍历到底,再根,然后右,其中对于每一个节点,都是有左和右的。这点要记住,就像遍历到4时,4没有左,但是有右,对于4的右孩子6,我们还要遍历6的左孩子,是需要一直访问到底的,但是因为6没有左子树,故而直接转向右子树7了,对于7,我们依旧要遍历7的左子树一直遍历到底,因为其没有左子树,因此看右子树,7没有右子树,结束。

        其实就这么简单。

void BiTree::InOrder(BiData * _root)//中序遍历递归{if (_root == nullptr)return;else{InOrder(_root->lChild);cout << _root->data << " ";InOrder(_root->rChild);}}


void BiTree::InOrderNonRec(BiData * _root)//中序遍历非递归{stack<BiData*> s;while (_root != nullptr || !s.empty()){while (_root != nullptr){s.push(_root);_root = _root->lChild;}if (!s.empty()){cout << s.top()->data << " ";_root = s.top()->rChild;s.pop();}}}

4.二叉树遍历-----后序遍历递归与非递归

        非递归,对于每一个节点,如果我们想要输出它,只有它既没有左孩子也没有右孩子或者它有孩子但是它的孩子(左孩子和右孩子,如果它有的话)已经被输出了,那就可以输出它了。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子和右孩子都在根结点前面被访问。也就是先依次遍历左子树和右子树,然后输出根节点。

         依旧对于上图中的myTree,因为对于一个节点,如果我们想要输出它,只有它既没有左孩子也没有右孩子或者它有孩子但是它的孩子(左孩子和右孩子,如果它有的话)已经被输出了。那么对于1的左子树来说,一直入栈,直到7,发现没有左子树和右子树了,输出7,此时对于6来说,6没有左子树,而它的右子树也输出完成,因此输出6,再到4,4没有左子树,右子树输出完成,所以输出4,至此,2的左子树输出完成,而2没有右子树,所以输出2,至此,1的左子树完成,那就要开始1的右子树了,同理,先操作完一个节点的左子树和右子树才可以输出它自己,因此直到5,输出5,然后3,此时1的右子树完成,终于可以输出1了。

void BiTree::PostOrder(BiData * _root)//后序遍历递归{if (_root == nullptr)return;else{PostOrder(_root->lChild);PostOrder(_root->rChild);cout << _root->data << " ";}}

void BiTree::PostOrderNonRec(BiData * _root)//后序遍历非递归{if (_root == nullptr)return;stack<BiData*> s;//栈BiData * pre = nullptr;//上一次访问的节点BiData * cur;//现在访问的节点s.push(_root);while (!s.empty()){cur = s.top();if ((cur->lChild == nullptr&&cur->rChild == nullptr) ||(pre != nullptr) && (pre == cur->lChild || pre == cur->rChild)){cout << cur->data << " ";pre = cur;s.pop();}else{if (cur->rChild)s.push(cur->rChild);if (cur->lChild)s.push(cur->lChild);}}}

5.二叉树遍历-----层次遍历

       层次遍历就是对于每一层(例如上面的myTree来说,1是第一层;2,3是第二层,4,5是第三层,依次下去),从左到右依次输出,这里需要用到队列,而不是栈,注意队列和栈的区别。对于根节点,先进队列,然后输出该节点,接着将根节点出队列,然后将节点的左右孩子(如果它有左右孩子的话)依次入队列,然后然后只要队列不为空,就取出第一个元素,输出该节点,并将该节点的左右孩子再依次入队列,依次下去。

void BiTree::LevelOrder(BiData * _root)//层次遍历{if (_root == nullptr)return;queue<BiData*> q;//队列q.push(_root);BiData * temp = _root;while (!q.empty()){temp = q.front();q.pop();cout << temp->data << " ";if (temp->lChild)q.push(temp->lChild);if (temp->rChild)q.push(temp->rChild);}}

6.计算节点数

int BiTree::Count(BiData * _root)//求节点数{if (_root == nullptr)return 0;elsereturn Count(_root->lChild) + Count(_root->rChild) + 1;}

7.计算叶子数

        这里在类里定义了一个leafNum用来记录叶子数

int BiTree::CountLeaf(BiData * _root)//求叶子数{if (_root == NULL);else{if (!_root->lChild && !_root->rChild)leafNum++;else{CountLeaf(_root->lChild);CountLeaf(_root->rChild);}}return leafNum;}



三:总结

       二叉树的实现主要是对递归的理解,仔细观察上面的实现,发现它们的实现都用到了递归。可是呢,大多数同学对递归的理解也只是模模糊糊的。对于递归的理解,网上的博客也是有很多,个人觉得,画图是最好的学习与认知方法,画出递归的具体运行情形,这样再遇到递归时能够在脑海构造出递归。

        这里要做出一些补充,关于二叉树的三种非递归遍历,如果大家仔细的话应该能发现这三种实现方式只有前序,中序的实现大致相似,后序相比前两个较为复杂。对于这方面,我也在百度上找到了统一的写法,链接是  更简单的非递归遍历二叉树的方法

         

1 0
原创粉丝点击