二叉树及二叉树的基本操作(基础面试题型)

一、相关概念

树是n( n>=0)个有限个数据的元素集合,它的数据的存储结构形状像一颗倒过来的树。根在上,叶在下:如图所示

《二叉树及二叉树的基本操作(基础面试题型)》

1.一个独立的节点也可看作一棵树,它既为根节点,又为叶子节点;

2.一个节点也没有称作空树;

《二叉树及二叉树的基本操作(基础面试题型)》

3.这是一颗典型的树,根节点为A;

《二叉树及二叉树的基本操作(基础面试题型)》

4.一个节点只有唯一父节点。

节点: 结点包含数据和指向其它节点的指针。
根节点: 树第一个结点称为根节点。
结点的度: 结点拥有的子节点个数。
叶节点: 没有子节点的节点(度为0)。
父子节点: 一个节点father指向另一个节点child, 则child为孩子节点, father为父亲节点 。
兄弟节点: 具有相同父节点的节点互为兄弟节点。
节点的祖先: 从根节点开始到该节点所经的所有节点都可以称为该节点的祖先。
子孙: 以某节点为根的子树中任一节点都称为该节点的子孙。
树的高度: 树中距离根节点最远节点的路径长度

如图示:

《二叉树及二叉树的基本操作(基础面试题型)》

5.树的存储结构

1 struct TreeNode 2 { 3 DataType data; //节点值 4 TreeNode* _firistchild; //第一个孩子 5 TreeMode* _nextchild; //第二个孩子 6  ... 7 };

有时候根据需要还会加入父节点,结构如下:

1  struct TreeNode 2  { 3 DataType data; //节点值 4 TreeNode* _parent; 5 TreeNode* _firistchild; //第一个孩子 6 TreeMode* _nextchild; //第二个孩子 7  ... 8 };

二、二叉树

1.二叉树:二叉树是一棵特殊的树, 二叉树每个节点最多有两个孩子结点, 分别称为左孩子和右孩子。如图:

 《二叉树及二叉树的基本操作(基础面试题型)》

2.存储结构

 《二叉树及二叉树的基本操作(基础面试题型)》

 1   template <class T>  2 struct TreeNode //定义二叉树结点  3  {  5 TreeNode<T>* _left; //指向左子树的指针  6 TreeNode<T>* _right; //指向右子树的指针  7 T _data; //节点数据  8 TreeNode(const T& n)  9  :_left(NULL) 10  ,_right(NULL) 11  ,_data(n) 12  {} 13 };

有时候根据需要还会加入父节点,结构如下:

《二叉树及二叉树的基本操作(基础面试题型)》

 1 template <class T>  2 struct TreeNode //定义二叉树结点  3 {  4 TreeNode<T>* _parent; //指向父节点的指针  5 TreeNode<T>* _left; //指向左子树的指针  6 TreeNode<T>* _right; //指向右子树的指针  7 T _data; //节点数据  8 TreeNode(const T& n)  9  :_left(NULL) 10  ,_right(NULL) 11  ,_data(n) 12  {} 13 };

3.特殊的二叉树

满二叉树:高度为N的满二叉树有2^N – 1个节点的二叉树。
完全二叉树: 若设二叉树的深度为h, 除第 h 层外, 其它各层 (1~ h-1) 的结点数都达到最大个数, 第 h 层所有的结点都连续集中在最左
边, 这就是完全二叉树

 《二叉树及二叉树的基本操作(基础面试题型)》《二叉树及二叉树的基本操作(基础面试题型)》

对于上面这颗完全二叉树:

前序遍历(先根遍历):( 1) : 先访问根节点; ( 2) : 前序访问左子树; ( 3) : 前序访问右子树; 【1 2 3 4 5 6】
中序遍历: ( 1) : 中序访问左子树; ( 2) : 访问根节点; ( 3) : 中序访问右子树; 【3 2 4 1 6 5】
后序遍历(后根遍历):( 1) : 后序访问左子树; ( 2) : 后序访问右子树; ( 3) : 访问根节点; 【3 4 2 6 5 1】
层序遍历: ( 1) : 一层层节点依次遍历。 【1 2 5 3 4 6】

三、对二叉树的相关操作

  1 #pragma once  2 #include <iostream>  3 #include <queue>  4 using namespace std;  5  6 template <class T>  7 struct BinaryTreeNode //定义二叉树结点  8 {  9 BinaryTreeNode<T>* _left; //指向左子树的指针  10 BinaryTreeNode<T>* _right; //指向右子树的指针  11 T _data; //节点数据  12 BinaryTreeNode(const T& n)  13  :_left(NULL)  14  ,_right(NULL)  15  ,_data(n)  16  {}  17 };  18  19 template <class T>  20 class BinaryTree  21 {  22 typedef BinaryTreeNode<T> Node;  23 public:  24 BinaryTree() //空树  25  :_root(NULL)  26  {}  27 BinaryTree(const BinaryTree<T>& tree) //拷贝构造  28  {  29 _root = _Copy(tree._root);  30  }  31 ~BinaryTree() //析构二叉树  32  {  33  _Destroy(_root);  34 _root = NULL;  35  }  36 BinaryTree<T>& operator=(const BinaryTree<T>& tree) //赋值运算符重载  37  {  38 if (this != &tree) {  39  _Destroy(_root);  40 _root = _Copy(tree._root);  41  }  42 return *this  43  }  44  45 /*BinaryTree<T>& operator=(const BinaryTree<T>& tree)  46  {  47  swap(_root, tree._root);  48  return *this;  49  }*/  50 BinaryTree(T* a, size_t n, const T& invalid) { //invalid无效节点(#)//创建节点个数为N的二叉树  51 size_t index = 0;  52 _root = _CreatTree(a, n, invalid, index);  53  }  54 void PrevOrder(){ //前序遍历二叉树递归法  55  _PrevOrder(_root);  56 cout << endl;  57  }  58 void PrevOrderNonR() //前序遍历二叉树非递归法  59  {  60  _PrevOrderNonR(_root);  61 cout << endl;  62  }  63 void InOrder() //中序遍历二叉树递归法  64  {  65  _InOrder(_root);  66 cout << endl;  67  }  68 void InOrderNonR() //中序遍历二叉树非递归法  69  {  70  _InOrderNonR(_root);  71 cout << endl;  72  }  73 void PostOrder() //后序遍历二叉树递归法  74  {  75  _PostOrder(_root);  76 cout << endl;  77  }  78 void PostOrderNonR() //后序遍历二叉树非递归法  79  {  80  _PostOrderNonR();  81 cout << endl;  82  } 83 void LevelOrder() //层序遍历二叉树递归法 84 { 85 _LevelOrder(_root); 86 cout << endl; 87 } 88 void LevelOrderNonR()//.queue//层序遍历二叉树非递归法 89 { 90 _LevelOrderNonR(_root); 91 cout << endl; 92 } 93 size_t Size(){ //二叉树总节点个数 94 return _Size(_root); 95 } 96 size_t CountLeafNode() //叶子节点 97 { 98 return _CountLeafNode(_root); 99 } 100 size_t Depth() //高度 101 { 102 return _Depth(_root); 103 } 104 size_t GetKLevelSize(size_t k) //第K层节点个数 105 { 106 return _GetKLevelSize(_root, k); 107 } 108 Node* Find(const T& x) //查找节点 109 { 110 return _Find(_root, x); 111 } 112 private: 113 Node * _root; //根节点 114 Node* _CreatTree(T* a, size_t n, const T& invalid, size_t &index) { //创建二叉树树 115 Node* root = NULL; 116 if (index < n && a[index] != invalid) { 117 root = new Node(a[index]); 118 root->_left = _CreatTree(a, n, invalid, ++index);//递归创建左子树 119 root->_right= _CreatTree(a, n, invalid, ++index);//递归创建右子树 120 } 121 return root; 122 } 123 Node* _Copy(Node* root) //copy二叉树 124 { 125 if (root == NULL) 126 return NULL; 127 Node* newroot = new Node(root->_data); 128 newroot->_left = _Copy(root->_left); 129 newroot->_right = _Copy(root->_right); 130 } 131 void _Destory(Node* root) //销毁二叉树树 132 { 133 if (root == NULL) 134 return; 135 _Destory(root->_left); 136 _Destory(root->_right); 137 delete root; 138 } 139 };

 这里封装了一些常见操作的接口,而没有给出具体的实现函数,这些操作也是面试中常见的题型,下一篇将详细解析这些对二叉树的基本操作。

 1 #include "BinaryTree.h"  2  3 void Test()  4 {  5 int array[] = { 1, 2, 3, '#', '#', 4, '#' , '#', 5, 6, '#', '#', '#' };  6 //int array[15] = { 1, 2, '#' , 3, '#' , '#' , 4, 5, '#' , 6, '#' , 7, '#' , '#' , 8 };  7 BinaryTree<int> tree1(array, sizeof(array) / sizeof(array[0]), '#');  8 //tree1.LevelOrder();  9  tree1.PrevOrder(); 10 } 11 int main() 12 { 13  Test(); 14  getchar(); 15 return 0; 16 }

 另一个问题:为什么要将这么多的接口封装起来呐?这里是使用C++来实现的,C++是面向对象的语言,在C++中封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。

 四、上述接口的具体实现函数及一些常见面试题型

1. 二叉树的前序/中序/后序遍历(递归&非递归) ;
2. 二叉树的层序遍历 ;
3. 求二叉树的高度 ;
4. 求二叉树的叶子节点的个数 ;
5. 求二叉树第k层的节点个数 ;
6. 判断一个节点是否在一棵二叉树中 ;
7.求二叉树的镜像;
8.判断两颗二叉树是否相等;
9.从二叉树中查找结点

1.第一题求解思路:

递归法求解,主要思想是转化为子问题,函数不断地调用自身来解决子问题,另外一个要点是确定递归的终止条件。对于本题,遍历一棵树,先遍历它的左子树和右子树,循环采用这种思想,当遇到空节点时停止遍历。不难写出如下代码:

 1 void _PrevOrder(Node* root)  2 { //前序遍历递归法  3 if (root == NULL)  4 return;  5 cout << root->_data << " ";  6 _PrevOrder(root->_left);  7 _PrevOrder(root->_right);  8 }  9 void _InOrder(Node* root)//中续遍历递归法 10  { 11 if (root == NULL) 12  { 13 return; 14  } 15 _InOrder(root->_left); 16 cout << root->_data << " "; 17 _InOrder(root->_right); 18  } 19 void _PostOrder(Node* root) //后序遍历递归法 20  { 21 if (root == NULL) 22  { 23 return; 24  } 25 _PostOrder(root->_left); 26 _PostOrder(root->_right); 27 cout << root->_data << " "; 28 }

前序/中序/后序遍历也叫先根/中根/后根遍历,主要区别就是遍历根的先后顺序,所以代码差别不大,主要区别也在访问根节点的先后顺序上。

非递归法遍历二叉树要借助栈来实现,对于前序遍历,先访问根节点,再将节点入栈,再访问左子树,再将左节点入栈,再访问此节点的右子树,再将左节点入栈,以此方法一直访问下去直到遇到空节点跳出循环,然后再将节点依次从栈中取出,以同样方法访问其右子树。按此方法实现代码如下:

 1     void _PrevOrderNonR(Node* root) //前序遍历非递归法  2  {  3 Node* cur = root;  4 stack<Node*> s1;  5 // 访问一颗子树的开始  6 while (cur || !s1.empty())  7  {  8 while (cur)  9  { 10 cout << cur->_data << " "; //访问当前树的根节点 11  s1.push(cur); 12 cur = cur->_left; //访问当前树的左子树 13  } 14 // 从栈里面取出,以为top节点的右树还没有访问 15 Node* stop = s1.top(); 16  s1.pop(); 17 //子问题,访问右右子树 18 cur = stop->_right; 19  } 20 cout << endl; 21 }

中序遍历,遇到一个节点不访问,先访问它的左节点,与前序遍历相似,先将节点入栈,访问它的左子树,先将左子树的根节点入栈,访问左子树的左子树,以此方法循环下去,直到遇到空节点跳出循环,这时取出栈顶节点先访问,这个节点要么只有左节点为空,要么左右节点都为空,所以访问之后将其出栈,再访问其右节点,方法与前面相同。代码如下:

 1     void _InOrderNonR(Node* root)//中续遍历非递归法  2  {  3 Node* cur = root;  4 stack<Node*> s1;  5 while (cur || !empty())  6  {  7 while (cur)  8  {  9 //访问一颗子树的开始 10  s1.push(cur); 11 cur = cur->_left; //访问右子树 12  } 13 Node* stop = s1.top(); 14 cout << cur->_data << " "; 15  s1.pop(); 16 //子问题方法访问右子树 17 cur = stop->_right; 18  } 19 cout << endl; 20 }

后序遍历非递归法,思想与前两种遍历相同,但是也有需要注意的地方,从根节点到左子树依次压栈,遇到空时跳出循环,然后取栈顶节点,若它的右子树为空则可直接访问,若不为空则访问右子树。因为后序遍历最后还要要访问根结点一次,所以要访问根结点两次。采取夹标志位的方法解决这个问题。逻辑图如下:

《二叉树及二叉树的基本操作(基础面试题型)》

 1     void _PostOrderNonR(Node* root) //后续遍历非递归法  2  {  3 Node* cur = root;  4 Node* prev = NULL;  5 stack<Node*> s1;  6 while (cur || !s1.empty()  7  {  8 while (cur)  9  { 10  s1.push(cur); 11 cur = cur->_left; //先将节点依次入栈 12  } 13 Node* stop = s1.top(); //取栈顶节点先访问它的右树 14 if (stop->_right == NULL || stop->_right == prev) 15  { 16 cout << stop->_data << " "; 17 prev = stop; //保存上次访问的节点 18  s1.pop(); 19  } 20 else { 21 cur = cur->_right; 22  } 23  } 24 cout << endl; 25 }

2.层序遍历也有两种思路

非递归法:与上述三种遍历的非递归法类似。依然需要借助其他容器来完成,不过这里用的是队列。这里利用队列尾进头出的性质依次将节点保存在队列中,同时依次取出头结点访问,然后保存它的左右节点。

 1     void _LevelOrderNonR(Node* root) //层序遍历非递归法  2  {  3 queue<Node*> myq;  4 if (root)  5  myq.push(root);  6 while (!myq.empty())  7  {  8 Node* front = myq.front();  9 cout << front->_data <<" "; 10  myq.pop(); 11 if (front->_left) 12 myq.push(front->_left); 13 if (front->_right) 14 myq.push(front->_right); 15  } 16 }

借助高度的递归法:

 1     void PrintNLevel(Node* root, size_t level)  2  {  3 if (root == NULL || level < 1) //空树或层数不合理  4  {  5 return;  6  }  7 if (level == 1)  8  {  9 cout << root->data << " "; 10  } 11 PrintNLevel(root->_left, level - 1); 12 PrintNLevel(root->_right, level - 1); 13  } 14 void _LevelOrder(Node* root) 15  { 16 if (root == NULL) 17  { 18 return; 19  } 20 int depth = Depth(root); 21 for (size_t i = 1; i < depth; i++) 22  { 23  PrintNLevel(root, i); 24 cout << endl; 25  } 26 }

这个算法先求出根结点的高度,depth=高度+1。函数PrintNLevel中,当level==1时才进行打印。树的根的高度是2,depth=3。然后,在_LevelOrder函数中,level从1开始传参,然后PrintNLevel会打印出1;之后level=2,进入PrintNLevel(root-_left,  level – 1)函数和PrintNLevel(root->_right, level – 1),level又等于1,就打印出2,3。以此类推,整棵树就按层打印出来了。

3.二叉树的高度

用递归法可以很方便的计算出来,子问题是子树中高度较高的高度再加一,依次递归下去,退出条件依然为 root==NULL。

1     size_t _Depth(Node* root) 2  { 3 if (root == NULL) 4 return 0; 5 //子问题 6 _Depth(root->_left) = left; //左子树的高度 7 _Depth((root->_right) = right; //右子树的高度 8 return left > right ? left + 1 : right + 1; 9 }

4. 叶子节点的个数

 1 size_t _Size(Node* root) {  //二叉树总节点个数  2 if (root = NULL)  3 return;  4 return _Size(root->_left) + _Size(root->_right) + 1;  5  }  6 //求二叉树叶子节点的个数  7 size_t _CountLeafNode(Node* root)  8  {  9 if (root == NULL) 10 return 0; 11 if (root->_left == NULL && root->_right == NULL)//当左右子树都为空时即为叶子节点 12 return 1; 13 return _CountLeafNode(root->_left) + _CountLeafNode(root->_right); 14 }

5.第k层的节点个数

 1     size_t _CountKLevel(Node* root, size_t k)// 求二叉树第k层的节点个数。  2  {  3 if (root == NULL)  4  {  5 return 0;  6  }  7 if (k == 1)  8  {  9 return 1; 10  } 11 _CountKLevel(root->_left, k - 1); 12 _CountKLevel(root->_right, k - 1); 13 }

6. 节点是否在树中

 1     bool JudgeNode(Node* root, T x) //判断一个节点是否在一棵二叉树中 ;  2  {  3 if (root==NULL)  4  {  5 return false;  6  }  7 if (root->_data==x)  8  {  9 return true; 10  } 11 return JudgeNode(root->_left, x) || JudgeNode(root->_right, x); //找到之后即返回,就不到另一个子树中查找了 12 }

7.求二叉树的镜像

 二叉树的镜像如图:

《二叉树及二叉树的基本操作(基础面试题型)》

思路是将子节点依次互换即可。

 1     //二叉树的镜像  2 void MirroRecursively(Node *root)  3  {  4 if (root==NULL)  5  {  6 return ;  7  }  8 if (root->_left==NULL&&root->_right==NULL)  9  { 10 return ; 11  } 12 Node* tmp = root->_left; 13 root->_left = root->_right; 14 root->_right = tmp; 15 if (root->_left) 16  { 17 MirroRecursively(root->_left); 18  } 19 if (root->_right) 20  { 21 MirroRecursively(root->_right); 22  } 23 }

8.二叉树是否相等

 1     //8、比较两个树是否相同  2 int is_equal(Node* t1, Node* t2) {  3 if (!t1 && !t2) { //都为空就相等  4 return 1;  5  }  6 if (t1 && t2 && t1->data == t2->data) { //有一个为空或数据不同就不判断了  7 if (is_equal(t1->_left, t2->_left))  8 if (is_equal(t1->_right, t2->_right)) {  9 return 1; 10  } 11  } 12 return 0; 13 }

9.从二叉树中查找结点

 1     //二叉树中查找节点  2 Node* _Find(Node* root, const T& x)  3  {  4 if (root == NULL)  5  {  6 return NULL;  7  }  8 if (root->_data == x)  9  { 10 return root; 11  } 12 Node* ret = _Find(root->_left, x); 13 if (ret) 14  { 15 return ret; 16  } 17 return _Find(root->_right, x); 18 }

 

    原文作者:滴巴戈
    原文地址: https://www.cnblogs.com/33debug/p/7248822.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞