二叉树的经典面试题总结

xiaoxiao2021-02-27  284

1、二叉树的构造

#include <iostream> #include <cassert> #include <queue> #include <stack> using namespace std; template <class T> struct TreeNode { TreeNode(const T& value = T()) :_value(value) ,_lchild(0) ,_rchild(0) {} T _value;//节点的值 TreeNode<T>* _lchild;//左孩子 TreeNode<T>* _rchild;//右孩子 }; template <class T> class BinaryTree { public: typedef TreeNode<T> Node; BinaryTree()//无参构造函数 :_root(NULL) {} BinaryTree(const T* a,size_t size,const T& invalid)//构造函数 { assert(a); size_t index = 0; _root = CreatTree(a,size,invalid,index); } BinaryTree(const BinaryTree<T>& b)//拷贝构造 { _root = Copy(b._root); } //现代写法的赋值运算符重载1 BinaryTree& operator=(const BinaryTree<T>& b) { if (this != &b) { Node* tmp = Copy(b._root); Destroy(_root) ; _root = tmp; //swap(_root,tmp); } return *this; } 现代写法的赋值运算符重载2 //BinaryTree& operator=(BinaryTree<T> b) //{ // swap(b._root,_root); // return *this; //} ~BinaryTree()//析构 { if (NULL != _root) { Destroy(_root); _root = NULL; } } protected: //按照先序遍历递归建树 Node* CreatTree(const T* a,size_t size,const T& invalid,size_t& index)//注意index的传值方式 { assert(a); Node* root = NULL; if (a[index] != invalid && index < size)//按先序遍历建树 { root = new Node(a[index]); root->_lchild = CreatTree(a,size,invalid,++index); root->_rchild = CreatTree(a,size,invalid,++index); } return root; } //拷贝对象 Node* Copy(Node* root) { Node* tmp = NULL; if(root) { tmp = new Node(root->_value); tmp->_lchild = Copy(root->_lchild); tmp->_rchild = Copy(root->_rchild); } return tmp; } //释放空间 void Destroy(Node*& root) { if(root)//用后序遍历方式释放空间 { Destroy(root->_rchild); Destroy(root->_lchild); delete root; root = NULL; } } private: Node* _root;//根节点 };

2、遍历二叉树 二叉树的遍历方式一共分为四种:先序遍历、中序遍历、后序遍历和层序遍历,而前三种遍历又分为递归遍历和非递归遍历,层序遍历则是借助队列先进先出的特性来辅助完成。 【递归遍历二叉树】

void preorder(Node* root)//先序遍历打印树的各个节点 { if (root) { cout<<root->_value<<" "; //访问当前节点的值 preorder(root->_lchild); //递归遍历左子树 preorder(root->_rchild); //递归遍历右子树 } } void inorder(Node* root)//中序遍历打印树的各个节点 { if (root) { preorder(root->_lchild); //递归遍历左子树 cout<<root->_value<<" "; //访问当前节点的值 preorder(root->_rchild); //递归遍历右子树 } } void postorder(Node* root)//后序遍历打印树的各个节点 { if (root) { preorder(root->_lchild); //递归遍历左子树 preorder(root->_rchild); //递归遍历右子树 cout<<root->_value<<" "; //访问当前节点的值 } } void levelorder(Node* root)//层序遍历打印树的各个节点 { queue<Node*> q; if (root) { q.push(root);//将根节点插进队列 } while(!q.empty()) { Node* front = q.front(); q.pop(); cout<<front->_value<<" "; if (front->_lchild) { q.push(front->_lchild); } if (front->_rchild) { q.push(front->_rchild); } } }

【非递归遍历二叉树】 提供给外部的接口:

public: void PreOrder()//先序遍历打印树的各个节点 { cout<<"先序遍历:"; preorderR(_root); cout<<endl; } void InOrder()//中序遍历打印树的各个节点 { cout<<"中序遍历:"; inorderR(_root); cout<<endl; } void PostOrder()//后序遍历打印树的各个节点 { cout<<"后序遍历:"; postorderR(_root); cout<<endl; } void LevelOrder()//层序遍历打印树的各个节点 { cout<<"层序遍历:"; levelorder(_root); cout<<endl; }

算法实现:

void preorderR(Node* root)//先序遍历打印树的各个节点 { Node* cur = root; stack<Node*> s; while (!s.empty() || cur)//只要当前节点和栈不同时为空,就说明树没遍历完 { while(cur)//先序遍历,遇到树根节点直接访问数据并将其压栈 { cout<<cur->_value<<" "; s.push(cur); cur = cur->_lchild; } Node* top = s.top();//取出栈顶元素,到此说明此节点以及其左子树已经访问过了 s.pop(); cur = top->_rchild;//以子问题的方式去访问右子树 } cout<<endl; } void inorderR(Node* root)//中序遍历打印树的各个节点 { Node* cur = root; stack<Node*> s; while(!s.empty() || cur)//只要当前节点和栈不同时为空,就说明树没遍历完 { while(cur)//中序遍历,遇到树根节点直接将其压栈 { s.push(cur); cur = cur->_lchild; } Node* top = s.top();//取出栈顶元素,到此说明此节点的左子树已经访问过了 cout<<top->_value<<" ";//访问栈顶元素(即根节点) s.pop(); cur = top->_rchild;//以子问题的方式去访问右子树 } cout<<endl; } void postorderR(Node* root)//后序遍历打印树的各个节点 { Node* cur = root; Node* prev = NULL;//上一个访问过的数据 stack<Node*> s; while(!s.empty() || cur)//只要当前节点和栈不同时为空,就说明树没遍历完 { //后序遍历,遇到树根节点直接将其压栈 while(cur) { s.push(cur); cur = cur->_lchild; } Node* top = s.top();//取栈顶元素,但不一定能访问 //当节点右子树为空或已经访问过时对其直接进行访问 if (top->_rchild==NULL || top->_rchild==prev) { cout<<top->_value<<" "; prev = top;//将prev更新为已经访问的节点 s.pop(); } else//以子问题的方式去访问右子树 { cur = top->_rchild; } } cout<<endl; } void levelorder(Node* root)//层序遍历打印树的各个节点 { queue<Node*> q; if (root) { q.push(root);//将根节点插进队列 } while(!q.empty()) { Node* front = q.front(); q.pop(); cout<<front->_value<<" "; if (front->_lchild) { q.push(front->_lchild); } if (front->_rchild) { q.push(front->_rchild); } } }

3、求二叉树的高度(深度) 外部接口:

size_t Depth()//求树的深度 { cout<<"depth:"; return depth(_root); }

算法实现:

size_t depth(Node* root)//求树的深度 { if (NULL == root) { return 0; } else { return depth(root->_lchild)>depth(root->_rchild)? (depth(root->_lchild)+1):(depth(root->_rchild)+1); } }

4、求二叉树中节点的个数 外部接口:

size_t Size()//求树中的节点个数 { cout<<"size:"; return size(_root); }

算法实现:

size_t size(Node* root)//求树中的节点个数 { size_t count = 0; if (NULL == root) { count = 0; } else { //当前节点 = 左子树节点 + 右子树节点 + 1 count = size(root->_lchild) + size(root->_rchild)+ 1; } return count; }

5、求二叉树中叶子节点的个数 外部接口:

size_t GetLeafSize() { cout<<"leaf_size:"; return getleafsize(_root); }

算法实现:

size_t getleafsize(Node* root)//求叶节点的个数 { if (NULL == root)//空树 { return 0; } if (NULL == root->_lchild && NULL == root->_rchild)//左叶节点右节点均为空,即 { return 1; } else//左子树的叶节点+右子树的叶节点 { return getleafsize(root->_lchild)+getleafsize(root->_rchild); } }

6、求二叉树第K层的节点个数(假设根节点为第1层) 外部接口:

size_t GetKLevelSize(size_t k)//树中第k层的节点个数 { cout<<k<<"_level_size:"; return getklevelsize(_root,k); }

算法实现:

size_t getklevelsize(Node* root,size_t k)//树中第k层的节点个数 { assert(k>0); size_t count = 0; if (NULL == root) { return 0; } if (k == 1) { count++; } else { count = getklevelsize(root->_lchild,k-1) + getklevelsize(root->_rchild,k-1); } return count; }

7、判断一个节点是否在二叉树中 外部接口:

bool Find(const T& data) //判断一个值为的节点是否在当前二叉树中 { return _Find(_root,data); }

算法实现:

bool _Find(Node* root,const T& data) //查找指定数据的节点 { if (NULL == root) { return false; } else if (root->_data == data) { return true; } else { bool isIn = false; if (root->_left && !isIn) isIn = _Find(root->_left,data); if (root->_right && !isIn) isIn = _Find(root->_right,data); return isIn; } }

8、求两个节点的最近公共祖先 外部接口:

Node* sameAncester(Node* n1,Node* n2) //求两个节点的最近公共祖先 { //return _sameAncester1(_root,n1,n2); //return _sameAncester2(_root,n1,n2); return _sameAncester3(_root,n1,n2); }

算法实现:

Node* _sameAncester1(Node*& root,Node* n1,Node* n2)//求两个节点的最近公共祖先(时间复杂度:O(N^2)) { assert(n1 && n2); //保证两个节点都在当前树中,且当前树不为空 if (root && Find(n1->_data) && Find(n2->_data)) { //其中一个节点为根节点,直接返回根节点 if (root->_data == n1->_data || root->_data == n2->_data) { return root; } //两个节点:1、都在左子树 2、都在右子树 3、一个在左子树,一个在右子树 else { Node* cur = root; //在当前树的左子树去找n1和n2节点,如果没找到就一定在右子树 bool tmp1 = _Find(cur->_left,n1->_data); bool tmp2 = _Find(cur->_left,n2->_data); //n1和n2都在左子树 if (tmp1 && tmp2) { return _sameAncester1(cur->_left,n1,n2); } //n1和n2都在右子树 else if(!tmp1 && !tmp2) { return _sameAncester1(cur->_right,n1,n2); } //n1与n2一个在左子树一个在右子树 else { return root; } } } return NULL; } bool _GetPath2(Node* root,Node* cur,stack<Node*>& s)//在根为root的树中查找节点cur的路径 { if (NULL == root || NULL == cur)//树为空 return false; s.push(root); //将当前节点入栈 if (cur->_data == root->_data) //找到路径 { return true; } if (_GetPath2(root->_left,cur,s))//在左子树中找路径 { return true; } if (_GetPath2(root->_right,cur,s))//在右子树中找路径 { return true; } s.pop(); return false; } Node* _sameAncester2(Node*& root,Node* n1,Node* n2)//求两个节点的最近公共祖先(时间复杂度:O(N)) { //树为空 if (NULL == root) { return NULL; } stack<Node*> s1; stack<Node*> s2; //用栈s1和s2分别保存节点n1和节点n2的路径 _GetPath2(root,n1,s1); _GetPath2(root,n2,s2); //将多余的路径删除 while (s1.size() > s2.size()) { s1.pop(); } while(s1.size() < s2.size()) { s2.pop(); } //找s1与s2中不同的节点 while(s1.top() != s2.top()) { s1.pop(); s2.pop(); } return s1.top(); } bool _GetPath3(Node* root,Node* cur,vector<Node*>& v) { if (NULL == root || NULL == cur)//树为空 return false; v.push_back(root); if (cur->_data == root->_data) //找到路径 { return true; } else { bool ret = false; if (root->_left && !ret)//在左子树中找路径 { ret = _GetPath3(root->_left,cur,v); } if (root->_right && !ret)//在右子树中找路径 { ret = _GetPath3(root->_right,cur,v); } if (ret == false && v.size() != 0)//如果不是正确路径上的节点,就去除该节点 { v.pop_back(); } return ret; } } Node* _sameAncester3(Node*& root,Node* n1,Node* n2) { //树为空 if (NULL == root) { return NULL; } //定义容器 vector<Node*> v1; vector<Node*> v2; //用容器v1和v2分别保存节点n1和节点n2的路径 _GetPath3(root,n1,v1); _GetPath3(root,n2,v2); 从下往上找 定义指向查找节点的迭代器指针 //vector<Node*>::iterator it1 = --v1.end(); //vector<Node*>::iterator it2 = --v2.end(); 将多余的路径节点删除掉 //while(v1.size() > v2.size()) //{ // --it1; //相应的迭代器要向前移 // v1.pop_back(); //} //while(v2.size() > v1.size()) //{ // --it2; //相应的迭代器要向前移 // v2.pop_back(); //} 找v1与v2中不同的节点 //while(it1 >= v1.begin() && it2 >= v2.begin()) //{ // if (*it1 == *it2) // { // return *it1; // } // --it1; // --it2; //} //从上往下找 //定义指向查找节点的迭代器指针 vector<Node*>::iterator it1 = v1.begin(); vector<Node*>::iterator it2 = v2.begin(); //找v1与v2中不同的节点 while(it1 != v1.end() && it2 != v2.end()) { if (*it1 != *it2)//找到第一个不相同的节点,返回前一个节点 { return *(--it1); } ++it1; ++it2; } //处理特殊情况,例如:v1:1->2->3 v2:1->2 if (it1 == v1.end() || it2 == v2.end()) return *(--it1); return NULL; }

9、判断一棵二叉树是否是平衡二叉树 算法实现

bool IsCompleteBT()//判断一棵树是否是完全二叉树 { bool flag = true;//判断一棵子树是否为一棵完全二叉树的标记 queue<Node*> q; //运用队列进行层序遍历 q.push(_root); while(!q.empty()) { Node* front = q.front();//保存队首节点 q.pop(); //将队首元素出队 /*如果队首元素的左树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/ if (NULL == front->_left) { flag = false; } else { /*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的左孩子不为空,说明不是完全二叉树*/ if (flag == false) { return false; } q.push(front->_left);//继续向后探索 } /*如果队首元素的右树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/ if (NULL == front->_right) { flag = false; } else { /*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的右孩子不为空,说明不是完全二叉树*/ if (flag == false) { return false; } q.push(front->_right);//继续向后探索 } } return true;//能走到这里说明一定是完全二叉树 }

10、求二叉树中最远的两个节点的距离 外部接口:

int RemoteDistance() //求二叉树中最远的两个节点的距离 { int tmp = 0; return _RemoteDistance1(_root,tmp); }

算法实现:

int _RemoteDistance1(Node* root,int& distance) { if (NULL == root) { return 0; } int tmp = _Depth(root->_left)+_Depth(root->_right);//递归去求每棵子树的最远距离 if (distance < tmp)//用全局变量保存每棵子树的最远距离,并不断更新 { distance = tmp; } return distance; }

11、由前序遍历和中序遍历重建二叉树 外部接口:

Node* Rebuild(T* prevOrder,T* inOrder,int n) //根据前序序列和中序序列重建二叉树 { assert(prevOrder && inOrder); int prevIndex = 0; //指向前序序列指针的下标 int inBegin = 0; //中序序列的首位置的下标 int inEnd = n-1; //中序序列的末位置的下标(闭区间) _root = _Rebuild(prevOrder,prevIndex,inOrder,inBegin,inEnd,n); return _root; }

算法实现:

Node* _Rebuild(T* prevOrder,int& prevIndex,T* inOrder,int inBegin,int inEnd,int n) { Node* root = NULL; if (prevIndex < n)//当前序序列的下标<n时才创建新结点 { root = new Node(prevOrder[prevIndex]); } if (NULL == root)//递归的返回条件 { return NULL; } if (inBegin == inEnd)//要重建树中只有一个节点 { return root; } else { //在中序序列中找到前序序列中的根 int i = inBegin; for ( ; i <= inEnd; ++i) { if (prevOrder[prevIndex] == inOrder[i]) break; } if (i > inEnd)//说明中序序列中没找到前序序列中的根,所给序列不匹配 { throw invalid_argument("所给序列不匹配!");//直接抛异常 } //将根节点作为分割线,将区间分为左右两部分,分别递归重建左右子树 root->_left = _Rebuild(prevOrder,++prevIndex,inOrder,inBegin,i-1,n); root->_right = _Rebuild(prevOrder,++prevIndex,inOrder,i+1,inEnd,n); return root; } }

12、判断一棵树是否是完全二叉树 算法实现:

bool IsCompleteBT()//判断一棵树是否是完全二叉树 { bool flag = true;//判断一棵子树是否为一棵完全二叉树的标记 queue<Node*> q; //运用队列进行层序遍历 q.push(_root); while(!q.empty()) { Node* front = q.front();//保存队首节点 q.pop(); //将队首元素出队 /*如果队首元素的左树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/ if (NULL == front->_left) { flag = false; } else { /*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的左孩子不为空,说明不是完全二叉树*/ if (flag == false) { return false; } q.push(front->_left);//继续向后探索 } /*如果队首元素的右树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/ if (NULL == front->_right) { flag = false; } else { /*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的右孩子不为空,说明不是完全二叉树*/ if (flag == false) { return false; } q.push(front->_right);//继续向后探索 } } return true;//能走到这里说明一定是完全二叉树 }

13、求二叉树的镜像 外部接口:

void GetMirrorTree()//求二叉树的镜像 { _GetMirrorTree(_root); }

算法实现:

void _GetMirrorTree(Node* root)//求二叉树的镜像 { if (NULL == root) { return; } swap(root->_left,root->_right);//交换左右子树 _GetMirrorTree(root->_left); //递归遍历左子树 _GetMirrorTree(root->_right);//递归遍历右子树 }

14、将二叉搜索树转换为一个已排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向 外部接口:

void MakeSearchToList()/*将二叉搜索树转换成一个排序的双向链表。\ 要求不能创建任何新的结点,只能调整树中结点指针的指向。*/ { Node* cur = _root; //寻找最左节点 while (cur->_left) { cur = cur->_left; } Node* prev = NULL; _MakeSearchToList1(_root,prev); //_MakeSearchToList2(_root); while(cur)//打印链表(树的结构变了之后析构会死循环) { cout<<cur->_data<<"<->"; cur = cur->_right; } cout<<"null"; }

算法实现:

void _MakeSearchToList1(Node* root,Node*& prev) { if (NULL == root)//如果当前树为空,则直接返回 return; _MakeSearchToList1(root->_left,prev);//递归左子树 //进行转换 root->_left = prev;//root第一次指向最左节点 if (prev)//如果上一个节点不为空,就将上一个节点的右指针指向当前节点root { prev->_right = root; } prev = root;//更新保存上一个节点 _MakeSearchToList1(root->_right,prev);//递归右子树 } void _InOrderList(Node* root,queue<Node*>& q)//将树的中序遍历的节点入到队列中 { if (NULL == root) return; _InOrderList(root->_left,q);//递归左子树 q.push(root); //将当前节点入队 _InOrderList(root->_right,q);//递归右子树 } void _MakeSearchToList2(Node* root) { if (NULL == root)//如果当前树为空则直接返回 return; queue<Node*> q; _InOrderList(root,q);//将所给树的中序序列push到队列q中 Node* head = q.front();//将队首节点(即所给树的最左节点)保存起来 q.pop(); head->_left = NULL;//将队首节点的左指针置空 Node* prev = head;//用prev保存当前节点 while(!q.empty())//当队列不空时进入循环 { Node* front = q.front();//获取队首节点 q.pop(); //将队首节点出队 prev->_right = front; //将前一个节点的右指针指向队首节点 front->_left = prev; //将队首节点的左指针指向上一个节点 front->_right = NULL; //将队首节点的右指针置空 prev = front; //更新保存上一个节点 } }
转载请注明原文地址: https://www.6miu.com/read-3735.html

最新回复(0)