查找

第四章 查找

4.1 查找概论

  1. 查找表:同一类型的数据构成的集合
  2. 关键字:数据元素种,某个数据项的值
  3. 静态查找表:只做查找操作
  4. 动态查找表:在查找的同时,做新增或删除操作

4.2 折半查找

  1. 折半查找有前提:
    (1)线性表中的记录必须是关键字有序
    (2)线性表必须采用顺序存储结构(不能是链表)

    /** 二分查找
     * @param arr  : 带查找数组
     * @param n : arr[0]不做存储,待查数据产品那个arr[1]开始
     * @param key :待查关键字
     * @return :返回角标
     */
    int binerySearch(int *arr,int n,int key){
        int low = 1; //从arr[1]开始查找
        int high = n;
        int mid;
        while(low <= high){
            mid = (low + high)/2;
            if(key < arr[mid]){
                high = mid-1;
            }else if(key > arr[mid]){
                low = mid + 1;
            }else{
                return mid;
            }
        }
        return -1;
    }
    

4.3 线性索引查找

  1. 索引:把关键字与对应记录相关联的过程
    (1)现行索引就是将索引项集合组织为现行结构
    (2)现行索引结构称作索引表
    (3)下面介绍3种索引表:稠密索引,分块索引,倒排索引

  2. 稠密索引
    对数据表中的i每一项都做索引记录,使得索引表中的记录条数和数据表中的记录条数一样。

  3. 分块索引:
    对数据项分块,使得快内数据无序,块间数据有序。索引表只对每块进行索引。

  4. 倒排索引:
    搜索引擎的索引。

4.4 二叉排序树

  1. 二叉查找树:
    又称二叉排序树,它是一个二叉树,具有如下性质:左子树结点上的值均小于根节点的值。右子树的值均大于根节点的值。
    ![image_1arpvjgbu18jrqfbmf5187t1gba9.png-58.4kB][11]

  2. 二叉排序树的递归查找

    typedef struct BiTNode{
        struct BiTNode *lchild, * rvhild;
        int data;
    }BiTNode,*BiTree;
    
    /**
     * 二叉排序树查找操作
     * @param node : node所链接的树
     * @param key :关键字值
     * @param f : 指针f指向node结点的双亲。初始调用值为NULL
     * @param p :查找成功时,p指向该数据结点;查找失败时,指向查找路径上最后访问的节点。初始时,p是NULL
     * @return
     */
    int serachBST(BiTree node,int key,BiTNode *f,BiTree *p){
        if(node == NULL){
            * p =f;
            return -1;
        }else if(key == node->data){
            *p = node;
            return 0;
        }else if(key < node->data){
            return serachBST(node->lchild,key,node,p);
        }else{
            return serachBST(node->rvhild,key,node,p);
        }
    }
    
    /**
     * insertBST(tree,93)
     * @param tree
     * @param key
     * @return
     */
    int insertBST(BiTree *tree,int key){
        BiTree  p, s;
        if(! serachBST(*tree,key,NULL,&p)){   // 查找不成功就开始插入将诶点
            s = (BiTree) malloc(sizeof(BiTNode));
            s->data = key;
            s->lchild = s->rvhild = NULL;
    
            if(p == NULL)   // 开始没有根节点,创建s结点作为根节点
                * tree = s;
            else if (key  < p->data)
                p->lchild = s;   // s作为做结点插入
            else
                p->rvhild = s;  // s作为右结点插入
            return 1;
        }else{
            return -1;   // 树中已经有关键字的结点,无需插入
        }
    }
    
    int main() {
        int a[10] = {1,2,45,234,12,6,78,123,43,111};
        BiTree  tree ;
        for (int i = 0; i < 10; i++) {
            insertBST(&tree ,a[i]);
        }
    }    
    
  3. 二叉排序树的节点删除
    (1)要删除的节点只有左子树/右子树
          直接删除结点,并把其左子树/右子树代替删除结点的位置
    (2)要删除的结点既有左子树,又有右子树
          1)用二叉排序树书中该结点的直接前驱和直接后继元素代替需要删除的结点的位置
          2)把替代元素的位置用其子树代替(此时替代元素只有一个子树,因为替代元素是最右结点或最左结点)
    eg:要删除结点47,用他的前驱和后继结点
    ![image_1arq5om701ia7728kn7vjn1uh416.png-148.6kB][12]

    int deleteNode(BiTree *node);
    /**
     * 删除二叉排序树的结点
     * @param tree 
     * @param key 
     * @return 
     */
    int deleteBST(BiTree *tree,int key){
        if(* tree == NULL)
            return -1;
    
        if (key == (*tree)->data)
            return deleteNode(tree);
        else if(key < (*tree)->data)
            return deleteBST(&(*tree)->lchild,key);
        else
            return deleteBST(&(*tree)->rvhild,key);
    }
    /**
     * 链接断链
     * @param node 
     * @return 
     */
    int deleteNode(BiTree *node){
        BiTree tmp,s;    // tmp记录被删除的结点
        if((*node)->rvhild == NULL) {   // 右子树为空,只要链接左子树
            tmp = *node;
            *node = (*node)->lchild;
            free(tmp);
        }else if((*node)->lchild == NULL){  // 左子树为空,只要链接右子树
            tmp = *node;
            *node = (*node)->rvhild;
            free(tmp);
        }else{               // 被删除结点既有左子树又有右子树
            tmp = *node;
            s = (*node)->lchild;
            while(s->rvhild != NULL){   // 找到左子树中的最右结点,就是被删除结点的前驱结点,把该结点代替删除结点的位置
                tmp = s;          // tmp指向s的前驱结点
                s = s->rvhild;   // s指向被删除结点左子树的最右结点
            }
            (*node)->data = s->data;
            if(tmp != *node)
                tmp->rvhild = s->lchild;
            else
                tmp->lchild = s->lchild;
            free(s);
        }
        return 1;
    }
    

(3)二叉排序树的出现,是为了查找某个结点的时候,比较的次数很少。但是设想一种极端情况,带查找的数据是一个从小到大的数组,构建成二叉排序树后,是一个极端的右斜树。此时,虽然是一个二叉排序树,但是却不够“平衡”,层次太深。我们希望一个二叉排序树的深度像一个完全二叉树一样,为\(log_2n\),那么其查找的时间复杂度就为\(O(logn)\)。下面就开始介绍如何把一个二叉树变成一个平衡的二叉排序树

4.5 平衡二叉树(AVL树)

  1. 平衡二叉树:一种二叉排序树结构,其左右子树的深度最多相差1.所以,判断一个二叉树是否是平衡二叉树,首先看其是否是二叉排序树,其次看他的左右子树高度差。
  2. 左子树高度-右子树高度的值 = 平衡因子(平衡因子的取值只能是-1,1,0)

4.6 B树与B+树

  1. B树
    (1)B树和B+树都是完全平衡查找树,这种平衡,要求树种的每个结点的平衡因子都是0
    (2)m阶B树(也称m路B树)的性质
          a)所谓m阶或者m路,是说每个结点最多有m个孩子结点。(含有m个分支的意思)
          b)为了增加B树查找的速度,他规定了每个结点的关键字个数要多,多个关键字之间不是相邻的整数,要有大小间隔。
                这些间隔就指向了不同范围的孩子结点。(如下图,65与67不相邻)。
          ![image.png-6.8kB][13]
          c)B数的节点结构:由三部分组成,开头是节点中关键字个数,然后依次交替的是指向孩子节点的指针与关键字的值
                其中,关键字ki的值 > 指针p(i-1)所指向孩子节点的所有关键字的值。
          ![关键字个数n | 指针p0|][14]
          d)B树规定:m路分支的B树,是说节点最多有m个分支,最少也要有\(\frac{m}{2}\)向上取整个分支。
                也就意味着:m路分支的B树,其节点中的关键字最少有\(\frac{m}{2}\)向上取证-1个,最多有m-1个关键字
          e)根节点例外于d中的条件,根节点最少只要2个分支即可,也就意味着,根节点可以只有一个关键字
    (3)B树的查找过程
          B树是查找硬盘数据的数据结构,他只能只有一个节点在内存中。
          当要查找的数不在内存节点的记录中时,会找到节点中指向该范围的孩子节点的硬盘地址。
          然后把这个地址上的节点加载到内存中进行查找。
    (4)B树的最底层叶子节点,是NULL指针。他没有任何关键字。只是作为查找失败的标志:
             只要查找到B树的叶子节点,证明记录不在B树中
             最底层非叶子结点,有关键字,称作终端节点
    (5)B树的高度范围:
             若B树中存储的总关键字个数为n,且最多有m个分支(m阶)。则其高度满足公式:
             \(【log_m(n+1)】 \leq h \leq 【log_\frac{m}{2}(\frac{(n+1)}{2})+1】\)
             eg:3阶B树,共有8个关键字,其高度为\(log_39\leq h \leq 【log_1.5(\frac{9}{2})+1】\)
    (6)B树的插入:(底层插,提中间点为父母)
             a)B树的关键字插入,均在最底层的非叶子结点中
             b)根据大小,找到位置插入。若发现插入后,节点的关键字个数 > m-1个。则把节点中中间的关键字,提到父节点中。然后拆分左右记录,挂到新增父节点关键字的两边。
    (7)B树关键字删除
             a)过删除的是非终端节点(非底层),删除关键字后,把两边指针指向的孩子节点合并
             b)若删除的是终端节点:先直接删除,若删除后发现关键字个数太少,则向兄弟节点借关键字。若兄弟节点关键字不够借,则把双亲结点的关键字下落到终端节点上,再把下落后形成的节点挂在原节点的兄弟双亲结点上。一层层的双亲结点下落,重新调整树形。
    (8)B树索引文件的例子:
             1)文件的每一行称作文件的每一个记录。B树索引文件,就是对这些行进行索引
             2)文件中的每一行会压缩成一个key(用来排序)和一个指向指针(指向文件记录真实内容)的关键字,存放在B树的节点中。
             3)B树索引后形成的节点,最大只能占一个内存页。(这样,从磁盘拿节点时不必换入多个页)
             4)B树节点开头的关键字个数字段,占2字节

  2. B+树
    (1)m个分支的B+树节点,有m个关键字
    (2)m阶B+树:B+树的所有节点最多有m个分支。最少有\(\frac{m}{2}\)个分支
    (3)B+树的叶子节点中,包含所有关键字。叶子节点的一层用指针链接形成一个链表,便于顺序查找所有关键字
    ![image.png-46.4kB][15]

posted @ 2016-09-05 10:37  lj72808up  阅读(181)  评论(0编辑  收藏  举报