《数据结构和算法》【链表描述的字典】含原代码

概述

字典是一些行如<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及以上版本。
花了两个小时,有问题直接评论。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值