【資料結構】二叉樹面試題總結
為了對二叉樹的知識進行鞏固,今天我們來解析5道二叉樹的經典面試題。
這五道面試題如下:
- 求二叉樹中最遠兩個結點的距離;
- 判斷一棵樹是否是完全二叉樹;
- 由前序和中序遍歷序列重建二叉樹 (前序序列:1 2 3 4 5 6 - 中序序列:3 2 4 1 6 5);
- 求二叉樹兩個結點的最近公共祖先;
將二叉搜尋樹轉化成有序的雙向連結串列;
現在我們來對這五道題進行分析:
求二叉樹中最遠的兩個節點之間的距離
這道題如果沒有進行全面的分析,都以為只有一下一種情況:
這時,我們只需要分別求出左右子樹的高度然後相加再加1(根節點),就能得到最遠的兩個節點之間的距離了。
經過仔細分析,我們發現還有以下這種情況:
時間負雜度為O(N*N)的解法
當二叉樹的根節點只有一顆子樹時,這時候二叉樹最遠節點的距離應該產生在子樹的左右子樹高度或這棵樹的高度再到根節點之間的距離兩者之間的,所以我們要求一顆樹最遠的兩個節點之間的距離,就需要求出這棵樹根節點的子樹的高度加上子樹根節點到這棵樹根節點的距離和子樹中最遠的兩個節點之間的距離,兩者的最大值就是這棵樹最遠的兩個節點之間的距離了。所以我們要求解一棵樹最遠的兩個節點之間的距離,就要去求子樹的高度和子樹的最遠的兩個節點之間的距離,直到我們求到子樹的根節點為NULL為止。
上面這種解法運用到了二叉樹先序遍歷的思想,先求根節點的最遠兩個節點之間的距離,再去求左子樹和右子樹最遠兩個節點之間的距離。下面我們來看一種時間複雜度為O(N)的解法
時間複雜度為O(N)的解法
這種解法就需要用到二叉樹後序遍歷的思想了,我們碰到樹的根節點先不去求當前根節點最遠的兩個節點之間的距離,轉而去求它左右子樹最遠的兩個節點之間的距離和左右子樹的高度,最後再來求當前根節點最遠的兩個節點之間的距離。
我們直接給出最優解法的程式碼:
//時間複雜度為O(N^2)的演算法
size_t DepthOfNode(BinaryTreeNode<int>* node)
{
if (node == NULL)
{
return 0;
}
//分別求左右子樹
size_t Left = DepthOfNode(node-> _Left) + 1;
size_t Right = DepthOfNode(node->_Right) + 1;
/*cout << "Left:"<<Left << endl;
cout << "Right:" << Right << endl;*/
return Left > Right ? Left : Right;
}
void _DisTanceOfNode(Node* root, size_t& LeftHigh, size_t RightHigh, size_t& Max);
size_t DisTanceOfNode(BinaryTree<int>& tree)
{
size_t LeftHight = 0;
size_t RightHight = 0;
size_t Max = 0;
_DisTanceOfNode(tree.ReturnRoot(), LeftHight, RightHight,Max);
return Max;
}
時間複雜度為O(N*N)的演算法
void _DisTanceOfNode(Node* root, size_t& LeftHigh, size_t RightHigh,size_t& Max)
{
if (root == NULL)
return;
if (root->_Left == NULL)
LeftHigh = 0;
if (root->_Right == NULL)
RightHigh = 0;
if (root->_Left != NULL)
_DisTanceOfNode(root->_Left, LeftHigh, RightHigh, Max);
if (root->_Right != NULL)
_DisTanceOfNode(root->_Right, LeftHigh, RightHigh, Max);
LeftHigh = DepthOfNode(root->_Left);
RightHigh = DepthOfNode(root->_Right);
//更新當前根節點的最遠兩個節點之間的距離
if (LeftHigh + RightHigh + 1 > Max)
Max = LeftHigh + RightHigh + 1;
}
void _DisTanceOfNode(Node* root, size_t& LeftHigh, size_t RightHigh, size_t& Max)
{
if (root == NULL)
{
return;
}
_DisTanceOfNode(root->_Left, LeftHigh, RightHigh, Max); //遞迴求左子樹最遠兩個節點之間的距離
_DisTanceOfNode(root->_Right, LeftHigh,RightHigh, Max); //求右子樹
LeftHigh = DepthOfNode(root->_Left);
RightHigh = DepthOfNode(root->_Right);
if (LeftHigh + RightHigh + 1 > Max)
Max = LeftHigh + RightHigh + 1;
}
void Test1()
{
int b[] = { 1, 2, 3, 5, '#', '#', '#', 4, '#', 6, '#', '#', '#' };
int a[15] = { 1, 2, '#', 3, '#', '#', 4, 5, '#', 6, '#', 7, '#', '#', 8 };
BinaryTree<int> t1(b, sizeof(b) / sizeof(b[0]), '#');
t1.InOrder();
t1.LevelOrder();
cout << "樹中最遠的兩個節點之間的距離是?" << DisTanceOfNode(t1) << endl;
}
重建二叉樹
題目中給出了先序遍歷和中序遍歷,讓根據遍歷二叉樹的序列來對這顆二叉樹進行構建,要重建這顆二叉樹我們就得要要建立二叉樹的根節點,然後再分別構建這顆二叉樹的左右子樹,二叉樹的根節點元素就是前序序列的第一個元素,這樣我們根節點就能構建完成,接下來我們在中序遍歷中找到根節點元素,這個元素之前的序列就為左子樹的中序遍歷,之後的序列就為右子樹的中序遍歷,接下來我們去遞迴分別建立左右子樹就可以了
struct BinaryTreeNode
{
int _value;
BinaryTreeNode* _Left;
BinaryTreeNode* _Right;
BinaryTreeNode(const int& x)
:_value(x)
, _Left(NULL)
, _Right(NULL)
{}
};
typedef BinaryTreeNode Node;
Node* CreateBinaryTree(vector<int>& front, vector<int>& mid,int size)
{
assert(front.size() == mid.size());
if (size <= 0||front.size()==0||mid.size()==0) //遞迴終止條件
return NULL;
//先建立根節點
Node* root = new Node(front[0]); //前序遍歷的第一個節點為根節點的元素
//在中序遍歷中找到根節點元素的所在位置
int index = 0;
for (index = 0; index < size; index++)
{
if (front[0] == mid[index])
break;
}
if (index == size)
{
cout << "Invaid Input!!!建立失敗" << endl;
exit(0);
}
//構建子序列
vector<int> b; //中序
vector<int> a; //先序遍歷
for (int i = 0; i < index; i++)
{
b.push_back(mid[i]);
a.push_back(front[i+1]);
}
//遞迴建立左子樹
root->_Left = CreateBinaryTree(a, b, a.size());
a.clear();
b.clear();
for (int i =index+1; i < size; i++)
{
b.push_back(mid[i]);
a.push_back(front[i]);
}
//遞迴建立右子樹
root->_Right = CreateBinaryTree(a, b, a.size());
a.clear();
b.clear();
return root;
}
判斷一棵樹是否是完全二叉樹
什麼是完全二叉樹呢? 如下:
若設二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層所有的結點都連續集中在最左邊,這就是完全二叉樹。
那麼我們怎麼判斷一棵樹是否是完全二叉樹呢,我們可以用一個佇列,通過層序遍歷的方式把二叉樹所有的節點都先入隊(包含空節點),當我們出隊的時候如果pop出來了空節點如果佇列中剩餘的所有節點都為NULL,那麼這棵樹就是完全二叉樹,如果佇列中還有非NULL節點,那麼這棵樹就不是完全二叉樹。
bool _LevelOrder(BinaryTreeNode<int>* root)
{
queue<Node*> q;
q.push(root);
while ((q.front())!=NULL)
{
q.push(q.front()->_Left);
q.push(q.front()->_Right);
q.pop();
}
while (q.size() != 0)
{
if (q.front() == NULL)
q.pop();
else
return false;
}
return true;
}
bool Is_Compelete(const BinaryTree<int>& t)
{
if (t.ReturnRoot() == NULL)
return true; //如果一棵樹為空樹,則這個樹可以看成完全二叉樹
return _LevelOrder(t.ReturnRoot());
}
void Test3()
{
//int a[15] = { 1, 2, '#', 3, '#', '#', 4, 5, '#', 6, '#', 7, '#', '#', 8 };
int a[] = { 1, 2, 3, '#', '#', '#', '#', 5, '#', '#' };
BinaryTree<int> t(a, sizeof(a) / sizeof(a[0]), '#');
cout<<"是否是完全二叉樹:"<<Is_Compelete(t)<<endl;
}
求兩個節點的最近公共祖先
求二叉樹的任意兩個節點的最近公共祖先問題,包含以下兩種情況
1:這兩個節點都不是最近公共祖先。
2:這兩個節點有一個是公共祖先。
方法一
我們可以藉助STL容器來實現這個問題,比方說一個順序表,我們從根節點開始,把從根節點到要查詢的節點之間的節點都pusn_back到vector中,這樣做等於說我們拿到了從根節點到需要求公共祖先的兩個節點的兩條路徑,我找到這兩條路徑中最後一個相等的節點,這個節點就是這兩個節點的最近公共祖先。
bool FindPath(BinaryTreeNode<int>* root, vector<int>& path,const int& value)
{
/*尋找到達某一個節點的路勁找到了返回true並儲存路勁*/
if (root == NULL)
{
return false; //樹為NULL則路勁不存在
}
path.push_back(root->_Data);
if (root->_Data == value)
return true;
/*在左右子樹中查詢*/
bool IfFind = FindPath(root->_Left, path, value) || FindPath(root->_Right, path, value);
if (IfFind)
return true;
path.pop_back(); //如果在該節點下沒有找到就刪除路勁當中的節點
return false;
}
Node* FindLCA(BinaryTree<int>& t,const int& n1,const int& n2)
{
vector<int> Findn1Path;
vector<int> Findn2Path;
if (FindPath(t.ReturnRoot(), Findn1Path, n1) && FindPath(t.ReturnRoot(), Findn2Path, n2))
{
int index = -1; //記錄最後一個相等鍵值的位置
int size = Findn1Path.size() > Findn2Path.size() ? Findn2Path.size() : Findn1Path.size();
for (int i = 0; i < size; i++)
{
if (Findn1Path[i] != Findn2Path[i])
break; //找到第一個不相等的鍵值
index = i;
}
if (index == -1)
return NULL; //index=0 表示沒有相等的鍵值
else
return new Node(Findn1Path[index]);
}
cout << "兩個鍵值中有一個鍵值在二叉樹中沒有節點" << endl;
return NULL;
}
void Test4()
{
int a[15] = { 1, 2, '#', 3, '#', '#', 4, 5, '#', 6, '#', 7, '#', '#', 8 };
BinaryTree<int> t(a, sizeof(a) / sizeof(a[0]), '#');
cout<<FindLCA(t, 3, 5)->_Data<<endl;
cout << FindLCA(t, 7, 8)->_Data << endl;
}
方法二:不需要容器的解法
Node* _GetAncestor(Node* root, Node* node1, Node* node2)
{
if (root == NULL)
return NULL;
Node* Left = _GetAncestor(root->_Left, node1, node2);
Node* Right = _GetAncestor(root->_Right, node1, node2);
if (Left&&Right) //如果Left和Right都不為NULL 則root節點為他們的最近祖先
return root;
if (root == node2)
return node2;
if (root == node1)
return node1;
if ((Right == NULL) && Left)
return Left;
if ((Left == NULL) && Right)
return Right;
return NULL;
}
Node* GetAncestor(BinaryTree<int>& tree, Node* node1, Node* node2)
{
//assert(node1);
//assert(node2);
Node* ancestor = _GetAncestor(tree.ReturnRoot(), node1, node2);
return ancestor;
}
void Test2()
{
int b[] = { 1, 2, 3, 5, '#', '#', '#', 4, '#', 6, '#', '#', '#' };
int a[15] = { 1, 2, '#', 3, '#', '#', 4, 5, '#', 6, '#', 7, '#', '#', 8 };
BinaryTree<int> t1(b, sizeof(b) / sizeof(b[0]), '#');
t1.InOrder();
t1.LevelOrder();
cout << "最近的祖先是?" << GetAncestor(t1,t1.Find(5),t1.Find(7))->_Data<< endl;
}
將一顆二叉搜尋樹轉化為雙向連結串列
void _TreeToList(Node* cur, Node*& prev)
{
if (cur == NULL)
return;
_TreeToList(cur->_left, prev);
cur->_left = prev;
if (prev != NULL)
prev->_right = cur;
prev = cur;
_TreeToList(cur->_right, prev);
}
Node* TreeToList(BinarySearchTree<int>& tree)
{
Node* Head = tree.ReturnRoot();
while (Head->_left)
{
Head = Head->_left; //二叉搜尋樹的最左節點就是單鏈表的第一個節點
}
Node* Prev = NULL;
Node* cur = tree.ReturnRoot();
_TreeToList(cur, Prev);
return Head;
}
void OrDerList(Node* root)
{
assert(root);
while (root)
{
cout << root->_key << " ";
root = root->_right;
}
cout << endl;
}
void Test5()
{
BinarySearchTree<int> t;
t.Insert(5);
t.Insert(4);
t.Insert(7);
t.Insert(3);
t.Insert(6);
t.Insert(9);
t.Insert(2);
t.Insert(0);
t.Insert(1);
t.Insert(8);
t.InorderR();
Node* head=TreeToList(t);
OrDerList(head);
}