C++函数式实现BST、线段树(单点修改)(1)

C++函数式实现BST、线段树(单点修改)(1)

偶然看到Common Lisp(CL)中BST的写法,又联想到了自己写过的单点修改的主席树,觉得这两者几乎差不多,因此拿来分享一下,也顺便应该能解决一下为什么主席树又有一个“函数式版本的线段树”名称

本来打算直接用CL写的......但是想到可能很多人并不熟悉CL,于是就用C++代替了。

目前只打算写单点更新的线段树,而至于区间更新的需要打lazy tag,我需要去学习一下区间更新的主席树再来更新......

本来想完整的写完,但是想到一次性写完可能显得会太长而导致太长不看的可能,所以还是留线段树到下一次吧。

本篇前置知识:BST(Binary Search Tree,二叉搜索树),C++基本语法


Part I: 函数式风格几个important properties(此段引用自Land Of Lisp, Ch14, "What is Functional Programming?")

  • 只要传入的参数相同,函数总是返回同样的结果。The function always returns the same result, as long as the same arguments are passed into it.
  • 函数不会引用定义在函数外部的变量,除非那个变量是常量。The function never references variables that are defined outside the function, unless we are certain that these variables will remain constant.
  • 函数不会修改变量的值。No variables are modified by the function.
  • 函数除了返回(动词)值以外什么也不干。(原谅本人直译......)The purpose of the function is to do nothing other than to return a result.
  • 函数不会对外界造成任何改变。The function doesn’t do anything that is visible to the outside world, such as pop up a dialog box on the screen or make your computer go "Bing!"
  • The function doesn't take information from an outside source, such as
    the keyboard or the hard drive.

Part II:函数式的BST实现

(new完没有delete)

用到的语法知识有:C++ explicit,nullptr,初始化列表,全局变量默认值

BST中节点的定义(注意声明了一个类型为Node*的全局变量root)

struct Node{
    int v;
    Node* le,*ri;
    explicit Node(int value=0,Node* lef=nullptr,Node* righ=nullptr)
            :v(value),le(lef),ri(righ){};
}*root;


BST的插入:

Node* bst_insert(int value,Node* bst){
    if(bst==nullptr)
        return new Node(value);
    if(value==bst->v)
        return bst;
    else if(value<bst->v)
        return new Node(bst->v,bst_insert(value,bst->le),bst->ri);
    else
        return new Node(bst->v,bst->le,bst_insert(value,bst->ri));
}

接下来讲解bst_insert:

(1)此方法接受一个value,即待插入的值,以及一个Node*,代表当前的bst的root,因此一开始使用时传入root

(2)此方法前两种情况为简单情况,一个是当前bst为空时,则返回一个新的、值为value的、左右子都为nullptr的节点。另一个是发现待插入的值和当前节点相等时,为避免重复直接返回自己。(其实这一步可以不要,不过,anyway,这反正不是重点......)

(3)第三种情况和第四种情况是对称的,看到第三种情况,是当前插入的值小于当前bst的root,那么就返回一个新节点,这个新节点的值是当前bst的root值,左子是在当前root的左子上调用bst_insert的返回值,右子是当前root的右子。

(4)第四种情况把上面的“小于”换成大于,“左”换成“右”,“右”换成“左”

画个图理解一下(纯手绘...请将就着看吧)

考虑在该树上插入新元素 9.5

第一次调用bst_insert为bst_insert(9.5,root)

返回值是new Node(10,bst_insert(9.5,root->le),root->ri)

在途中需要求出bst_insert(9.5,root->le)的值来完成构造函数,因此

第二次调用bst_insert为bst_insert(9.5,root->le)

返回值是new Node(8,root->le->le,bst_insert(9.5,root->le->ri))

在途中需要求出bst_insert(9.5,root->le->ri)来完成构造函数,因此

第三次调用bst_insert为bst_insert(9.5,root->le->ri)

返回值是new Node(9,root->le->ri->le,bst_inseret(9.5,root->le->ri->ri))

在途中需要求出bst_inseret(9.5,root->le->ri->ri)来完成构造函数,因此

第四次调用bst_insert为bst_inseret(9.5,root->le->ri->ri)

但是root->le->ri->ri为nullptr,因此会直接返回Node(9.5),因此返回过程如下

途中的次序表示第几次的返回值,黑线表示连接到了原来的节点

我们可以发现,插入的返回值就是一条链,代表的是从真正的root节点一直到被插入的地方的链,其余的节点都是连接到的原来的节点上。因此我们只要令root等于第一次调用bst_insert的返回值就OK了。

可以发现,这其中我们并没有对原来bst上进行修改,而是创建了一条新链,而让这条链和之前的bst共用某些节点。(单点更新的主席树!)

这个联系留到下一次再发。


BST插入的使用:

int main(){
    srand(time(0));
    for(int i=0;i<10;++i)
        root=bst_insert(rand()%100,root);
    dfs(root);//随便遍历一下,表明it works pretty well. 2333333333
    return 0;
}

void dfs(Node* bst){
    if(bst!=nullptr){
        cout<<bst->v<<' ';
        dfs(bst->le);dfs(bst->ri);
    }
}

Part III:其他

我觉得我应该把函数式删除拿出来写一个番外篇...这毕竟和写作本文的主旨没有关联。

本篇中的BST完整代码:

github.com/Hatsunespica

假如觉得有什么地方写的错误或不好or可以改进的地方,请尽管提出来。

第二篇地址 :C++函数式实现BST、线段树(单点修改)(2)

编辑于 2018-04-04

文章被以下专栏收录