声明:本文为学习数据结构与算法分析(第三版) 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"<
一般情况下,将关键码和值存储成相关联的关系。字典中任何一条基本元素包含了一条记录,以及与该记录相关的关键码。这就是所谓的键-值对。实现如下:
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–