首页 > 代码库 > <数据结构与算法分析 C++描述> 表/栈/队列

<数据结构与算法分析 C++描述> 表/栈/队列

这一章主要内容:

* 抽象数据类型(ADT)的概念

* 实现表/栈/队列

* 了解这三个数据结构的应用场景

1. ADT

ADT: abstract data type, 是抽象的数学模型,在该模型上定义了一系列的操作。使用它的人,不需要了解它的存储方式,只关心它的逻辑特征。可以使用三元组的方法来表示(D,S,P),D是数据对象,S是数据之间的关系,P是对数据的基本操作,具体介绍,可以参考帖子:点击打开链接

2. 表ADT

表的数据自然是单个元素,而元素之间的关系是前驱与后继,操作包括查找/插入/删除等操作。

2.1 表的简单数组实现

数组是静态分配的,查找是花费常数时间(O(1))的,而插入和删除是线性时间(O(N)),如果操作发生在表的末尾,就不需要移动任何时间,花费常数时间(O(1)).

在C++中,包含有公共数据结构的实现--STL(standard template library)-标准模板库。

Vector基于数组实现,可以复制并且其占用的内存可以自动回收(通过析构函数),可以调整Vector的大小,以及容量(容量的改变是通过为基本数组分配一个新的内存块,然后复制旧的内存块到新块中,再释放旧块的内存)。在进行插入和删除操作时,需要位置标记,这里使用通用的迭代器(其实就是指针)。有三个问题:如何得到迭代器;迭代器执行什么操作;哪些表ADT方法需要迭代器作为形参。源码如下:

/*
 * 用数组的方式实现线性表
 */
#include<iostream>
using namespace std;

template <typename Object>
class Vector
{
	private:
		int theSize;
		int theCapacity;
		Object * objects;

	public:
		//构造函数
		explicit Vector(int initSize = 0) : theSize(initSize), theCapacity( initSize + SPACE_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.capacity();

				objects = new Object[ capacity() ];
				for(int k=0; k<size(); k++) {
					objects[k] = rhs.objects[k];
				}
			}
			return this;
		}

		//调整Vector的大小
		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 < newCapacity; 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];
		}

		//判断Vector是否为空
		bool empty() const {
			return size()==0;
		}

		//获得Vector的大小
		int size() const {
			return theSize;
		}

		//获得Vector的容量
		int capacity() const {
			return theCapacity;
		}

		//在尾部插入元素
		void push_back(const Object &x) {
			if(theSize == theCapacity) {
				reserve( 2*theCapacity +1);
			}
			objects[theSize] = x;
			theSize++;
		}

		//弹出尾部元素
		void pop_back() {
			theSize--;
		}

		//返回尾部元素
		const Object & back() const {
			return objects[theSize-1];
		}

		//使用指针来定义迭代器
		typedef Object * iterator;
		typedef const Object * const_iterator;	

		//获取Vector开始的迭代器
		iterator begin() {
			return &objects[0];
		}

		const_iterator begin() const {
			return &objects[0];
		}

		//获取Vector结束的迭代器
		iterator end() {
			return &objects[ size() ];
		}

		const_iterator end() const {
			return &objects[size()];
		}

		enum { SPACE_CAPACITY = 16 };
};


int main()
{
	Vector<int> test;
	test.push_back(2);
	test.push_back(3);

	//通过迭代器来打印Vector的内容
	//TODO:怎么调用前面定义的迭代器呢
	for(Vector<int>::iterator itr = test.begin(); itr < test.end(); itr++) {
	//for(int i=0; i < test.size(); i++) {
		cout << *itr << " ";
	}
	cout << endl;
	bool vectorEmpty = test.empty();
	if(vectorEmpty) 
		cout << "the vector is empty" << endl;
	return 0;
}

2.2 双向链表的实现

Vector是静态分配的数组,而List的空间则不是连续的,通过前向和后向指针来表明元素之间的关系。源码如下:

#include<iostream>
using namespace std;

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

	private:
		int theSize;
		Node *head;
		Node *tail;

		void init()
		{
			theSize = 0;
			head = new Node;
			tail = new Node;
			head->next = tail;
			tail->prev = head;
		}	

	public:
		//const_iterator define
		class const_iterator
		{
			protected:
				Node *current;

				Object & retrieve() const
				{
					return current->data;
				}

				const_iterator(Node *p) : current(p) { }

				friend class List<Object>;

			public :
				const_iterator():current(NULL) {}

				const Object & operator*() const 
				{
					return retrieve();
				}

				//前缀,++itr
				const_iterator & operator++ ()
				{
					current = current->next;
					return *this;
				}

				//后缀,itr++
				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);
				}

		};

		//iterator define
		class iterator : public const_iterator
		{
			protected:
				iterator(Node *p) : const_iterator(p) {}

				friend class List<Object>;

			public:
				iterator() {}
				Object & operator* ()
				{
					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;
				}
			
		};

	public:
		List() 
		{
			init();
		}

		List(const List & rhs)
		{
			init();
			*this = rhs;
		}

		~List()
		{
			clear();
			delete head;
			delete tail;
		}

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

		}

		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()
		{
			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());
		}

		//插入节点,先创建这个节点,给它的prev和next赋值,然后再让p->prev->next和p->prev都指向这个新创建的节点
		iterator insert(iterator itr, const Object &x)
		{
			Node *p = itr.current;
			theSize++;
			//return iterator(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;
			return iterator(newNode);
		}

		//删除节点,让p->prev->next指向p->next即可
		iterator erase(iterator itr)
		{
			Node *p = itr.current;
			p->prev->next = p->next;
			p->next->prev = p->prev;

			iterator result(p->next);
			delete p;
			theSize--;
			return result;
		}

		iterator erase(iterator start, iterator end)
		{
			for( iterator itr = start; itr != end;)
				itr = erase(itr);
			return end;
		}


};


int main()
{
	List<int> test;
	test.push_back(1);
	test.push_back(2);
	test.push_back(3);
	for(List<int>::iterator itr = test.begin(); itr != test.end(); itr++) {
		cout << *itr << " " ;
	}
	cout << endl;
	return 0;
}
issues:在编写代码中,出现了一次段错误,使用gdb调试是在执行插入操作时,p->prev->next这一句会出现段错误,查看p->prev指向了NULL,发现在初始化函数init中,将tail->prev=head,写成了tail->next=head了。这样逻辑上肯定就不对了。

3. 栈ADT

栈其实就是一个表,只是操作有所不同而已。在栈中是后入先出(LIFO)。push是元素入栈--输入,pop是元素出栈,top是取出元素。这里通过继承的方式,使用Vector来实现简单的栈。

#include "vector.cpp"
template<typename Object>
class Stack: public Vector<Object>
{
	public:
		void push(const Object & data)
		{
			this->push_back(data);
		}

		void pop()
		{
			this->pop_back();
		}

		const Object &top()
		{
			return this->back();
		}
};

int main()
{
	Stack<int> test;
	test.push(1);
	test.push(2);
	test.pop();
	int a = test.top();
	cout << "the top element is " << a << endl;
	return 0;
}

应用:平衡符号(编译器的检查语法的规则-括号匹配)/后缀表达式/函数调用。这里讲一下尾递归。 递归是直接或者间接调用自己,也就是分而治之的思想,可以使程序更加容易理解,但是会占用栈空间,如果递归很多次的话,会导致栈空间的耗尽。 测试程序如下:

#include<iostream>
using namespace std;
//循环输出多个元素
void print(int start, int end)
{
	if(start == end)
		return;
	cout << start << endl;
	start++;
	print(start, end);
}

int main()
{
	print(1,20000000);
	return 0;
}
在我的PC上,运行到261812次就会出错。使用:

ulimit -s
可以查看PC的默认栈空间,我的为8192,我将空间*2=16384之后,运行次数也会加倍,变为:523989。当然现在编译器都可以做相应的优化(gcc  -O2),将尾递归转化为循环,常量栈,具体帖子可以参见清水河畔的交流贴:点击打开链接 点击打开链接

3. 队列ADT

同样,队列ADT也是表,是一个先进先出的表(FIFO). 可以考虑用循环数组实现。代码后面会补上。。明天回家。。

应用:排队论等

                                                                                                                                                                                                                                                   


<数据结构与算法分析 C++描述> 表/栈/队列