链表描述的字典
概述
字典是一些行如<k,v>的数对所组成的集合,其中k是关键字,v是与关键字k所对应的值(类似数组,值与下标对应)。一个字典的任意两个数对,其下标不相同。
多重字典允许有相同下标的数对存在。
相关操作
- 判断是否空
- 返回数对个数
- 通过关键字找一个数对
- 插入一个数对
- 删除一个数对
抽象数据类型
抽象数据类型 Dictionary
{
实例
关键字各不相同的一组数据对
操作
empty();如果空,返回true,不空,返回false
size();返回字典的数对个数
find(k);返回关键字k对应的数对
insert(p);插入数对p
erase(k);删除关键字为k的数对
}
C++的抽象类表示
头文件dictionary.h
#ifndef dictionary_
#define dictionary_
using namespace std;
template<class K, class E>
class dictionary
{
public:
virtual ~dictionary() {}
virtual bool empty() const = 0;
// return true if dictionary is empty
virtual int size() const = 0;
// return number of pairs in dictionary
virtual pair<const K, E>* find(const K&) const = 0;
// return pointer to matching pair
virtual void erase(const K&) = 0;
// remove matching pair
virtual void insert(const pair<const K, E>&) = 0;
// insert a (key, value) pair into the dictionary
};
#endif
字典—C++的链表描述
节点的数据结构定义
节点的数据data:使用C++中的pair作为字典中的一个数对。
头文件pairNode.h
#ifndef pairNode_
#define pairNode_
using namespace std;
template <class K, class E>
struct pairNode
{
typedef pair<const K, E> pairType;
pairType element;
pairNode<K,E> *next;
pairNode(const pairType& thePair):element(thePair){}
//很巧妙,初始化的同时将next也赋值
pairNode(const pairType& thePair, pairNode<K,E>* theNext)
:element(thePair){next = theNext;}
};
#endif
它有两隔函数:仅仅建立一个节点,或者建立该节点的同时赋值next,这很有用处。
类linked_list_dictionary 的定义
该类继承自抽象类dictionary,目前没有添加实现代码,接下来我们逐步添加。
template<class K,class E>
class linked_list_dictionary :public dictionary<K, E>
{
public:
linked_list_dictionary();
virtual ~linked_list_dictionary();
void output(ostream & out) const //注意:const不要少,不然会报错
// 通过 dictionary 继承
virtual bool empty() const override
{
}
virtual int size() const override
{
}
virtual pair<const K, E>* find(const K & findKey) const override
{
}
virtual void erase(const K & eraseKey) override
{
}
virtual void insert(const pair<const K, E>& insertPair) override
{
}
protected:
int nodeNum; //当前节点数目
pairNode<K, E> * firstNode;
};
私有成员
nodeNum标记还有多少节点。在构造函数中,它会被初始化为0,成功执行insert会使它++,成功执行erase会使它–。
firstNode,表示头节点。假如它是null,则字典空。
判断是否空
virtual bool empty() const override
{
if (nodeNum == 0)return true;
return false;
}
构造函数的实现
这里操作很简单,没有节点,nodeNum 初始化为0,你可能会疑惑firstNode 怎么不new一下或者malloc一下,等看到函数insert的实现时,你就明白了。
linked_list_dictionary()
{
nodeNum = 0;
firstNode = NULL;
}
size()返回当前的节点数
这没什么讲的
virtual int size() const override
{
return nodeNum;
}
插入一个节点
按key的大小插入,由小到大。
插入一个节点,就需要一个东西来表示插入的位置。
使用双节点p1和p2,它们共同表示插入的位置。
p1被初始化为firstNode,p2为NULL
插入位置的查找
判断当前节点的key与给定参数的key来决定。p2来改变插入节点之前的一个节点的next等于插入节点,这需要记住p1之前的位置。p2 = p1,这一步要优先完成。假如当前节点p1的key 小于给定参数insertPair的key,说明给定参数insertPair要排在这个节点之后,所以,p1要往后移动一个位置,p1->element.first < insertPair.first完成判断,操作p1 = p1->next实现位置的移动。
p1 != NULL是有必要的!访问一个节点的前提是这个节点存在,否则就会报错,因为你在访问一个不存在的东西,可计算机内存中是存在的,所以就会随机访问一个位置。这是不允许的,会报错。
覆写value
在while循环之后,说明,p1可能为null,但通常是因为给定参数的key不再小于p1的key。如果他们相等,就执行更新操作。
if (p1 != NULL && p1->element.first == insertPair.first)实现这部分判断,p1->element.second = insertPair.second实现值的更新,并return。
新节点的创建
pairNode<K, E> * newNode = new pairNode<K, E>(insertPair, p1);这段代码实现新节点的创建。还记得之前介绍的第二个结构体函数么?忘了快去看看。它在创建一个新节点的同时,为该节点的next赋值。p1用来表示新节点的下一个节点的位置。因为已经排除等于了,并且结束了循环,所以p1此刻一定是下一个节点的位置。
考虑三种情况:在头部插入,在中间插入,在尾部插入。
- 在头部插入:此刻,p2必然是null,p2不是null的前提是while循环体至少执行一次。在头部插入意味着element.first < insertPair.first是false。只需要让firstNode等于新节点,即firstNode = newNode;
- 在中间插入:p2肯定被赋值了,并且指向插入节点的前一个节点, p2->next = newNode完成节点的next赋值关联。
- 在尾部插入:p1是null,在尾部插入,则函数参数的key大于字典中全部节点的key,while循环要执行到p1==NULL才能结束。你可能会问,也许字典一开始就是空呢?我知道你的意思,在这个情况下,p2肯定也是空,所以优先判断p2是不是null。在尾部插入时,p2不是null,p2指向插入节点的前一个节点,依旧使用p2->next = newNode完成节点的next赋值关联。与在中间插入唯一不同在于,新节点的next是null。
三种情况可由以下两行代码实现。如果空字典,会建立一个新字典。
if (p2 == NULL)firstNode = newNode;
else p2->next = newNode;
下面是insert的完整代码。
virtual void insert(const pair<const K, E>& insertPair) override
{
pairNode<K, E> * p1 = firstNode,*p2=NULL;
while (p1 != NULL && p1->element.first < insertPair.first)
{
p2 = p1;
p1 = p1->next;
}
if (p1 != NULL && p1->element.first == insertPair.first)
{
p1->element.second = insertPair.second;
return;
}
pairNode<K, E> * newNode = new pairNode<K, E>(insertPair, p1);
if (p2 == NULL)firstNode = newNode;
else p2->next = newNode;
++nodeNum;
return;
}
删除一个节点
具体做法类似插入一个节点,依旧使用双节点,找到了就delete它。先看代码:
virtual void erase(const K & eraseKey) override
{
pairNode<K, E> * p1 = firstNode, *p2 = NULL;
while (p1!=NULL && p1->element.first<eraseKey)
{
p2 = p1;
p1 = p1->next;
}
if (p1 != NULL && p1->element.first==eraseKey)
{
if (p2 == NULL)firstNode = firstNode->next;
else p2->next = p1->next;
delete p1;
--nodeNum;
}
}
while循环的操作等同于insert中while循环的操作。
假如表不空,并且参数的键key与当前节点p1的键key相同,则删除它。if (p1 != NULL && p1->element.first==eraseKey)实现判断。
考虑以下三种情况:
考虑三种情况:删除头节点,在中间删除,删除尾结点。
- 删除头节点:删除头结点,说明while循环没有执行,则p2是NULL,这种情况下,头结点要往后移动一下,firstNode = firstNode->next完成移动。
- 在中间删除:让当前节点p1的上一个节点p2的next等于当前节点的next,p2->next = p1->next完成操作。
- 删除尾结点:p1等于null会怎么样?if判断不成立,函数直接结束,所以,p1肯定不是null,这时,它指向最后一个节点,p1的next才是null。此刻,p2需要指向p1的next,操作等同于在中间删除:p2->next = p1->next。
查找一个节点
virtual pair<const K, E>* find(const K & findKey) const override
{
pairNode<K, E> * currentNode = firstNode;
while (currentNode!=NULL && currentNode->element.first<findKey)
{
currentNode = currentNode->next;
}
if (currentNode != NULL && currentNode->element.first == findKey)
return &(currentNode->element);
return NULL;
}
这个很简单,函数要返回一个节点,数据类型是pair<const K, E>*,这个函数是指针函数(返回指针的函数),while循环来查找位置,currentNode!=NULL的判断不能少,访问节点的前提是节点不空。while循环结束后有三种可能:当前节点currentNode 的key与参数相同;currentNode 的key大于参数;未找到这个key,即参数大于字典中所有的key。
if (currentNode != NULL && currentNode->element.first == findKey)判断是否相等。
析构函数
很简单,头结点firstNode只要非NULL,让nextNode 等于头结点,头结点等于自己的next,再delete nextNode;
virtual ~linked_list_dictionary()
{
pairNode<K, E> * nextNode = NULL;
while (firstNode!=NULL)
{
nextNode = firstNode;
firstNode=firstNode->next;
delete nextNode;
/*也可以nextNode等于头结点的next,再删除头结点,重置头结点为nextNode
nextNode=firstNode->next;
delete firstNode;
firstNode=nextNode;
*/
}
nodeNum = 0;
}
输出
类中定义
void output(ostream & out) const //注意:const不要少,不然会报错
{
pairNode<K, E> * currentNode = NULL;
for (currentNode = firstNode;
currentNode != NULL;
currentNode = currentNode->next)
{
out << currentNode->element.first
<< " "
<< currentNode->element.second
<< endl;
}
}
类外定义
template<class K, class E>
ostream & operator<<(ostream & out,const linked_list_dictionary<K,E> & x)//注意:x必须是引用
{
x.output(out);
return out;
}
完整的类linked_list_dictionary的代码
现在看起来是不是很简单呢?
头文件linked_list_dictionary.h
clude<iostream>
#include"dictionary.h"
#include"pairNode.h"
#include<string>
template<class K,class E>
class linked_list_dictionary :public dictionary<K, E>
{
public:
linked_list_dictionary()
{
nodeNum = 0;
firstNode = NULL;
}
virtual ~linked_list_dictionary()
{
pairNode<K, E> * nextNode = NULL;
while (firstNode!=NULL)
{
nextNode = firstNode;
firstNode=firstNode->next;
delete nextNode;
/*
nextNode=firstNode->next;
delete firstNode;
firstNode=nextNode;
*/
}
nodeNum = 0;
}
void output(ostream & out) const //注意:const不要少,不然会报错
{
pairNode<K, E> * currentNode = NULL;
for (currentNode = firstNode;
currentNode != NULL;
currentNode = currentNode->next)
{
out << currentNode->element.first
<< " "
<< currentNode->element.second
<< endl;
}
}
// 通过 dictionary 继承
virtual bool empty() const override
{
if (nodeNum == 0)return true;
return false;
}
virtual int size() const override
{
return nodeNum;
}
virtual pair<const K, E>* find(const K & findKey) const override
{
pairNode<K, E> * currentNode = firstNode;
while (currentNode!=NULL && currentNode->element.first<findKey)
{
currentNode = currentNode->next;
}
if (currentNode != NULL && currentNode->element.first == findKey)
return &(currentNode->element);
return NULL;
}
virtual void erase(const K & eraseKey) override
{
pairNode<K, E> * p1 = firstNode, *p2 = NULL;
while (p1!=NULL && p1->element.first<eraseKey)
{
p2 = p1;
p1 = p1->next;
}
if (p1 != NULL && p1->element.first==eraseKey)
{
if (p2 == NULL)firstNode = firstNode->next;
else p2->next = p1->next;
delete p1;
--nodeNum;
}
}
virtual void insert(const pair<const K, E>& insertPair) override
{
pairNode<K, E> * p1 = firstNode,*p2=NULL;
while (p1 != NULL && p1->element.first < insertPair.first)
{
p2 = p1;
p1 = p1->next;
}
if (p1 != NULL && p1->element.first == insertPair.first)
{
p1->element.second = insertPair.second;
return;
}
pairNode<K, E> * newNode = new pairNode<K, E>(insertPair, p1);
if (p2 == NULL)firstNode = newNode;
else p2->next = newNode;
++nodeNum;
return;
}
protected:
int nodeNum;
pairNode<K, E> * firstNode;
};
//不能作为类的成员函数出现
template<class K, class E>
ostream & operator<<(ostream & out,const linked_list_dictionary<K,E> & x)//注意:x必须是引用
{
x.output(out);
return out;
}
main函数测试
main.cpp文件
#include"linked_list_dictionary.h"
int main()
{
//******int类型测试
linked_list_dictionary<int, int> object1;
pair<int, int> pair1;
pair1.first = 1, pair1.second = 1;
object1.insert(pair1);
pair1.first = 2, pair1.second = 2;
object1.insert(pair1);
pair1.first = 3, pair1.second = 3;
object1.insert(pair1);
pair1.first = 4, pair1.second = 4;
object1.insert(pair1);
pair1.first = 5, pair1.second = 5;
object1.insert(pair1);
cout << object1;
object1.erase(3);
cout << "******************************"<< endl
<<object1;
//string 类型测试*****************************************
linked_list_dictionary<string, string> objStr;
pair<string, string> pairStr;
string firstStr, secondStr;
firstStr = "key1";
secondStr = "val1";
pairStr.first = firstStr;
pairStr.second = secondStr;
objStr.insert(pairStr);
firstStr = "key2";
secondStr = "val2";
pairStr.first = firstStr;
pairStr.second = secondStr;
objStr.insert(pairStr);
firstStr = "key3";
secondStr = "va13";
pairStr.first = firstStr;
pairStr.second = secondStr;
objStr.insert(pairStr);
cout << objStr;
return 0;
}
写在最后
.h文件.cpp文件自己建立,在该页面复制到对应的文件中,推荐使用VisualStudio2013及以上版本。
花了两个小时,有问题直接评论。