记录刷题中遇到的知识点方便复习(十五)(二叉树2)

插播总结C++ STL容器 

参考: C++STL【容器】详解 (全站最详细) - 知乎 (zhihu.com)

1. 顺序容器

1.1 vector 可变大小数组

1.2 deque 双端队列

1.3 list 双向循环链表

1.4 forward_list 单向链表

1.5 array 固定数组

2. 关联式容器

2.1 set

2.2 map

3 容器适配器

之所以叫容器适配器,是因为用来适配容器来提供其它不一样的功能。通过对应的容器和成员函数来实现我们需要的功能

3.1 stack 堆栈 先进后出

3.2 queue 队列 先进先出

3.3 pirority_queue 优先队列

队列操作在二叉树中很多

总结一下队列的基本操作:

参考:C++队列queue用法详解_queue c++ 用法-CSDN博客C++ Queue(队列) - 菜鸟教程 (cainiaojc.com)

队列属于先进先出类型,在普通队列中,元素的插入在尾部,而删除则从前面开始。

1. 创建队列

1.1 普通初始化

queue<int> q;

1.2 用容器实现的队列

queue<char, list<char>>q1; //list

queue<int, deque<int>>q2;  //deque

//不能用vector

2. 常用操作

  • size() 返回队列长度
  • empty() 如果队列空则返回true
  • push() 在队尾插入一个元素   (注意vector是push_back())
  • pop() 删除队列第一个元素,无返回值
  • front() 返回队列中的第一个元素
  • back() 返回队列中最后一个元素

 目前看来队列的操作跟前面遇到的那些容器操作没有太大的区别

 有了队列的基础知识,广度优先搜索(BFS)可以理解了。图解可以看深度优先搜索(DFS)和广度优先搜索(BFS)-CSDN博客

以力扣102题二叉树的层序遍历为例

BFS的思路就是,我们将二叉树看作一层一层的节点,按层数去查找,根节点在第一层,第一层的广度就是节点数为1,以此类推。

我们要始终牢记队列是队头出队,队尾入队。

首先将根节点存入队列,此时队列的元素数就是当前层数的广度1,然后将根节点出队,左右子树入队,这是第二层,此时队列中的元素数就是当前层的广度,然后创建一个新节点指向队头节点,将队头节点的左右子树入队,队头出队,循环直至父节点都出队,以此类推,队列为空为止。

代码:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        if(root == nullptr) return ans;
        
        queue<TreeNode *> q;
        q.push(root);
        
        while(!q.empty()){
            int n = q.size();
            ans.push_back(vector<int>());
            for(int i = 1; i<=n; i++){
                TreeNode *p = q.front();
                q.pop();
                ans.back().push_back(p->val);
                if(p->left) q.push(p->left);
                if(p->right) q.push(p->right);
            }          
        }
        return ans;
    }
};

之前还提到了深度优先搜索算法里面,除了递归还有的方法

总结一下的基础操作:

参考:栈的详解(C/C++数据结构)_c++栈-CSDN博客<C++>学习:栈(Stack)操作_c++ stack-CSDN博客                 c++数据结构之栈_c++栈-CSDN博客二叉树遍历总结:DFS+BFS【C++】 - Ueeei - 博客园 (cnblogs.com)

栈是先进后出类型的

1. 栈的定义

typedef int ElemType;
typedef struct _SqStack {
	ElemType* base; //栈底指针
	ElemType* top; //栈顶指针
}SqStack;

2. 初始化栈

stack<TreeNode*> stack; //可以是不同数据类型

3. 栈的基本操作

  • top():返回一个栈顶元素的引用,类型为 T&。如果栈为空,返回值未定义。
  • size():  返回栈中元素的个数。
  • empty():  在栈中没有元素的情况下返回 true。
  • push(): 让某元素入栈
  • pop(): 让栈顶元素出栈

4. 深度优先搜索-二叉树

4.1 前序遍历

vector<int> preorderTraversal(TreeNode* root) {
    vector<int> res;
    stack<TreeNode*> stk;
    while (root != nullptr || !stk.empty()) { 
        // 从根开始一直往左入栈,直到最左结点入栈结束       
        while(root){            
            stk.push(root);
            res.push_back(root->val);                            
            root = root->left;                         
        }
        //处理栈内节点
        root = stk.top();
        stk.pop();
        root = root->right;                     
    }
    return res;
}

4.2 中序遍历

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> res;
    stack<TreeNode*> stk;
    while (root != nullptr || !stk.empty()) {
        while (root != nullptr) {
            stk.push(root);
            root = root->left;
        }
        root = stk.top();
        stk.pop();
        res.push_back(root->val);
        root = root->right;        
    }
    return res;
}

4.3 后序遍历

后续遍历跟前序和中序都不同,需要两个栈来操作,一个栈进行根->右->左(类似前序遍历)的操作,另一个栈反向输出实现左->右->根

vector<int> postOrderNocur(TreeNode *root) {
    vector<int> res;

    stack<TreeNode*> s; // 第一个栈,保证前序遍历(根-右-左)的执行
    stack<TreeNode*> p; // 第二个栈,实现反向输出左右根

    while (root != nullptr || !s.empty()) {
        while (r) {
            s.push(root);
            p.push(root); // p与s同步push
            root = root->right;
        }
        r = s.top();
        s.pop(); // p不需要pop;s执行pop的原因是需要将前序遍历执行下去
        r = r->left;        
    }
    while (!p.empty()) {
        res.push_back(p.top()->val);
        p.pop();
    }
    return res;
}

三种遍历方式最核心部分是相似的,递归的方式虽然简单(上一篇博客有讲),但是要牢记非递归的方式,后面做题会用到。

接着做题了

三十八、LeetCode第三十八题(108)

将有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵平衡二叉搜索树。

平衡二叉树指的是所有节点左右子树的深度相差都不超过1。

这道题自己做出来了

用的二分法和递归:将给出的数组从中点一分为二,中点为根节点(不考虑奇偶),左边数组作为左子树,右边数组为右子树,接着二分递归。

class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        int mid = nums.size()/2;
        if(nums.size() == 0) return nullptr;
        vector<int> s1;
        vector<int> s2;
        s1.assign(nums.begin(), nums.begin()+mid);
        s2.assign(nums.begin()+mid+1, nums.end());
        TreeNode *root = new TreeNode(nums[mid]);
        root->left = sortedArrayToBST(s1);
        root->right = sortedArrayToBST(s2);
        return root;
    }
};

三十九、LeetCode第三十九题(98)

验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

这道题也是递归和非递归的方法

根据前面学的非递归的遍历方法,我们考虑中序遍历整个二叉树,得到的应该是一个递增的数列,于是可以有以下解法:在中序遍历的基础上加一个元素值的大小比较:

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        if(root == nullptr) return false;
        vector<int> res;
        stack<TreeNode*>stk;
        while(root != nullptr || !stk.empty()){
            while(root){
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            res.push_back(root->val);
            if(res.size()>1 && res[res.size()-1] <= res[res.size()-2]) return false;
            stk.pop();
            root = root->right;
        }
        return true;
    }
};

但是这样做有一个冗余步骤就是,我们并不需要存储整个遍历的结果,于是可以不用vector,而只是比较每次的root->val与前一个的大小,代码如下:. - 力扣(LeetCode)

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> stack;
        long long inorder = (long long)INT_MIN - 1;

        while (!stack.empty() || root != nullptr) {
            while (root != nullptr) {
                stack.push(root);
                root = root -> left;
            }
            root = stack.top();
            stack.pop();
            // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if (root -> val <= inorder) {
                return false;
            }
            inorder = root -> val;
            root = root -> right;
        }
        return true;
    }
};

递归的方式我一开始也写了,但是仅考虑了每个子树的左右子树能够满足要求,但是还要考虑整个二叉树的左子树元素值小于右子树的元素值,于是这里面还是存在一个限制大小范围的过程:

//官方题解
class Solution {
public:
    bool helper(TreeNode* root, long long lower, long long upper) {
        if (root == nullptr) {
            return true;
        }
        if (root -> val <= lower || root -> val >= upper) {
            return false;
        }
        return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
    }
    bool isValidBST(TreeNode* root) {
        return helper(root, LONG_MIN, LONG_MAX);
    }
};

注意这里初始化的数据类型都是long long,我想不到这点,所以就还是用了数组存储中序遍历的方式。

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值