树
一,树的算法总结
总结树需要掌握的几点知识点:
1、树的遍历方式:前序,中序,后序,层序
2、树的递归方式
3、树的迭代(栈)
4、树的创建(列表或者是字典)
5、树的动态规划
下面总结一下常见代码:
//前序遍历
void PreOrderTraverse1(TreeNode* root){
if (root == NULL)return;
cout << root->val << '\t';
PreOrderTraverse1(root->left);
PreOrderTraverse1(root->right);
}
```cpp
//中序遍历
void InOrderTraverse(TreeNode* root){
if (root == NULL)return;
InOrderTraverse(root->left);
cout << root->val << "\t";
InOrderTraverse(root->right);
}
```cpp
//后序遍历
void PostOrderTraverse(TreeNode* root){
if (root == NULL)return;
PostOrderTraverse(root->left);
PostOrderTraverse(root->right);
cout << root->val << "\t";
}
//层次遍历
void LevelOrderTraverse(TreeNode* root){
if (root == NULL)return;
queue<TreeNode*> temp;
temp.push(root);
while (!temp.empty()){
TreeNode *p = temp.front();
cout << p->val << '\t';
temp.pop();
if (p->left)temp.push(p->left);
if (p->right)temp.push(p->right);
}
————————————————
在这里插入代码片
有关树的一些疑难问题及部分解答:
1.如何得到树的节点个数?
遍历这棵树,用一个值记录节点个数即可;
2.如何得到以可树的高度?
递归得到每个节点的左右子节点的高度,在加上当前节点自己,返回给上一层调用,这样就会一层一层的得到从每个叶子节点往上返回的高度,最终得到树的高度;
3.如何得到一棵树的镜像?(就是每个节点的左右子节点调换位置);
采用前序、中序、后序遍历中的任何一种,从叶子节点开始交换左右节点的指针,往上实现交换指针即可;
4.根据树的前序和中序遍历结果得到一棵树?
二叉树
1.二叉树的遍历算法
二叉树的遍历主要分为三种:先序遍历,中序遍历和后序遍历。还有一种就是按照层次遍历。
先序遍历指的就是先访问本节点,再访问该节点的左和右;
中序遍历指的就是:先访问左,再访问本节点,最后访问右;
后序遍历指的就是:先访问左右,最后访问本节点。
层次遍历:按照树的每一层进行遍历。
树的节点的数据结构常声明为:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}
————————————————
2.二叉树的一些其他算法
(1). 二叉树的深度
递归版本非常简洁,也非常易懂;迭代版本则需要利用我们之前介绍的按照层次遍历,层数就是二叉树的深度。
//递归版本
int TreeDepth(TreeNode *pRoot) {
return pRoot ? 1 + max(TreeDepth(pRoot->left),
TreeDepth(pRoot->right)) : 0;
}
//迭代版本
int TreeDepth2(TreeNode *pRoot) {
queue<TreeNode *> Q;
Q.push(pRoot);
int depth = 0;
while(!Q.empty()) {
int len = Q.size();
++depth;
while(len--) {
TreeNode *curr_node = Q.front();
Q.pop();
if(curr_node) {
Q.push(curr_node->left);
Q.push(curr_node->right);
}
}
}
return depth - 1; //将叶节点的空孩子节点也算作一层了,所以减1
}
————————————————
(2). 二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。使用递归,当节点存在至少一个孩子时,交换左右孩子,再递归处理。
//二叉树的镜像
void Mirror(TreeNode *pRoot) {
if (pRoot && (pRoot->left || pRoot->right)) {
std::swap(pRoot->left, pRoot->right);
Mirror(pRoot->left);
Mirror(pRoot->right);
}
return;
————————————————
(3). 二叉树的下一个结点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
节点的数据结构表示为:
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next; //父节点
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {}
};
————————————————
.重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
//重建二叉树
TreeNode *reConstructBinaryTree(vector<int>::iterator pre_first,
vector<int>::iterator pre_last,
vector<int>::iterator vin_first,
vector<int>::iterator vin_last) {
if (vin_last - vin_first != pre_last - pre_first ||
pre_last == pre_first ||
vin_first == vin_last) {
return nullptr;
}
TreeNode *curr_node = new TreeNode(*pre_first);
if (pre_last == pre_first + 1) return curr_node;
auto iter = vin_first;
while (iter < vin_last) {
if (*iter == *pre_first) break;
iter++;
}
int len = iter - vin_first;
curr_node->left = reConstructBinaryTree(pre_first + 1, pre_first + len + 1, vin_first, iter);
curr_node->right = reConstructBinaryTree(pre_first + len + 1, pre_last, iter + 1, vin_last);
return curr_node;
}
TreeNode *reConstructBinaryTree(vector<int> pre, vector<int> vin) {
return reConstructBinaryTree(pre.begin(), pre.end(), vin.begin(), vin.end());
}
————————————————
树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
//树的子结构
bool isSubtree(TreeNode *pRoot1, TreeNode *pRoot2) {
//判断以pRoot2为根节点的树是否是以pRoot1为根节点的树的子树
if (!pRoot2) return true;
if (!pRoot1) return false;
return pRoot1->val != pRoot2->val ? false :
isSubtree(pRoot1->left, pRoot2->left) &&
isSubtree(pRoot1->right, pRoot2->right);
}
bool HasSubtree(TreeNode *pRoot1, TreeNode *pRoot2) {
if(!pRoot1 || !pRoot2) return false;
return isSubtree(pRoot1, pRoot2) ||
isSubtree(pRoot1->left, pRoot2) ||
isSubtree(pRoot1->right, pRoot2);
————————————————
查找
一、二叉树
结合了链表插入的灵活性和有序数组查找的高效性。
每一个结点包含:一个结点计数器(以该结点为根的子树中的结点总数),一个左结点,一个右结点,一个键,一个值。每个结点的键均大于其左子树上的键而小于其右子树上的键。
int get(Node x, int key){
if(x == null)
return null;
if(key < x.key)
return get(x.left, key);
else if (key > x.key)
return get(x.right, key);
else
return x.value;
}
void put(int key, int val){
root = put(root, key, val);
}
Node put(Node x, int key, int val){
if (x == null)
return new Node(key, val, 1);//最后一个参数为结点计数器
if(key < x.key)
x.left = put(x.left, key, val);
else if (key > x.key)
x.right = put(x.right, key, val);
else
x.value = val;
x.N = size(x.left) + size(x.right) + 1;
return x;
}
————————————————
运行时间取决于树的形状,树的形状取决于键被插入的先后顺序,最好情况是平衡树,最坏情况是线性树。
删除树的最小结点也就是删除树中最左侧的结点,因为结点的大小顺序为:左<中<右
附加:
平衡二叉树
优点:最优的查找效率和空间需求;能够进行有序性相关操作
缺点:链接需要额外空间
二分查找算法是在有序数组中用到的较为频繁的一种算法,二分查找算法则更优
在未接触二分查找算法时,最通用的一种做法是,对数组进行遍历
思维导图:
![树与二叉树关系紧密,通过对知识点的掌握理解从而解决更多的问题](https://img-blog.csdnimg.cn/20210430145746245.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BpZXBpZTg4MjA=,size_16,color_FFFFFF,t_70#pic_center