Trees
樹
樹的含義,樹的特徵
什麼是樹
樹的節點是一對多,並且不會出現迴路
一些名詞解釋
- 樹的根節點沒有父節點(父親,雙親,雙親節點),一顆樹只有一個根節點
- 樹中沒有子結點(孩子,兒子,子女)的節點叫做葉子節點
- 有相同的父節點的節點們叫做兄弟節點
- 深度:由根節點向下計算到該節點,根節點在第0層
- 高度:該節點到葉子節點
- 度:節點的子樹的個樹,葉子節點度為0
二叉樹
如果每個節點有0, 1, 2個子結點,那麼這棵樹就可以稱為二叉樹。二叉樹由左子樹和右子樹構成。
二叉樹的種類
- 嚴格二叉樹 每個節點必須剛剛好有兩個子結點,或者沒有子節點
- 滿二叉樹 每個節點必須剛剛好有兩個子結點,而且葉子節點的層數相同
- 完全二叉樹 除最後一層外的其餘層都是滿的,並且最後一層要麼是滿的,要麼右邊缺少連續若干節點,滿二叉樹是一種完全二叉樹
二叉樹的性質
假設樹的高度是h,根在第0層(如果在1層)
- 滿二叉樹第i層有 2^i 個節點
- 滿二叉樹的節點個樹是 2^(h+1) - 1
- 滿二叉樹中葉子節點的個數是 2^h
- 完全二叉樹的節點個數介於 2^h 和 2^(h+1) - 1之間
- 有n個節點的完全二叉樹深度為log2 n
二叉樹的資料結構
struct BinaryTreeNode {
int data;
struct BinaryTreeNode *left;
struct BinaryTreeNode *right;
};
二叉樹的遍歷
- DLR Process the current node data, process left subtree and then process right subtree
- LDR Process left subtree, process the current node data and the process right subtree
- LRD Process left subtree, process right subtree and the process the current node data
根據以上,我們得到三種遍歷方式:
- 先序遍歷 的順序是DLR
- 中序遍歷 的順序是LDR
- 後序遍歷 的順序是LRD
還有一種另外的遍歷方式:
- 層次遍歷
先序遍歷
先序遍歷流程:
- 訪問根節點
- 先序遍歷左子樹
- 先序遍歷右子樹
void PreOrder(struct BinaryTreeNode *root) {
if (root) {
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
,但是我們不知道在先序遍歷中,左子樹佔了陣列哪些,右子樹佔了哪些,求出這個就解決問題了。}
非遞迴實現:
- 處理當前節點
- 將當前節點入棧
- 處理左子樹
- 彈出棧頂元素
- 處理該節點的右子樹
void PreOrderNonRecursive(struct BinaryTreeNode *root) {
struct Stack *S = CreateStack();
while(1) {
while(root) {
printf("%d ". root->data);
Push(S, root);
// If left subtree exists, add to stack
root = root->left;
}
if (IsEmptyStack(S)) {
break;
}
root = Pop(S);
// process right subtree
root = root->right;
}
DeleteStack(S);
}
中序遍歷
中序遍歷流程:
- 中序遍歷左子樹
- 訪問根節點
- 中序遍歷右子樹
void InOrder(struct BinaryTreeNode *root) {
if (root) {
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
}
非遞迴實現:
與前序遍歷非遞迴類似,不同的是,前序遍歷是在處理左子樹之前處理節點,而中序遍歷處理節點是在彈出之後(表明左子樹的操作已經完成了)
void InOrderNonRecursive(struct BinaryTreeNode *root) {
struct Stack *S = CreateStack();
while(1) {
while(root) {
Push(S, root);
root = root->left;
}
if (IsEmptyStack(S)) {
break;
}
root = Pop(S);
printf("%d ", root->data);
root = root->right;
}
}
後序遍歷
後序遍歷流程:
- 後序遍歷左子樹
- 後序遍歷右子樹
- 訪問根節點
void PostOrder(struct BinaryTreeNode *root) {
if (root) {
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
}
非遞迴實現:
後序遍歷是左右中的順序,也就是說,在處理完一個節點的左子樹之後,我們需要回到這個節點一次,然後處理完該節點的右字數之後,我們還需要回到這個節點一次,我們應該在第二次回到這個節點的時候去處理這個節點。問題是我們如何區分我們這次返回是從左還是從右
所以我們使用一個 previous 來記錄上一個遍歷過的節點,如果 previous 是當前所在節點的左孩子,那我們就是在剛對當前節點的左子樹操作完成,那我們就要該對它的右子樹操作了。如果 previous 是當前所在節點的右孩子,說明我們對當前所在節點的右子樹處理過了(它的左右子樹都處理過了),我們就可以訪問它了。
void PostOrderNonRecursive(struct BinaryTreeNode *root) {
struct SimpleArrayStack *S = CreateStack();
struct BinaryTreeNode *previous = NULL;
do {
while (root != NULL) {
Push(S, root);
root = root->left;
}
while (root == NULL && !IsEmptyStack(S)) {
root = Top(S);
if (root->right == NULL || root->right == previous) {
printf("%d ", root->data);
Pop(S);
previous = root;
root = NULL;
} else {
root = root->right;
}
}
} while (!IsEmptyStack(S))
}
層次遍歷
層次遍歷流程:
利用佇列,先進先出,把出隊元素的左孩子和右孩子入隊
void LevelOrder(struct BinaryTreeNode *root) {
struct BinaryTreeNode *temp;
struct Queue *Q = CreateQueue();
if (!root) {
return;
}
EnQueue(Q, root);
while (!IsEmptyQueue(Q)) {
temp = DeQueue(Q);
//Process current node
printf("%d ", temp->data);
if (temp->left) {
EnQueue(Q, temp->left);
}
if (temp->right) {
EnQueue(Q, temp->right);
}
}
DeleteQueue(Q);
}
問題
樹的大部分問題都可以套用遞迴的模板
void func(Tree *root) {
//PreOrder
func(root->left);
//InOrder
func(root->right);
//PostOrder
}
遞迴 我們要指導我們定義的函式是做什麼的,利用遞迴來推匯出結果,但不用糾結每次遞迴具體做了什麼
樹相關的問題,最重要的搞清楚當前節點應該做什麼,然後對子結點遞迴,遞迴會讓子節點做相同的事
第一題 找到二叉樹裡最大的元素
思路:先找左子樹裡最大的元素,再找右子樹裡最大的元素,讓他們與根節點的值比較大小
int FindMax(struct BinaryTreeNode *root) {
int root_val, left, right, max = INT_MIN;
if (root) {
root_val = root->data;
left = FindMax(root->left);
right = FindMax(root->right);
if (left > right) {
max = left;
} else {
max = right;
}
if (root_val > max) {
max = root_val;
}
}
return max;
}
第二題 求一棵二叉樹的深度
思路:想一下假如已經知道二叉樹的左子樹和右子樹的深度,如何計算二叉樹的深度?左子樹深度 LD ,右子樹深度 RD ,那麼深度就是 max(LD, RD) + 1。先求左,在求右,最後訪問根(又是一個後序遍歷)
int getDepth(struct BinaryTreeNode *root) {
int LD, RD;
if (root) {
return 0;
} else {
LD = getDepth(root->left);
RD = getDepth(root->right);
return (LD > RD ? LD : RD) + 1;
}
}
第三題 填充每個節點的下一個右側節點指標
思路:把每一層的節點都用 next
指標連起來,那對節點的操作就是 root->left->next = root->right
(但是這樣做有點小問題,同一層,不一定都是一個節點下的兄弟節點)
這是對一個節點來操作,很明顯,我們這樣做,無法連線同一層全部的相鄰節點,所以一個節點做不到,我們就讓兩個節點來做。
將每兩個相鄰節點都連線起來
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *left;
* struct Node *right;
* struct Node *next;
* };
*/
struct Node* connect(struct Node* root) {
if (!root) {
return NULL;
}
ConnectTwoPoint(root->left, root->right);
return root;
}
void ConnectTwoPoint(struct Node* node1, struct Node* node2) {
if (!node1 || !node2) {
return;
}
node1->next = node2;
ConnectTwoPoint(node1->left, node1->right);
ConnectTwoPoint(node2->left, node2->right);
ConnectTwoPoint(node1->right, node2->left);
}
第四題 從前序與中序遍歷序列構造二叉樹
思路:先確定根節點的值,構造出了根節點之後,再遞迴對根節點的左右子樹遞迴,就能構造出左右子樹了
我們可以知道根節點的值就是先序遍歷的第一個元素的值,然後在中序遍歷中,根節點對應位置的左右分別就是根節點的左子樹和右子樹,但是我們不知道在先序遍歷中,左子樹佔了陣列哪些,右子樹佔了哪些,求出這個就解決問題了。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
struct TreeNode* build(int* preorder,int preStart,int preEnd,int* inorder,int inStart,int inEnd) {
if(preStart > preEnd) {
return NULL;
}
// 構造根節點
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = preorder[preStart];
int index = 0;
// 在中序遍歷陣列中找到根節點對應的下標
for(int i = inStart; i <= inEnd; i++) {
if(inorder[i] == preorder[preStart]) {
index=i;
break;
}
}
// 左子樹元素個數
int leftSize = index - inStart;
// 構造左右子樹
root->left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1);
root->right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd);
return root;
}
struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int inorderSize){
if(preorderSize == 0) {
return NULL;
}
return build(preorder, 0, preorderSize-1, inorder, 0, inorderSize-1);
}
哈夫曼樹
哈夫曼樹定義
又稱最優二叉樹,帶權路徑最小
構造方法
- 從集合中選出最小的兩個值,構造二叉樹,新的二叉樹的權值等於兩個值的和
- 從集合中刪除這兩個值,加入新值
- 重複進行 1 2
相關文章
- Trees and Segments
- Traversal of trees
- Trees and XOR Queries AgainAI
- go Exercise: Equivalent Binary TreesGoUI
- 617-Merge Two Binary Trees
- The trees stand together with ability ranks and rune words
- 【LeetCode】617. Merge Two Binary TreesLeetCode
- 默克爾樹 Merkle trees(一)
- LSM(Log Structured Merge Trees ) 筆記Struct筆記
- LeetCode之Leaf-Similar Trees(Kotlin)LeetCodeMILAKotlin
- LeetCode 617. Merge Two Binary TreesLeetCode
- LeetCode 1305 All Elements in Two Binary Search TreesLeetCode
- [20181220]Bushy Join Trees in Oracle 12.2.txtOracle
- LeetCode之All Possible Full Binary Trees(Kotlin)LeetCodeKotlin
- 題解:CF1237E Balanced Binary Search Trees
- 迴歸樹(Regression Trees)模型的優缺點模型
- P10842 【MX-J2-T3】Piggy and Trees 題解
- CF1902F.Trees and XOR Queries Again-點分治、線性基AI
- Codeforces-Round#548(Div.2)-C-Edgy Trees-快速冪
- 96-Unique Binary Search Trees 二叉搜尋樹的數量
- Leetcode PHP題解--D45 D45 872. Leaf-Similar TreesLeetCodePHPMILA
- 機器學習演算法系列(二十)-梯度提升決策樹演算法(Gradient Boosted Decision Trees / GBDT)機器學習演算法梯度