区块链智能合约Append-only B-tree

本文考虑使用区块链智能合约solidity语言实现简单B树的构建、插入元素方法和查询方法。B树的实现难点在于结点的分裂的操作、分裂的判断、元素的移动等。智能合约实现的难点在于solidity语言中不存在‘指针’这一数据结构,增加了对于依赖指针的树状数据结构的实现难度。考虑可以使用mapping来存储结点数据结构,使用数组来存放child结点的键。进而达到使用mapping+数组下标来替代指针的目的。

struct node{
        uint[] keys;
        uint[] child;
        bool leaf;
        uint8 n;
    }

在结构体node当中,keys存放数据的键,child存放此结点的孩子结点的下标,leaf代表此节点是否为叶子节点,n为此结点中已经存放的key的数目。

uint root = 0;
uint8 t=2;
mapping(uint=>node) tree;

同时,为了实现的方便,还需要存储B树的根节点的下标root。B树的度t也需要单独列出。mapping为存放树节点的映射。
Properties of B-Tree

  1. All leaves are at same level.
  2. A B-Tree is defined by the term minimum degree ‘t’. The value of t depends upon disk block size.
  3. Every node except root must contain at least t-1 keys. Root may contain minimum 1 key.
  4. All nodes (including root) may contain at most 2t – 1 keys.
  5. Number of children of a node is equal to the number of keys in it plus 1.
  6. All keys of a node are sorted in increasing order. The child between two keys k1 and k2 contains all keys in range from k1 and k2.
  7. B-Tree grows and shrinks from root which is unlike Binary Search Tree. Binary Search Trees grow downward and also shrink from downward.
  8. Like other balanced Binary Search Trees, time complexity to search, insert and delete is O(Logn).

Insertion
1) Initialize x as root.
2) While x is not leaf, do following
..a) Find the child of x that is going to to be traversed next. Let the child be y.
..b) If y is not full, change x to point to y.
..**c) **If y is full, split it and change x to point to one of the two parts of y. If k is smaller than mid key in y, then set x as first part of y. Else second part of y. When we split y, we move a key from y to its parent x.
3) The loop in step 2 stops when x is leaf. x must have space for 1 extra key as we have been splitting all nodes in advance. So simply insert k to x.

Note that the algorithm follows the Cormen book. It is actually a proactive insertion algorithm where before going down to a node, we split it if it is full. The advantage of splitting before is, we never traverse a node twice. If we don’t split a node before going down to it and split it only if new key is inserted (reactive), we may end up traversing all nodes again from leaf to root. This happens in cases when all nodes on the path from root to leaf are full. So when we come to the leaf node, we split it and move a key up. Moving a key up will cause a split in parent node (because parent was already full). This cascading effect never happens in this proactive insertion algorithm. There is a disadvantage of this proactive insertion though, we may do unnecessary splits.

Let us understand the algorithm with an example tree of minimum degree ‘t’ as 3 and a sequence of integers 10, 20, 30, 40, 50, 60, 70, 80 and 90 in an initially empty B-Tree.

Initially root is NULL. Let us first insert 10.

Let us now insert 20, 30, 40 and 50. They all will be inserted in root because maximum number of keys a node can accommodate is 2*t – 1 which is 5.

Let us now insert 60. Since root node is full, it will first split into two, then 60 will be inserted into the appropriate child.

Let us now insert 70 and 80. These new keys will be inserted into the appropriate leaf without any split.

Let us now insert 90. This insertion will cause a split. The middle key will go up to the parent.

pragma solidity ^0.4.11;

contract btree{
    uint root = 0; //root pointer index
    uint8 constant t=3; //minimum degree
    struct node{
        uint[2*t-1] keys; // An array of keys
        uint[2*t] child; //child pointers
        bool leaf; //boolean whether it is a leaf node
        uint8 n; // current key number
    }
    
    
    mapping(uint=>node) tree; //storage of tree nodes
    
    function ssindex(uint id,uint lb,uint ub)internal constant returns(uint i,uint j){
        node tt = tree[id];
        i=0;
        
        while(i<tt.n && lb>tt.keys[i]){
            i++;
        }
        j=i;
        while(j<tt.n && ub>tt.keys[j]){
            j++;
        }
    }
    
    function range(uint lb, uint ub)constant public returns(uint){
        return _range(root,lb,ub);
    }
    function _range(uint id,uint lb, uint ub) internal constant returns(uint){
        node storage tt = tree[id];
        uint r = 0;
        if(ub<tt.keys[0] || lb>tt.keys[tt.n-1]){
            
        }
        else{
            for(uint i=0;i<tt.n;i++){
                if(tt.keys[i]>=lb && tt.keys[i] <=ub){
                    r += tt.keys[i];
                }
            }
        }
        
        if(tt.leaf==false){
            uint lbi;
            uint ubi;
            (lbi,ubi) = ssindex(id,lb,ub);
            for(i=lbi;i<=ubi;i++){
                r += _range(tt.child[i],lb,ub);
            }
        }
        
        return r;
    }
    
    // Function to search key k in subtree rooted with this node
    function _search(uint id,uint k)internal constant returns(uint ){
        uint i=0;
        while(i<tree[id].n && k > tree[id].keys[i]){
            i++;
        }
        if(tree[id].keys[i]==k){
            return k;
        }
        if(tree[id].leaf==true){
            return 0;
        }
        return _search(tree[id].child[i],k);
    }
    
    function search(uint k)public constant returns(uint){
        return _search(root,k);
    }
    
    function insert_list(uint[] l)public{
        for(uint i=0;i<l.length;i++){
            insert(l[i]);
        }
    }
    // The main function that inserts a new key in this B-Tree
    function insert(uint k) public{
        // If tree is empty
        if(root == 0){
            root = 2*k; //set an identifier of root
            uint[2*t-1] tk;
            uint[2*t] tc;
            tree[root] = node({
                keys: tk,
                child:tc,
                leaf:true,
                n:1
            }); //initilize a new node with specific parameters
            tree[root].keys[0]=k; //insert key k into root
        }
        else{
            //if root is full
            if(tree[root].n==(2*t-1)){
                //create a node node as the parent of the root
                uint s = 2*k;
                uint[2*t-1] tk2;
                uint[2*t] tc2;
                tree[s] = node({
                keys:tk2,
                child:tc2,
                leaf:false,
                n:0
            });
                //set root as the first child of node s
                tree[s].child[0] = root;
                //split the first child of s:root node
                splitchild(k,s,0);
                
                // New root has two children now.  Decide which of the
                // two children is going to have new key
                uint i=0;
                if(tree[s].keys[0]<k){
                    i++;
                }
                insertnonfull(tree[s].child[i],k);
                
                //change root
                root = s;
            }
            else{
                // If root is not full, call insertNonFull for root
                insertnonfull(root,k);
            }
        }
    }
    
    function splitchild(uint k,uint id,uint i)internal{
        uint z = 3*(id+i)*(k+1);//set a new identifier of the node z
        uint y = tree[id].child[i];
        // Create a new node which is going to store (t-1) keys
        // of y
        uint[2*t-1] tk;
        uint[2*t] tc;
        tree[z] = node({
                keys:tk,
                child:tc,
                leaf:tree[y].leaf,
                n:t-1
            });
        // Copy the last (t-1) keys of y to z
        for(uint j=0;j<t-1;j++){
            tree[z].keys[j] = tree[y].keys[j+t];
        }
        // Copy the last t children of y to z
        if(tree[y].leaf == false){
            for(j=0;j<t;j++){
                tree[z].child[j] = tree[y].child[j+t];
            }
        }
        // Reduce the number of keys in y
        tree[y].n = t-1;
        
        // Since this node is going to have a new child,
        // create space of new child
        for(j=tree[id].n;j>=i+1;j--){
            tree[id].child[j+1] = tree[id].child[j];
        }
        // Link the new child to this node
        tree[id].child[i+1]=z;
        
        // A key of y will move to this node. Find location of
        // new key and move all greater keys one space ahead
        if(tree[id].n>0){
            for(j=tree[id].n-1;j>=i&&j>=1;j--){
            tree[id].keys[j+1] = tree[id].keys[j];
            }
            if(j==0){
                tree[id].keys[j+1] = tree[id].keys[j];
            }
        }
        
        // Copy the middle key of y to this node
        tree[id].keys[i] = tree[y].keys[t-1];
        // Increment count of keys in this node
        tree[id].n = tree[id].n + 1;
    }
    
    // A utility function to insert a new key in this node
    // The assumption is, the node must be non-full when this
    // function is called
    function insertnonfull(uint id,uint k)internal{
        node storage tt = tree[id];
        // Initialize index as index of rightmost element
        uint i = tt.n-1;
        // If this is a leaf node
        if(tt.leaf == true){
            // The following loop does two things
            // a) Finds the location of new key to be inserted
            // b) Moves all greater keys to one place ahead
            while(i>=1 && tt.keys[i]>k){
                tt.keys[i+1] = tt.keys[i];
                i--;
            }
            if(i==0&&tt.keys[i]>k){
                // Insert the new key at found location
                tt.keys[i+1] = tt.keys[i];
                tt.keys[i] = k;
            }else{
                // Insert the new key at found location
                tt.keys[i+1] = k;
            }
            
            tt.n = tt.n+1;
        }
        else{// If this node is not leaf
            // Find the child which is going to have the new key
            while(i>=1 && tt.keys[i] > k){
                i--;
            }
            if(i==0 && tt.keys[i]>k){
                // See if the found child is full
                if(tree[tt.child[i]].n==(2*t-1)){
                    // If the child is full, then split it
                    splitchild(k,id,i);
                    // After split, the middle key of C[i] goes up and
                    // C[i] is splitted into two.  See which of the two
                    // is going to have the new key
                    if(tt.keys[i]<k)
                        i++;
                }
                insertnonfull(tt.child[i],k);
            }
            else{
                // See if the found child is full
                if(tree[tt.child[i+1]].n==(2*t-1)){
                    // If the child is full, then split it
                    splitchild(k,id,i+1);
                    // After split, the middle key of C[i] goes up and
                    // C[i] is splitted into two.  See which of the two
                    // is going to have the new key
                    if(tt.keys[i+1]<k)
                        i++;
                }
                insertnonfull(tt.child[i+1],k);
            }
        }
    }
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,287评论 0 10
  • 中国银行天津分行招聘实习生、管理培训生招聘人数:10人要求:全日制大学本科及以上学历、大三以上年级每周确保3天以上...
    职道达人阅读 131评论 0 0
  • 2017年10月13日 第一周的周末到来,那种想马上放松的心情立马就蹦出来,就像是囚犯突然获得自由想马上享受一般。...
    Me0327阅读 156评论 0 0
  • 这几天在路上移动办公,非常怀念当年写的一款工具JsonFormatter,不过当时是在win下做android和x...
    a_mean阅读 1,187评论 4 12