美文网首页
六、查找

六、查找

作者: MinoyJet | 来源:发表于2017-08-30 10:03 被阅读0次

    六、查找

    1. 静态查找表

    静态查找表在查找过程中不改变表中数据——不插不删,故采用顺序存储结构。它适用于数据不变动或不常变动的表。根据静态查找表中数据是否按关键字有序,又可分为顺序查找(无序)和折半查找(有序)。

    实现

    //静态查找表类
    template<typename D>class SqTable
    {//带模板的静态查找表类
    protected:
      D *elem;  //存储基址,0 好单元留空
      int length;
    public:
      SqTable()
      {//构造函数
        elem = NULL;
        length = 0;
      }
      ~SqTable()
      {//析构函数
        if (elem != NULL)
          delete[] elem;
      }
      void CreateSeqFromFile(char* FileName)
      {//由数据文件构造静态查找表
        ifstream fin(FileName);
        fin >> length;
        elem = new D[length + 1];
        assert(elem != NULL);
        for(int i = 1; i <= length; i++)
          InputFromFile(fin, elem[i]);
        fin.close();
      }
      void OutputToFile(char* FileName)const
      {//向数据文件输出静态查找表中的所有数据元素
        ofstream fout(FileName);
        if (elem != NULL)
          for(int i = 1; i <= length; i++)
            OutputToFile(fout, elem[i]);
        fout.close();
      }
      int SearchSeq(KeyType k)const
      {//在无序的静态查找表中顺序查找主关键字等于k的数据元素,若找到返回位置,否则返回0
        int i;
        elem[0].key = k;
        for(i = length; !EQ(k, elem[i].key); i--)
        return i;  //找不到时,i 为 0
      }
      int SearchBin(KeyType k)const
      {//在有序的静态查找表中折半查找主关键字等于k的数据元素,若找到返回位置,否则返回0
        int mid, low = 1, high = length;
        while(low <= high)
        {
          mid = (low + high) / 2;
          if EQ(k, elem[mid].key)
            return mid;
          else if LT(k, elem[mid].key)  //小于
            high = mid - 1;
          else  //大于
            low = mid + 1;
        }
        return 0;  //表中不存在返回 0
      }
      bool GetElem(int i, D &e)const
      {//用 e 返回静态查找表中第 i 个元素的值
        if (i < 1 || i > length)
          return false;
        e = elem[i];
        return true;
      }
      void Traverse(void(*visit) (D*))const
      {//按顺序遍历
        for(int i = 1; i <= length; i++)
          visit(&elem[i]);
      }
    };
    

    折半查找在大数据量时很有效。利用 STL 可以对无序数据进行查找。

    2. 静态树表

    如果有序数据的查找概率已知且差别很大,则折半查找并不是最佳的。这种情况可采用静态树表算法:把有序的静态查找表根据被查找的概率生成一棵二叉树,使查找二叉树每个结点的左右子树的概率尽量相等,以此缩短平均查找长度。这棵二叉树称为 “次优查找树” ,可以证明它不是最优的,但是近似最优。

    实现

    //结点数据结构
    struct S
    {
      KeyType key;  //关键字
      int weight;  //权值
      int sw;  //累计权值
    };
    
    //静态树表类
    template<typename D>class SOSTree: public SqTable<D>
    {//带模板并继承 SqTable<D> 的静态树表类
    private:
      void FindSW()
      {//按照有序表中各数据元素的 weight 域累计权值 sw
        if (length > 0)
        {
          elem[0].sw = 0;  //置边界值
          cout << endl << "sw = 0  " ;
          for(int i = 1; i <= length; i++)
          {
            elem[i].sw = elem[i - 1].sw + elem[i].weight;
            cout << setw(6) << elem[i].sw;
          }
        }
      }
      void SecondOptimal(BiTNode<D>* &p, int low, int high)
      {//由有序表递归构造次优查找树 p
        int j, i = low;
        int dw = elem[high].sw + elem[low - 1].sw;
        int min = abs(elem[high].sw - elem[low].sw);
        for(j = low + 1; j <= length; ++j)
          if (abs(dw - elem[j].sw - elem[j-1].sw) < min)
          {
            i = j;
            min = abs(dw - elem[j].sw - elem[j-1].sw);
          }
        p = new BiTNode<D>;
        assert(p != NULL);
        p->data = elem[i];
        if (i ==low)  
          p->lchild = NULL;
        else
          SecondOptimal(p->rchild, low, i-1);
        if (i == high)
          p->rchild = NULL;
        else
          SecondOptimal(p->lchild, i+1, high);
      }
    public:
      BiTNode<D> t;  //采用二叉链表结构的二叉树 t 作为次优查找树
      void CreateSOSTree()
      {//由有序表构造次优查找树 t 
        if (length > 0)
        {
          FindSW();
          SecondOptimal(t.root, 1, length);
        }
      }
      BiTNode<D>* SearchSOSTree(KeyType k)const
      {//查找关键字等于 k 的元素,返回其指针,否则返回空指针
        BiTNode<D>* p = t.root;
        while(p != NULL)
        {
          if EQ(k, p->data.key)
            return p;
          else if LT(k, p->data.key)
            p = p->lchild;
          else
            p = p->rchild;
        }
        return NULL;
      }
    };
    

    3. 哈希表的插入、删除及查找

    哈希表也称 “散列表” ,它是通过计算求得关键字的哈希地址的。如果哈希地址冲突小,在数据量大的情况下查找效率是很高的。

    实现

    //开地址法哈希表
    const int SUCCESS = 1;  //成功
    const int UNSUCCESS = 0;  //不成功
    const int DUPLICATE = -1;  //副本
    const int N = 4;  //hashsize[] 的容量
    int hashsize[N] = {11, 19, 37, 73};  //哈希表容量递增表,一个合适的素数序列
    
    template<typename D>class HashTable;
    {//带模板的开地址法哈希表类
    private:
      D *elem;  //存储基址
      int count, length;
      int sizeindex;  //当前容量
      int *rando;  //随机数数组指针
      int Hash(KeyType key)
      {//一个简单的哈希函数
        return key % length;
      }
      int Hash2(KeyType key)
      {//双散列探查法的第二个哈希函数
        return key % (length-2);
      }
      void Random()
      {//建立伪随机数组(用于随机探查法)
        bool *ra = new bool[length];  //[0] 不用
        rando = new int[length];  //[0] 不用
        int i;
        for(i = 1; i < length; i++)  //设置 ra[i] 初值
          ra[i] = false;  //i 不再随机数数组的标志
    //  srand(time(0));  //设置随机数种子
        for(i = 1; i < length; i++)  //依次给 rando[i] 赋随机值
        {
          do
          {
            rando[i] = rand() % (length - 1) + 1;  //给rando[i]赋值(1 ~ length-1)
            if (!ra[rando[i]])  //伪随机数数组中没有此数
              ra[rando[i]] = true;  //赋值成功
            else
              rando[i] = 0;  //赋值失败
          }while(rando[i] == 0);  //赋值失败则重新赋值
          cout << "rando[" << i << "] = " << rando[i] << endl;
        }
        delete[] ra;
      }
      int d(int i, KeyType key)  
      {//返回第 i 次冲突的增量
        switch(type)
        {
        case 0: return i;  //线性探查法(1, 2, 3, ...)
        case 1: return ((i+1) / 2) * ((i+1) / 2) * (int)pow(-1, i-1);
                //二次探查法(1, -1, 4, -4, 9, -9, ...)
        case 2: return i * Hash2(key);  //双散列探查法
        case 3: return rando[i];  //随机探查法
        default: return i;  //默认线性探查法
        }
      }
      void collision(KeyType key, int &p, int i)
      {//开地址法求得关键字为 key 的第 i 次冲突的地址 p
        p = (Hash(key) + d(i, key)) % length;  //哈希函数加增量后在求余
        if (p < 0)  //求余得到负数(二次探查可能会出现)
          p = p + length;  //保证 p 为非负数
      }
      void RecreateHashTable()
      {//重建哈希表
        int i, len = length;
        D *p = elem;
        sizeindex++;  //增大存储容量为下一个序列数
        if (sizeindex < N)
        {
          length = hashsize[sizeindex];
          elem = new D[length];
          assert(elem != NULL);
          for(i = 0; i < length; i++)
            elem[i].key = EMPTY;  //未填数据的标志
          for(i = 0; i < len; i++)
            if (p[i].key != EMPTY && p[i].key != TOMB)  //在原哈希表[i]有数据
              InsertHash(p[i]);
          delete[] p;
          if (type == 3)
            Random();
        }
      }
    public:
      int type;  //探查法类型
      HashTable()
      {//构造函数,构造一个空的哈希表
        count = 0;  //当前数据元素个数
        sizeindex = 0;  //初始存储容量
        length = hashsize[sizeindex];  //当前哈希表容量
        elem = new D[length];  
        assert(elem != NULL);
        for(int i = 0; i < length; i++)
          elem[i].key = EMPTY;
        cout << "请输入探查法类型(0:线性;1:二次;2:双散列;3:随机):" ;
        cin >> type;
        if (type == 3)
          Random();
        else
          Random = NULL;
      }
      ~HashTable()
      {//析构函数,销毁哈希表
        if (elem != NULL)
          delete[] elem;
        if (type == 3)
          delete[] rando;
      }
      bool SearchHash(KeyType key, int &p, int &c)
      {//在开放定址哈希表中查找关键字为key的元素,以 p 指示待查元素位置,并返回SUCCESS
       //否则以 p 指示插入位置,并返回UNSUCCESS,用 c 记冲突次数,供建表插入时参考
        int c1, tomb = -1;
        p = Hash(key);
        while(elem[p].key == TOMB || elem[p].key != EMPTY && !EQ(key,elem[p].key))
        {//该位置元素已被删除或该位置中填有数据,并且与待查找的关键字不相等
          if (elem[p].key == TOMB && tomb == -1)
          {
            tomb = p;
            c1 = c;
          }
          c++;
          if (c <= hashsize[sizeindex] / 2)
            collision(key, p, c);
          else
            break;
        }
        if EQ(key, elem[p].key)
          return true;
        else
        {
          if (tomb != -1)
          {
            p = tomb;
            c = c1;
          }
          return false;
        }
      }
      int InsertHash(D e)
      {//查找不成功时将数据元素 e 插入到开放定址哈希表中,并返回SUCCESS;查找成功时返回
       //DUPLICATE,不插入元素;若冲突次数过大,则不插入,并重建哈希表,返回UNSUCCESS
        int p, c = 0;
        if (SearchHash(e.key, p, c))
          return DUPLICATE;
        else if (c < hashsize[sizeindex] / 2)
        {
          elem[p] = e;
          ++count;
          return SUCCESS;
        }
        else
        {
          cout << "按哈希地址的顺序遍历重建前的哈希表:" << endl;
          TraverseHash(visit);
          cout << "重建哈希表" << endl;
          RecreateHashTable();
          return UNSUCCESS;
        }
      }
      bool DeleteHash(KeyType key, D &e)
      {//删除关键字等于key的元素,成功返回true,并将该位置关键字设为TOMB;否则返回false
        int p, c;
        if (SearchHash(key, p, c))
        {
          e = elem[p];
          elem[p].key = TOMB;
          --count;
          return true;
        }
        elem
          return false;
      }
      D GetElem(int i)const
      {//返回元素[i]的值
        return elem[i];
      }
      void TraverseHash(void(*visit) (int, D*))const
      {//按哈希地址的顺序遍历哈希表
        int i;
        cout << "哈希地址 0 ~ " << length - 1 << endl;
        for(i = 0; i < length; i++)
          if (elem[i].key != EMPTY && elem[i].key != TOMB)
            visit(i, &elem[i]);
      }
    };
    

    增加了哈希表的删除算法,就要考虑删除数据给插入和查找带来的影响。设置被删除结点的关键字为 TOMB ,在查找过程中找到 TOMB ,并不说明要查找的关键字不存在,还要继续往后查找,否则不能确定哈希表中不存在该关键字;在插入过程中找到 TOMB ,要记下该位置,以便最后将数据插入到此处。

    4. 动态查找表

    动态查找表在查找过程中可改变表中数据,即可插入或删除数据,故一般采用链式存储结构。它适用于数据经常变动的表。之前介绍的二叉排序树、平衡二叉树、伸展树及红黑树都是动态查找表。

    4.1 B 树

    B 树是平衡的 m 路查找树, “B” 表示平衡。

    平衡二叉树的查找效率很高,但在数据量非常大,以至于内存空间不够容纳平衡二叉树所有结点的情况下,就得另辟蹊径。B 树是解决这个问题的一种很好的结构。

    实现

    //B 树的 3 种模板结构
    template<typename D>struct Record
    {//B 树数据结构
      KeyType key;
      D others;
    };
    
    template<typename D>struct BTNode
    {//B 树结点结构
      int keynum;  //关键字个数
      BTNode<D> *parent;
      BTNode<D> *children[m + 1];
      KeyType key[m + 1];  //关键字数组,[0] 未用
      Record<D> *recptr[m + 1];  //数据指针数组,[0] 未用
    };
    
    template<typename D>struct Result
    {//查找结构结构
      BTNode<D> *pt;  //指向关键字所在的 B 树结点
      int i;  //i = 0 - m - 1 ,在 B 树结点中的关键字序号
      bool tag;  //true:查找成功;false:查找失败
    };
    

    B 树的结点结构和前面介绍过的结点结构有一个重要的区别:它不是把整个数据都存放在结点中,而是仅在结点中存放数据的关键字和数据的地址两项。而在结点查找到关键字后,再根据其地址找到数据。当数据所占的存储空间非常大时,这样做的好处是减小了结点所占用的存储空间,也减小了整个 B 树占用的存储空间。

    B 树是 m 路查找树,它的每个结点最多可以有 m-1 个关键字,m 棵子树(m = 2 时即为二叉树,有一个关键字,两颗子树)。子树总比关键字的数量多 1 ,故 key[0] 和 recptr[0] 单元不用,而 children[0] 要用。[m] 单元在正常情况下是不用的,只是在结点的 keynum = m-1 ,又向该结点插入关键字时,临时占用 [m] 单元。然后就要把该结点尽量平均地分裂成两个结点。

    实现

    //B 树类
    template<typename D>class BTree
    {//带模板的 B 树类
    private:
      BTNode<D> *root;
      int s;  //s 为分裂结点的中值,与 B 树的阶 m 有关
      int MinEmpt;  //存 record[] 中具有最小序号的空位置
      Record<D> record[N];  //存放数据的数组
      void DestroyBTree(BTNode<D>* t)
      {//递归销毁 t 为根的 B 树
        if (t != NULL)
        {
          for(int i = 0; i <= t->keynum; i++)
            DestroyBTree(t->children[i]);
          delete t;
          t = NULL;
        }
      }
      int Search(BTNode<D>* p, KeyType k)const
      {//在 p->key[1 - keynum] 中顺序查找 i 使得 p->key[i] <= k <= p->key[i + 1]
        int i = 0, j;
        for(int j = 1; j <= p->keynum; j++)
          if LQ(p->key[j], k)
            i = j;
          else
            break;
          retun i;
      }
      void MoveItim2(BTNode<D>* p, int i, BTNode<D>* q, int j)
      {//将结点 q[j] 中的 key 和 recptr  2项移动到结点 p[i]
        p->key[i] = q->key[j]; 
        p->recptr[i] = q->recptr[j];
      }
      void MoveItim3(BTNode<D>* p, int i, BTNode<D>* q, int j)
      {//将结点 q[j] 中的 3 项移动到结点 p[i]
        p->key[i] = q->key[j]; 
        p->recptr[i] = q->recptr[j];
        p->children[i] = q->children[j];
      }
      void Copy(BTNode<D>* q, int i, Record<D>* r)
      {//将数据地址 r 和关键字 r->key 分别赋给 q->recptr[i] 和 q->key[i]
        q->key[i] = r->key;
        q->recptr[i] = r;
      }
      void Insert(BTNode<D>* q, int i, Record<D>* r, BTNode<D>* ap)
      {//将数据地址 r 和 r->key 分别赋给 q->recptr[i+1] 和 q->key[i+1]
       //q->children[i+1] 指向结点 *ap 
        for(int j = q->keynum; j > i; j--)  //空出 *q[i+1]
          MoveItim3(q, j+1, q, j);
        Copy(q, i+1, r);
        q->children[i + 1] = ap;
        if (ap != NULL)
          ap->parent = q;
        q->keynum++;
      }
      void split(BTNode<D>* q, BTNode<D>* &ap)
      {//将结点 *q 分裂成两个结点,前一半保留在 *q ,后一半移入新生结点 *ap
        ap = new BTNode<D>;
        ap->children[0] = q->children[s];  //结点 *q 的后一半移入结点 *ap
        if (ap->children[0] != NULL)
          ap->children[0]->parent = ap;
        for(int i = s+1; i <= m; i++)
        {
          MoveItim3(ap, i-s, q, i);
          if (ap->children[i-s] != NULL)
            ap->children[i-s]->parent = ap;
        }
        ap->keynum = m - s;
        q->keynum = s - 1;  //前一半保留,修改 *q 的关键字个数
      }
      void NewRoot(Record<D>* r, BTNode<D>* ap)
      {//生成含信息(r, ap)的新的根结点,原根结点 root 和 ap 为其子树指针
        BTNode<D> *p = new BTNode<D>;
        p->parent = NULL;
        p->keynum = 1;
        Copy(p, 1, r);
        p->children[0] = root;
        if (root != NULL)
          root->parent = p;
        p->children[1] = ap;
        if (ap != NULL)
          ap->parent = p;
        root = p;
      }
      void InsertBTree(Record<D>* r, BTNode<D>* q, int i)
      {//在结点 *q 的 key[i] 与 key[i+1] 之间插入关键字 r->k 和地址 r 
       //若引起结点过大,则沿双亲链进行必要的结点分裂,使得仍是 B 树
        BTNode<D> *ap = NULL;
        bool finished = false;
        while(q && !finished)
        {
          Insert(q, i, r, ap);
          if (q->keynum)  //关键字未超出容量
            finished = true;
          else  //超出容量,分裂结点 *q
          {
            r = q->recptr[s];
            split(q, ap);
            q = q->parent;
            if (q != NULL)
              i = Search(q, r->key);
          }
        }
        if (!finished)  //是空树或根结点已分裂为结点 *q 和 *ap
          NewRoot(r, ap);
      }
      bool Move(BTNode<D>* &p)
      {//p 指向删除关键字后关键字的个数不足的结点,如果其左或右兄弟有多余的关键字,
       //则移给 p ,返回 true ;否则返回 false
        BTNode<D> *a, *f = p->parent;
        int i, j;
        for(i = 0; f->children[i] != p; i++)
        if (i > 0 && f->children[i-1]->keynum > (m-1)/2)
        {//情况一:有左兄弟且其有多个关键字
          a = f->children[i-1];
          for(j = p->keynum; j > 0; j--)
            MoveItim3(p, j+1, p, j);
          p->children[1] = p->children[0];
          MoveItim2(p, 1, f, i);
          p->keynum++;
          MoveItim2(f, i, a, a->keynum);
          p->children[0] = a->children[a->keynum];
          if (a->children[a->keynum] != NULL)
            a->children[a->keynum]->parent = p;
          a->keynum--;
          return true;
        }
        else if (i < f->keynum && f->children[i+1]->keynum > (m-1)/2)
        {//情况二:有右兄弟且其关键字数目大于 (m-1)/2
          a = f->children[i+1];
          MoveItim2(p, p->keynum+1, f, i+1);
          MoveItim2(f, i+1, a, 1);
          p->children[p->keynum + 1] = a->children[0];
          p->keynum++;
          if (a->children[0] != NULL)
            a->children[0]->parent = p;
          for(int j = 1; j < a->keynum; j++)
          {
            MoveItim2(a, j, a, j+1);
            a->children[j-1] = a->children[j]; 
          }
          a->children[a->keynum - 1] = a->children[a->keynum];
          a->keynum--;
          return true;
        }
        return false;
      }
      BTNode<D>* Merge(BTNode<D>* &p)
      {//合并结点
        BTNode<D> *b, *f = p->parent;
        int i, j;
        for(i = 0; f->children[i] != p; i++)
        if (i > 0)
        {//*p 有左邻兄弟
          b = f->children[i-1];
          for(j = 0; j <= p->keynum; j++)
            if (p->children[j] != NULL)
              p->children[j]->parent = b;
          ++b->keynum;
          MoveItim2(b, b->keynum, f, i);
          b->children[b->keynum] = p->children[0];
          for(j = 1; j <= p->keynum; j++)
          {
            ++b->keynum;
            MoveItim3(b, b->keynum, p, j);
          }
          delete p;
          for(j = i+1; j <= f->keynum; j++)
            MoveItim3(f, j-1, f, j);
          f->keynum--;
        }
        else
        {//这样 b 还是 p 的左兄弟,合并到左兄弟,则是在左兄弟后面加关键字
          b = p;
          p = f->children[i+1];
          for(j = 0; j <= p->keynum; j++)
            if (p->children[j] != NULL)
              p->children[j]->parent = b;
          ++b->keynum;
          MoveItim2(b, b->keynum, f, i+1);
          b->children[b->keynum] = p->children[0];
          for(j = 1; j <= p->keynum; j++)
          {
            ++b->keynum;
            MoveItim3(b, b->keynum, p ,j);
          }
          delete p;
          for(j = i+1; j < f->keynum; j++)
          MoveItim3(f, j, f, j+1);
          f->keynum--;
        }
        return b;
      }
    public:
      BTree()
      {//构造函数
        root = NULL;
        for(int i = 0; i < N; i++)
          record[i].key = EMPTY;
        MinEmpt = 0;
        s = (m+1) / 2;
      }
      ~BTree()
      {//析构函数
        DestroyBTree(root);
      }
      BTNode<D>* Root()const
      {//返回 B 树根结点指针
        return root;
      }
      void TraverseBTree(BTNode<D>* t, void(*visit) (Record<D>))const
      {//按关键字顺序遍历
        if (t != NULL)
          for(int i = 0; i <= t->keynum; i++)
          {
            if (i > 0)
              visit(*(t->recptr[i]));
            if (t->children[i] != NULL)
              TraverseBTree(t->children[i], visit);
          }  
      }
      Rusult<D> SearchBTree(KeyType k)const
      {//在 B 树中查找关键字 k ,返回结果(pt, i, tag),若成功,则 tag = true
       //pt 所指结点的第 i 个关键字等于 k ,否则 tag = false,等于 k 的关键字应
       //插在 pt 所指结点的第 i 和第 i+1 个关键字之间
        BTNode<D> *p = root, *q = NULL;
        bool found = false;
        int i = 0;
        Result<D> r;
        while(p != NULL && !found)
        {
          i = Search(p, k);
          if (i > 0 && p->key[i] == k)
            found = true;
          else
          {
            q = p;
            p = p->children[i];
          }
        }
        if (found)
        {
          r.pt = p;
          r.tag = true;
        }
        else
        {
          r.pt = q;
          r.tag = false;
        }
        r.i = i;
        return r;
      }
      bool InsertRecord(Record<D> re)
      {//B 树中不存在 re.key ,且 record[] 中有空位置,将数据 re 插入到 record[] 和 
       //B 树中,成功返回 true,否则返回 false
        Result<D> u = SearchBTree(re.key);
        if (u.tag)
          return false;
        if (MinEmpt < N)
        {
          record[MinEmpt] = re;
          InsertBTree(&record[MinEmpt], u.pt, u.i);
          for(int k = MinEmpt+1; k < N; k++)
            if (record[k].key == EMPTY)
            {
              MinEmpt = k;
              break;
            }
          if (k == N)
            MinEmpt = N;
          return true;
        }
        else
          return false;
      }
      bool DeleteBTree(Record<D> &re, KeyType k)
      {//在 B 树中删除关键字为 k 的数据,用 re 返回该数据
        int i, j;
        BTNode<D> *p, *q;
        Result<D> u = SearchBTree(k);
        if (u.tag == 0)
          return false;
        i = u.i;
        p = u.pt;
        re = *(p->recptr[i]);
        p->recptr[i]->key = EMPTY;
        if (p->recptr[i] - record < MinEmpt)
          MinEmpt = p->recptr[i] - record;
        if (p->children[i-1] != NULL)
        {
          q = p->children[i-1];
          while(q->children[q->keynum] != NULL)
            q = q->children[q->keynum];
          if (q->keynum > (m-1)/2)
          {
            MoveItim2(p, i, q, q->keynum);
            p = q;
            i = q->keynum;
          }
          else
          {
            q = p->children[i];
            while(q->children[0] != NULL)
              q = q->children[0];
            MoveItim2(p, i, q, 1);
            p = q;
            i = 0;
          }
        }
        for(j = i+1; j <= p->keynum; j++)
          MoveItim2(p, j-1, p, j);
        p->keynum--;
        while(p->keynum < (m-1)/2 && (p != root))
        {
          if (!Move(p))
            p = Merge(p);
          p = p->parent;
        }
        if (p == root && root->keynum == 0)
        {
          root = root->children[0];
          if (root != NULL)
            root->parent = NULL;
          delete p;
        }
        return true;
      }
    };
    

    与二叉排序树和平衡二叉树一样, B 树中每个关键字是唯一的。所以在插入数据时,先要查找 B 树中是否存在该关键字。如果存在,则不能插入。新数据总是要插在最底层的非叶子结点中,这就保证了所有叶子结点都出现在同一层次。当结点的关键字由于插入超出了最大限度,就要进行分裂。一个结点分成 3 部分,中间关键字并入原来的父结点中,左右两部分分别是中间关键字的左右孩子。如果原来的父结点由于中间关键字的并入超出了最大限度,则继续分裂,直至生成新的根结点。

    在 B 树删除关键字,如果在最底层的非叶子结点,则直接删除;否则,类似于二叉排序树和平衡二叉树,删除其前驱或后继关键字(它们一定在最底层的非叶子结点中),再将其前驱或后继关键字和指针复制到删除的关键字和指针处。删除关键字导致结点的关键字少于最低限度,就要进行合并。如果合并导致父结点的关键字少于最低限度,则继续合并,直至根结点中没有关键字。这时删除根结点,B 树的层数减 1 。

    B 树是用于处理大数据量的查找操作。其中数据量大到不能存放在内存数组 record[] 中,要将数据存放在外存的多个文件中。甚至内存也放不下整个 B 树,内存中只能存放 B 树的一个结点。所以,B 树的每个结点都存于外存的文件中,结点中的指针内容是文件名和偏移量(数据在文件中的位置)。查找过程是首先将 B 树的根结点文件放入内存中,依据关键字进行查找。随时关闭查找过的 B 树结点,再在内存中随时打开新的 B 树结点,直至查找结束。为了提高速度,就要尽量减少打开、关闭文件的次数,并尽量增大 B 树每个结点可容纳的关键字数,从而降低 B 树的层数。

    由于真正的 B 树每个结点的关键字非常多,即 keynum 很大,Search() 函数在 1 ~ keynum 中查找 i 应采用折半查找法而不是顺序查找法。

    4.2 键树

    键树用于关键字为字符串的情况,故键树也称为 “词典查找树” 。可以用孩子-兄弟二叉链表表示键树,称为 “双链键树” ;也可以用树的多重链表示键树,称为 “Trie 树” 。

    推荐阅读
    查找算法(V)键树——双链树和Trie树
    键树 B树 B+树(注意,该文中有些许错误,请注意甄别)

    相关文章

      网友评论

          本文标题:六、查找

          本文链接:https://www.haomeiwen.com/subject/rauzdxtx.html