B-树的学习笔记与C实现及 简单的RPC编程实践——HelloWorld的实现

(学习的主要对象是《算法导论》上B-树章节)

应用环境:

  辅存和主存的矛盾,主存只能维持有限的页数,其他页存于辅存上,使用时调入内存。

 

B树的定义:

  是一个具有如下性质的有根树:

  (1)每个结点x有以下域:

    (a) n[x],存放结点x的关键字数;

    (b) n[x]个关键字本身,以非降序存放;

    (c) leaf[x],1代表x是叶子,0代表x是内结点。

  (2)每个内结点包含n[x]+1各指向其子女的指针。叶结点对这个域没有定义。

  (3)各关键字对其各子树关键字范围进行分隔。

  (4)每个叶结点深度相同。

  (5)每个结点包含的关键字有上界和下界,用t表示最小度数,则有

    (a)每个非根结点至少t-1各关键字。每个非根的内结点至少t个子女。若树非空,则根至少有一个关键字。

    (b)每个结点包含至多2t-1个关键字。因此一个内结点至多可以有2t个子女。

相关算法简述:

  搜索算法:

    类似于二叉查找树。

  分裂算法:

    对于关键字达到2t-1时的满结点(可能)将做的操作。简述为,将中间关键字提升至父结点,同时将原结点分裂成两个。

  插入算法:

    利用了分裂算法,在插入时,逐步下降到要查入的结点,沿途对满结点进行分裂,若不满,直接插入即可,用辅助算法insert_nonful实现。

  删除算法:

    比较复杂,逐步下降时,对于将来不满足关键字数大于t-1的结点做出调整。调整有多种形式:合并两个关键字为t-1的兄弟;下降父结点至一个关键字所在子树的子结点,同时上升它的一个关键字大于等于t的兄弟结点的关键字至父结点;对于逐步下降后含关键字的叶子,直接删除关键字。

    这个简述看上去比较混乱,直接理解算法即可,或者参考算法导论上相关叙述。。

 

心得体会:

  与红黑树相反,B-树的插入和删除在下降时进行调整,而前者是先操作,然后逐步向上调整使其满足性质。因此B-树不需要指向父亲的指针。

  同时,B-树由于是在下降过程中调整,因此它不能直接对非根结点调用delete,这样会导致调整不完全。

  而且对于B-树关键字和子结点指针操作时,下标比较容易引起混乱。有一个小技巧需要可以减少混乱:下标为i的关键字,其左边的孩子下标也是i。不过编写算法时最好画个示意图,比较清楚。

 

以下是自己编写的C语言版本的B-树,其中各个操作已经过验证和调整,暂未发现遗留的bug。如果想实现自己的测试过程,调整main函数里面的操作即可。

复制代码
#include <stdio.h>
#include <stdlib.h>

#define BT_T 3//B树的度    

struct bnode *bt_search(struct bnode *, char);
struct bnode *bt_creat();
int bt_split_child(struct bnode *, int, struct bnode *);
struct bnode *bt_insert(struct bnode *, char);
int bt_insert_nonful(struct bnode *, char);
int bt_delete(struct bnode*, char);

struct bnode {
    int num;
    int leaf; //1 is leaf
    char value[2*BT_T -1];
    struct bnode *child[2*BT_T];
};



struct bnode *bt_search(struct bnode *p, char k) {
    int i;
    i = 0;
    while ((i < p->num) && (k > p->value[i])) 
        i++;

    if ((i<=p->num) && (k == p->value[i])) {
        printf("[search]%c's num is %d\n",k,i);
        return p;
    }
    if (p->leaf) {
        printf("not found.\n");
        return NULL;
    }
    else {
        printf("DISK-READ: c%d\n", i); //DISK-READ()
        return bt_search(p->child[i],k);
    }
}

struct bnode *bt_creat() {
    struct bnode *p;
    p = (struct bnode *)malloc(sizeof(struct bnode));
    p->leaf = 1;
    p->num = 0;
    printf("DISK-WRITE: root of new T\n");
    return p;
}

int bt_split_child(struct bnode *x, int i, struct bnode *y) {
    int j;
    struct bnode *z;
    z = (struct bnode *)malloc(sizeof(struct bnode));
    printf("in split\n");
    z->leaf = y->leaf;
    z->num = BT_T -1 ;
    for (j=0;j<BT_T-1;j++)
        z->value[j] = y->value[j+BT_T];
    if (!(y->leaf))
        for(j=0;j<=BT_T-1;j++) //there was a bug.
            z->child[j] = y->child[j+BT_T];
    y->num = BT_T -1;
    for(j=x->num +1;j>=i+1;j--)//
        x->child[j+1] = x->child[j];
    x->child[i+1] = z;
    for(j=x->num -1;j>=i-1;j--)
        x->value[j+1] = x->value[j];
    x->value[i] = y->value[BT_T-1];
    x->num++;
    printf("DISK-WRITE(y):in split\n");
    printf("DISK-WRITE(z):in split\n");    
    printf("DISK-WRITE(x):in split\n");
}

struct bnode* bt_insert(struct bnode *x, char k) {//x is root
    struct bnode *s;
    if (x->num == 2*BT_T-1) {
        s = (struct bnode *)malloc(sizeof(struct bnode));
        s->leaf = 0;
        s->num = 0;
        s->child[0] = x;
        bt_split_child(s,0,x);
        bt_insert_nonful(s,k);
        return s;
    }
    else {
        bt_insert_nonful(x,k);
        return x;
    }
}

int bt_insert_nonful(struct bnode *x, char k) {
    int i;
    struct bnode *p;
    i = x->num-1;
    if (x->leaf)    {
        while((i>=0)&&(k<x->value[i])) {
            x->value[i+1] = x->value[i];
            i--;
        }
        x->value[i+1] = k;
        printf("(!!!!)%c %d\n",x->value[i+1],i+1);
        x->num = x->num+1;
        printf("DISK-WRITE(x):in bt_insert_nonful\n");
    }
    else
    {
        while((i>=0)&&(k<x->value[i-1]))
            i=i--;
        i++;
        printf("DISK-READ(c%d[x]):in insert_nonful\n",i);
        p = x->child[i];
        if (p->num == (2*BT_T - 1)) {
            bt_split_child(x,i,p);
            if (k>x->value[i])
                i++;
        }
        bt_insert_nonful(x->child[i],k);//there was a bug: bt_insert_nonful(p,k);
    }
    return 1;
}

int bt_delete(struct bnode* x, char k) {
    int i,j,lnum,rnum;
    struct bnode *p;
    i=0;
    while ((i<x->num) && (k>x->value[i]))
        i++;
    if ((i<=x->num) && (k == x->value[i])) {
        if(x->leaf) {//情况(1),不能对叶结点的指针直接调用bt_delete(),这样相当于跳过了情况(3)
            for(;i<x->num-1;i++) 
                x->value[i] = x->value[i+1];
            x->num --;
            return 1;
        }
        else {    //情况2
            lnum = x->child[i]->num;
            rnum = x->child[i+1]->num;
            if (lnum >= BT_T) {
                x->value[i] = x->child[i]->value[lnum-1];
                bt_delete(x->child[i],x->value[i]);
                return 2;
            }
            else if (rnum >= BT_T) {
                x->value[i] = x->child[i+1]->value[0];
                bt_delete(x->child[i+1],x->value[0]);
                return 2;
            }
            else {
                //合并k两个孩子结点,并把k下降到这个结点
                x->child[i]->value[BT_T-1] = k;
                for(j=0;j<BT_T-1;j++)    {
                    x->child[i]->value[BT_T+j] = x->child[i+1]->value[j];
                    x->child[i]->child[BT_T+j] = x->child[i+1]->child[j];
                    }
                x->child[i]->num = 2*BT_T -1;
                
                //修改x,使其原k右边的孩子与x断开
                for(j=i;j<x->num-1;j++)    {
                    x->value[j] = x->value[j+1];
                    x->child[j+1] = x->child[j+2];
                    }
                x->num--;

                //递归删除k
                bt_delete(x->child[i],k);
                return 2;
            }
        }
    }
    else {
        if(bt_search(x->child[i],k) == NULL) {
            printf("[delete]not found!");
            return 0;
            }
        p = x->child[i];
        if (x->child[i]->num >= BT_T) {
            bt_delete(x->child[i],k);
            return 3;
            }
        else if ((i>0)&&(x->child[i-1]->num >= BT_T)) {//情况3a其1,左兄弟可用
            for(j=BT_T-2;j>=0;j--)
                p->value[j+1] = p->value[j];
            p->value[0] = x->value[i-1];
            
            for(j=BT_T;j>=1;j--)
                p->child[j] = p->child[j-1];
            p->child[0] = x->child[i-1]->child[x->child[i-1]->num];
            x->value[i-1] = x->child[i-1]->value[x->child[i-1]->num-1];
            x->child[i-1]->num--;
            bt_delete(x->child[i],k);
            return 3;
            }        
        else if ((i<x->num-1)&&(x->child[i+1]->num >= BT_T)) {//情况3b其2,右兄弟可用
            p->num++;
            p->value[p->num-1] = x->value[i];
            p->child[p->num] = x->child[i+1]->child[0];
            x->value[i] = x->child[i+1]->value[0];

            p=x->child[i+1];//为了便于编码

            for(j=0;j<p->num-1;j--)
                p->value[j] = p->value[j+1];
            
            for(j=0;j<p->num;j--)
                p->child[j] = p->child[j+1];
            p->num--;
            bt_delete(x->child[i],k);
            return 3;
            }
        else if (i>0) {//情况3b其1,与左兄弟合并
            x->child[i-1]->value[BT_T-1] = x->value[i-1];
            for(j=0;j<BT_T-1;j++)
                x->child[i-1]->value[BT_T+j] = x->child[i]->value[j];
            for(j=0;j<=BT_T-1;j++)
                x->child[i-1]->child[BT_T+j] = x->child[i]->child[j];
            for(j=i-1;j<=x->num-2;j++) {
                x->value[j]=x->value[j+1];
                x->child[j+1] = x->child[j+2];
                }
            x->num--;
            x->child[i-1]->num = 2*BT_T -1;
            bt_delete(x->child[i-1],k);
            return 3;
            }
        else if (i<x->num-1) {//情况3b其2,与右兄弟合并
            x->child[i]->value[BT_T-1] = x->value[i];
            for(j=0;j<=BT_T-1;j++)
                x->child[i]->value[BT_T+j] = x->child[i+1]->value[j];
            for(j=0;j<=BT_T;j++)
                x->child[i]->child[BT_T+j] = x->child[i+1]->child[j];
            for(j=i;j<x->num-1;j++) {
                x->value[i] = x->value[i+1];
                x->child[j+1] = x->child[j+2];
            }
            x->num--;
            x->child[i]->num = 2*BT_T -1;
            bt_delete(x->child[i],k);
            return 3;
            }
    }
}        

int main() {
    struct bnode *p,*root;
    char a;
    root = bt_creat();
    for (a='A';a<='Z';a++) 
        if ((a!='H')&&(a!='I')) 
            root = bt_insert(root,a);
 
    bt_delete(root,'M');
    bt_search(root,'N');    
    bt_search(root,'O');

    return 1;
}


  
  
近期课程的作业需要用到RPC编程,除了课堂上学到的知识,还得亲自动手。打算先写个简单的HelloWorld练习一下,顺便复习一下学到的知识。
  RPC意为远程过程调用协议(Remote Procedure Call Protocol)。编制好单机应用程序,然后划分为两个或多个程序片,加入通信协议使得每片可以在单独的计算机上运行。从一个程序片调用另一个程序片的过程称为远程过程调用,即RPC。它是一个C/S模型,调用程序称为rpc client,被调用程序片称为rpc server。
  对于RPC的编程过程可以简化如下:

1.构建解决问题的常规应用程序;

2.选择一组过程形成远程程序,以便将远程程序转移到远程机器中,通过这种方法将程序分解;

3.为远程程序编写RPC界面(xxx.idl),包括远程的名字及其编号,还有对其参数的申明,选择远程程序号和版本号;

4.运行rpcgen检查该界面,如果合法,便生成四个源代码文件:xxx.h(类型说明文件)、xxx_XDR.c(XDR转换例程)、xxx_clnt.c(客户端的stub)以及xxx_svc.c(服务守护过程,服务端的stub) ,这些文件将在客户和服务器程序中使用;

5.为客户端和服务器端编写stub接口例程;

6.编译并链接客户程序。它由四个主要文件组成:去掉了远程过程的程序、客户端的stub(rpc生成)、客户端的stub接口以及XDR过程( rpc生成)。

7.编译并链接服务器程序。它由四个主要文件组成:远程过程组成的程序、服务器的stub(rpc生成)、服务器端的stub接口以及XDR过程( rpc生成)。

8.在远程机器上启动服务器,接着在本机上启动客户。
 
  简单来讲,需要做的主要工作就是编写应用程序并分片,编写接口(规格说明文件),编写stub接口例程,编写主函数。通信协议的实现和XDR数据格式的统一交给rpcgen完成,它会自动生成相关的代码。
  练习的代码的参考: http://wenku.baidu.com/view/58e19446b307e87101f696c6.html这个程序的目的是客户端接收一个参数(设置为HelloWorld),发送给服务器,然后服务器显示自己的提示信息和客户端发来的信息。
 
  调试不是很顺利,最开始时遇到了提示:Cannot register service: RPC: Unable to receive; errno = Connection refused。上网搜索得知,是因为服务器没有开启端口映射的功能,开启portmap就可以了。 
sudo /etc/init.d/portmap restart  当然,我的Linux虚拟机没有安装portmap,需要安装。执行下面的命令:
sudo apt-get install portmap
  此时仍然报错:Cannot register service: RPC:  Authentication error; why = Client credential too weak。这个看着百思不得其解,因为参数设置不是按照参考代码就是自动生成的(只有program编号是根据划分规则修改的),完全不知道错在哪里,查了很多资料也一头雾水。最后看到了一项搜索结果的预览( 点击查看),提到了Fedora7出现这种问题改成超级用户即可。虽然现在虚拟机里用的是Ubuntu11.10,不过还是在运行编译好的文件前加了个sudo。输完密码果然显示需要的结果,这个小练习可以告一段落了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值