目录
一、数据结构-栈
1.1 栈的定义
1.2 栈的 ADT (Abstract Data Type)
1.3 栈的顺序存储结构及实现
二、数据结构-队列
2.1 队列的定义
2.2 队列的 ADT
2.3 队列的顺序存储结构与实现
2.4 优先队列
2.5 各种队列异同点
一、数据结构-栈
1.1 栈的定义
栈(Stack)可以看成是一种特殊的线性表。
限定仅只能在表尾端进行插入和删除的线性表。
栈顶:表尾端被称之为栈顶。
栈底:和表尾相对应的另一端,称之为栈底。
时间有序表:LIFO (Last In First Out)后进先出特征的线性结构。
1.2 栈的 ADT (Abstract Data Type)
template <class ElemType>
class AbsStack {public:AbsStack ( ) { } // 默认构造函数virtual ~ AbsStack ( ) { } // 析构函数virtual int IsEmpty ( ) const = 0; // 判栈空吗?virtual int IsFull ( ) const = 0; // 判栈满吗?virtual void MakeEmpty( ) = 0; //将栈清空。virtual void Push ( const ElemType & X ) = 0; //新结点进栈。virtual void Pop ( ) = 0; // 栈顶结点出栈。virtual const ElemType & Top( ) const = 0; // 取栈顶结点数据值。private:AbsStack( const AbsStack & ) { } // 冻结复制另一堆栈的构造函数。
};
1.3 栈的顺序存储结构及实现
与线性表类似,栈也有两种存储结构,即顺序存储和链表存储结构。
栈的顺序存储结构亦称顺序栈。
栈的顺序存储结构是利用一批地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时设指针 top 指向栈顶元素的当前位置。
通常用一维数组来实现栈的顺栈存储,习惯上以数组下标小的一端做栈底,当top=0时为空栈。数据元素不断进栈时,栈顶指针top不断地加1,当top达到数组的最大下标值时为栈满。
顺序表示的栈的程序实现:
static const int InitStackSize = 10;
template <class ElemType> class Stack: public AbsStack<ElemType> {
private:ElemType * Array; // 存放结点的数组名。int top; // top - 栈顶指针。int MaxSize; // 栈内能够存放结点的最大个数,即栈的容量。void DoubleArray( int Max ); // 更新数组的容量。public:Stack ( ); // 构造函数, 初始时构造大小为InitStackSize的栈。~Stack ( ) { delete [ ] Array; }; // 析构函数,释放占用的连续空间。void MakeEmpty ( ) { top = -1; }; // 将栈清空。int IsEmpty ( ) const { return top = = -1; }; //栈空为True,否则为False。int IsFull ( ) const { return top = = MaxSize-1; }; //栈满为True,否则为False。const ElemType & Top( ) const ; // 读取栈顶结点。void Push ( const ElemType & X ); // 将X的值进栈。void Pop ( ); // 栈顶结点出栈。const Stack & operator = ( const Stack & R );
};template<class ElemType>
Stack<ElemType> :: Stack( ) : top( -1), MaxSize(InitStackSize) { //构造函数, 空间大小为MaxSizeArray = new ElemType[MaxSize];
}
template <class ElemType> const ElemType & Stack<ElemType> :: Top( ) const {// 对非空栈,读取栈顶结点并返回结点的值。Exception( IsEmpty( ), ”Underflow:Satck is Empty!”);return Array[top];
}
template <class ElemType> void Stack<ElemType>::Push ( const ElemType & X ) { if ( top + 1 = = MaxSize ) DoubleArray( 2 * MaxSize );Array[++top] = X; // 新结点放入新的栈顶位置。
}
template <class ElemType> void Stack<ElemType>::Pop() { //对非空栈,将栈顶结点出栈Exception( IsEmpty( ), ”Stack is underflow!”);top--;
}template <class ElemType>
const Stack<ElemType> & Stack<ElemType> :: operator = ( const Stack<ElemType> & R ) { if ( this = = &R ) return *this; delete [ ]Array; Array = new ElemType[R.MaxSize]; // 分配存储单元。top = R.top; for( int j=0; j<= top; j++ ) Array[j] = R.Array[j]; // 逐个复制结点。return *this;
}
template <class ElemType>
void Stack<ElemType> :: DoubleArray( int Max ) { ElemType * oldarr = Array;Exception( MaxSize≥Max, “New size is too small!”);Array = new ElemType[Max];for( int k = 0; k <= top; k++ ) Array[k] = oldarr[k]; // 逐个复制结点。MaxSize = Max;delete [ ] oldarr;return;
}
多栈共享一块顺序存储空间:栈顶指针指向实际栈顶的下一个单元。
二个栈共享一块顺序存储空间:栈顶指针指向实际栈顶的下一个单元。指针相遇时,栈满。
top 是栈顶指针。栈顶和栈底如图所示。
栈的 Push 操作:
栈的 Pop 操作:
二、数据结构-队列
2.1 队列的定义
队列(Queue)也是一种特殊的线性表。在现实中例子很多,如客户到银行办理业务往往需要排队,先来的先办理,晚来的则排在队尾等待。队列也有两种存储结构,即顺序存储和链表存储结构。下面主要讲顺序存储的实现。
在表的一端进行插入,而在另一端进行删除的线性表。
队尾:进行插入的一端。
队首:进行删除的一端。
时间有序表:FIFO (先进先出)特征的线性结构。
2.2 队列的 ADT
template <class ElemType> class AbsQueue {
public:AbsQueue ( ) { } // 默认构造函数virtual ~ AbsQueue ( ) { } // 析构函数virtual int IsEmpty ( ) const = 0; // 判队空吗?virtual int IsFull ( ) const = 0; // 判队满吗?virtual void MakeEmpty( ) = 0; //将队清空。virtual void EnQueue( const ElemType & X ) = 0; //新结点进队。virtual void DeQueue( ) = 0; // 队首结点出队。virtual const ElemType & Front( ) const = 0; // 取队首结点数据值。
private:AbsQueue ( const AbsQueue & ) { } // 冻结复制另一队列的构造函数。};
2.3 队列的顺序存储结构与实现
队列的表示
出队时应先判队是否空:条件 rear == front
不空则出队,注意 front 是否会由最高下标跳至最低下标(循环)。
进队时应先判队是否满:条件 ( ( rear + 1) % maxSize ) == front
不满则进队,注意 rear 是否会由最高下标跳至最低下标(循环)。
基本操作的实现程序:进队。
template <class ElemType>void Queue<ElemType>::EnQueue(const ElemType & x) {// 值为x的结点进队。if ( IsFull( ) ) DoubleQueue( ); Array[rear] = x; // 若队满,则数组容量扩大一倍。Increment( rear); // rear 加 1;若为 MaxSize,则rear取0。
}
int IsFull( ) const {return ( rear + 1) % MaxSize = = front ; }
基本操作进队的实现程序:扩大数组容量
template <class ElemType>void Queue<ElemType>::EnQueue(const ElemType & x) {// 值为x的结点进队。if ( IsFull( ) ) DoubleQueue( ); Array[rear] = x;Increment( rear);
}template <class ElemType>
int IsFull( ) const {return ( rear + 1) % MaxSize = = front ; } template <class ElemType>
void Queue<ElemType>::DoubleQueue( ) {//重新创建数组单元个数多一倍的新数组,复制原队列中的结点。int NewSize = 2 * MaxSize; ElemType * old = Array;Array = new ElemType[NewSize];for ( int j = 0, k = front; k < rear; j++, Increment(k) ) Array[j] = old[k];front = 0; // 新的队首指针。rear = j; // 新的队尾指针。MaxSize = NewSize; delete [ ]old;
}
基本操作出队的实现程序:
template <class ElemType> void Queue<ElemType>::DeQueue( ) {//对于非空队列,将队首结点出队。Exception( IsEmpty( ), ”Queue is underflow!”);Increment( front );
}
int IsEmpty() const {return front = = rear;} //判断队列是否空.。为空返回True,否则返回False。
求队中结点的个数:( rear - front + MaxSize ) % MaxSize
front 和 rear 分别是队首和队尾指针。它们指示着真正的队首和真正的队尾结点。
template <class ElemType> class Queue : public AbsQueue<ElemType> {private:ListNode<ElemType> * front; // 队首指针。ListNode<ElemType> * rear; // 队尾指针。public:Queue ( ) : front(NULL), rear(NULL) { } // 构造函数: 队首指针和队尾指针初始化。~Queue ( ) { MakeEmpty( ); } //析构函数,释放占用的连续空间。void MakeEmpty ( ); // 将队列清空。int IsEmpty ( ) const { return front = = NULL; } // 队空为True,否则为False。int IsFull ( ) const { return 0; } // 总认为为False。const ElemType & Front ( ) const ; // 读取队首结点数据值。void EnQueue ( const ElemType & X ); // 将X的值进队。void DeQueue ( ); // 将队首结点出队。const Queue & operator = ( const Queue & R );};
进队操作:
template <class ElemType>
void Queue<ElemType>::EnQueue ( const ElemType & X ) { if ( IsEmpty( ) ) front = rear= new ListNode <ElemType> ( X );else rear = rear->Next = new ListNode<ElemType> ( X );
}
2.4 优先队列
优先权有序表:具有特征高优先权的结点将先离开的线性结构。和到达时刻无关。
实现方法:结点中处包含数据场外,还有本结点的优先权数。优先权数越小(如本书) ,优先级越高。或者优先权数越大,优先级越高。
顺序存储的优先队列:用数组
2.5 各种队列异同点
数据结构中普通队列、循环队列和优先队列的异同点如下:
相同点:
-
先进先出原则:普通队列和循环队列都遵循先进先出(FIFO)的原则,即最早进入队列的元素将最早被移除。优先队列虽然也遵循某种顺序,但其核心特点不在于FIFO,而是根据元素的优先级来决定出队的顺序。
-
基本操作:这些队列类型都支持基本的入队(在队列尾部添加元素)和出队(从队列头部移除元素)操作。
不同点:
-
存储和循环使用:
- 普通队列:使用数组或链表来存储元素,当元素被移除后,其空间不会被再次利用,可能导致空间浪费。
- 循环队列:通过逻辑上首尾相连的方式实现队列空间的循环利用,提高了存储空间的利用率。循环队列需要额外的机制来判断队列是满还是空1。
- 优先队列:与普通队列和循环队列在存储空间使用上的主要区别在于其出队顺序。优先队列根据元素的优先级进行出队,而不是按照FIFO的原则。
-
出队顺序:
- 普通队列和循环队列:按照元素进入队列的顺序进行出队。
- 优先队列:出队顺序基于元素的优先级,优先级高的元素先出队2。
-
应用场景:
- 普通队列和循环队列:常用于需要按照特定顺序处理元素的场景,如操作系统中的进程调度、广度优先搜索等3。
- 优先队列:特别适用于需要根据优先级处理元素的场景,如任务调度、图像处理中的优先级渲染等34。
-
实现和性能:
- 普通队列和循环队列:实现相对简单,性能稳定。循环队列在减少内存分配和释放的开销方面可能更优。
- 优先队列:实现通常基于堆数据结构(如最大堆或最小堆),因此具有不同的性能特点。插入和删除元素的时间复杂度通常为O(logN)2。
总结来说,普通队列和循环队列主要在存储空间的利用和循环使用上有所不同,而优先队列则主要在元素的出队顺序和实现机制上与其他两种队列有所区别。这些队列类型各自适用于不同的应用场景和需求。