基于二分查找的有序符号表(C++实现)———学习笔记

基于有序数组的符号表查找(二分法)


相比于无序链表的符号表顺序查找,使用基于有序数组的符号表二分法查找可以根据索引,有效减少每次查找所需比较次数,因为每次都可以快速找到数组的中间元素,而如果想通过无序链表找到中间节点的数据,唯一的方法就是遍历链表。

在开始前,需要明确的是,我们要维护两个数组,使他们作为实现本次符号表查找的数据结构。这两个数组分别是key(键)数组和value(值)数组,并且保证他们有序。本篇在类模版的实现中,将其写成私有变量。

vector<KEY> m_key;
vector<VALUE> m_value;

为简化代码(懒),本篇省去写对两个数组进行排序的构造函数。测试时,直接输入两个有序数组即可,支持动态扩充数组。

一.符号表查找的基本方法

    int SymbleTable_size(); //返回有序数组大小
    int rank_normal(KEY key);//返回键有序数组中,小于指定键的键的数量
    VALUE get(KEY key);//根据指定键,返回该键对应的值
    void put(KEY key, VALUE value);//输入新的键值对,分别插入到两个有序数组当中
    KEY min();//返回键数组中最小的键
    KEY max();//返回键数组中最大的键
    KEY select(int k);// 获得排名为K的键
    KEY ceiling(KEY key);// 大于等于key键的最小键
    KEY floor(KEY key);// 小于等于key键的最大键
    bool isEmpty();// 判断有序数组是否为空
    void deleteKey(KEY key);//删除某个键值对,其键指定为key
    void print_as();//输出键值对内容

其中KEY 和 VALUE 是类模版的类型参数,分别表示键的数据类型和值的数据类型,本次测试时规定key是int类型,value是 float 类型,如下所示。

 vector<int> key{1,3,5,7,9,11,13,15};
 vector<float> value{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8};

1.rank()

在这些方法中,rank()是核心方法,它几乎支持了以上所有的方法实现。下面我们先来梳理rank()的具体作用。从内容上来说,rank()是一个二分法查找数组中指定元素并返回其索引的方法,但它在本篇中,保持两个特性
1.如果键数组中,存在我们所输入的指定的键,那么就返回在数组中比指定键小的键的数量,比如我们键数组为[1,2,3,4,5],我们想要查找键 5,令int i = rank(5),i的值就是4,4既是比键5小的键的数量,还是查找指定键的索引,所以 m_key[i] == key(key为指定键)可以判断键数组中是否存在指定的键,如存在,可以继续进行其他操作,比如更新键值对,

2.如果键数组中,并不存在指定的键,那么还是要返回比这个键小的键的数量,这个特性可以使得插入新键值对方法顺利完成,或者在删除键值对以及根据指定键获得值的方法中直接返回等。
基于以上对rank的期待,让我们实现这个rank吧,有两种方法可以实现,分别是迭代和递归,两者实现的功能等价。
rank()的迭代版本实现如下所示

template<typename KEY, typename VALUE> int ArraySearch<KEY, VALUE>::rank_normal(KEY key)
{
  lo = 0;
  hi = SymbolTable_size()-1;
  while(lo <= hi)
  {
  int mid = lo + (hi-lo)/2;
  if(key > m_key[mid]) lo = mid + 1;
  else if(key < m_key[mid]) hi = mid-1;
  else return mid;
  }
  return lo;
}

若最终返回的lo,该方法虽然没有找到指定的键,但还是返回了lo作为比指定键小的键的数量,大家可以通过一个[1,2,3,4]这样一个简单的键数组验证一下。lohi会在该方法执行的过程中逼近指定键,若没有找到指定键,最终lo会变成我们所要的结果,下面给出递归方法。

template<typename KEY, typename VALUE> int ArraySearch<KEY, VALUE>::rank_iter(KEY key, int lo, int hi)
{
  if(lo > hi) return lo;
  mid = lo + (hi - lo)/2
  if(key > m_key[mid]) return rank_iter(KEY key, mid+1, hi);
  else if(key < m_key[mid]) return rank_iter(KEY key, lo, mid-1);
  else return mid;
}

如上所述,迭代法和递归法实现功能相同。接下来就可以基于rank()来实现其他的方法了。

2.put

当用户需要插入一个新的键值对,或者通过一个已有的key更新这个key的value时,put()函数可以解决这两个问题。

大概的算法流程是
首先获得数组中小于等于指定键的键的数量i
(1)如果不存在指定键,那么就在i后面的位置添加一个指定键,并在值数组相应位置添加新的值(实现此方法需要i之后的数据整体往后推一个位置,代价较高,因此如果某个符号表数据量很大并且需要经常插入新的键值对,那么这个有序数组的二分法查找就不怎么适用了)
(2)如果存在指定键,那就更新该键的值(通过i更新)

template<typename KEY, typename VALUE> void ArraySearch<KEY, VALUE>::put(KEY key, VALUE value)
{
 int i = rank_normal(key);
 if(m_key[i] == key)
 {
  m_value[i] = value;
 }
 else
 {
 m_key.resize(m_key.size()+1);
 m_value.resize(m_value.size()+1);
 for(j = m_key.size();j > i; --j)
 {
  m_key[j] = m_key[j-1];
  m_value[j] = m_value[j-1];
 }
 m_key[i] = key;
 m_value[i] = value;
 }
}

3.get

get()方法用于通过给定键,获得其value,同样需要rank方法的支持,这里需要通过获得rank返回值来确定指定键是否存在于键数组当中,实现较简单,代码如下所示。

template<typename KEY, typename VALUE> VALUE ArraySearch<KEY, VALUE>::get(KEY key)
{
    int i = rank_normal(key);
   
    if(isEmpty())
    {
        cout << "符号表为空" << endl;
        return m_value[0];
    }
   
    if(m_key[i] == key) return m_value[i];
    else
    {
        cout << "未找到该键" << endl;
        return m_value[0];
    }
   
}

4.delete

delete方法同样需要移动部分数组,来保证代表键值对的两个数组大小减1。
大致算法流程如下:
1.如果键数组为空,则返回
2.通过rank获取小于指定键的键的数量i,并通过i判断指定键是否在键数组当中。
(1)如果存在,直接将i位置后面的所有键值对数据向前移动一个位置,然后重新规定两个平行数组大小。
(2)如果不存在,就返回。

template<typename KEY, typename VALUE> void ArraySearch<KEY, VALUE>::deleteKey(KEY key)
{

    if(isEmpty())
    {
        cout << "nothing could be deleted" << endl;
         return;
    }
    int i = rank_normal(key);
    if(m_key[i] != key)
      return;
    for(int j = i; j < m_key.size(); j++)
    {
    m_key[j] = m_key[j + 1];
    m_value[j] = m_value[j + 1];
    }
    m_key.resize(m_key.size()-1);
    m_value.resize(m_value.size()-1);
    print_as();
    
}

4.其他方法

其他方法较简单,不多赘述。

5.完整代码

//
//  The_symbol_table.hpp
//  Dat_Structure
//
//  Created by  云子谣 on 2020/2/27.
//  Copyright © 2020  云子谣. All rights reserved.
//

#ifndef The_symbol_table_hpp
#define The_symbol_table_hpp

#include <stdio.h>
#include <vector>
#include <iostream>


#endif /* The_symbol_table_hpp */

using namespace std;
template<typename KEY,typename VALUE> class ArraySearch
{
public:
    ArraySearch(vector<KEY> kk, vector<VALUE> vv);
    ~ArraySearch();
    int Array_size();
    int rank_normal(KEY key);
    VALUE get(KEY key);
    void put(KEY key, VALUE value);
    KEY min();
    KEY max();
    KEY select(int k);
    KEY ceiling(KEY key);
    KEY floor(KEY key);
    bool isEmpty();
    void deleteKey(KEY key);
    void print_as();
private:
    vector<KEY> m_key;
    vector<VALUE> m_value;
};

template<typename KEY, typename VALUE> ArraySearch<KEY, VALUE>::ArraySearch(vector<KEY> kk, vector<VALUE> vv)
{
    m_key= kk;
    m_value = vv;
    
}
template<typename KEY, typename VALUE> ArraySearch<KEY, VALUE>::~ArraySearch(){}
template<typename KEY, typename VALUE> int ArraySearch<KEY, VALUE>::Array_size()
{
    return m_key.size();
}
template<typename KEY, typename VALUE> bool ArraySearch<KEY, VALUE>::isEmpty()
{
    return (Array_size() == 0);
}
template<typename KEY, typename VALUE> int ArraySearch<KEY, VALUE>::rank_normal(KEY key)
{
    int lo = 0, hi = Array_size()-1;
    //假如key数组为[1,2,3,4,5,6],lo = 0 指第一个元素索引,hi为该数组最后一个元素的索引5
    while(lo <= hi)
    {
        int mid = lo + (hi-lo)/2;
        if(key > m_key[mid]) lo = mid + 1;
        else if(key < m_key[mid]) hi = mid-1;
        else return mid;
    }
    return lo;//此时如果查找的数据存在,mid就是所查找的数据, 但此时应返回比查找的数组小的数据数量,在二分法算法执行的过程中,lo和hi会慢慢逼近mid,即会慢慢逼近要查找的key,所以lo最终会变成离key最近但比key小的索引
    
    

    
}
template<typename KEY, typename VALUE> void ArraySearch<KEY, VALUE>::put(KEY key, VALUE value)
{
    int i = rank_normal(key);
    if(i <= m_key.size() && m_key[i] == key)
    {
        //m_value.resize();
        //m_value[i] = value;
        //此时,i虽然代表了比key小的键值的个数,但同时,m_key[i]也代表了所要检索的键,
        //m_key[i] == key,判断 想要更新的键值对是否在表中
        //若不在需要重新插入å
        m_value[i] = value;
        return;
    }
    m_value.resize(m_value.size()+1);
    m_key.resize(m_key.size()+1);
    for(int j = m_key.size(); j > i; --j)
    {
        m_key[j] = m_key[j - 1];
        m_value[j] = m_value[j - 1];
    }
    m_key[i] = key;
    m_value[i] = value;
    
}


template<typename KEY, typename VALUE> VALUE ArraySearch<KEY, VALUE>::get(KEY key)
{
    if(isEmpty())
    {
        cout << "符号表为空" << endl;
        return m_value[0];
    }
    int i = rank_normal(key);
    if(i <= m_key.size() && m_key[i] == key)
    {
        return m_value[i];
    }
    else
    {
        cout << "未找到该键" << endl;
        return m_value[0];
    }
    
}





template<typename KEY, typename VALUE> void ArraySearch<KEY, VALUE>::print_as()
{
    typename vector<KEY>::iterator iter_k;
    typename vector<VALUE>::iterator iter_v;
 
  cout << "-------display key--------" << endl;
  for(iter_k = m_key.begin(); iter_k != m_key.end(); iter_k++)
  {
        
      cout << *(iter_k) << endl;
  }
  cout << "-------display VALUE--------" << endl;
  for(iter_v = m_value.begin();iter_v != m_value.end(); iter_v++)
  {
      cout << *(iter_v) << endl;
  }
}

template<typename KEY, typename VALUE> KEY ArraySearch<KEY, VALUE>::min()
{
    return m_key[0];
}

template<typename KEY, typename VALUE> KEY ArraySearch<KEY, VALUE>::max()
{
    return *(m_key.end()-1);
}

template<typename KEY, typename VALUE> KEY ArraySearch<KEY, VALUE>::select(int k)
{
    return m_key[k];
}

template<typename KEY, typename VALUE> KEY ArraySearch<KEY, VALUE>::ceiling(KEY key)
{
    int i = rank_normal(key);
    return m_key[i];
}
template<typename KEY, typename VALUE> KEY ArraySearch<KEY, VALUE>::floor(KEY key)
{
    int i = rank_normal(key);
    if(m_key[i] == key)
    {
        return m_key[i];
    }
    else
    {
        return m_key[i-1];
    }
}

template<typename KEY, typename VALUE> void ArraySearch<KEY, VALUE>::deleteKey(KEY key)
{
    if(isEmpty())
    {
        cout << "nothing could be deleted" << endl;
         return;
    }
    int i = rank_normal(key);
    if(m_key[i] != key)
        return;
    for(int j = i; j < m_key.size(); ++j)
    {
        m_key[j] = m_key[j+1];
        m_value[j] = m_key[j+1];
    }
    m_key.resize(m_key.size()-1);
    m_value.resize(m_value.size()-1);
    print_as();
    
       
}

6.二分查找分析

在N个键的有序数组中进行的二分查找最多需要(lgN+1)次比较,个人理解是虽然二分查找速度很快,但在处理插入删除问题时,没有链表的灵活性高。
下一篇会实现一个二叉查找树,它即有链表的灵活性,也有二分查找的效率。


参考资料:《算法》第四版

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值