一、知识点介绍
树的基本概念
子树:树是一个有限集合,子树则是该集合的子集。就像套娃一样,一棵树下面还包含着其子树。
比如,树T1 的子树为 树T2、T3、T4,树T2的子树为 T5、T6. 上图中还有许多子树没有标记出来。
结点(Node):一个结点包括一个数据元素和若干指向其子树分支。
比如,在树T1 中,结点A 包括一个数据元素A 和 三个指向其子树的分支。上图中共有 17 个结点。
根结点(Root):一颗树只有一个树根,这是常识。在数据结构中,“树根”即根节点。
比如,结点A 是树 T1 的根结点;结点C 是树T1 的子结点,是树 T3 的根结点。
度(Degree):一个结点拥有的子树数。
比如,结点A 的度为 3,结点G 的度为 3,结点H 的度为 1.
叶子(Leaf)/ 终端结点:度为 0 的结点被称为叶子结点,很形象吧。
比如,对于树 T1来说,结点F、I、K、L、M、N、O、P、Q 均为叶子。
分支结点 / 非终端结点:和叶子结点相对,即度不为 0 的结点。
内部结点:顾名思义,在树内部的结点,即不是根结点和叶子结点的结点。
孩子(Child)、双亲(Parent)、兄弟(Sibling)、堂兄弟、祖先、子孙这些概念和族谱上的相同。
比如,对于结点B 来说:结点A 是其双亲结点,结点E、F 是其孩子结点,结点C、D 是其兄弟结点,结点K 是其子孙结点。
层次(Level):从根结点开始,根为第一次,根的孩子为第二层,依次往下。
比如,结点K 在树 T1 中的层次为 4.
深度(Depth)/ 高度:指树的最大层次。
比如,树 T1 的深度为 4.
有序树:如果结点的各子树从左到右是有次序的、不能颠倒,则为有序树,否则为无序树。对于有序树的孩子来说,最左边的孩子称为第一个孩子,最右边的孩子称为最后一个孩子。
比如,如果树T1是一个有序树,则其根结点的第一个孩子为结点B,最后一个孩子为结点D.
二、练习题目
(1) 1361. 验证二叉树
(2) 1367. 二叉树中的列表
三、算法思路
1. 验证二叉树
(1) 算法思路:1. 找出树的root;2. 判断树是否合理
(2) 如何找出树的root呢?root既不再左子树中,也不在右子树中而且有且只有一个,所以可以用一个哈希表deg存放,左子树和右子树所有元素的次数。遍历这个哈希表,如果元素个数为0说明就是root。如果有两个或以上的root,说明树不合理;
(3) 树不合理的情况:1. 1~n-1没有都出现一遍;2. 有多个root(第二点已经说过了);3. 1~n-1中的数字出现了两次及以上; 4. 每个结点的度超过2.
(4) 1. 用一个哈希表hash存放每个元素出现的次数,如果哈希表的值不为零的话,说明树合理;2. 这个在第二点已经解决了; 3. 哈希表deg存放每个结点度的大小,deg[i]大于1的话,就返回false;4. cnt代表当前的度的大小,如果cnt大于1的话,返回false
class Solution {
int hash[10010];
void dfs(vector<int>& leftChild, vector<int>& rightChild, int x) {
hash[x] = 1;
if(leftChild[x] != -1) {
dfs(leftChild, rightChild, leftChild[x]);
}
if(rightChild[x] != -1) {
dfs(leftChild, rightChild, rightChild[x]);
}
}
public:
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
int deg[10010];
memset(hash, 0, sizeof(hash));
memset(deg, 0, sizeof(deg));
//bool ans = true;
for(int i = 0; i < leftChild.size(); ++i) {
if(leftChild[i] != -1) {
++deg[leftChild[i]];
}
if(rightChild[i] != -1) {
++deg[rightChild[i]];
}
}
int cnt = 0;
int root = -1;
for(int i = 0; i < n; ++i) {
if(deg[i] == 0) {
++cnt;
root = i;
if(cnt > 1) return false;
}
if(deg[i] > 1) return false;
}
if(cnt == 0) return false;
dfs(leftChild, rightChild, root);
for(int i = 0 ; i < n; ++i) {
if(!hash[i]) return false;
}
return true;
}
};
2. 二叉树中的列表
(1) 先递归找到二叉树中和链表第一个元素相等的元素;如果没找到的话直接返回false
(2)如果找到的话,在通过向下遍历找到是否有连续的值(也就是找一遍左子树再找一遍右子树和链表的值相等;如果没有就返回false
(3) 处理一下特殊情况:如果链表有值,树没值返回false;如果链表没值,树有值返回true。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool check(TreeNode* root, ListNode* head) {
if(root == NULL) {
return (head == NULL);
}
if(head == NULL) return true;
if(root->val != head->val) {
return false;
}
return check(root->left, head->next) || check(root->right, head->next);
}
bool isSubPath(ListNode* head, TreeNode* root) {
if(check(root, head)) {
return true;
}
if(root == NULL) return false;
return isSubPath(head, root->left) || isSubPath(head, root->right);
}
};