OJ 資料結構:樹——建樹
阿新 • • 發佈:2019-02-03
根據題目要求不同,建一顆樹有以下幾種方式
按給定序列依次插入節點建樹
適用BST按非葉節點的孩子節點資訊建樹
適用一般樹按中序遍歷序列+其他一種遍歷序列建樹
適用於二叉樹
給定序列插入建數
適用BST
實現
//這種情況適合使用 雙鏈表 結構
//因此結頂結構定義為
struct Node
{
int data;
shared_ptr<Node> left_child, right_child;
Node(int _d = -1):
data(_d),
left_child(nullptr),
right_child(nullptr )
{ }
};
//建樹的過程就是不斷插入節點的過程
//插入節點操作如下
void Insert(shared_ptr<Node> &root, int data)
{
if (root == nullptr)
{
root = make_shared<Node>(data);
return;
}
else if (data >= root->data)
{
Insert(root->right_child, data);
}
else
{
Insert(root->left_child, data );
}
}
shared_ptr<Node> Create( vector<int>::iterator b, vector<int>::iterator e)
{
shared_ptr<Node> root;
for (auto it = b; it != e; it++)
{
Insert(root, *it);
}
return root;
}
根據孩子節點資訊建樹
適用所有樹
這種情況一般用下標來索引節點,適合用 靜態儲存結構 來建樹,也就是用陣列
如 PAT A1053
實現
//使用靜態儲存
//用一個vector儲存孩子節點的下標
struct Node
{
int weight;
vector<int> child;
}Tree[maxn];
//N是總節點的個數
//M是非葉節點的個數
cin >> N >> M >> S;
//讀入每個點的權值資訊
for (size_t i = 0; i < N; i++)
{
cin >> Tree[i].weight;
}
//讀入每個非葉節點的孩子節點資訊
while (M--)
{
int index, child_count;
cin >> index >> child_count;
for (size_t i = 0; i < child_count; i++)
{
int child_index;
cin >> child_index;
Tree[index].child.push_back(child_index);
}
}
根據中序+一種其他遍歷序列建樹
適用二叉樹
根據中序遍歷(inorder)的序列以及 前序(preorder)、後續(postorder)、層序(sequence order) 三者中的任一種可以建立一顆二叉樹
中序+前序
演算法思想
- 對於前序序列,第一個總是根節點,而中序序列中根節點在序列中間,將序列劃分為左右兩邊,左邊是左子樹的節點,右邊是右子樹節點,由此可以知道左右子樹的節點數
- 前序序列的剩餘序列中,前面部分是左子樹的節點,後面部分是右子樹的節點
- 不斷遞迴至序列長度為0
實現
//樹節點定義,用雙鏈表
struct Node
{
int data;
shared_ptr<Node> left_child;
shared_ptr<Node> right_child;
Node(int _data = -1) :
data(_data),
left_child(nullptr),
right_child(nullptr)
{ }
};
//####根據前序和中序序列建樹###
shared_ptr<Node> Create(int preL, int preR, int inL, int inR)
{
//序列長度為0,退出遞迴
if (preL > preR)
{
return nullptr;
}
//前序序列的第一位是根節點
int root_index = preL;
shared_ptr<Node> root(new Node(preOr[root_index]));
//在中序序列中找到根節點的位置
int k = inL;
for (; k <= inR; k++)
{
if (inOr[k] == postOr[root_index])
{
break;
}
}
//根據中序中根節點的位置劃分左右子樹的大小
int num_leftTree = k - inL;
int num_rightTree = inR - k;
//向左右子樹遞迴
//【遞迴的引數容易搞錯,建議畫一個簡易樹和序列對照看】
root->left_child = Create(preL+1 , preL+num_leftTree, inL, inL + num_leftTree - 1);
root->right_child = Create(preR - num_rightTree , postR-num_rightTree+1, inR - num_rightTree + 1, inR);
return root;
}
中序+後續
演算法思想
和前序+中序建樹一致,不同在於後續序列的最後一個節點是根節點
實現
//####根據後續和中序序列建樹###
shared_ptr<Node> Create(int postL, int postR, int inL, int inR)
{
//序列長度為0,退出遞迴
if (postL > postR)
{
return nullptr;
}
//後續序列的最後一位是根節點
int root_index = postR;
shared_ptr<Node> root(new Node(postOr[root_index]));
//在中序序列中找到根節點的位置
int k = inL;
for (; k <= inR; k++)
{
if (inOr[k] == postOr[root_index])
{
break;
}
}
//根據中序中根節點的位置劃分左右子樹的大小
int num_leftTree = k - inL;
int num_rightTree = inR - k;
//向左右子樹遞迴
root->left_child = Create(postL , postL + num_leftTree-1 , inL, inL + num_leftTree - 1);
root->right_child = Create(postR - num_rightTree , postR-1, inR - num_rightTree + 1, inR);
return root;
}
中序+層序
我們都知道中序+層序可以建一顆二叉樹,但演算法書一般跳過這裡不講,題目中也不常見到,但原理還是相似的,實現起來也很簡單。
演算法思想
對照這個圖講一下演算法思想
- 中序的作用仍然是劃分左右子樹
依次將層序序列插入一個空樹
這並不是一個BST,插入時如何知道插入的位置呢?
答案是根據中序序列來確定
模擬這個過程:1.建立一個空樹 root
2.將層序第一個A插入root,此時root是空,直接插入
3.插入第二個 B,root不為空
4.在中序中搜索B,發現B在其父節點(A)的左側
5.將B插入A的左子樹
6.直到將層序序列中所有節點插入完畢
實現
//插入函式
void Insert(shared_ptr<Node> &root, char data)
{
if (root == nullptr)
{
root = make_shared<Node>(data);
return;
}
//根據data在中序中出現在其父節點的左邊還是右邊來確定插往左子樹還是右子樹
auto it_data = find(begin(inOr), end(inOr), data);
auto it_p_data = find(begin(inOr), end(inOr), root->data);
if (it_data < it_p_data)
{
Insert(root->left_child, data);
}
else
{
Insert(root->right_child, data);
}
}
shared_ptr<Node> Create(int b, int e)
{
shared_ptr<Node> root;
for (auto it = b; it != e; it++)
{
Insert(root, seqOr[it]);
}
return root;
}
用中序、層序建樹後,前序、後續遍歷的結果