数据结构:字典

声明:本文为学习数据结构与算法分析(第三版) Clifford A.Shaffer 著的学习笔记,代码有参考该书的示例代码。

字典

碎碎语:其实我一直对这个数据结构不是很了解。
字典 (Dictionary) 作为数据库的一个简单接口,提供在数据库中存储、查询和删除记录的可能。
字典中有定义关键码 (search key)的概念。而关键码则必须是可比的
字典的ADT如下:

template<typename Key, typename E>
class Dictionary
{
    Dictionary(const Dictionary& ) {}
    Dictionary& operator= (const Dictionary&) {}
    public:
    Dictionary() {}
    virtual ~Dictionary() {}
    virtual void clear() = 0;
    virtual void insert(const Key&, const E&) = 0;
    virtual E remove(const Key&) = 0;
    virtual E removeAny() = 0;
    virtual E find(const Key&) const = 0;
    virtual int length() const = 0;
};

insert 函数和 find 函数是这个类的核心。find 函数接受一个任意的关键码,从字典中找出这个关键码,并且将相关的记录返回。如果有多条记录匹配,则返回任意一条。
removeAny 函数提供了用户随意选择一条记录,并进行操作。

应用举例

假如实现了一个工资记录的字典。Payroll 类有很多域,每个域都可以作为搜索关键码。
下面展示了对Payroll 的存储,一个是对ID进行检索,一个对name进行检索。

class Payroll
{
    int ID;
    string name;
    string address;

    public:
    Payroll(int inID, string inname, string inaddr):ID(inID), name(inname), address(inaddr) {}
    ~Payroll() {}
    int getID() { return ID; }
    string getname() { return name; }
    string getaddr() { return address; }
};

int main()
{
    Udict<int, Payroll*> dict;
    Udict<string, Payroll*> namedict;
    Payroll *foo1, *foo2, *findfoo1, *findfoo2;
    foo1 = new Payroll(5, "Joe", "Anytown");
    foo2 = new Payroll(10, "John", "Mytown");

    dict.insert(foo1->getID(), foo1);
    dict.insert(foo2->getID(), foo2);

    namedict.insert(foo1->getname(), foo1);
    namedict.insert(foo2->getname(), foo2);
    findfoo1 = dict.find(5);
    if(findfoo1!=nullptr)
        cout<else
        cout<<"nullptr"<"John");
    if(findfoo2 != nullptr)
        cout<else
        cout<<"nullptr"<

Key-Value 实现

一般情况下,将关键码和值存储成相关联的关系。字典中任何一条基本元素包含了一条记录,以及与该记录相关的关键码。这就是所谓的键-值对。实现如下:

template
class KVpair
{
    Key k;
    E e;
    public:
    KVpair() {}
    KVpair(Key key, E value):k(key), e(value) {}
    KVpair(const KVpair& pair)
    {
        k = pair.key();
        e = pair.value();
    }
    KVpair& operator= (const KVpair& pair)
    {
        k = pair.key();
        e = pair.value();
        return *this;
    }
    Key key() const { return k; }
    E value() const { return e; }
    void setKey(Key ink) { k = ink; }
    void set(Key ink, E ine) { k = ink, e = ine; }
    bool operator>(const KVpair& o) const
    {
        return k>o.key();
    }
    bool operator< (const KVpair& o) const
    {
        return k

* 与课本不同的是,
* 重载< 和 > 号是为了便利 SList 中 insert 函数的实现。其中 SList 在List.h 中单独实现,采用链表的方式,这是为了能够使得 SList 成为一个可以重用的模板类

有序字典和无序字典

书本给出了有序字典和无序字典的实现,无序字典使用了线性表的数据结构,实现比较简单。
而有序字典根据关键码进行排序插入,另外实现了一个有序线性表,实现如下:

template
class SList:protected LList
{
    public:
    SList(int size): LList(size) {}
    SList() {}
    ~SList() {}
    void insert (const E& it)
    {
        if(length()==0)
        {
            LList::insert(it); return;
        }
        moveToStar();
        int left = 0, right = length()-1;
        int mid;
        auto get = [&](int pos) -> const E&
        {
            moveToPos(pos);
            return getValue();
        };
        auto f = [&]() -> bool
        {
            if(left>=right)
            {
                if(get(left)return false;
            }
            mid = left + (right-left)/2;
            const E& temp = get(mid);
            if(it1;
                return true;
            }
            else
            {
                return false;
            }
        };
        while( f() ) ;
        LList::insert(it);
    }
    using LList::moveToStar;
    using LList::prev;
    using LList::next;
    using LList::length;
    using LList::moveToPos;
    using LList::getValue;
    using LList::currPos;
    using LList::clear;
    using LList::moveToEnd;
    using LList::remove;
};

其中,涉及到关键码的比较,但是我将整个键值对封装为E的模板了,所以键值对中是需要定义实现重载大于、小于号的。
同时,在键值对的类中,应该使用指针指向相应的记录,再将指针与关键码关联起来

关于二分查找的一点点小问题

实现有序字典的话,二分查找几乎是需要的使用技能了。
在实现的过程中,我先按照自己的想法,实现如下:

/*
因为 find 函数被修饰成 const 了,并不能调用类中其他非 const 的
函数,即使类中有SList 成员,也不能调用 SList 成员的非const 函数。
这时候就需要用到指针,使用 SList 指针,就可以成功在 find 函数中调
用 SList 的非const成员函数了
*/
E find(const Key& k) const
{
    if(length()==0)
        return nullptr;
    int l = 0, r = length()-1, mid;
    while(l>=r)
    {
        mid = l+(r-l)/2;
        //get(int) 会返回在mid处的值
        auto temp = get(mid);
        if(temp.key()>k)
            l = mid+1;
        else if(temp.key()1;
        else
            break;
    }
    if(get(left).key()==k)
        return get(left).value();
    else
        return nullptr;
}

很明显,我的代码略长,实现也不算很完美,然后参考了书上的代码 ,修改如下:

E find(const Key& k) const
{
    int l = -1, r = length();
    while(l+1!=r)
    {
        int i = l+(r-l)/2;
        auto temp = get(i);
        if(temp.key()>k) l = i;
        else if(temp.key()else return temp.value();
    }
    return nullptr;
}

简洁明了,同时避免了需要增加判断字典为空时的状态

所有实现代码均可以在本人github上找到:
xiaosa233
–END–

你可能感兴趣的:(数据结构)