php數據結構之二叉樹
樹是一種比較重要的數據結構, 尤其是二叉樹。二叉樹是一種特殊的樹,在二叉樹中每個節點最多有兩個子節點,一般稱為左子節點和右子節點(或左孩子和右孩子),並且二叉樹的子樹有左右之 分,其次序不能任意顛倒。二叉樹是遞歸定義的,因此,與二叉樹有關的題目基本都可以用遞歸思想解決,當然有些題目非遞歸解法也應該掌握,如非遞歸遍歷節點 等等。本文努力對二叉樹相關題目做一個較全的整理總結,希望對找工作的同學有所幫助。
二叉樹節點定義如下:
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
相關鏈接:
輕松搞定面試中的鏈表題目
題目列表:
1. 求二叉樹中的節點個數
2. 求二叉樹的深度
3. 前序遍歷,中序遍歷,後序遍歷
4.分層遍歷二叉樹(按層次從上往下,從左往右)
5. 將二叉查找樹變為有序的雙向鏈表
6. 求二叉樹第K層的節點個數
7. 求二叉樹中葉子節點的個數
8. 判斷兩棵二叉樹是否結構相同
9. 判斷二叉樹是不是平衡二叉樹
10. 求二叉樹的鏡像
11. 求二叉樹中兩個節點的最低公共祖先節點
12. 求二叉樹中節點的最大距離
13. 由前序遍歷序列和中序遍歷序列重建二叉樹
14.判斷二叉樹是不是完全二叉樹
詳細解答
1. 求二叉樹中的節點個數
遞歸解法:
(1)如果二叉樹為空,節點個數為0
(2)如果二叉樹不為空,二叉樹節點個數 = 左子樹節點個數 + 右子樹節點個數 + 1
參考代碼如下:
int GetNodeNum(BinaryTreeNode * pRoot)
{
if(pRoot == NULL) // 遞歸出口
return 0;
return GetNodeNum(pRoot->m_pLeft) + GetNodeNum(pRoot->m_pRight) + 1;
}
2. 求二叉樹的深度
遞歸解法:
(1)如果二叉樹為空,二叉樹的深度為0
(2)如果二叉樹不為空,二叉樹的深度 = max(左子樹深度, 右子樹深度) + 1
參考代碼如下:
int GetDepth(BinaryTreeNode * pRoot)
{
if(pRoot == NULL) // 遞歸出口
return 0;
int depthLeft = GetDepth(pRoot->m_pLeft);
int depthRight = GetDepth(pRoot->m_pRight);
return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1);
}
3. 前序遍歷,中序遍歷,後序遍歷
前序遍歷遞歸解法:
(1)如果二叉樹為空,空操作
(2)如果二叉樹不為空,訪問根節點,前序遍歷左子樹,前序遍歷右子樹
參考代碼如下:
void PreOrderTraverse(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return;
Visit(pRoot); // 訪問根節點
PreOrderTraverse(pRoot->m_pLeft); // 前序遍歷左子樹
PreOrderTraverse(pRoot->m_pRight); // 前序遍歷右子樹
}
中序遍歷遞歸解法
(1)如果二叉樹為空,空操作。
(2)如果二叉樹不為空,中序遍歷左子樹,訪問根節點,中序遍歷右子樹
參考代碼如下:
void InOrderTraverse(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return;
InOrderTraverse(pRoot->m_pLeft); // 中序遍歷左子樹
Visit(pRoot); // 訪問根節點
InOrderTraverse(pRoot->m_pRight); // 中序遍歷右子樹
}
後序遍歷遞歸解法
(1)如果二叉樹為空,空操作
(2)如果二叉樹不為空,後序遍歷左子樹,後序遍歷右子樹,訪問根節點
參考代碼如下:
void PostOrderTraverse(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return;
PostOrderTraverse(pRoot->m_pLeft); // 後序遍歷左子樹
PostOrderTraverse(pRoot->m_pRight); // 後序遍歷右子樹
Visit(pRoot); // 訪問根節點
}
4.分層遍歷二叉樹(按層次從上往下,從左往右)
相當於廣度優先搜索,使用隊列實現。隊列初始化,將根節點壓入隊列。當隊列不為空,進行如下操作:彈出一個節點,訪問,若左子節點或右子節點不為空,將其壓入隊列。
void LevelTraverse(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return;
queue<BinaryTreeNode *> q;
q.push(pRoot);
while(!q.empty())
{
BinaryTreeNode * pNode = q.front();
q.pop();
Visit(pNode); // 訪問節點
if(pNode->m_pLeft != NULL)
q.push(pNode->m_pLeft);
if(pNode->m_pRight != NULL)
q.push(pNode->m_pRight);
}
return;
}
5. 將二叉查找樹變為有序的雙向鏈表 要求不能創建新節點,只調整指針。
遞歸解法:
(1)如果二叉樹查找樹為空,不需要轉換,對應雙向鏈表的第一個節點是NULL,最後一個節點是NULL
(2)如果二叉查找樹不為空:
如果左子樹為空,對應雙向有序鏈表的第一個節點是根節點,左邊不需要其他操作;
如果左子樹不為空,轉換左子樹,二叉查找樹對應雙向有序鏈表的第一個節點就是左子樹轉換後雙向有序鏈表的第一個節點,同時將根節點和左子樹轉換後的雙向有序鏈 表的最後一個節點連接;
如果右子樹為空,對應雙向有序鏈表的最後一個節點是根節點,右邊不需要其他操作;
如果右子樹不為空,對應雙向有序鏈表的最後一個節點就是右子樹轉換後雙向有序鏈表的最後一個節點,同時將根節點和右子樹轉換後的雙向有序鏈表的第一個節點連 接。
參考代碼如下:
/******************************************************************************
參數:
pRoot: 二叉查找樹根節點指針
pFirstNode: 轉換後雙向有序鏈表的第一個節點指針
pLastNode: 轉換後雙向有序鏈表的最後一個節點指針
******************************************************************************/
void Convert(BinaryTreeNode * pRoot,
BinaryTreeNode * & pFirstNode, BinaryTreeNode * & pLastNode)
{
BinaryTreeNode *pFirstLeft, *pLastLeft, * pFirstRight, *pLastRight;
if(pRoot == NULL)
{
pFirstNode = NULL;
pLastNode = NULL;
return;
}
if(pRoot->m_pLeft == NULL)
{
// 如果左子樹為空,對應雙向有序鏈表的第一個節點是根節點
pFirstNode = pRoot;
}
else
{
Convert(pRoot->m_pLeft, pFirstLeft, pLastLeft);
// 二叉查找樹對應雙向有序鏈表的第一個節點就是左子樹轉換後雙向有序鏈表的第一個節點
pFirstNode = pFirstLeft;
// 將根節點和左子樹轉換後的雙向有序鏈表的最後一個節點連接
pRoot->m_pLeft = pLastLeft;
pLastLeft->m_pRight = pRoot;
}
if(pRoot->m_pRight == NULL)
{
// 對應雙向有序鏈表的最後一個節點是根節點
pLastNode = pRoot;
}
else
{
Convert(pRoot->m_pRight, pFirstRight, pLastRight);
// 對應雙向有序鏈表的最後一個節點就是右子樹轉換後雙向有序鏈表的最後一個節點
pLastNode = pLastRight;
// 將根節點和右子樹轉換後的雙向有序鏈表的第一個節點連接
pRoot->m_pRight = pFirstRight;
pFirstRight->m_pLeft = pRoot;
}
return;
}
6. 求二叉樹第K層的節點個數
遞歸解法:
(1)如果二叉樹為空或者k<1返回0
(2)如果二叉樹不為空並且k==1,返回1
(3)如果二叉樹不為空且k>1,返回左子樹中k-1層的節點個數與右子樹k-1層節點個數之和
參考代碼如下:
int GetNodeNumKthLevel(BinaryTreeNode * pRoot, int k)
{
if(pRoot == NULL || k < 1)
return 0;
if(k == 1)
return 1;
int numLeft = GetNodeNumKthLevel(pRoot->m_pLeft, k-1); // 左子樹中k-1層的節點個數
int numRight = GetNodeNumKthLevel(pRoot->m_pRight, k-1); // 右子樹中k-1層的節點個數
return (numLeft + numRight);
}
7. 求二叉樹中葉子節點的個數
遞歸解法:
(1)如果二叉樹為空,返回0
(2)如果二叉樹不為空且左右子樹為空,返回1
(3)如果二叉樹不為空,且左右子樹不同時為空,返回左子樹中葉子節點個數加上右子樹中葉子節點個數
參考代碼如下:
int GetLeafNodeNum(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return 0;
if(pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL)
return 1;
int numLeft = GetLeafNodeNum(pRoot->m_pLeft); // 左子樹中葉節點的個數
int numRight = GetLeafNodeNum(pRoot->m_pRight); // 右子樹中葉節點的個數
return (numLeft + numRight);
}
8. 判斷兩棵二叉樹是否結構相同
不考慮數據內容。結構相同意味著對應的左子樹和對應的右子樹都結構相同。
遞歸解法:
(1)如果兩棵二叉樹都為空,返回真
(2)如果兩棵二叉樹一棵為空,另一棵不為空,返回假
(3)如果兩棵二叉樹都不為空,如果對應的左子樹和右子樹都同構返回真,其他返回假
參考代碼如下:
bool StructureCmp(BinaryTreeNode * pRoot1, BinaryTreeNode * pRoot2)
{
if(pRoot1 == NULL && pRoot2 == NULL) // 都為空,返回真
return true;
else if(pRoot1 == NULL || pRoot2 == NULL) // 有一個為空,一個不為空,返回假
return false;
bool resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft); // 比較對應左子樹
bool resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight); // 比較對應右子樹
return (resultLeft && resultRight);
}
9. 判斷二叉樹是不是平衡二叉樹
遞歸解法:
(1)如果二叉樹為空,返回真
(2)如果二叉樹不為空,如果左子樹和右子樹都是AVL樹並且左子樹和右子樹高度相差不大於1,返回真,其他返回假
參考代碼:
bool IsAVL(BinaryTreeNode * pRoot, int & height)
{
if(pRoot == NULL) // 空樹,返回真
{
height = 0;
return true;
}
int heightLeft;
bool resultLeft = IsAVL(pRoot->m_pLeft, heightLeft);
int heightRight;
bool resultRight = IsAVL(pRoot->m_pRight, heightRight);
if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) // 左子樹和右子樹都是AVL,並且高度相差不大於1,返回真
{
height = max(heightLeft, heightRight) + 1;
return true;
}
else
{
height = max(heightLeft, heightRight) + 1;
return false;
}
}
10. 求二叉樹的鏡像
遞歸解法:
(1)如果二叉樹為空,返回空
(2)如果二叉樹不為空,求左子樹和右子樹的鏡像,然後交換左子樹和右子樹
參考代碼如下:
BinaryTreeNode * Mirror(BinaryTreeNode * pRoot)
{
if(pRoot == NULL) // 返回NULL
return NULL;
BinaryTreeNode * pLeft = Mirror(pRoot->m_pLeft); // 求左子樹鏡像
BinaryTreeNode * pRight = Mirror(pRoot->m_pRight); // 求右子樹鏡像
// 交換左子樹和右子樹
pRoot->m_pLeft = pRight;
pRoot->m_pRight = pLeft;
return pRoot;
}
11. 求二叉樹中兩個節點的最低公共祖先節點
遞歸解法:
(1)如果兩個節點分別在根節點的左子樹和右子樹,則返回根節點
(2)如果兩個節點都在左子樹,則遞歸處理左子樹;如果兩個節點都在右子樹,則遞歸處理右子樹
參考代碼如下:
bool FindNode(BinaryTreeNode * pRoot, BinaryTreeNode * pNode)
{
if(pRoot == NULL || pNode == NULL)
return false;
if(pRoot == pNode)
return true;
bool found = FindNode(pRoot->m_pLeft, pNode);
if(!found)
found = FindNode(pRoot->m_pRight, pNode);
return found;
}
BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot,
BinaryTreeNode * pNode1,
BinaryTreeNode * pNode2)
{
if(FindNode(pRoot->m_pLeft, pNode1))
{
if(FindNode(pRoot->m_pRight, pNode2))
return pRoot;
else
return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2);
}
else
{
if(FindNode(pRoot->m_pLeft, pNode2))
return pRoot;
else
return GetLastCommonParent(pRoot->m_pRight, pNode1, pNode2);
}
}
遞歸解法效率很低,有很多重復的遍歷,下面看一下非遞歸解法。
非遞歸解法:
先求從根節點到兩個節點的路徑,然後再比較對應路徑的節點就行,最後一個相同的節點也就是他們在二叉樹中的最低公共祖先節點
參考代碼如下:
bool GetNodePath(BinaryTreeNode * pRoot, BinaryTreeNode * pNode,
list<BinaryTreeNode *> & path)
{
if(pRoot == pNode)
{
path.push_back(pRoot);
return true;
}
if(pRoot == NULL)
return false;
path.push_back(pRoot);
bool found = false;
found = GetNodePath(pRoot->m_pLeft, pNode, path);
if(!found)
found = GetNodePath(pRoot->m_pRight, pNode, path);
if(!found)
path.pop_back();
return found;
}
BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, BinaryTreeNode * pNode1, BinaryTreeNode * pNode2)
{
if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
return NULL;
list<BinaryTreeNode*> path1;
bool bResult1 = GetNodePath(pRoot, pNode1, path1);
list<BinaryTreeNode*> path2;
bool bResult2 = GetNodePath(pRoot, pNode2, path2);
if(!bResult1 || !bResult2)
return NULL;
BinaryTreeNode * pLast = NULL;
list<BinaryTreeNode*>::const_iterator iter1 = path1.begin();
list<BinaryTreeNode*>::const_iterator iter2 = path2.begin();
while(iter1 != path1.end() && iter2 != path2.end())
{
if(*iter1 == *iter2)
pLast = *iter1;
else
break;
iter1++;
iter2++;
}
return pLast;
}
在上述算法的基礎上稍加變化即可求二叉樹中任意兩個節點的距離了。
12. 求二叉樹中節點的最大距離
即二叉樹中相距最遠的兩個節點之間的距離。
遞歸解法:
(1)如果二叉樹為空,返回0,同時記錄左子樹和右子樹的深度,都為0
(2)如果二叉樹不為空,最大距離要麽是左子樹中的最大距離,要麽是右子樹中的最大距離,要麽是左子樹節點中到根節點的最大距離+右子樹節點中到根節點的最大距離,同時記錄左子樹和右子樹節點中到根節點的最大距離。
參考代碼如下:
int GetMaxDistance(BinaryTreeNode * pRoot, int & maxLeft, int & maxRight)
{
// maxLeft, 左子樹中的節點距離根節點的最遠距離
// maxRight, 右子樹中的節點距離根節點的最遠距離
if(pRoot == NULL)
{
maxLeft = 0;
maxRight = 0;
return 0;
}
int maxLL, maxLR, maxRL, maxRR;
int maxDistLeft, maxDistRight;
if(pRoot->m_pLeft != NULL)
{
maxDistLeft = GetMaxDistance(pRoot->m_pLeft, maxLL, maxLR);
maxLeft = max(maxLL, maxLR) + 1;
}
else
{
maxDistLeft = 0;
maxLeft = 0;
}
if(pRoot->m_pRight != NULL)
{
maxDistRight = GetMaxDistance(pRoot->m_pRight, maxRL, maxRR);
maxRight = max(maxRL, maxRR) + 1;
}
else
{
maxDistRight = 0;
maxRight = 0;
}
return max(max(maxDistLeft, maxDistRight), maxLeft+maxRight);
}
13. 由前序遍歷序列和中序遍歷序列重建二叉樹
二叉樹前序遍歷序列中,第一個元素總是樹的根節點的值。中序遍歷序列中,左子樹的節點的值位於根節點的值的左邊,右子樹的節點的值位
於根節點的值的右邊。
遞歸解法:
(1)如果前序遍歷為空或中序遍歷為空或節點個數小於等於0,返回NULL。
(2)創建根節點。前序遍歷的第一個數據就是根節點的數據,在中序遍歷中找到根節點的位置,可分別得知左子樹和右子樹的前序和中序遍
歷序列,重建左右子樹。
-
BinaryTreeNode * RebuildBinaryTree(int* pPreOrder, int* pInOrder, int nodeNum) { if(pPreOrder == NULL || pInOrder == NULL || nodeNum <= 0) return NULL; BinaryTreeNode * pRoot = new BinaryTreeNode; // 前序遍歷的第一個數據就是根節點數據 pRoot->m_nValue = pPreOrder[0]; pRoot->m_pLeft = NULL; pRoot->m_pRight = NULL; // 查找根節點在中序遍歷中的位置,中序遍歷中,根節點左邊為左子樹,右邊為右子樹 int rootPositionInOrder = -1; for(int i = 0; i < nodeNum; i++) if(pInOrder[i] == pRoot->m_nValue) { rootPositionInOrder = i; break; } if(rootPositionInOrder == -1) { throw std::exception("Invalid input."); } // 重建左子樹 int nodeNumLeft = rootPositionInOrder; int * pPreOrderLeft = pPreOrder + 1; int * pInOrderLeft = pInOrder; pRoot->m_pLeft = RebuildBinaryTree(pPreOrderLeft, pInOrderLeft, nodeNumLeft); // 重建右子樹 int nodeNumRight = nodeNum - nodeNumLeft - 1; int * pPreOrderRight = pPreOrder + 1 + nodeNumLeft; int * pInOrderRight = pInOrder + nodeNumLeft + 1; pRoot->m_pRight = RebuildBinaryTree(pPreOrderRight, pInOrderRight, nodeNumRight); return pRoot; }
同樣,有中序遍歷序列和後序遍歷序列,類似的方法可重建二叉樹,但前序遍歷序列和後序遍歷序列不同恢復一棵二叉樹,證明略。
14.判斷二叉樹是不是完全二叉樹
若設二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層所有的結點都連續集中在最左邊,這就是完全
二叉樹。
有如下算法,按層次(從上到下,從左到右)遍歷二叉樹,當遇到一個節點的左子樹為空時,則該節點右子樹必須為空,且後面遍歷的節點左
右子樹都必須為空,否則不是完全二叉樹。
bool IsCompleteBinaryTree(BinaryTreeNode * pRoot)
{
if(pRoot == NULL)
return false;
queue<BinaryTreeNode *> q;
q.push(pRoot);
bool mustHaveNoChild = false;
bool result = true;
while(!q.empty())
{
BinaryTreeNode * pNode = q.front();
q.pop();
if(mustHaveNoChild) // 已經出現了有空子樹的節點了,後面出現的必須為葉節點(左右子樹都為空)
{
if(pNode->m_pLeft != NULL || pNode->m_pRight != NULL)
{
result = false;
break;
}
}
else
{
if(pNode->m_pLeft != NULL && pNode->m_pRight != NULL)
{
q.push(pNode->m_pLeft);
q.push(pNode->m_pRight);
}
else if(pNode->m_pLeft != NULL && pNode->m_pRight == NULL)
{
mustHaveNoChild = true;
q.push(pNode->m_pLeft);
}
else if(pNode->m_pLeft == NULL && pNode->m_pRight != NULL)
{
result = false;
break;
}
else
{
mustHaveNoChild = true;
}
}
}
return result;
}
php數據結構之二叉樹