1. 抽象数据类型(ADT)
- 抽象数据类型(Abstract Data Type, ADT): 带有一组操作的一些对象的集合。
2. 表 ADT
- 表(list):形如 A0, A1, A2, ···, A N-1,这个表的大小是N。
- 大小为 0 的特殊的表称为空表(empty list)
- 对于除空表外的任何表,称 Ai 后继Ai-1(或继 Ai-1 之后,i < N)并称 Ai-1 前驱 Ai(i > 0)。表中的第一个元素是A0,最后一个元素是AN-1。元素Ai在表中的位置(position)是 i 。
- 表ADT的一些操作:
- printList 和 makeEmpty 是常用的操作;
- find 返回某一项第一次出现的位置;
- insert 和 remove 一般是从表的某个位置插入和删除某个元素;
- findKth 返回(作为参数而被指定的)某个位置上的元素
如果列表是34、12、52、16、12,则 find(52)返回2;insert(x、2)可以将表变成34、12、x、52、16、12(如果我们插入到给定的位置);remove(52)可以将该表变成34、12、x、16、12。
2.1 表的简单数组实现
- 虽然数组由固定容量所创建,但 vector 类(其内部存储一个数组)在需要的时候可以使其容量成倍地增长。
- 数组实现可使 printList 以线性时间运行,而 findKth 操作则花费常数时间;插入和删除操作的最坏情况为 O(N),平均情况需移动表的一半元素,需要线性时间,如果所有操作都发生在表的尾端(high end of the list),则花费 O (1) 时间。
- 如果表是通过在尾端(high end)进行插入操作建成的,此后只发生对数组的访问(即只有 findKth 操作)。在这种情况下,数组是表的一种恰当的实现。
2.2 简单链表
为了避免插入和删除的线性开销,需保证表可以不连续存储,否则表的每个部分都可能需要整体移动。
- 链表(linked list)由一系列节点组成,这些节点不必在内存中相连。每一个节点均含有表元素和一个链(link),该链指向包含该元素后继元的另一个节点。我们称之为 next 链。最后一个单元的next链指向 nullptr。
从链表中删除:
- remove 方法可以通过修改一个 next 指针来实现。
向链表插入:
- insert 方法使用 new 操作符创建一个新节点,再执行两次 next 指针操作。
原则上如果知道变动将要发生的地方,那么插入或从链表中删除一项的操作值涉及常数个节点链的改变。
典型的链表拥有到该表两端的链。
单向链表(每个节点均存储指向其下一节点的链)的局限性:在删除最后一项时,需要找出只想最后节点的项,将其 next 链改为 nullptr,但单向链表指向最后节点的链不提供关于最后节点的前驱节点的任何信息。
解决方案:
使用双向链表(doubly linked list),让每个节点持有一个指向它在表中的前驱节点的链。
带有头节点和尾节点的双向链表:
带有头节点和尾节点的空的双向链表:
插入:
删除:
3. STL中的 vector 和 list
-
C++ 语言在其库中包含一些常用数据结构的实现。该语言这一部分通常叫作标准模板库(Standard Template Library, STL)。表 ADT(List ADT)是在 STL 中实现的数据结构之一。一般说来,这些数据结构被称为集合(collection)或容器(container)。
-
表ADT 的两种流行实现方法:vector 和 list
- vector 提供表 ADT 的一种可增长的数组实现。
- 优点:以常数时间可索引(indexable)的
- 缺点:插入新项和删除现有项的代价高昂,除非变化发生在vector的尾端。
- list 提供表 ADT 的双向链表实现。
- 优点:插入新项和删除现有项代价低廉,但假设变动的位置是已知的。
- 缺点:list不容易被索引。
- vector 和 list 两者在执行查找时都是低效的。
- vector 提供表 ADT 的一种可增长的数组实现。
vector 和 list 两者均为类模板,它们用其所存储的项的类型来实例化而成为具体的类。二者有几个方法是共有的。前三种方法实际上对所有的 STL 容器都是可用的:
- int size( ) const:返回容器中元素的个数。
- void clear( ):从容其中删除所有元素。
- bool empty( ) const:若容器不含有元素则返回 true,否则返回 false。
vector 和 list 两者都支持以常数时间向表的尾端添加和从表的尾端删除的操作。 vector 和 list 两者都支持以常数时间访问表的前端项。这些操作是:
- void push_back(const Object & x):把 x 添加到表的尾端。
- void pop_back( ) :删除位于表的尾端的对象。
- const Object & back( ) const:返回位于表的尾端处的对象(还提供一个返回引用的修改函数)。
- const Object & front( ) const:返回位于表的前端处的对象(还提供一个返回引用的修改函数)。
因为双向链表在其前端可以进行高效的改动,而 vector 不能,所以下列两个方法只对 list 是可用的:
- void push_front( const Object & x):把 x 添加到表的前端。
- void pop_front( ):删除位于表的前端处的对象。
vector 有自己的方法集,这些方法 list 不具备。有两个方法可以进行高效的索引操作,而其余两个方法允许程序员查看和改变内部容量。这些方法是:
- Object & operator[ ] ( int idx ):返回 vector 中下标为 idx 的对象,不带界限检验(还提供一个返回常量引用的访问函数)。
- Object & at( int idx ):返回 vector 中下标为 idx 的对象,带有界限检验(还提供一个返回常量引用的访问函数)。
- int capacity( ) const:返回 vector 的内部容量。
- void reserve (int newCapacity):设置新的容量。如果有好的估计可用,那么可用于避免扩展 vector。
4. vector 的实现
提供一个 第一类类型(first-class type) 的 vector 类模板,其含义为,不同于C++中的原始数组(primitive array),该 vector对象可以被复制,并且所用内存可以(通过其析构函数)被自动回收。
C++原始数组的一些重要特征:
- 数组就是指向一块内存块的指针变量;数组的具体大小必须由程序员单独确定。
- 内存块可以通过 new[ ] 分配,但此后必须通过 delete[ ] 释放。
- 内存块不能重新调整大小(但可以获得一个新的、根据推测可能更大的内存块,并利用原来的内存块初始化,然后将原内存块释放)。
vector类:
#include<algorithm>
template<typename Object>
class Vector
{
public:
// 构造函数,允许用户指定初始大小(默认值为0),
// 然后用比大小稍大的容量初始化数据成员
explicit Vector(int initSize = 0) : theSize{initSize},
theCapacity{ initSize + SPARE_CAPACITY}
{
objects = new Object[theCapacity];
}
// 拷贝构造函数
Vector(const Vector & rhs) : theSize{rhs.theSize},
theCapacity{ rhs.theCapacity }, objects{ nullptr }
{
objects = new Object[theCapacity];
for(int k = 0; k < theSize; ++k)
objects[k] = rhs.objects[k];
}
// 拷贝赋值
Vector & operator= (const Vector & rhs)
{
Vector copy = rhs;
std::swap(*this, copy);
return *this;
}
~Vector()
{ delete [ ] objects; }
// 移动构造函数
Vector (Vector && rhs) : theSize{ rhs.theSize },
theCapacity{ rhs.theCapacity }, objects{ rhs.objects }
{
rhs.objects = nullptr;
rhs.theSize = 0;
rhs.theCapacity = 0;
}
// 移动 operator=
Vector & operator= (Vector && rhs)
{
std::swap(theSize, rhs.theSize)
std::swap(theCapacity, rhs.theCapacity);
std::swap(objects,rhs.objects);
return *this;
}
void resize(int newSize)
{
if(newSize > theCapacity)
reserve(newSize*2);
theSize = newSize;
}
// 扩展容量
void reserve(int newCapacity)
{
if(newCapacity < theSize)
return;
Object *newArray = new Object[ newCapacity ];
for(int k = 0; k < theSize; ++k)
newArray[k] = std::move(objects[k]);
theCapacity = newCapacity;
std::swap(objects, newArray);
delete [ ] newArray;
}
Object & operator[](int index)
{ return objects[ index ]; }
const Object & operator[](int index) const
{ return objects[ index ];}
bool empty() const
{ return size()==0; }
int size() const
{ return theSize; }
int capacity() const
{ return capacity; }
void push_back(const Object & x)
{
if(theSize == theCapacity)
reserve(2 * theCapacity + 1);
objects[theSize++] = x;
}
void push_back(Object && x)
{
if(theSize == theCapacity)
reserve(2*theCapacity + 1);
object[theSize++] = std::move(x);
}
void pop_back()
{
--theSize;
}
const Object & back() const
{
return objects[theSize - 1];
}
/**
* iterator 和 const_iterator 内嵌类型的声明
* begin 返回第一个数组位置的内存地址
* end 返回第一个非法数组位置的内存地址
*/
typedef Object* 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()]; }
static const int SPARE_CAPACITY = 16;
private:
int theSize; // 大小
int theCapacity; // 容量
Object * objects; // 原始数组
};
5. list 的实现
list类:
template<typename Object>
class List
{
private:
struct Node
{
Object data; // 所存储的项
Node *prev; // 指向前一节点的指针
Node *next; // 指向下一节点的指针
Node(const Object & d = Object{}, Node *p = nullptr, Node *n = nullptr)
: data{d}, prev{p}, next{n} { }
Node(Object && d, Node *p = nullptr, Node *n = nullptr)
: data{std::move(d)}, prev{p}, next{n} { }
};
public:
class const_iterator
{
public:
const_iterator() : current{ nullptr }
{ }
const Object & operator* ( ) const
{ return retrieve(); }
const_iterator & operator++( )
{
current = current->next;
return *this;
}
const_iterator operator++(int)
{
const_iterator old = *this;
++(*this);
return old;
}
bool operator==(const const_iterator & rhs) const
{ return current == rhs.current; }
bool operator!=(const const_iterator & rhs) const
{ return !(*this==rhs); }
protected:
Node *current;
Object & retrieve() const
{ return current->data; }
const_iterator(Node *p) : current{p}
{ }
//friend声明,赋予了 List 类访问 const_iterator 的非公有成员的权利
friend class List<Object>;
};
// 继承(inheritance):iterator 类继承了 const_iterator 的所有数据和方法
class iterator : public const_iterator
{
public:
iterator()
{ }
Object & operator*()
{ return const_iterator::retrive(); }
const Object & operator*() const
{ return const_iterator::operator*();}
iterator & operator++()
{
this->current = this->current->next;
return *this;
}
iterator operator++(int)
{
iterator old = *this;
++( *this );
return old;
}
protected:
iterator(Node *p) : const_iterator{ p }
{ }
friend class List<Object>;
};
public:
List()
{ init(); }
~List()
{
clear();
delete head;
delete tail;
}
List(const List & rhs)
{
init();
for(auto & x : rhs)
push_back(x);
}
List & operator= (const List & rhs)
{
List copy = rhs;
std::swap(*this,copy);
return *this;
}
List(List && rhs)
: theSize{rhs.theSize}, head{rhs.head}, tail{rhs.tail}
{
rhs.theSize = 0;
rhs.head = nullptr;
rhs.tail = nullptr;
}
List & operator= (List && rhs)
{
std::swap(theSize, rhs.theSize);
std::swap(head, rhs.head);
std::swap(tail, rhs.tail);
return *this;
}
iterator begin()
{ return {head->next}; }
const_iterator begin() const
{ return {head->next}; }
iterator end()
{ return {tail}; }
const_iterator end() const
{ return {tail}; }
int size() const
{ return theSize; }
bool empty() const
{ return size() == 0; }
// clear方法,通过反复删除成员项直至 List 为空来完成清除工作
void clear()
{
while(!empty())
pop_front();
}
Object & front()
{ return *begin();}
const Object & front() const
{ return *begin();}
Object & back()
{ return *--end();}
const Object & back() const
{ return *--end();}
void push_front(const Object & x)
{ insert(begin(), x); }
void push_front(Object && x)
{ insert(begin(), std::move(x)); }
void push_back(const Object & x)
{ insert(endd(), x); }
void push_back(Object && x)
{ insert(end(), std::move(x)); }
void pop_front()
{ erase(begin()); }
void pop_back()
{ erase(--end()); }
// 在 itr 前插入 x
iterator insert(iterator itr, const Object & x)
{
Node *p = itr.current;
theSize++;
return { p->prev = p->prev->next = new Node{x, p->prev, p}};
// 对节点指针的赋值具体步骤
// Node *newNode = new Node{x, p->prev, p};
// p->prev->next = newNode;
// p->prev = newNode;
// 合并为:
// p->prev = p->prev->next = new Node{x, p->prev, p}
}
// 在 itr 前插入 x
iterator insert(iterator itr, Object && x)
{
Node *p = itr.current;
theSize++;
return { p->prev = p->prev->next = new Node{std::move(x), p->prev, p}};
}
// 删除在 itr 处的项
iterator erase(iterator itr)
{
Node *p = itr.current;
iterator retVal{p->next};
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
theSize--;
return retVal;
}
iterator erase(iterator from, iterator to)
{
for(iterator itr = from; itr != to;)
itr = erase(itr);
return to;
}
private:
int theSize; // 大小
Node *head; // 指向头节点的指针
Node *tail; // 指向尾节点的指针
void init()
{
theSize = 0;
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
}