of {$slidecount} ½ {$title} ATZJG.NET {$author}

首页






表、栈和队列
抽象数据类型(ADT)


Haifeng Xu


(hfxu@yzu.edu.cn)

This slide is based on the book of Mark Allen Weiss
Data Structures and Algorithm Analysis in C++
张怀勇等译.

And some materials come from the book : The C++ Standard Library, written by Nicolai M. Josuttis.

目录

表、栈和队列

表、栈和队列

表、栈和队列是最简单和最基本的三种数据结构.

每一个有意义的程序都将明晰地至少使用一个这样的数据结构.

则在程序中总是要间接地用到, 而不管你在程序中是否进行了声明.

抽象数据类型(ADT)

抽象数据类型(Abstract Data Type)

抽象数据类型是带有一组操作的一些对象的集合.

对于 ADT 这个集合, 可以有像 加(add)删除(remove)大小(size)包含(contains) 这样一些操作.

表 ADT

表 ADT

我们将处理形如 \[A_0,A_1,A_2,\ldots,A_{N-1}\] 的一般的.

这个表的大小是 N. 我们称大小为 0 的表为空表(empty list).

对于除空表之外的表,

对于表的操作

由程序者自身来判断函数的功能是否恰当.

表的简单数组实现

表的简单数组实现

对表的所有操作都可以使用数组来实现. 虽然数组是静态分配的, 但是内部存储数组的 vector 类允许在需要的时候将数组的大小增加一倍.

数组实现使得

简单链表

简单链表(simple linked list)

为了避免插入和删除的线性开销, 我们需要允许表可以不连续存储, 否则表的部分甚至全部就需要整体移动.

下图表达了链表的一般思想.

Layer 1 A 0 A 1 A 4 A 2 A 3

findKth(i)

remove

Layer 1 A 0 A 1 A 4 A 2 A 3

insert

insert 方法需要使用 new 操作符从系统取得一个新结点, 此后执行两次引用调整. 其一般思想如下图给出, 其中的虚线表示原来的指针.

Layer 1 A 0 A 1 A 4 A 2 A 3 X

双向链表

将链表的每一个结点都添加一个指向上一项的链接, 如下图.

这称为双向链表(doubly linked list).

STL 中的向量和表

STL 中的向量和表

标准模板库 STL (Standard Template Library)

表 ADT 有两个流行的实现:

vector 的优缺点

list 的优缺点

vectorlist 两者在查找时效率都很低.

vector 和 list 两者的公共方法

事实上, 上面三种方法对所有的 STL 容器都适用.

vector 和 list 的方法

vector 和 list 的方法

Methods Meaning vector list
void push_back(const Object & x)
在表的末尾添加 x
void pop_back()
删除表的末尾的对象
const Object & back() const
返回表的末尾的对象(也提供返回引用的修改函数)
const Object & front() const
返回表的前端的对象(也提供返回引用的修改函数)
void push_front(const Object & x)
list 的前端添加 x
void pop_front()
list 的前端删除对象
Object & operator[](int idx)
返回 vectoridx 索引位置的对象, 不包含边界检测(也提供返回常量引用的访问函数)
Object & at(int idx)
返回 vectoridx 索引位置的对象, 包含边界检测(也提供返回常量引用的访问函数)
int capacity() const
返回 vector 的内部容量
void reserve(int new Capacity)
设定 vector 的新容量

迭代器

迭代器(iterator)

一些表的操作, 例如在表的中部进行插入和删除的操作, 需要位置标记. 在 STL 中, 通过内置类型 iterator 来给出位置.

在描述某些方法的时候, 为简明起见, 我们使用 iterator. 但是, 在编写代码的时候还是使用实际的嵌套类名称.

需要考虑的三个问题

  1. 如何得到迭代器?
  2. 迭代器可以执行什么操作?
  3. 哪些表 ADT 方法需要迭代器作为形参?

获得迭代器

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;

在循环终止检测中, i!=v.size()itr!=v.end() 两者都试图检测循环计数器是否已经“超出边界”。

迭代器的方法

于是上面的代码可以写成

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 未自增前的地址所存的值.

需要迭代器的容器操作

实验

#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::list first;                                // 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;
}

const_iterator

const_iterator

*itr 的结果不只是迭代器所指的项的值, 也是该项本身.

实验

假设我们需要将一个集合里的所有项都改为一个特殊的值. 下面的例程工作于 vectorlist.

template <typename Container, typename Object>
void change( Container & c, const Object & newValue )
{
    typename Container::iterator itr = c.begin( );
    while( itr != c.end( ) )
    {
		*itr++ = newValue;
    }
}

当然, 上面的例程在编译上其实没有问题. 为了发现潜在的问题, 假设 Container c 通过常量引用传递至例程.

考察下面打印整数 list 的代码, 其中一行代码试图暗中修改 list.

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; 

编译会出错.

const_iteratoroperator* 返回常量引用, 这样 const_iterator*itr 就不能出现在赋值语句的左边.

编译器还会要求必须使用 const_iterator 类遍历常量集合. 如下所示, 有两个版本的 begin 和两个版本的 end 来实现这个功能.

两个版本的 begin 可以在同一个类里, 因为方法(无论访问函数还是修改函数)的定常性被认为是函数签名的一部分.

实验

使用 const_iterator 打印任意集合的例子.

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;
};

从上面的代码可以看出, reserve() 不会将向量的容量进行缩减. 因此, 对于删除元素这个操作, 其 references, pointers, iterators 也会继续有效, 继续指向动作发生前的位置. 而插入操作则可能会使 references, pointers, iterators 失效(因为插入可能导致 vector 重新配置空间).

List 类

List 类

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;
    }
};

const_iterator 的公有方法都使用操作符重载. 这里最有趣的是 operator++ 的实现. 在语法上, 前缀++和后缀++是不同的, 因此需要对不同的形式分别编写例程. 它们拥有相同的名字, 因此必须用不同的签名来区分.

C++ 需要通过给前缀形式指定空参数表, 给后缀形式指定一个(匿名的) int 参数来赋予其与前缀形式在标识上的不同. 从这里的实现可以看出, 后缀形式的 operator++ 调用了前缀形式的 operator++, 因此显然前缀形式要比后缀形式来得快.

List 类的应用

微软的 FAT 文件系统就采用了链式结构来组织文件块在磁盘上的存放. 不过 FAT 系统效率很低, 早已经被淘汰. 后来微软推出了 NTFS 文件系统. 而 UNIX 的文件系统比较先进, 它将文件以索引结构来组织.

思考一下采用链式结构的 FAT 文件系统为什么效率地下?

栈(stack)

栈(stack) 是插入和删除操作被限制只能在一个位置上进行的表. 该位置是表的末端, 称为栈的顶(top)

栈模型

例如, 硬币储存罐, 弹匣等.

栈的实现

栈的实现

由于栈是一个表, 因此任何实现表的方法都能实现栈.

栈的数组实现

使用 vector 中的 back, push_backpop_back 来实现.

在某些机器上, 若在带有自增和自减寻址功能的寄存器上操作, 则对于整数的 pushpop 可以写成机器指令.

大多现代计算机将栈操作作为其指令系统的一部分, 这个事实强化了这样一种思想, 即在计算机科学中, 栈很可能是继数组之后的最基本的数据结构.

栈的实现代码

栈的实现代码

#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;

};


栈的应用

栈的应用

平衡符号

编译器检查程序的语法错误, 但是常常由于缺少一个符号(如遗漏一个花括号或是注释起始符)导致编译器列出上百行的错误, 而真正的错误却并没有找出.

在这个情况下, 一个有用的工具就是一个检验是否所有的东西都成对出现的程序.

为简单起见, 我们仅就圆括号、方括号和花括号进行检验并忽略出现的任何其他字符.

一个简单的算法是用栈. 叙述如下:

  1. 做一个空栈, 读入字符直至文件尾.
  2. 如果字符是一个开放符号, 则将其压入栈中.
  3. 如果字符是一个封闭符号, 那么若栈为空, 则报错; 若栈不为空, 则将栈元素弹出.
  4. 如果弹出的符号不是对应的开放符号, 则报错.
  5. 在文件尾, 如果栈非空则报错.

可以确信, 这个算法是线性的, 并且事实上它只需对输入进行一次检验. 因此, 是联机(online)的并且相当快.

可以做一些附加的工作来决定当检测出错时如何处理——例如判断可能的原因.

后缀表达式

在你的计算器(如便携计算器, 手机应用软件)中输入下面的式子

4.99+5.99+6.99*1.06=

查看结果是 $19.05$ 还是 $18.39$ ?

再试一下

4.99*1.06+5.99+6.99*1.06=

  1. A1=4.99*1.06
  2. A1=A1+5.99
  3. A2=6.99*1.06
  4. A1=A1+A2

可以将这种操作顺序写为:

4.99 1.06 * 5.99 + 6.99 1.06 * +

这种记法叫作 后缀(postfix)逆波兰记法(reverse Polish notation).

计算这个问题最容易的方法是使用栈.

后缀表达式的优点

中缀表达式到后缀表达式的转换(Infix to Postfix conversion)

栈不仅可以用来计算后缀表达式的值, 而且还可以用来将一个标准形式的表达式(或叫作中缀表达式)转换成后缀表达式.

a + b * c + (d * e + f) * g
a b c * + d e * f + g * +

算法:

  1. 当读到一个操作数时, 立即把它放到输出中.
  2. 操作符、左括号不立即输出, 而是压入栈中.
  3. 如果遇见一个右括号, 那么就将栈元素依次弹出并输出, 直到遇到对应的左括号为止. 但这个左括号只被弹出并不输出.
  4. 如果遇到任何其他符号(如 +, *, (), 那么从栈中弹出栈元素直到发现优先级更低的元素为止.
    • 有一个例外: 除非是在处理 ) 的时候, 否则决不从栈中移走 ( .
    • + 的优先级最低, ( 的优先级最高.
    • 从栈中弹出元素的工作完成后, 我们再将操作符压入栈中.
  5. 最后, 如果读到输入的末尾, 则将栈元素弹出直到该栈变成空栈, 将符号写到输出中.

队列 ADT

使用单向链表实现队列 ADT

#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

队列 ADT

使用循环数组实现队列 ADT

直接使用数组实现队列并不太好. 因为如果使用数组, 每次在头部删除元素(出队), 都要将其他元素进行移动. 要使得出队后其他元素在数组中不移动, 可以使用循环数组.



STL 中的 vector 类

STL 中的 vector 类

vector 的构造函数与析构函数

操作 效果
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中的所有元素, 并释放内存.

STL 中的 list 类

STL 中的 list 类

list 的构造函数与析构函数

操作 效果
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中的所有元素, 并释放内存.

例子: list 的使用

使用迭代器遍历 list 中的所有元素

#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;
}

注意, 使用前置式递增 ++pos 要比后置式递增 pos++ 效率高, 更快, 因为后者需要一个临时对象, 因为它必须存放迭代器的原本位置并将它返回. 所以一般情况下, 建议使用 ++pos.

使用栈平衡符号

使用栈平衡符号

#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;
}

注意

这里将开符号 (, [, {, < 对应的 symb 设定为正数, 而将闭符号 ), ], }, > 对应的 symb 设定为负数. 而为了方便判断开符号和闭符号的配对, 我们根据它们的ASCII码设定了相应的 symb 值. 比如

使用栈平衡符号

输出出错的位置

刚才的代码对整个文档中的开符号 (, [, {, < 和闭符号 ), ], }, > 进行了匹配判断. 但是如果有错误, 并没有指出错误在哪里. 我们将其作一些改进, 输出不匹配字符所在的行数和列数, 以确定位置方便修改.

为简单起见, 这里只列出 match() 函数的代码.

//改进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;
}

End






Thanks very much!

This slide is based on Jeffrey D. Ullman's work, which can be download from his website.