This slide is based on the book of Mark Allen Weiss
张怀勇等译.
And some materials come from the book : The C++ Standard Library, written by Nicolai M. Josuttis.
表、栈和队列是最简单和最基本的三种数据结构.
每一个有意义的程序都将明晰地至少使用一个这样的数据结构.
而
对于 ADT 这个集合, 可以有像
我们将处理形如
\[A_0,A_1,A_2,\ldots,A_{N-1}\]
的一般的
这个表的大小是
对于除空表之外的表,
由程序者自身来判断函数的功能是否恰当.
对表的所有操作都可以使用数组来实现. 虽然数组是静态分配的, 但是内部存储数组的
数组实现使得
为了避免插入和删除的线性开销, 我们需要允许表可以不连续存储, 否则表的部分甚至全部就需要整体移动.
下图表达了链表的一般思想.
将链表的每一个结点都添加一个指向上一项的链接, 如下图.
这称为
表 ADT 有两个流行的实现:
int size() const //返回容器内的元素个数
void clear() //删除容器中所有的元素
bool empty() //如果容器中没有元素, 则返回 true, 否则返回 false.
事实上, 上面三种方法对所有的
void push_back(const Object & x) |
在表的末尾添加 |
||
void pop_back() |
删除表的末尾的对象 | ||
const Object & back() const |
返回表的末尾的对象(也提供返回引用的修改函数) | ||
const Object & front() const |
返回表的前端的对象(也提供返回引用的修改函数) | ||
void push_front(const Object & x) |
在 |
||
void pop_front() |
在 |
||
Object & operator[](int idx) |
返回 |
||
Object & at(int idx) |
返回 |
||
int capacity() const |
返回 |
||
void reserve(int new Capacity) |
设定 |
一些表的操作, 例如在表的中部进行插入和删除的操作, 需要位置标记. 在
在描述某些方法的时候, 为简明起见, 我们使用
iterator begin(): 返回指向容器的第一项的一个适当的迭代器.
iterator end(): 返回指向容器的终止标志(容器中最后一项的后面的位置, 在“边界之外”)的一个适当的迭代器.
for(int i=0;i!=v.size();++i) cout << v[i] << endl;
使用迭代器改写
for(vector<int>::iterator itr=v.begin();itr!=v.end();itr???) cout << itr??? << endl;
在循环终止检测中,
于是上面的代码可以写成
for(vector<int>::iterator itr=v.begin(); itr!=v.end(); ++itr) cout << *itr << endl;
使用操作符重载可以允许迭代器访问当前项, 然后也可以写成
vector<int>::iterator itr=v.begin(); while(itr!=v.end()) cout << *itr++ << endl;//注意 *itr++ 返回结果是 itr 未自增前的地址所存的值.
iterator insert(iterator pos, const Object & x)添加
iterator erase(iterator pos)删除迭代器所给出位置的对象.
iterator erase(iterator start, iterator end)删除所有从位置
c.erase(c.begin(),c.end())
#ifndef REMOVEOTHERITEM_H_INCLUDED #define REMOVEOTHERITEM_H_INCLUDED template <typename Container> void removeEveryOtherItem( Container & lst ) { typename Container::iterator itr = lst.begin( ); while( itr != lst.end( ) ) { itr = lst.erase( itr );//注意返回的是调用之前 itr 所指对象的下一个位置. if( itr != lst.end( ) ) ++itr; } } #endif // REMOVEOTHERITEM_H_INCLUDED
// constructing lists // http://www.cplusplus.com/reference/list/list/list/ #include <iostream> #include <list> #include "removeOtherItem.h" int main () { // constructors used in the same order as described above: std::listfirst; // empty list of ints std::list second (4,100); // four ints with value 100 std::list third (second.begin(),second.end()); // iterating through second std::list fourth (third); // a copy of third // the iterator constructor can also be used to construct from arrays: int myints[] = {16,2,77,29,2,5,10,12,15}; std::list fifth (myints, myints + sizeof(myints) / sizeof(int) ); std::cout << "The contents of fifth are: "; for (std::list ::iterator it = fifth.begin(); it != fifth.end(); it++) std::cout << *it << ' '; std::cout << '\n'; removeEveryOtherItem(fifth); for (std::list ::iterator it = fifth.begin(); it != fifth.end(); it++) std::cout << *it << ' '; std::cout << '\n'; return 0; }
假设我们需要将一个集合里的所有项都改为一个特殊的值. 下面的例程工作于
template <typename Container, typename Object> void change( Container & c, const Object & newValue ) { typename Container::iterator itr = c.begin( ); while( itr != c.end( ) ) { *itr++ = newValue; } }
当然, 上面的例程在编译上其实没有问题. 为了发现潜在的问题, 假设
考察下面打印整数
void print(const list<int> & lst, ostream & out = cout) { list<int>::iterator itr=lst.begin();// 编译器会提示错误. while(itr != lst.end()) { out << *itr << endl; *itr=0; // This is fishy !!! (That means the thing or situation is strange or isn’t as it seems.) itr++; } }
因此,
list<int>::iterator itr=lst.begin();
需要改为
list<int>::const_iterator itr=lst.begin();
其次,
*itr=0;
编译会出错.
编译器还会要求必须使用
两个版本的
使用
template <typename Container> void printCollection( const Container & c, ostream & out = cout ) { if( c.empty( ) ) out << "(empty)"; else { typename Container::const_iterator itr = c.begin( ); out << "[ " << *itr++; // Print first item while( itr != c.end( ) ) out << ", " << *itr++; out << " ]" << endl; } }
template <typename Object> class Vector { public: explicit Vector( int initSize = 0 ) : theSize( initSize ), theCapacity( initSize + SPARE_CAPACITY ) { objects = new Object[ theCapacity ]; } Vector( const Vector & rhs ) : objects( NULL ) { operator=( rhs ); } ~Vector( ) { delete [ ] objects; } const Vector & operator= ( const Vector & rhs ) { if( this != &rhs ) { delete [ ] objects; theSize = rhs.size( ); theCapacity = rhs.theCapacity; objects = new Object[ capacity( ) ]; for( int k = 0; k < size( ); k++ ) objects[ k ] = rhs.objects[ k ]; } return *this; } void resize( int newSize ) { if( newSize > theCapacity ) reserve( newSize * 2 + 1 ); theSize = newSize; } void reserve( int newCapacity ) { if( newCapacity < theSize ) return; Object *oldArray = objects; objects = new Object[ newCapacity ]; for( int k = 0; k < theSize; k++ ) objects[ k ] = oldArray[ k ]; theCapacity = newCapacity; delete [ ] oldArray; } 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 theCapacity; } void push_back( const Object & x ) { if( theSize == theCapacity ) reserve( 2 * theCapacity + 1 ); objects[ theSize++ ] = x; } void pop_back( ) { theSize--; } const Object & back ( ) const { return objects[ theSize - 1 ]; } 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( ) ]; } enum { SPARE_CAPACITY = 16 }; private: int theSize; int theCapacity; Object * objects; };
从上面的代码可以看出,
template <typename Object> class List { private: struct Node { Object data; Node *prev; Node *next; Node( const Object & d = Object( ), Node *p = NULL, Node *n = NULL ) : data( d ), prev( p ), next( n ) { } }; public: class const_iterator { public: const_iterator( ) : current( NULL ) { } const Object & operator* ( ) const { return retrieve( ); } //前置++ const_iterator & operator++ ( ) { current = current->next; return *this; } //例如 ++itr 调用了零参数 operator++ //后置++ const_iterator operator++ ( int ) { const_iterator old = *this; ++( *this ); //调用了上面的前置++ return old; } //itr++ 调用了单参数 operator++(int), 这里的 int 参数不使用, 其存在的意义仅仅在于给出一个不同的标识, 以区别零参数的 operator++ 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 class List < Object >; //友元声明(friend declaration), 允许List类访问const_iterator类的非公有成员. }; class iterator : public const_iterator { public: iterator( ) { } Object & operator* ( ) { return retrieve( ); } const Object & operator* ( ) const { return const_iterator::operator*( ); } iterator & operator++ ( ) { current = current->next; return *this; } iterator operator++ ( int ) { iterator old = *this; ++( *this ); return old; } iterator & operator-- ( ) { current = current->prev; return *this; } iterator operator-- ( int ) { iterator old = *this; --( *this ); return old; } protected: iterator( Node *p ) : const_iterator( p ) { } //注意这里的初始化列表实际上调用了父类 List::const_iterator 中的构造函数 List::const_iterator::const_iterator(Node * p) friend class List < Object >; //友元声明(friend declaration), 允许List类访问iterator类的非公有成员. }; public: List( ) { init( ); } ~List( ) { clear( ); delete head; delete tail; } List( const List & rhs ) { init( ); *this = rhs; } const List & operator= ( const List & rhs ) { if( this == &rhs ) return *this; clear( ); for( const_iterator itr = rhs.begin( ); itr != rhs.end( ); ++itr ) push_back( *itr ); return *this; } void init( ) { theSize = 0; head = new Node; tail = new Node; head->next = tail; tail->prev = head; } iterator begin() { return iterator( head->next ); } const_iterator begin() const { return const_iterator( head->next ); } iterator end() { return iterator( tail); } const_iterator end() const { return const_iterator( tail); } int size() const {return theSize;} bool empty() const {return size()==0;} 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_back( const Object & x ) { insert( end( ), x ); } void pop_front( ) { erase( begin( ) ); } void pop_back( ) { erase( --end( ) ); } //insert Object x before itr. iterator insert( iterator itr, const Object & x ) { /* See Figure 3.18 */ //注意这里可以直接访问itr.current, 是因为 iterator 已经声明其中有友元 List<Object> 了. Node *p = itr.current; theSize++; //既然是插入, 当然 theSize 得增加 1. //注意最后一句 return iterator( p->prev=p->prev->next=new Node(x, p->prev, p) ); } // Erase item at itr. iterator erase( iterator itr ) { /* See Figure 3.20 */ Node *p = itr.current; iterator retVal( p->next ); //定义了一个名为 retVal 的迭代器, 这里 retVal 是 returnValue 的缩写. p->prev->next = p->next; p->next->prev = p->prev; delete p; theSize--; return retVal; } iterator erase( iterator start, iterator end ) { /* See Figure 3.20 */ for( iterator itr = start; itr != end; ) { itr = erase( itr ); } return end; } private: int theSize; Node *head; Node *tail; void init( ) { theSize = 0; head = new Node; tail = new Node; head->next = tail; tail->prev = head; } };
C++ 需要通过给前缀形式指定空参数表, 给后缀形式指定一个(匿名的)
微软的 FAT 文件系统就采用了链式结构来组织文件块在磁盘上的存放. 不过 FAT 系统效率很低, 早已经被淘汰. 后来微软推出了 NTFS 文件系统. 而 UNIX 的文件系统比较先进, 它将文件以索引结构来组织.
思考一下采用链式结构的 FAT 文件系统为什么效率地下?
例如, 硬币储存罐, 弹匣等.
由于栈是一个表, 因此任何实现表的方法都能实现栈.
使用
在某些机器上, 若在带有自增和自减寻址功能的寄存器上操作, 则对于整数的
大多现代计算机将栈操作作为其指令系统的一部分, 这个事实强化了这样一种思想, 即在计算机科学中, 栈很可能是继数组之后的最基本的数据结构.
#pragma once #include <vector> using namespace std; template <typename Object> class Stack { public: Stack(int n = 0) : elements(n), top(-1) {}; ~Stack() {}; void push(const Object & elementToPush) { //elements.length() << 1 是移位运算, 比 //elements.length() * 2 要快 if (++top == elements.size()) { elements.resize(elements.size() << 1); } elements[top] = elementToPush; } bool pop(Object & poppedElement) { //如果栈是空的 if (top == -1) return false; //如果栈非空, 那么位于 top 处的元素就是栈顶元素 poppedElement = elements[top];//按传址方式传递, 见参数传递方式 top--; //元素已删除, 应该检查是否可以将数组减半, 以节省堆内存. int trysize = elements.length(); while ((top + 1 <= trysize >> 2) && trysize > 2) trysize >>= 1; if (trysize < elements.length()) { //try //{ elements.resize(trysize); //} //catch(e){} } return true; } // returns the element at the top of the stack bool peek(Object & topElement) { if (top == -1) return false; topElement = elements[top]; return true; } //也可以按自己的想法命名, 比如 isEmpty() bool empty() const { return top == -1; } void makeEmpty() { top = -1; //使用 resize() 释放更多动态内存 //但有可能我们正处于使用完所有堆内存的边缘上, 此时 resize() 函数 //无法创建一个拥有两个元素的新数组. 所以使用 try...catch try { elements.resize(2); } catch (bad_alloc e) { //bad_alloc //当使用动态内存分配操作符new时, 如果得不到内存空间的分配, 就抛出类型为 bad_alloc 的异常 //bad_alloc 是一种从基类 exception 派生的类型. } } typedef Object * iterator; typedef const Object * const_iterator; iterator begin() { return &elements[0]; } iterator end() { return &elements[elements.size()]; } const_iterator begin() const { if (top == -1) { return nullptr; } else { return &elements[0]; } } const_iterator end() const { return &elements[elements.size()]; } int size() const { return top+1; } private: vector<Object> elements; int top; };
编译器检查程序的语法错误, 但是常常由于缺少一个符号(如遗漏一个花括号或是注释起始符)导致编译器列出上百行的错误, 而真正的错误却并没有找出.
在这个情况下, 一个有用的工具就是一个检验是否所有的东西都成对出现的程序.
为简单起见, 我们仅就圆括号、方括号和花括号进行检验并忽略出现的任何其他字符.
一个简单的算法是用栈. 叙述如下:
可以确信, 这个算法是线性的, 并且事实上它只需对输入进行一次检验. 因此, 是联机(online)的并且相当快.
可以做一些附加的工作来决定当检测出错时如何处理——例如判断可能的原因.
在你的计算器(如便携计算器, 手机应用软件)中输入下面的式子
4.99+5.99+6.99*1.06=
查看结果是
再试一下
4.99*1.06+5.99+6.99*1.06=
可以将这种操作顺序写为:
4.99 1.06 * 5.99 + 6.99 1.06 * +
这种记法叫作
计算这个问题最容易的方法是使用栈.
后缀表达式的优点
栈不仅可以用来计算后缀表达式的值, 而且还可以用来将一个标准形式的表达式(或叫作
a + b * c + (d * e + f) * g
a b c * + d e * f + g * +
算法:
#ifndef QUEUE_H #define QUEUE_H //使用单向链表实现队列 //这里单向链表的实现特别是迭代器部分参考了之前的List类 //其余也参考了《C++ Primer Plus》中关于 Queue 的代码, 见第6版 P462. //这里使用了模板类 template <typename Object> class Queue { private: struct Node { Object item; struct Node * next; }; enum {Q_SIZE=10}; public: //create queue with a qs limit Queue(int qs=Q_SIZE): qsize(qs), front(nullptr), rear(nullptr), items(0) { } //析构函数 dtor ~Queue() { clear(); } //复制构造函数, 调用operator=函数 Queue(const Queue& rhs) { *this = rhs; } //operator=函数, 深度复制 const Queue& operator=(const Queue& rhs) { if (this == &rhs) return *this; // handle self assignment //assignment operator clear( ); for( const_iterator itr = rhs.begin( ); itr != rhs.end( ); ++itr ) enqueue( *itr ); return *this; } //队列是否为空 bool empty() const { return items==0; } //队列是否已满 bool isfull() const { return items==qsize; } //返回队列中的元素个数//int queuecount() const int size() const { return items; } void clear() { Node * temp; while(front!=nullptr) { temp=front; front=front->next; delete temp; } } //入列, add item to end bool enqueue(const Object & item) { if(isfull()) return false; Node * add=new Node; //create Node //on failure, new throws std::bad_alloc exception add->item=item; //set node pointers add->next=nullptr; //or NULL items++; if(front == nullptr) front=add; else rear->next=add; rear=add;//更新rear return true; } //出列, remove item from front bool dequeue(Object & item) { if(front==nullptr) return false; item=front->item; items--; Node* temp=front; front=front->next; delete temp; if(items==0) rear=nullptr; return true; } 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; const Object & retrieve( ) const { return current->item; } const_iterator( Node *p ) : current( p ) { } friend class Queue < Object >; }; class iterator : public const_iterator { public: iterator( ) { } Object & operator* ( ) { return this->current->item; //这里使用 return this->retrieve(); 编译出错. } 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; } iterator & operator-- ( ) { this->current = this->current->prev; return *this; } iterator operator-- ( int ) { iterator old = *this; --( *this ); return old; } protected: //Node *current; iterator( Node *p ) : const_iterator( p ) { } /** * iterator 继承自 const_iterator() * Object & retrieve( ) const * { return current->data; } * */ friend class Queue < Object >; }; iterator begin() { return iterator( front ); } const_iterator begin() const { return const_iterator( front ); } iterator end() { return iterator( nullptr); } const_iterator end() const { return const_iterator( nullptr ); } private: Node * front;//指向第一个结点的指针, pointer to front of Queue Node * rear; //指向最后一个结点的指针, pointer to rear of Queue int items; //current number of items in Queue const int qsize;//maximum number of items in Queue }; #endif // QUEUE_H
直接使用数组实现队列并不太好. 因为如果使用数组, 每次在头部删除元素(出队), 都要将其他元素进行移动. 要使得出队后其他元素在数组中不移动, 可以使用循环数组.
操作 | 效果 |
---|---|
vector<Elem> c; | 生成一个空的向量c, 其中没有任何元素. |
vector<Elem> c1(c2); | 生成与 c2 同样的一个向量 c1, 所有元素都被复制. |
vector<Elem> c(n); | 利用元素的默认构造函数生成一个大小(size)为 $n$ 的向量. |
vector<Elem> c(n,elem); | 生成一个大小为 $n$ 的向量, 其中每个元素的值都是 elem. |
vector<Elem> c(begin, end); | 生成一个向量c, 以区间[begin, end]作为元素初值. |
c.~vector<Elem>(); | 销毁向量c中的所有元素, 并释放内存. |
操作 | 效果 |
---|---|
list<Elem> c; | 生成一个空的链表(list)c, 其中没有任何元素. |
list<Elem> c1(c2); | 生成与 c2 同样的一个链表 c1, 所有元素都被复制. |
list<Elem> c(n); | 利用元素的默认构造函数生成一个大小(size)为 $n$ 的链表. |
list<Elem> c(n,elem); | 生成一个大小为 $n$ 的链表, 其中每个元素的值都是 elem. |
list<Elem> c(begin, end); | 生成一个链表c, 以区间[begin, end]作为元素初值. |
c.~list<Elem>(); | 销毁链表c中的所有元素, 并释放内存. |
#include <iostream> #include <list> using namespace std; int main() { cout << "使用迭代器遍历 list" << endl; list<char> coll; // list container for character elements //append elements from 'a' to 'z' for(char c='a'; c<='z'; ++c) { coll.push_back(c); } /* print all elements * - iterate over all elements */ list<char>::const_iterator pos; for(pos=coll.begin(); pos!=coll.end(); ++pos) { cout << *pos << ' '; } cout << endl; return 0; }
注意, 使用前置式递增
#include<iostream> #include<fstream> #include<stack> #include<cstdlib> using namespace std; bool match(string &file); int main() { string filename; cout << "*************************************" << endl; cout << "* This program will check whether the symbols" << endl; cout << "* () [] {} <> are nested correctly. " << endl; cout << "*************************************" << endl; cout << "Please enter the file name: "; getline(cin,filename); cout << "\nInput file: " << filename << "\n" << endl; if(match(filename)) { cout << "****Congratulations!!****\nYour file's symbols are matched." << endl; } else { cout << "^^^Sorry^^^\nPlease correct your file" << endl; } return 0; } bool match(string &file) { ifstream File; stack<char> CC; File.open(file.c_str()); if(!File) { cerr << "Error!!\nUnable to find the file!" << endl; exit(1); } char CH; int symb; while(File.get(CH)) { switch(CH) { case'(':symb=1;break; case')':symb=-1;break; case'[':symb=2;break; case']':symb=-2;break; case'{':symb=2;break; case'}':symb=-2;break; case'<':symb=2;break; case'>':symb=-2;break; default:symb=0; } if(symb > 0) { CC.push(CH); cout << "push " << CH << " in stack" << endl; }else if(symb<0) { if(CC.top()==CH+symb) { CC.pop(); cout << "pop " << CH << endl; } else CC.push(CH); } } File.close(); if(CC.size()>0) { return false; } return true; }
这里将开符号
刚才的代码对整个文档中的开符号
为简单起见, 这里只列出
//改进match()函数, 使之可以记录行数和列数 bool match(string &file) { ifstream File; stack<char> CC; File.open(file.c_str()); if(!File) { cerr<<"Error!!\nUnable to find the file!"<<endl; exit(1); } char CH; int symb; int number_of_lines=1; int number_of_columns=0; int number_of_characters=0; while(!File.eof()) { File.get(CH); ++number_of_characters; if(CH=='\n') { ++number_of_lines; number_of_columns=0; }else { ++number_of_columns; } switch(CH) { case'(':symb=1;break; case')':symb=-1;break; case'[':symb=2;break; case']':symb=-2;break; case'{':symb=2;break; case'}':symb=-2;break; case'<':symb=2;break; case'>':symb=-2;break; default:symb=0; } if(symb>0) { CC.push(CH); //cout<<"push "<<CH<<" in stack"<<endl; }else if(symb<0) { if(CC.size()>0 && CC.top()==CH+symb) {//如果栈中有元素, 且top处的元素和CH是匹配的, 则弹出top处的元素. CC.pop(); //cout<<"pop "<<CH<<endl; } else { CC.push(CH); //cout<<"push "<<CH<<" in stack"<<endl; cout<<"Line: "<<number_of_lines<<" ; Col: "<<number_of_columns<<endl; cout<<"-------------------"<<endl; } } } cout<<"Total characters: "<<number_of_characters-number_of_lines<<endl; if(number_of_characters==0) { number_of_lines=0; number_of_columns=0; } cout<<"Total lines: "<<number_of_lines<<endl; File.close(); if(CC.size()>0) { return false; } return true; }