抽象数据类型(abstract datatype, ADT)是带有一组操作的一些对象的集合。诸如表、集合、图以及与它们各自的操作一起形成的这些对象都可以看做是抽象数据类型。
对于集合ADT,可以有像加(add)、删除(remove)、大小(size)以及包含(contains)这样一些操作。
我们将处理形如A0,A1,A2,…,AN-1的表,表的大小是N。大小为0的表为空表。
我们说Ai后继Ai-1,并称Ai-1前驱Ai,不定义A0的前驱元,也不定义AN-1的后继元。
要在表ADT上进行操作的集合:printList、makeEmpty、find返回首次出现的位置、insert、remove、findKth返回某个位置上的元素、next、previous。
对表的任何操作都可以用数组来实现。内部存储数组的vector类允许在需要的时候将数组大小增加一倍。这解决了使用数组的最严重的问题。
数组实现使得printList以线性时间执行,findKth则花费常数时间。然而,插入和删除的花费有可能是昂贵的,最坏情况为O(N)。
如果插入和删除在整个表中都发生,特别是在表的前段发生的话,数组就不是一个好选择了,下一节讨论另一种选择:链表。
为了避免插入和删除的线性开销,需要允许表可以不连续存储。下图表达了链表(linked list)的一般思想。
链表由一系列不必在内存中相连的结点组成。每个结点都含有表元素和到包含该元素后继元的结点的链(link)。称之为next链,最后一个单元的next链指向null。
printList和find花费线性时间,findKth操作不如数组实现的效率高,remove和insert花费常数时间。
删除最后一项有点麻烦,因为必须找到最后项前面的项,更改其next链接到NULL,然后更新这个保持为最后一项的链接。在经典的链表里每个结点存储指向下一节点的链接,但是没有提供关于上一个结点的任何信息。
双向链表(doublelinked list)可以解决这个问题。
C++的库中包含有公共数据结构的实现。C++的这部分内容就是众所周知的标准模板库(Standard Template Library, STL)。表ADT就是STL中实现的数据结构之一。一般称这些数据结构为集合或者容器。
表ADT的两个流行实现:vector给出了可增长数组实现,优点是可在常量时间索引,缺点是插入和删除代价昂贵;list给出了双向链表实现,优点是插入和删除代价小,缺点是不容易索引。Vector和list在查找时效率都很低。
在STL中,通过内置类型iterator来给出位置。例如list
1、 获得迭代器
STL定义了一对方法:
Iterator begin():返回指向容器第一项的一个迭代器
Iterator end():返回指向容器的终止标识(容器最后一项的后面位置)的迭代器
2、 迭代器方法
迭代器最常见的操作如下:
Itr++和++itr:推进迭代器itr至下一个位置。
*itr:返回存储在迭代器itr指定位置的对象的引用。
Itr1==itr2:如果itr1和itr2都指向一个位置就返回true
Itr1!=itr2:如果itr1和itr2都指向不同位置就返回true
3、 需要迭代器的容器操作
Iterator insert(iterator pos, const Object &x):添加x到表中迭代器pos所指向的位置之前的位置。返回值是一个指向插入项位置的迭代器。
Iterator erase(iterator pos):删除迭代器所给出位置的对象。返回值是调用之前pos所指向元素的下一个元素的位置。这个操作使pos失效,pos指向的容器变量已经被删除。
Iterator erase(iterator start, iterator end):删除所有从位置start开始、直到位置end(但是不包括end)的所有元素。
STL提供的解决方案是每一个集合不仅包含嵌套的iterator类型,也包含嵌套的const_iterator类型。它们之间的主要区别是: const_iterator的operator*返回常量引用,这样就不能出现在赋值语句的左边。
更进一步地,编译器还要求必须使用const_iterator类遍历常量几个。
本节给出一个vector类模板的实现。
类模板命名为Vector,主要的细节:
1、Vector将仍然是基本数组(通过一个指针变量来指向分配的内存块)。数组的容量和当前数组项数目存储在Vector中。
2、Vector将实现三大函数,为复制构造函数和operator=提供深复制,同时也提供析构函数来回收基本数组。
3、Vector提供resize例程来改变Vector的大小:提供reserve例程来改变Vector的容量。容量的改变时通过为基本数组分配一个新的内存块,然后复制旧内存块到新块中,再释放旧块的内存。
4、提供operator[]实现。
5、提供基本的例程,例如size、empty、clear、back、pop_back、push_back。
6、支持嵌套的iterator和const_iterator类型,并提供关联的begin和end方法。
下面是Vector类的实现。
#include "stdafx.h"
template
class Vector
{
public:
explicitVector(int initSize = 0)
:theSize(initSize), theCapacity(initSize+ SPARE_CAPACITY)
{ objects = newObject[theCapacity]; }
Vector(constVector &rhs) : objects(NULL)
{ operator=(rhs);}
~Vector()
{ delete[]objects; }
const Vector&operator=(constVector &rhs)
{
if(this != &rhs)
{
delete[]objects;
theSize = rhs.size();
theCapacity = rhs.capacity();
objects = newObject[capacity()];
for(int k = 0; k < size(); k++)
{
objects[k] = rhs.objects[k];
}
return*this;
}
}
void resize(int newSize)
{
if(newSize> theCapacity)
reserve(2*theCapacity+1);
theSize = newSize;
}
voidreserve(int newCapacity)
{
if(newCapacity< theSize)
return;
Object *oldArray = objects;
objects = newObject[newCapacity];
for(int k = 0; k < theSize; k++)
{
objects[k] = oldArray[k];
}
theCapacity = newnewCapacity;
delete[]oldArray;
}
Object &operator[](int index)
{ returnobjects[index]; }
const Object&operator[](intindex) const
{ returnobjects[index]; }
bool empty()
{ returnsize() == 0; }
int size() const
{ returntheSize; }
intcapacity() const
{ returntheCapacity; }
voidpush_back(const Object &x)
{
if(theSize== theCapacity)
reserve(2*theCapacity +1);
objects[theSize++] = x;
}
voidpop_back()
{ theSize--; }
const Object&back() const
{ returnobjects[theSize-1]; }
typedefObject * iterator;
typedef const Object * const_iterator;
iterator begin()
{ return&objects[0]; }
const_iterator begin() const
{ return&objects[0]; }
iterator end()
{ return&objects[size()]; }
const_iterator end() const
{ return&objects[size()]; }
enum {SPARE_CAPACITY = 16 };
private:
int theSize;
inttheCapacity;
Object *objects;
};
本节提供一个可用的list类模板的实现,命名为List,使用双向链表实现。
需要4个类:
1、 List类本身
2、 Node类。结点包含数据和用来指向其前和其后的结点的指针,以及构造函数
3、 const_iterator类。抽象了位置的概念,是公有的嵌套类。存储指向当前结点的指针,并且提供基本迭代器操作的实现,以及所有的重载操作符,例如=、==、!=和++。
4、 iterator类。除了operator*操作返回所指项的引用,而不是该项的常量引用外,具有与const_iterator相同的功能。
添加一些额外的结点,称为哨兵结点。在头部的节点称为表头结点,在末端的节点称为尾结点。
List的实现:
#include "stdafx.h"
template
class List
{
private:
struct Node
{
Object data;
Node *prev;
Node *next;
Node( constObject &d = Object(), Node *p = NULL, Node *n = NULL)
:data(d), prev(p), next(n){}
};
public:
classconst_iterator
{
public:
const_iterator():current(NULL)
{}
constObject &operator*() const
{ returnretrieve(); }
const_iterator &operator++()
{
current = current->next;
return*this;
}
const_iterator &operator++(int)
{
const_ierator old = *this;
++(*this)
returnold;
}
bool operator==(constconst_iterator &rhs) const
{ returncurrent==rhs.current; }
bool operator!=(constconst_iterator &rhs) const
{ return!(*this == rhs); }
protected:
Node *current;
Object &retrieve() const
{ returncurrent->data; }
const_iterator(Node *p):current(p)
{}
friend class List
Node类包括所存储的项、指向Node之前及之后的指针和一个构造函数。
Iterator继承自const_iteraor,需要使用const_iteraor的地方都可使用Iterator。
List的数据成员包括指向表头结点和尾结点的指针,也记录数据成员的大小。
Begin和end方法返回适当的迭代器,实现中返回一个已构造的迭代器,构造函数使用指向Node的指针作为参数。
Clear方法重复执行删除操作来删除每一项,直到List变为空。
Front到pop_back方法都是通过包含和使用迭代器来工作的。Insert在某个位置之前插入,push_back在末尾标记之前插入。
Const_iterator存储指向当前节点的指针,为protected,这样允许从Const_iterator继承的类具有访问权限。Const_iterator的构造函数在List类的begin和end的实现中用到,此构造函数不可以是公有的,但是又希望iterator类可以访问它,因此设为受保护的,然而没有提供List类访问这个构造函数的权限,解决办法是友元声明,允许List类访问Const_iterator的非公有成员。
Const_iterator的公有方法都使用操作符重载,operator==、operator!=和operator*。Operator++的前缀和后缀版本是不同的,C++给前缀形式指定空参数表,给后缀形式指定一个int参数来赋予前缀和后缀形式不同的标识。这样++itr调用零参数operator++;而itr++调用单参数operator++,这个int参数永远也不适用,其存在的意义在于给出一个不同的标识。
构造函数和三大函数:零参数构造函数和复制构造函数都必须分配表头结点和尾结点,给出一个私有的init例程,生成一个空List。析构函数回收表头节点和尾结点,所有其他结点在析构韩式调用clear时回收。
Insert例程:
Erase例程:
栈又称为LIFO(后进先出)表。
两个流行实现:链接结构;数组。
1、 栈的链表实现
单向链表。表顶端插入实现push,删除表顶端实现pop。
2、 栈的数组实现
使用vector中的back、push_back和ppo_back实现。每个栈有一个theArray和一个topOfStack(对空栈,其值为-1)。
1、平衡符号
检查语法错误,是否缺符号?是否符号成对出现?
2、后缀表达式
计算器计算:
如4*2+5+6*2的后缀或逆波兰记法为42*5+62*+
3、 中缀到后缀的转换
将一个标准形式的表达式(或叫做中缀表达式)转换成后缀表达式。
例如a+b*c+(d*e+f)*g的后缀表达式为abc*+de*f+g*+
4、 函数调用
像栈一样,队列(queue)也是表,但是插入在一端进行,而删除在另一端进行。
队列基本操作是enqueue(入队),dequeue(出队)。
同栈一样,任何表的实现都是合法的。这里讨论数组实现。
保存一个数组theArray以及位置front和back,代表表队列的两端,还记录实际存在于队列中的元素的个数currentSize。
循环数组实现: