7.22 leetcode+数据库索引

最近有好多leetcode都没更新,因为刷到了链表二叉树,简单题都比较简单,因为二叉树和链表都天然的很适合各类递归算法,因此需要对于递归算法有一些深入理解,记得刚开始学习的时候也是不理解递归的本质是什么,现在看起来其实可以将其想象为一颗树。每到一个节点(递归开始之前)就决定进入哪些分枝,在递归结束之后决定如何处理得到的结果,以及要给父亲节点返回点什么。回溯和分枝限界也差不多都是一样的道理。但是按照我的理解递归(栈)只能实现DFS,如果要实现BFS就需要借助于队列等数据结构来进行处理。

递归前面用的蛮多的,今天就说一道题好了,然后复习一下非递归二叉树的遍历;

bool isValidBST(TreeNode* root) {
        //递归验证,左边是否是二叉搜索树
        return helper(root,LONG_MIN,LONG_MAX);
    }

    bool helper(TreeNode* root,long l,long r)
    {
        if(root == nullptr) return true;

        if(root->val <= l || root->val >= r) return false;

        return helper(root->left,l,root->val) && helper(root->right,root->val,r);
    }

这道题目主要是为了验证这棵树是不是二叉搜索树,从定义上来讲,二叉搜索树的每一个节点都有自己的取值范围如果在右边应该是比父亲节点小或者大,这道题目来说就可以从root节点开始递归遍历是否满足条件,需要在进入之前就开始遍历,如果出现了false的情况就可以直接终止二叉搜索树上的游走。

二叉树非递归遍历:可以参考这一篇

复盘一道leetcode:比较经典的一道题目,但是上手做的时间比较久:

先序+中序建立二叉树,递归过程中,如果希望一个变量每一次进入递归都要增加,就需要把这个变量设置在内存中,而不是栈中,因为栈只会记录压栈前一刻这一个变量的取值。

这一题也是,另外还有特别需要注意的一点就是二叉树的建立需要连接一个null值,不然会出现野指针的情况,这在c++中是一个严重的问题。

最后递归过程就是自上而下的递归即可,在每一次递归进入之前就建立好节点,为了时间复杂度的问题,这里还开了一个哈希进行O(1)查找。

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //特殊情况
        

        TreeNode* root; 
        //left,right 是一个全部闭合的区间 
        map<int,int> map_vec;

        for(int i= 0 ; i < inorder.size(); i ++)
        {
            map_vec[inorder[i]] = i;
        } 
        return help(preorder,inorder,0,inorder.size()-1,map_vec);
        
    }

    //返回一棵树的根节点
    TreeNode* help(vector<int>& preorder,vector<int> & inorder,int left, int right,map<int,int>& map_vec){
      
        if(left > right)
        {
            return nullptr;
        }
        TreeNode* root = new TreeNode(preorder[root_num]);
        int root_val = preorder[root_num];
        root_num+=1;
        root->left = help(preorder,inorder,left,map_vec[root_val]-1,map_vec);
        root->right =  help(preorder,inorder,map_vec[root_val]+1,right,map_vec);    
        return root;  
    }


普通索引和唯一索引:

普通索引不是唯一索引,因此对该索引进行搜索的时候需要往后查找。

唯一索引只要找到就可以返回,因为是唯一的。

change-buffer:change buffer 只限于用在普通索引的场景下,而不适用于唯一索引。那么,现在有一个问题就是:普通索引的所有场景,使用 change buffer 都可以起到加速作用吗?

 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer 记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。

因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。

反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在 change buffer,但之后由于马上要访问这个数据页,会立即触发 merge 过程。这样随机访问 IO 的次数不会减少,反而增加了 change buffer 的维护代价。所以,对于这种业务模式来说,change buffer 反而起到了副作用。

回到我们文章开头的问题,普通索引和唯一索引应该怎么选择。其实,这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响。所以,我建议你尽量选择普通索引。

如果所有的更新后面,都马上伴随着对这个记录的查询,那么你应该关闭 change buffer。而在其他情况下,change buffer 都能提升更新性能。

数据库优化器的逻辑:

优化器选择索引,首先优化器选择索引的目的为找到一个最优的执行方案,并用最小的代价去执行语句。
优化器如何判定扫描行数?优化器有个统计量,叫做索引的区分度。
采样统计的时候,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数SSADW

字符串字段索引:

1.字符串可以直接添加索引,也可以定义字符串的一部分作为索引。

alter table SUser add index index2(email(6));

例如,如果按照email(6)来说,只选取了前六个字节建立索引,由于InnoDB支持最左匹配原则。缩短字节的数量有助于减少索引的占比。
如果是没有限制字节的索引则匹配到需要的记录之后,只会向后匹配一次。
但是如果是限制字节的索引,则需要一直遍历到最左匹配不满足的情况。
因此如何选择字符串索引的长度是一个需要实际探究的点。
 

2. 

select id,email from SUser where email='zhangssxyz@xxx.com';

如果使用 index1(即 email 整个字符串的索引结构)的话,可以利用覆盖索引,从 index1 查到结果后直接就返回了,不需要回到 ID 索引再去查一次。而如果使用 index2(即 email(6) 索引结构)的话,就不得不回到 ID 索引再去判断 email 字段的值。

index2 已经包含了所有的信息,但 InnoDB 还是要回到 id 索引再查一下,因为系统并不确定前缀索引的定义是否截断了完整信息。(因为这个索引上只有前面的字节)

字段非常长的话如何加索引:

比如身份证,其实大多数人的前面的数字都是差不多的,因此采用这样的索引区分度不高,可以采用以下手段:

一、倒序存储: 有效信息在后面,甚至可以乱序存储
二、对该字段采用hash字段

  • 26
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值