二叉樹翻轉(遞歸+非遞歸)

前言

二叉樹翻轉是一道經典的面試編程題,經常出現在各大公司的招聘筆試面試環節。

這裏還有個趣事,Homebrew 的作者 Max Howell 某天去 Google 面試,面試官出了一道反轉二叉樹的題目,然而 Max Howell 沒答上來,結果被拒。面試官的評語是:“我們 90% 的工程師使用您編寫的軟件,但是您卻無法在面試時在白板上寫出翻轉二叉樹這道題,所以滾蛋吧”。

可見,在求職面試過程中,即使你是一位優秀的程序員,如果答不上算法題,那麼在算法方面的能力將被面試官認爲是不及格的,甚至無法被聘用。

問題描述

給定一個二叉樹,輸出其鏡像。

Input:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

Output:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

遞歸實現

  • 問題分析

翻轉一個二叉樹,直觀上看,就是把二叉樹的每一層左右順序倒過來。比如問題中的例子,第三層 1-3-6-9 經過變換後變成了9-6-3-1,順序反過來就對了。

再仔細觀察一下,對於上面的例子,根結點的左子結點及其所有的子孫結點構成根節點的左子樹,同樣的,根結點的右子結點及其所有的子孫節點構成根結點的右子樹。因此翻轉一個二叉樹,就是把根結點的左子樹翻轉一下,同樣的把右子樹翻轉一下,在交換左右子樹就可以了。

當然,翻轉左子樹和右子樹的過程和當前翻轉二叉樹的過程沒有區別,就是遞歸的調用當前的函數就可以了。

因此,翻轉二叉樹的步驟可總結如下:
(1)交換根結點的左子結點與右子結點;
(2)翻轉根結點的左子樹(遞歸調用當前函數);
(3)翻轉根結點的右子樹(遞歸調用當前函數)。

  • 具體實現
// 二叉樹結點結構體
struct BinaryTreeNode
{
	int m_key;
	BinaryTreeNode* m_pLeft;
	BinaryTreeNode* m_pRight;

	BinaryTreeNode() {
		m_key = 0;
		m_pLeft = m_pRight = nullptr;
	}
};

// @brief: 翻轉二叉樹
// @param: root 二叉樹根結點
// @ret: 翻轉後的二叉樹根結點
BinaryTreeNode* invertBT(BinaryTreeNode* root) {
	if (root == nullptr) return root;

	// 交換左右孩子結點
	auto tmp = root->m_pLeft;
	root->m_pLeft = root->m_pRight;
	root->m_pRight = tmp;

	// 遞歸處理左右子樹
	invertBT(root->m_pLeft);
	invertBT(root->m_pRight);
	return root;
}
  • 驗證實現

驗證代碼涉及二叉樹的創建和遍歷,驗證如下:

// @brief: 前序遞歸遍歷
void preorderRecursion(BinaryTreeNode* root) {
	if (root == nullptr) return;
	cout << " " << root->m_key;
	preorderRecursion(root->m_pLeft);
	preorderRecursion(root->m_pRight);
}

// @brief: 根據前序序列和中序序列構建二叉樹
// @param: preOrder:前序序列; midOrder:中序序列; len:結點數
// @ret: 二叉樹根結點
BinaryTreeNode* constructPreMid(int* preOrder, int* midOrder, int len) {
	if (preOrder == nullptr || midOrder == nullptr || len <= 0) return nullptr;

	// 前序遍歷的第一個值就是根節點
	int rootKey = preOrder[0];
	BinaryTreeNode* root = new BinaryTreeNode;
	root->m_key = rootKey;

	// 只有一個結點
	if (len == 1 && *preOrder == *midOrder) return root;

	// 在中序序列中找到根結點
	int* rootMidOrder = midOrder;
	// 左子樹結點數
	int leftLen = 0;
	while (*rootMidOrder != rootKey && rootMidOrder <= (midOrder + len - 1)) {
		++rootMidOrder;
		++leftLen;
	}

	// 在中序序列未找到根結點,輸入錯誤
	if (*rootMidOrder != rootKey) return nullptr;

	// 構建左子樹	
	if (leftLen>0) {
		root->m_pLeft = constructPreMid(preOrder + 1, midOrder, leftLen);
	}

	// 構建右子樹
	if (len - leftLen - 1>0) {
		root->m_pRight = constructPreMid(preOrder + leftLen + 1, rootMidOrder + 1, len - leftLen - 1);
	}
	return root;
}

int main() {
	// 前序+中序構建二叉樹
	int preorder[] = {4,2,1,3,7,6,9};
	int midorder[] = {1,2,3,4,6,7,9};
	auto root = constructPreMid(preorder, midorder, 7);
	preorderRecursion(root);
	cout << endl;
	
	// 翻轉二叉樹
	auto invertRoot = invertBT(root);
	cout << "--- after invert ---" << endl;
	preorderRecursion(invertRoot);	// 4,7,9,6,2,3,1
}

運行輸出:

4 2 1 3 7 6 9
--- after invert ---
4 7 9 6 2 3 1

非遞歸實現

  • 問題分析

二叉樹反轉,實際上是遍歷二叉樹的每一個結點,對其左右結點進行交換。那麼我們可以採用層序遍歷,使用隊列來存放待遍歷的結點。

具體步驟如下:
(1)首先把二叉樹的根結點送入隊列;
(2)訪問隊首結點,把它的左子結點和右子結點分別入隊列,然後交換其左右子結點,最後隊首結點出隊列;
(3)重複上面兩步操作,直至隊列空。

  • 具體實現
// @brief: 非遞歸翻轉二叉樹
// @param: 二叉樹根結點
// @ret: 翻轉後的二叉樹根結點
BinaryTreeNode* invertBTNonrecu(BinaryTreeNode* root) {
	if (root == nullptr) return root;
	queue<BinaryTreeNode*> queue;
	queue.push(root);
	while (!queue.empty()){
		// 取隊首結點
		BinaryTreeNode* cur = queue.front();
		
		// 左右子結點入隊列
		if (cur->m_pLeft != nullptr) queue.push(cur->m_pLeft);
		if (cur->m_pRight != nullptr) queue.push(cur->m_pRight);
		
		// 交換左右子結點
		auto tmp = cur->m_pLeft;
		cur->m_pLeft = cur->m_pRight;
		cur->m_pRight = tmp;

		// 隊首結點出隊列
		queue.pop();
	}
	return root;
}
  • 驗證實現
int main(){
	// 前序+中序構建二叉樹
	int preorder[] = {4,2,1,3,7,6,9};
	int midorder[] = {1,2,3,4,6,7,9};
	BinaryTreeNode* root = constructPreMid(preorder, midorder, 7);
	preorderRecursion(root);
	cout << endl;
	
	// 非遞歸翻轉二叉樹
	cout << "--- after non-recursive invert ---" << endl;
	auto invertRoot = invertBTNonrecu(root);
	preorderRecursion(invertRoot);	// 4,7,9,6,2,3,1
}

運行輸出:

4 2 1 3 7 6 9
--- after non-recursive invert ---
 4 7 9 6 2 3 1

參考文獻

[1] LeetCode.Invert Binary Tree
[2] 簡書.明星程序員被Google掛掉的故事

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章