1.先序/中序/後序周遊二叉樹(遞歸非遞歸實作),層序周遊二叉樹周遊
2.求二叉樹的高度
size_t Height()//擷取該樹的高度
{
return _Height(_pRoot);
}
size_t _Height(Node* pRoot)//擷取數樹的深度,根節點為1
{
if(pRoot==NULL)
return ;
if(pRoot->_pLeft==NULL && pRoot->_pRight==NULL)
return ;
size_t LeftHeight = _Height(pRoot->_pLeft);
size_t RightHeight = _Height(pRoot->_pRight);
if(LeftHeight > RightHeight)
return LeftHeight+;
else
return RightHeight+;
}
3.葉子節點的個數
size_t GetLeefNode()//擷取葉子節點的個數(左子樹葉子節點加上右子樹的葉子節點)
{
return _GetLeefNode(_pRoot);
}
size_t _GetLeefNode(Node* pRoot)//擷取葉子節點的個數---遞歸左子樹葉子節點加上右子樹的葉子節點數
{
if(pRoot==NULL)
return ;
if(pRoot->_pLeft==NULL || pRoot->_pRight==NULL)
return ;
size_t pLeftLeefNode = _GetLeefNode(pRoot->_pLeft);
size_t pRightLeefNode = _GetLeefNode(pRoot->_pRight);
return pLeftLeefNode+pRightLeefNode;
}
4.求第k層節點的個數
size_t GetKLevelNode(const size_t& k)//擷取第K層節點總數
{
return _GetKLevelNode(_pRoot,k);
}
size_t _GetKLevelNode(Node* pRoot,size_t k)//擷取第k層節點個數--遞歸K-1層節點數
{
if(pRoot==NULL || k<= || k>_Height(pRoot))
return ;
if(k == )
return ;
size_t LeftKLevelNode = _GetKLevelNode(pRoot->_pLeft,k-);
size_t RightKLevelNode = _GetKLevelNode(pRoot->_pRight,k-);
return LeftKLevelNode+RightKLevelNode;
}
2、3、4這三道常見面試題都是采用根據先序周遊的方法,來對節點進行遞歸操作
5.樹中兩個節點的最低公共祖先
思考分析:
該題根據題目給出的條件不同,将會有不同的簡便算法;
1>.該樹是二叉收索樹。
二叉收索樹的左孩子小于根節點,根節點小于右孩子,所 以我們可以直接根據節點的值,對左右子樹進行判斷,進而得到結果
2>.該樹是普通樹,但有pParent指針。此時我們可以根據pPaent指針将該問題轉化為求相交兩個連結清單第一個相交的節點
3>.該樹是普通的樹,不含pParent指針
我們今天就介紹普通的樹,有三種得到最低公共祖先的方法
-
從根節點開始周遊節點,判斷所求節點是否在其左右子樹,若在,繼續向下周遊,否則,根節點就是它的雙親節點
缺點:重複周遊節點,時間開銷大
//樹中兩個節點的最低公共祖先(實作2---利用先序周遊每一個節點,并判斷兩個節點是否在該節點的左右分支上,在,則該節點即是最低公共祖先,否則,繼續周遊查找)
Node* GetLastCommonParent2(Node* pNode1,Node* pNode2)
{
return _GetLastCommonParent2(_pRoot,pNode1,pNode2);
}
//樹中兩個節點的最低公共祖先---實作2(重複周遊節點并進行判斷,該方法的時間消耗大)
Node* _GetLastCommonParent2(Node* pRoot,Node* pNode1,Node* pNode2)
{
if(pRoot==NULL || pNode1==NULL ||pNode2==NULL)
return NULL;
Node* pCur = NULL;
if(IsFindNode(pRoot,pNode1) && IsFindNode(pRoot,pNode2))//若在根為pRoot的子樹中查找到pNode和pNode2,則繼續向下尋找,若找不到,二叉樹的根為最低祖先
{
if(pCur=_GetLastCommonParent2(pRoot->_pLeft,pNode1,pNode2))
return pCur;
if(pCur=_GetLastCommonParent2(pRoot->_pRight,pNode1,pNode2))
return pCur;
return pRoot;
}
return NULL;
}
- 普通樹,用輔助記憶體棧實作,查找兩個棧中最後一個相同的元素
Node* _GetLastCommonParent1(Node* pRoot,Node* pNode1,Node* pNode2)
{
if(pRoot == NULL || pNode1==NULL || pNode2==NULL)
return NULL;
stack<Node*> s1;
stack<Node*> s2;
//調用查找該節點并儲存路徑的函數
bool flags = false;
FindNodeAndPush(pRoot,pNode1,s1,flags);
flags = false;
FindNodeAndPush(pRoot,pNode2,s2,flags);
//進行出棧操作,找到最低公共祖先
while(!s1.empty() && !s2.empty())
{
if(s1.size()>s2.size())
s1.pop();
else if(s1.size() < s2.size())
s2.pop();
else
{
while(s1.top() != s2.top())
{
s1.pop();
s2.pop();
}
return s1.top();
}
}
return NULL;
}
void FindNodeAndPush(Node* pRoot,Node* pNode,stack<Node*> &s,bool& flags)//從根節點開始查找該節點并儲存路徑到棧
{
if(pRoot==NULL || pNode == NULL)
return;
s.push(pRoot);
if(pRoot == pNode)
{
flags = true;
return;
}
if(!flags)
FindNodeAndPush(pRoot->_pLeft,pNode,s,flags);
if(!flags)
FindNodeAndPush(pRoot->_pRight,pNode,s,flags);
if(!flags)
{
s.pop();
return;
}
return;
}
- 利用輔助空間連結清單,将問題轉化為求兩個連結清單的最後一個相交的節點
Node* _GetLastCommonParent3(Node* pRoot,Node* pNode1,Node* pNode2)
{
if(pRoot==NULL || pNode1==NULL ||pNode2==NULL)
return NULL;
list<Node*> l1;
list<Node*> l2;
//從根節點開始查找節點并儲存路徑
bool flags = false;
FindNodeAndRemPath(pRoot,pNode1,l1,flags);
flags = false;
FindNodeAndRemPath(pRoot,pNode2,l2,flags);
//求兩個相交連結清單中最後一個相交的節點
return FindCrossPointOfTwoList(l1,l2);
}
void FindNodeAndRemPath(Node* pRoot,Node* pNode,list<Node*>& l,bool& flags)//從根節點開始查找節點并儲存路徑
{
if(pRoot==NULL)
return;
l.push_back(pRoot);
if(pRoot == pNode)
{
flags = true;
return;
}
if(!flags)
FindNodeAndRemPath(pRoot->_pLeft,pNode,l,flags);
if(!flags)
FindNodeAndRemPath(pRoot->_pRight,pNode,l,flags);
if(!flags)
{
l.pop_back();
return;
}
}
Node* FindCrossPointOfTwoList(list<Node*> l1,list<Node*> l2)//求兩個相交連結清單中最後一個相交的節點
{
//Node* pCur1 = l1.front();
//Node* pCur2 = l2.front();
list<Node*>::iterator it1 = l1.begin();
list<Node*>::iterator it2 = l2.begin();
Node* pCur = NULL;
while(it1 != l1.end() && it2 != l2.end())
{
if(*it1 == *it2)
{
pCur = *it1;
it1++;
it2++;
}
else
{
return pCur;
}
}
return NULL;
}
6.求二叉樹的鏡像
- 先序周遊得到結果
//擷取一棵樹的鏡像---實作1(利用先序周遊)
void GetMirrorTree1()
{
_pRoot = _GetMirrorTree1(_pRoot);
}
//擷取一棵樹的鏡像樹---先序周遊實作
Node* _GetMirrorTree1(Node* pRoot)
{
if(pRoot == NULL || (pRoot->_pRight==NULL && pRoot->_pLeft==NULL))
return NULL;
std::swap(pRoot->_pLeft,pRoot->_pRight);
Node* pCur = NULL;
pCur = _GetMirrorTree(pRoot->_pLeft);
pCur = _GetMirrorTree(pRoot->_pRight);
return pRoot;
}
- 層序周遊的到結果
//擷取一棵樹的鏡像---實作2(利用層序周遊)
void GetMirrorTree2()
{
_pRoot = _GetMirrorTree2(_pRoot);
}
//擷取一棵樹的鏡像樹---層序周遊實作
Node* _GetMirrorTree2(Node* pRoot)
{
if(pRoot==NULL)
return NULL;
queue<Node*> q;
q.push(pRoot);
Node* pCur = NULL;
while(!q.empty())
{
pCur=q.front();
if(pCur->_pLeft!=NULL || pCur->_pRight!=NULL)
std::swap(pCur->_pLeft,pCur->_pRight);
q.pop();
if(pCur->_pLeft!=NULL)
q.push(pCur->_pLeft);
if(pCur->_pRight!=NULL)
q.push(pCur->_pRight);
}
return pRoot;
}
7.判斷一棵樹是否為完全二叉樹
//判斷一棵樹是否為完全二叉樹
bool IsCompleteTree()
{
return _IsCompleteTree(_pRoot);
}
bool _IsCompleteTree(Node* pRoot)//判斷一顆樹是不是完全二叉樹
{
//用一個隊列來儲存資料,從根節開始,通路他的左右子樹,若左右子樹都存在,則說明該樹有可能成為完全二叉樹,左右節點入棧
//取隊列的頭節點,通路其左右子樹,若隻有左子樹存在,說明有可能是完全二叉樹,入棧繼續通路,此時該節點前面的節點都滿足
//完全二叉樹的要求,若後面的節點有一個孩子節點,則說明該樹不是完全二叉樹;
//若隻有右子樹存在,則節點沒有左子樹該樹一定不是完全二叉樹
//若該節點沒有左右孩子,同隻有左孩子一樣,繼續通路後面的節點進行判斷
if(pRoot==NULL)
return true;
queue<Node*> q;
q.push(pRoot);
Node* pCur = NULL;
while(!q.empty())
{
pCur = q.front();
if(pCur->_pLeft!=NULL && pCur->_pRight!=NULL)
{
q.push(pCur->_pLeft);
q.push(pCur->_pRight);
}
else if(pCur->_pLeft!=NULL)
{
q.push(pCur->_pLeft);
//判斷處理
q.pop();
while(!q.empty())
{
pCur=q.front();
if(pCur->_pLeft!=NULL || pCur->_pRight!=NULL)
return false;
q.pop();
}
}
else if(pCur->_pRight!=NULL)
{
return false;
}
else
{
//判斷處理
q.pop();
while(!q.empty())
{
pCur=q.front();
if(pCur->_pLeft!=NULL || pCur->_pRight!=NULL)
return false;
q.pop();
}
}
if(!q.empty())
q.pop();
}
return true;
}
8.用先序和中序清單重建二叉樹
void RebuildTree(char* preorder,char* inorder)//根據先序數列和中序數列重建二叉樹
{
if(preorder==NULL || inorder==NULL)
return;
size_t height1 = strlen(preorder);
size_t height2 = strlen(inorder);
size_t index = ;
_pRoot = _RebuildTree(_pRoot,preorder,preorder+height1-,inorder,inorder+height2-,index);
}
Node* _RebuildTree(Node* pRoot,char* pstart,char* pend,char* instart,char* inend,size_t& index)
{
if(pstart==NULL || pend==NULL || instart==NULL || inend==NULL)
return NULL;
if(instart>inend)
{
--index;
return NULL;
}
pRoot = new Node;
pRoot->_data = pstart[index];
size_t idx=;
for(idx=; idx<(inend-instart); ++idx)
{
if(pstart[index]==instart[idx])
break;
}
pRoot->_pLeft = _RebuildTree(pRoot->_pLeft,pstart,pend,instart,instart+idx-,++index);
pRoot->_pRight = _RebuildTree(pRoot->_pRight,pstart,pend,instart+idx+,inend,++index);
return pRoot;
}
9.根據中序和後序數列重建二叉樹
void RebuildTree2(char* inorder,char* postorder)//根據後序數列和中序數列重建二叉樹
{
if(inorder==NULL || postorder==NULL)
return;
size_t height1 = strlen(inorder);
size_t height2 = strlen(postorder);
if(height1 != height2)
return;
size_t index=height2-;
_pRoot = _RebuildTree2(_pRoot,inorder,inorder+height1-,postorder,postorder+height2-,index);
}
//根據中序數列和後序數列重建二叉樹
Node* _RebuildTree2(Node* pRoot,char* instart,char* inend,char* poststart,char* postend,size_t& index)
{
if(instart==NULL || inend==NULL || poststart==NULL || postend==NULL)
return NULL;
if(instart>inend)
{
++index;
return NULL;
}
pRoot = new Node;
pRoot->_data = poststart[index];
size_t idx=;
for(; idx<(inend-instart); ++idx)
{
if(poststart[index]==instart[idx])
break;
}
pRoot->_pRight = _RebuildTree2(pRoot->_pRight,instart+idx+,inend,poststart,postend,--index);
pRoot->_pLeft = _RebuildTree2(pRoot->_pLeft,instart,instart+idx-,poststart,postend,--index);
return pRoot;
}
10.擷取一棵完全二叉樹所有節點的個數
-
普通周遊計數
缺點:需要周遊每個節點,時間開銷大
size_t GetAllNode(size_t& count)//利用先序周遊,查找每一個節點并計數,時間複雜度O(N),N是總的節點個數
{
return _GetAllNode(_pRoot,count);
}
size_t _GetAllNode(Node* pRoot,size_t& count)
{
if(pRoot==NULL)
return count;
if(pRoot!=NULL)
++count;
count = _GetAllNode(pRoot->_pLeft,count);
count = _GetAllNode(pRoot->_pRight,count);
}
- 代碼優化:注意條件—完全二叉樹
//計算一棵完全二叉樹的所有節點的個數---優化,時間複雜度O(logN)2(平方)
size_t GetAllNode1(size_t& count)
{
return _GetAllNode1(_pRoot,count);
}
size_t _GetAllNode1(Node* pRoot,size_t& count)
{
if(pRoot==NULL)
return ;
if(pRoot->_pLeft==NULL && pRoot->_pRight==NULL)
return ;
int h = GetHight(pRoot);//求深度O(logN)
size_t ret = ;
if((ret=GetHight(pRoot->_pRight)+)==h)//左子樹為滿二叉樹,用公式計算,右子樹周遊計算
{
count = ((<<(h-))-++_GetAllNode(pRoot->_pRight,count));//O(logN)
}
else
{
count = ((<<(h-))-++_GetAllNode(pRoot->_pLeft,count));//右子樹為滿二叉樹,用公式計算,左子樹周遊計算
}
return count;
}
size_t GetHight(Node* pRoot)//計算樹的最大深度
{
Node* pCur = pRoot;
size_t ret=;
while(pCur)
{
pCur = pCur->_pLeft;
ret++;
}
return ret;
}
11.判斷一棵樹是不是平衡二叉樹
-
普通解法—從根節點開始通路,判斷其左右子樹是否為平衡二叉樹,若不是,這直接退出,若是,繼續通路左右子樹
缺點:計算節點左右子樹高度時重複計算,多次通路,時間開銷大
bool IsBalanceTree1()//實作1---利用先序周遊的方法,但是節點重複周遊了,從根節點開始計算并比較每個子樹的深度
{
return _IsBalanceTree1(_pRoot);
}
bool _IsBalanceTree1(Node* pRoot)//判斷一棵樹是不是平衡二叉樹---實作1
{
//從根節點開始,得到左右子樹的高度,比較,若不滿足條件,則直接退出,後面再不用比較
if(pRoot==NULL)
return true;
int left= _Height(pRoot->_pLeft);
int right = _Height(pRoot->_pRight);
int diff = left-right;
if(diff> || diff<-)
return false;
return (_IsBalanceTree1(pRoot->_pLeft) && _IsBalanceTree1(pRoot->_pRight));
}
- 利用後序周遊的方法實作對節點的通路
bool IsBalanceTree2()//實作二---利用後序周遊的方法,保證每個節點隻周遊一次,根據根節點的深度為左右子樹深度的最大值加一
{
int depth = ;
return _IsBalanceTree2(_pRoot,depth);
}
bool _IsBalanceTree2(Node* pRoot,int& depth)//判斷一棵樹是不是平衡二叉樹---實作2
{
//從樹的最左端的節點開始通路其左右子樹,這裡沒有調用_Height函數,最左端的節點的左子樹高度一定為1,最右端的右節點的深度也是1
//再遞歸回退的過程中,得到節點左子樹的高度和右子樹的高度(下層高度+1),比較,若滿足條件,則繼續回退,得到目前層的高度,繼續比較上一層
//若不滿足,則直接傳回錯誤
if(pRoot==NULL)
{
depth = ;
return true;
}
int left;
int right;
if(_IsBalanceTree2(pRoot->_pLeft,left)&&_IsBalanceTree2(pRoot->_pRight,right))
{
int diff = left-right;
if(diff<= && diff>=-)
{
depth = +((left>right)? left:right);
return true;
}
return false;
}
return false;
}