說明:本博文在以前面試題的基礎上彙總了二叉樹的常見面試題。
二叉樹中封裝的功能有:
void _CreateTree(Node*& pRoot, const T array[], size_t size, size_t& index, const T& invalid);//創建樹
Node* _CopyBinaryTree(Node* pRoot);//拷貝樹
void _Destroy(Node*& pRoot);//清空樹
void _PreOrder(Node* pRoot);//遞歸實現:先序遍歷
void _PreOrder_nor(Node* pRoot);//非遞歸實現:先序遍歷
void _InOrder(Node* pRoot);//遞歸實現:中序遍歷
void _InOrder_nor(Node* pRoot);//非遞歸實現:中序遍歷
void _PostOrder(Node* pRoot);//遞歸實現:後序遍歷
void _PostOrder_nor(Node* pRoot);//非遞歸實現:後序遍歷
void _LevelOrder(Node* pRoot);//層序遍歷
Node* _GetParent(Node* pRoot, Node* x);//獲取雙親結點
Node* _Find(Node* pRoot, const T& value);//找到值爲value的結點
size_t _Height(Node* pRoot);//獲取樹的高度
size_t _GetLeefNode(Node* pRoot);//獲取葉子結點個數
size_t _GetKLevelNode(Node* pRoot, size_t k);//獲取某一層結點個數
void _GetBinaryMirror(Node* pRoot);//遞歸實現:將二叉樹變爲其鏡像
bool IsCompleteBinaryTree();//判斷二叉樹樹是否是完全二叉樹
二叉樹信息:
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
template <typename T>
struct BinaryTreeNode //節點信息
{
BinaryTreeNode(const T& data)
:m_data(data)
, m_pLeft(nullptr)
, m_pRight(nullptr)
{}
T m_data;
BinaryTreeNode<T>* m_pLeft;//左孩子
BinaryTreeNode<T>* m_pRight;//右孩子
};
template <typename T>
class BinaryTree//將成員函數的實現進行簡單的封裝,用戶只能顯示調用公共接口
{
typedef BinaryTreeNode<T> Node;
public:
BinaryTree()//無參構造函數
:m_pRoot(nullptr)
{}
BinaryTree(const T array[], size_t size, const T& invalid)//帶參構造函數
{
size_t index = 0;
_CreateTree(m_pRoot, array, size, index, invalid);
}
BinaryTree(const BinaryTree<T>& t)//拷貝構造函數
{
m_pRoot = _CopyBinaryTree(t.m_pRoot);
}
BinaryTree<T>& operator=(const BinaryTree<T>& t)//賦值運算符重載成員函數
{
_Destroy(m_pRoot);
m_pRoot = _CopyBinaryTree(t.m_pRoot);
return *this;
}
~BinaryTree()//析構函數
{
_Destroy(m_pRoot);
}
void PreOrder()//前序遍歷
{
cout << "前序遍歷:";
_PreOrder_nor(m_pRoot);
cout << endl;
}
void InOrder()//中序遍歷
{
cout << "中序遍歷";
_InOrder_nor(m_pRoot);
cout << endl;
}
void PostOrder()//後序遍歷
{
cout << "後序遍歷";
_PostOrder_nor(m_pRoot);
cout << endl;
}
void LevelOrder()//層序遍歷
{
cout << "層序遍歷";
_LevelOrder(m_pRoot);
cout << endl;
}
Node* GetParent(Node* x)//獲取該結點的雙親結點
{
return _GetParent(m_pRoot, x);
}
Node* Find(const T& value)//找到值爲value的結點
{
return _Find(m_pRoot, value);
}
Node* GetLeftChild(Node* pCur)//返回左孩子
{
if (pCur)
return pCur->m_pLeft;
return nullptr;
}
Node* GetRightChild(Node* pCur)//返回右孩子
{
if (pCur)
return pCur->m_pRight;
return nullptr;
}
size_t Height()//獲取樹的高度
{
return _Height(m_pRoot);
}
size_t GetLeefNode()//獲取葉子結點個數
{
return _GetLeefNode(m_pRoot);
}
size_t GetKLevelNode(size_t k)//獲取某一層結點個數
{
return _GetKLevelNode(m_pRoot, k);
}
void GetBinaryMirror_Nor();// 將二叉樹變爲其鏡像:非遞歸
void GetBinaryMirror() // 將二叉樹變爲其鏡像:遞歸版本
{
_GetBinaryMirror(m_pRoot);
}
// 利用層序遍歷來處理--> 關鍵:找第一個度不爲2的結點-->後續結點
// 如果有孩子則不是完全二叉樹
// 否則:是
bool IsCompleteBinaryTree();
private:
void _CreateTree(Node*& pRoot, const T array[], size_t size, size_t& index, const T& invalid);//創建樹
Node* _CopyBinaryTree(Node* pRoot);//拷貝樹
void _Destroy(Node*& pRoot);//清空樹
void _PreOrder(Node* pRoot);//遞歸實現:先序遍歷
void _PreOrder_nor(Node* pRoot);//非遞歸實現:先序遍歷
void _InOrder(Node* pRoot);//遞歸實現:中序遍歷
void _InOrder_nor(Node* pRoot);//非遞歸實現:中序遍歷
void _PostOrder(Node* pRoot);//遞歸實現:後序遍歷
void _PostOrder_nor(Node* pRoot);//非遞歸實現:後序遍歷
void _LevelOrder(Node* pRoot);//層序遍歷
Node* _GetParent(Node* pRoot, Node* x);//獲取雙親結點
Node* _Find(Node* pRoot, const T& value);//找到值爲value的結點
size_t _Height(Node* pRoot);//獲取樹的高度
size_t _GetLeefNode(Node* pRoot);//獲取葉子結點個數
size_t _GetKLevelNode(Node* pRoot, size_t k);//獲取某一層結點個數
void _GetBinaryMirror(Node* pRoot);//遞歸實現:將二叉樹變爲其鏡像
private:
BinaryTreeNode<T>* m_pRoot;//根結點
};
利用數組,創建一個二叉樹(比如“124#7#35##68#”表示先序遍歷順序,#用來表示空結點)
template <typename T>//創建樹
void BinaryTree<T>::_CreateTree(Node*& pRoot, const T array[], size_t size, size_t& index, const T& invalid)
{
if ((index < size) && (array[index] != invalid))
{
pRoot = new Node(array[index]);
_CreateTree(pRoot->m_pLeft, array, size, ++index, invalid);
_CreateTree(pRoot->m_pRight, array, size, ++index, invalid);
}
}
拷貝一個二叉樹
template <typename T>//拷貝樹
BinaryTreeNode<T>* BinaryTree<T>::_CopyBinaryTree(Node* pRoot)
{
Node* pNewRoot = nullptr;
if (pRoot)
{
pNewRoot = new Node(pRoot->m_data);
pNewRoot->m_pLeft = _CopyBinaryTree(pRoot->m_pLeft);
pNewRoot->m_pRight = _CopyBinaryTree(pRoot->m_pRight);
}
return pNewRoot;
}
銷燬一顆二叉樹
template <typename T>
void BinaryTree<T>::_Destroy(Node*& pRoot)//清空樹
{
if (pRoot)
{
_Destroy(pRoot->m_pLeft);
_Destroy(pRoot->m_pRight);
delete pRoot;
pRoot = nullptr;
}
}
遞歸實現前序遍歷
template <typename T>
void BinaryTree<T>::_PreOrder(Node* pRoot)//遞歸實現:前序遍歷
{
if (pRoot)
{
cout << pRoot->m_data << " ";
_PreOrder(pRoot->m_pLeft);
_PreOrder(pRoot->m_pRight);
}
}
非遞歸實現前序遍歷
template <typename T>
void BinaryTree<T>::_PreOrder_nor(Node* pRoot)//非遞歸實現:前序遍歷(利用棧實現)
{
if (nullptr == pRoot)//空樹
return;
stack<Node*> s;
s.push(pRoot);
while (!s.empty())
{
Node* pTemp = s.top();
cout << pTemp->m_data << " ";
s.pop();//pop放在push之前,由於棧的LIFO特性
if (pTemp->m_pRight)
s.push(pTemp->m_pRight);//一定要先存入右子樹,後存入左子樹,因爲棧的LIFO特性
if (pTemp->m_pLeft)
s.push(pTemp->m_pLeft);
}
}
遞歸實現中序遍歷
template <typename T>
void BinaryTree<T>::_InOrder(Node* pRoot)//遞歸實現:中序遍歷
{
if (pRoot)
{
_InOrder(pRoot->m_pLeft);
cout << pRoot->m_data << " ";
_InOrder(pRoot->m_pRight);
}
}
非遞歸實現中序遍歷
template <typename T>
void BinaryTree<T>::_InOrder_nor(Node* pRoot)//非遞歸實現:中序遍歷(1,找到樹最左邊的節點,並保存路徑 2,訪問當前節點,並將其右子樹作爲根節點。3,重複12)
{
if (nullptr == pRoot)
return;
stack<Node*> s;
Node* pCur = pRoot;
while (!s.empty() || pCur)//注意:後面的條件,因爲是中序遍歷,當根節點出棧之後,還需遍歷右子樹
{
while (pCur)//找到最左邊的葉子結點,將路徑上結點入棧
{
s.push(pCur);
pCur = pCur->m_pLeft;
}
Node* pTemp = s.top();
cout << pTemp->m_data << " ";
s.pop();
pCur = pTemp->m_pRight;//進入右子樹
}
}
遞歸實現後序遍歷
template <typename T>
void BinaryTree<T>::_PostOrder(Node* pRoot)//後序遍歷
{
if (pRoot)
{
_PostOrder(pRoot->m_pLeft);
_PostOrder(pRoot->m_pRight);
cout << pRoot->m_data << " ";
}
}
非遞歸實現後序遍歷
template <typename T>
void BinaryTree<T>::_PostOrder_nor(Node* pRoot)//非遞歸實現:後序遍歷
{/*順序:總是:左-->右-->根
1,找到最左邊的葉子結點
2,訪問最左邊的結點(即棧頂結點)
3,若棧頂元素的兄弟結點爲空,或者不爲空但已經訪問過了(不爲空時,採用標記避免陷入死循環(一直保存右孩子)中),就輸出棧頂結點值
4,否則,進入棧頂元素的兄弟結點所在樹中,進行1-2步迭代
*/
if (nullptr == pRoot)//空樹
return;
stack<Node*> s;
Node* pCur = pRoot;//當前樹的根結點
Node* pPre = nullptr;//最近一次訪問的節點
while (!s.empty() || pCur)//樹不爲空,或者樹爲空時根結點不爲空(即還沒有將節點放入棧中)
{
while (pCur)//找到該樹最左邊的葉子結點,並且保存路徑
{
s.push(pCur);
pCur = pCur->m_pLeft;//一直找左孩子。
}
pCur = s.top();//取出棧頂元素
if ((nullptr == pCur->m_pRight) || (pPre == pCur->m_pRight))//若棧頂元素的右孩子爲空,或者棧頂元素的右孩子孩子不爲空並且等於剛纔輸出的節點(避免陷入死循環:一直保存右孩子)
{
cout << pCur->m_data << " ";//打印根結點的前提是:右子樹爲空
s.pop();
pPre = pCur;
pCur = nullptr;//置爲空,使得下次迭代中,跳出尋找那次迭代中的樹的最左邊結點,避免陷入死循環(一直保存左孩子)
}
else
pCur = pCur->m_pRight;
}
}
實現層序遍歷
template <typename T>
void BinaryTree<T>::_LevelOrder(Node* pRoot)//層序遍歷(利用隊列實現)
{ /*
畫圖理解:
1,先將根節點入隊列,
2,進入循環,隊首結點出隊列,輸出其值。
3,將其左右孩子節點入隊列,
4,進行2-3循環,直到隊列爲空
*/
if (!pRoot)
return;
queue<Node*> q;
q.push(pRoot);
while (!q.empty())
{
Node* pCur = q.front();
q.pop();
cout << pCur->m_data << " ";
if (pCur->m_pLeft)
q.push(pCur->m_pLeft);
if (pCur->m_pRight)
q.push(pCur->m_pRight);
}
}
遞歸實現獲取雙親結點
template <typename T>
BinaryTreeNode<T>* BinaryTree<T>::_GetParent(Node* pRoot, Node* x)//遞歸實現:獲取雙親結點
{
if ((nullptr == pRoot) || (pRoot == x))//空樹或者該結點是根結點,沒有雙親結點,返回nullptr
return nullptr;
if ((pRoot->m_pLeft == x) || (pRoot->m_pRight == x))//若x是根結點個左右孩子,返回該根結點
return pRoot;
//以上兩條判斷語句是遞歸出口
//利用遞歸,將問題變爲在左右子樹中尋找x的雙親結點
Node* parent = nullptr;
if (parent = _GetParent(pRoot->m_pLeft, x))//左子樹中尋找
return parent;
if (parent = _GetParent(pRoot->m_pRight, x))//右子樹中尋找
return parent;
return nullptr;//在左右子樹中未找到x的雙親結點。
}
遞歸實現查找某一值
template <typename T>
BinaryTreeNode<T>* BinaryTree<T>::_Find(Node* pRoot, const T& value)//遞歸實現:找到值爲value的結點
{
if (nullptr == pRoot)//空樹或空結點
return nullptr;
if (pRoot->m_data == value)
return pRoot;
//以上兩條語句是遞歸出口
//利用遞歸,將問題變爲在左右子樹中尋找值爲value的節點
Node* p_node = nullptr;
if (p_node = _Find(pRoot->m_pLeft, value))//左子樹中尋找
return p_node;
if (p_node = _Find(pRoot->m_pRight, value))//右子樹中尋找
return p_node;
return nullptr;//在左右子樹中未找到值爲value的結點。
}
遞歸實現獲取樹的高度
template <typename T>
size_t BinaryTree<T>::_Height(Node* pRoot)//遞歸實現:獲取樹的高度
{
if (nullptr == pRoot)//空樹或空節點
return 0;
//以上語句爲遞歸出口
//利用遞歸:獲取左右子樹的高度,並將左右子樹的高度中較大的高度加1,返回(當前樹的pRoot不爲空,所以加1)
size_t h_left = _Height(pRoot->m_pLeft);//獲得左子樹高度
size_t h_right = _Height(pRoot->m_pRight);//獲得右子樹高度
return h_left > h_right ? h_left + 1 : h_right + 1;//返回樹的高度
}
遞歸實現獲取葉子結點個數
template <typename T>
size_t BinaryTree<T>::_GetLeefNode(Node* pRoot)//遞歸實現:獲取葉子結點個數
{
if (nullptr == pRoot)//空樹或空結點
return 0;
if ((nullptr == pRoot->m_pLeft) && (nullptr == pRoot->m_pRight))//當前節點時葉子結點
return 1;
//以上兩個判斷語句是遞歸出口
//利用遞歸,獲取左右子樹的葉子結點個數
size_t leaves_left = _GetLeefNode(pRoot->m_pLeft);//獲取左子樹葉子結點數
size_t leaves_right = _GetLeefNode(pRoot->m_pRight);//獲取右子樹葉子結點數
return leaves_left + leaves_right;//返回左右子樹葉子結點之和
}
遞歸實現獲取第K層節點個數
template <typename T>
size_t BinaryTree<T>::_GetKLevelNode(Node* pRoot, size_t k)//遞歸實現:獲取某一層結點個數
{
if ((nullptr == pRoot) || (1 > k))//空樹或空結點、k小於1
return 0;
if (1 == k)//k=1表示當前結點
return 1;
//以上兩個判斷語句是遞歸出口
//利用遞歸,獲取左右子樹中處於k-1層結點個數
size_t nodes_left = _GetKLevelNode(pRoot->m_pLeft, k - 1);//獲取左子樹中k-1層結點個數
size_t nodes_right = _GetKLevelNode(pRoot->m_pRight, k - 1);//獲取右子樹中k-1層結點個數
return nodes_left + nodes_right;
}
非遞歸獲取二叉樹的鏡像
template <typename T>
void BinaryTree<T>::GetBinaryMirror_Nor()// 將二叉樹變爲其鏡像:非遞歸
{
if (nullptr == m_pRoot)//空樹
return;
//利用層序遍歷的思想,將每一層中,每一個節點按順序放入隊列中,然後改變其左右孩子的位置
queue<Node*> q;
Node* pCur = m_pRoot;
q.push(pCur);
while (!q.empty())
{
pCur = q.front();
q.pop();//由於是隊列,對於出隊列和入隊列的順序沒有要求
if (pCur->m_pLeft || pCur->m_pRight)//如果當前節點不是葉子結點(即:有左右孩子)
{
std::swap(pCur->m_pLeft, pCur->m_pRight);//交換左右孩子
//將存在的左孩子或右孩子入隊列
if (pCur->m_pLeft)
q.push(pCur->m_pLeft);
if (pCur->m_pRight)
q.push(pCur->m_pRight);
}
}
}
遞歸獲取二叉樹的鏡像
template <typename T>
void BinaryTree<T>::_GetBinaryMirror(Node* pRoot) // 將二叉樹變爲其鏡像:遞歸版本
{
if (nullptr == pRoot)//空樹
return;
if ((nullptr == pRoot->m_pLeft) && (nullptr == pRoot->m_pRight))//如果_是葉子結點
return;
//以上兩條判斷語句是遞歸出口
std::swap(pRoot->m_pLeft, pRoot->m_pRight);//交換左右孩子
//將存在的左右孩子樹,作爲新樹,執行相同功能
//if (pRoot->m_pLeft),不加判空語句,在下次遞歸中會判空。
_GetBinaryMirror(pRoot->m_pLeft);
//if (pRoot->m_pRight),不加判空語句,在下次遞歸中會判空。
_GetBinaryMirror(pRoot->m_pRight);
}
判斷一個樹是否是完全二叉樹
/*
由於完全二叉樹的規律,可以使用以下解法。
1,利用層序遍歷的順序找到第一個度爲0或度爲1(只有左子樹,沒有右子樹)的結點。(關鍵)
2,若在層序遍歷中,該結點之後存在某個結點擁有孩子,則不是完全二叉樹。
3,否則,是完全二叉樹。
*/
template <typename T>
bool BinaryTree<T>::IsCompleteBinaryTree()
{
if (nullptr == m_pRoot)
return true;//空樹也是完全二叉樹
queue<Node*> q;
Node* pCur = m_pRoot;
q.push(pCur);
bool flag = false;//flag表示是否找到了滿足條件的節點
while (!q.empty())
{
pCur = q.front();
q.pop();
if (!flag && pCur->m_pLeft && pCur->m_pRight)//flag=false且該結點度爲2
{
q.push(pCur->m_pLeft);
q.push(pCur->m_pRight);
}
else if ((nullptr == pCur->m_pLeft) && pCur->m_pRight)//該結點只有右子樹,沒有左子樹,不是完全二叉樹
{
return false;
}
else if (flag && (pCur->m_pLeft || pCur->m_pRight))//flag=true且該結點有孩子節點,不是完全二叉樹
{
return false;
}
else//剩餘情況,度爲0,度位1(只有左子樹,沒有右子樹,另外一種情況在if_2中排除)。(度爲2情況在if_1, if_3中已經排除)。
flag = true;//找到滿足條件的結點,並且標記已找到。
}
return true;//遍歷二叉樹過程中,沒有提前退出,表示是一個完全二叉樹
}