使用JS 编写BTree

在学习MySql内部机制过程中,了解到BTree和BTree+ 的原理,作为多年的程序员一时手痒难耐,决定自己也写一个简易版的BTree 来自我提升。

介绍不多讲,反正网上一大堆,我是完全按照下图的树拆解流程图来做:


BTree 图解.gif

预录入值:

const checked_list=[6,10,4,14,5,11,15,3,2,12,1,7,8,8,6,3,6,21,5,15,15,6,32,23,45,65,7,8,6,5,4];

代码如下:

<script>
/*
key
value
parent_pointer
prev_pointer
next_pointer
sub_prev_pointer
sub_next_pointer
*/

//树
let tree={};
//节点id,自增
let node_auto_increment_id=new Date().getTime();
//定义一个叶子最大宽度
const NODE_WIDTH=3;

//测试校验生成的树是否符合规则。
function toString(node,key=0,index=1,arrange=0,output=true){
    node=firstNode(node);
    let strings=[];
    while(node){
        strings.push(`父层:${key}:第${index}层的${arrange==0?"":(arrange==1?"左边":"右边")}的KEY为:${node.key}`);
        if(node.sub_prev_pointer){
            strings=strings.concat(toString(node.sub_prev_pointer,node.key,index+1,1,false));
        }

        if(node.sub_next_pointer){
            strings=strings.concat(toString(node.sub_next_pointer,node.key,index+1,2,false));
        }
        node=node.next_pointer;
    }
    if(output && strings.length>0){
//        console.log(`第${index}层的属于KEY:${key}的${arrange==0?"":(arrange==1?"左边":"右边")}键值:${strings.join(',')}`);            
        let t=1;
        for(const str of strings){
            if(str.indexOf('左')>-1){
                console.warn(str+'           '+t);
            }
            else{
                console.log(str+'            '+t);
            }
            t++;
        }
    }
    if(!output)
        return strings;
}

//初始化
function init(num,value){
    tree=createNode(num,value);
}

//检测当前层的节点是否超出最大叶子宽度
function outOfNodeWidth(check_node){
    let width=1;
    let prev_pointer=check_node.prev_pointer;
    let next_pointer=check_node.next_pointer;
    while(prev_pointer){
        prev_pointer=prev_pointer.prev_pointer;
        width++;
    }
    while(next_pointer){
        next_pointer=next_pointer.next_pointer;
        width++;
    }
    return width>NODE_WIDTH;
}

//获取当前层第一个节点
function firstNode(check_node){
    while(true){
        if(!check_node.prev_pointer)break;
        check_node=check_node.prev_pointer;
    }
    return check_node;
}

//获取当前层指定位置的节点
function findLevelNode(check_node,index){
    check_node=firstNode(check_node);
    for(let i=0;i<index;i++){
        if(check_node.next_pointer)
            check_node=check_node.next_pointer;
        else
            break;
    }
    return check_node;
}

//创建一个新的临时节点
function createNode(key,value){
    node_auto_increment_id++;
    return {
        id:node_auto_increment_id,
        key,
        value,
        prev_pointer:null,
        next_pointer:null,
        sub_prev_pointer:null,
        sub_next_pointer:null,
    };
}

//把新节点填充进指定某层
function fillInLevel1Node(check_node,new_node){
    check_node=firstNode(check_node);
    //从第一个节点开始,去进行插入。
    let rt_node=check_node;
    //新节点居然已经在同一层有出现
    
    while(rt_node){
        if(rt_node.id==new_node.id){
            return;
        }
        rt_node=rt_node.next_pointer;
    }

    rt_node=check_node;
    while(rt_node){
        if(rt_node.sub_next_pointer && rt_node.sub_next_pointer.id==new_node.id){
            rt_node.sub_next_pointer=null;
        }
        if(rt_node.sub_prev_pointer && rt_node.sub_prev_pointer.id==new_node.id){
            rt_node.sub_prev_pointer=null;
        }        
        rt_node=rt_node.next_pointer;
    }
    
    while(true){
        if(new_node.key<check_node.key){
            if(check_node.prev_pointer){
                check_node.prev_pointer.next_pointer=new_node;
            }
            new_node.prev_pointer=check_node.prev_pointer;
            new_node.next_pointer=check_node;
            check_node.prev_pointer=new_node;
            return;
        }
        if(check_node.next_pointer){
            check_node=check_node.next_pointer;
        }
        else{
            break;
        }
    }
    check_node.next_pointer=new_node;
    new_node.prev_pointer=check_node;

}

//添加节点,对外暴露的方法
function appendNode(new_key,value) {
    
    let new_node=createNode(new_key,value);
    let check_node=firstNode(tree);
    let parent_nodes=[];
    //找出符合的节点
    while(check_node){
        if(check_node.key<new_node.key){
            if((!check_node.next_pointer || check_node.next_pointer.key>=new_node.key) && check_node.sub_next_pointer){
                parent_nodes.push(check_node);
                check_node=check_node.sub_next_pointer;
            }
            else if(check_node.next_pointer){
                check_node=check_node.next_pointer;
            }
            else{
                break;
            }
        }
        else{
            if(check_node.sub_prev_pointer){
                parent_nodes.push(check_node);
                check_node=check_node.sub_prev_pointer;
            }
            else{
                break;
            }
        }
    }

    //把新节点插入到这个平层
    fillInLevel1Node(check_node,new_node);
    
    //如果当前层的节点数已经超过节点宽了。
    //那么就需要把节点往上摆
    while(check_node){
        //如果平层节点没有超出总值,那么就可以跳出
        if(!outOfNodeWidth(check_node)){
            break;
        }
        const parent_node= parent_nodes.pop();
        //如果超出了,那就要挤出中间节点做父节点,然后一直往上检查。
        const pop_check=findLevelNode(check_node,Math.floor(NODE_WIDTH*0.5));
        if(pop_check.prev_pointer){
            pop_check.sub_prev_pointer=pop_check.prev_pointer;
            pop_check.prev_pointer.next_pointer=null;
            pop_check.prev_pointer=null;
        }
        if(pop_check.next_pointer){
            pop_check.sub_next_pointer=pop_check.next_pointer;
            pop_check.next_pointer.prev_pointer=null;
            pop_check.next_pointer=null;
        }

        //第一次拆分时是没有根节点的,有根节点就代表属于第二次拆分
        if(parent_node && parent_node.id!=pop_check.id){
            fillInLevel1Node(parent_node,pop_check);
            check_node=parent_node;
        }
        else if(!parent_node){
            tree=pop_check;
        }
    }
}

function searchNode(key,pos_node=null,debug=false){
    let check_node=pos_node;
    if(!check_node){
        check_node=firstNode(tree);
    }
    let link_count=0;
    let node_link_count=0;
    while(true){
        if(check_node.key==key && (pos_node==null || check_node!=pos_node)){
            if(debug)console.log(`一共进行了${link_count}次指针移动,${node_link_count}层下探。`);
            return check_node;
        }
        //如果KEY是小于当前NODE的话,那就可能要往左下探,
        if(key<check_node.key){
            if(!check_node.sub_prev_pointer){
                if(debug)console.log(`一共进行了${link_count}次指针移动,${node_link_count}层下探。`);
                return null;
            }
            else{
                link_count++;
                node_link_count++;
                check_node=check_node.sub_prev_pointer;
                check_node=firstNode(check_node);
            }
        }
        //剩下就是大于当前NODE的情况,
        else if(check_node.next_pointer){//如果有右层节点
            //如果小于右层节点,则需要往下探
            if(key<check_node.next_pointer.key){
                if(check_node.sub_next_pointer){
                    link_count++;
                    node_link_count++;
                    check_node=check_node.sub_next_pointer;
                    check_node=firstNode(check_node);
                }
                else{
                    if(debug)console.log(`一共进行了${link_count}次指针移动,${node_link_count}层下探。`);
                    return null;
                }
            }
            else if(key>check_node.next_pointer.key){  //如果比右边的node 还要大的话,则要继续往右探
                link_count++;
                check_node=check_node.next_pointer;
            }
            else{
                if(debug)console.log(`一共进行了${link_count}次指针移动,${node_link_count}层下探。`);
                return check_node.next_pointer;
            }
        }
        //如果没有右层节点,则检查有没有下层节点
        else if(check_node.sub_next_pointer){
            link_count++;
            node_link_count++;
            check_node=check_node.sub_next_pointer;
            check_node=firstNode(check_node);
        }
        //如果都没有
        else{
            if(debug)console.log(`一共进行了${link_count}次指针移动,${node_link_count}层下探。`);
            return null;
        }
    }
    console.log(`一共进行了${link_count}次指针移动,${node_link_count}层下探。`);
    return null;
}
</script>

按照图片,编写输出结果:

const checked_list=[6,10,4,14,5,11,15,3,2,12,1,7,8,8,6,3,6,21,5,15,15,6,32,23,45,65,7,8,6,5,4];

let offset=0;

//然后一直插入新节点
for(const checked of checked_list){
    if(offset==0){
        init(checked,`val:${checked}:${offset}`);
    }
    else{
        appendNode(checked,`val:${checked}:${offset}`);
    }
    offset++;
}

//把树的数据打印出来
//toString(tree);
//搜索某一个节点:例如21
//searchNode(21);

写好后,也挺喜欢折腾一下自己,验证使用了BTree后,与普通的List 枚举性能可以相差多大。生成10万个随机系数,并且做好排序,进行10万次搜索,代码如下:

console.log('配置初始化完成,开始进行比较......');

let findAnwserCount=0;
let start_date=new Date().getTime();
for(let i=0;i<load_count;i++){
    let random=Math.floor(Math.random()*(load_count-1)+1);
    let key=start_id+random;
    
    if(searchNode(key,null,false)){
        findAnwserCount++;
    }
}
let end_date=new Date().getTime();

console.log(`通过使用BTree方式的搜索运行完${load_count}次后,找到的总数:${findAnwserCount},总消耗时间:${(end_date-start_date)}毫秒`);


start_date=new Date().getTime();
findAnwserCount=0;
for(let i=0;i<load_count;i++){
    let random=Math.floor(Math.random()*(load_count-1)+1);
    let key=start_id+random;
    for(const item of listItem){
        if(item.key==key){
            findAnwserCount++;
            break;
        }
    }    
}
end_date=new Date().getTime();

console.log(`通过使用List方式的搜索运行完${load_count}次后,找到的总数:${findAnwserCount},总消耗时间:${(end_date-start_date)}毫秒`);

最后输出结果:

所有配置准备初始化......
配置初始化完成,开始进行比较......

通过使用BTree方式的搜索运行完100000次后,找到的总数:100000,总消耗时间:33毫秒

通过使用List方式的搜索运行完100000次后,找到的总数:100000,总消耗时间:5734毫秒
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容