本文最后更新于 936 天前,其中的信息可能已经有所发展或是发生改变。
本章将定制并实现更加基本,且更为常用的两类数据结构——栈与队列。与此前介绍的向向量和列表一样,它们也属于线性序列结构,故其中存放的数据对象之间也具有线性次序。相对于一般的序列结构,栈与队列的数据操作范围仅限于逻辑上的特定某端。然而,得益于其简洁性与规范性,它们既成为构建更复杂、更高级数据结构的基础,同时也是算法设计的基本出发点,甚至常常作为标准配置的基本数据结构以硬件形式直接实现。因此无论就工程或理论而言,其基础性和地位都是其它结构无法比拟的。
相对于向量和列表,栈与队列的外部接口更为简化和紧凑,故亦可视作向量与列表的特例。因此 C++
的继承与封装机制在此可以大显身手。得益于此,本章的重点将不再拘泥于对数据结构。 内部实现机制的展示,并转而更多地从其外部特性出发,结合若干典型的实际问题介绍栈与队列的具体应用。
首先对于章节2,我们实现了向量和列表的模板类。本章所需实现的栈和队列的数据结构,在前者的基础上进行继承。我们将之前实现好的类模板进行封装为头文件,在之后的继承实现时,可以直接引用已经封装好的数据结构,然后提供其特有的接口。
我们以 Vector.h
的封装为例,其余的封装方法类似。
| #pragma once |
| |
| typedef int Rank; |
| #define DEFAULT_CAPACITY 3 |
| |
| template <typename T> class Vector{ |
| protected: |
| |
| Rank _size; |
| int _capacity; |
| T *_elem; |
| |
| |
| void copyFrom(T const *A, Rank lo, Rank hi); |
| void expand(); |
| void shrink(); |
| |
| public: |
| |
| Vector(int c = DEFAULT_CAPACITY, int s = 0, T v = 0){ |
| _elem = new T[_capacity = c]; |
| for (_size = 0; _size < s; _elem[_size ++] = v); |
| } |
| |
| Vector(T const *A, Rank n) { copyFrom(A, 0, n); } |
| Vector(T const *A, Rank lo, Rank hi) { copyFrom(A, lo, hi); } |
| Vector(Vector<T> const &V) { copyFrom(V._elem, 0, V._size); } |
| Vector(Vector<T> const &V, Rank lo, Rank hi) { copyFrom(V._elem, lo, hi); } |
| |
| |
| ~Vector() { delete[] _elem; } |
| |
| |
| |
| |
| T get(Rank r); |
| int capacity() const{ return _capacity; } |
| Rank size() const { return _size; } |
| bool empty() const { return !_size; } |
| Rank find(T const &e, Rank lo, Rank hi) const; |
| Rank find(T const &e) const { return find(e, 0, _size); } |
| Rank search(T const &e, Rank lo, Rank hi) const; |
| Rank search(T const &e) const { return (_size <= 0) ? -1 : search(e, 0, _size); } |
| |
| |
| T &operator[](Rank r) const; |
| Vector<T> &operator=(Vector<T> const &); |
| void put(Rank r, T const &e); |
| void unsort() { unsort(0, _size); } |
| void unsort(Rank lo, Rank hi); |
| void reverse() { reverse(0, _size); } |
| void reverse(Rank lo, Rank hi); |
| Rank insert(Rank r, T const &e); |
| Rank insert(T const &e) { return insert(_size, e); } |
| int remove(Rank lo, Rank hi); |
| T remove(Rank r); |
| int deduplicate(); |
| int uniquify(); |
| |
| |
| void traverse(void (*)(T &)); |
| template <typename VST> |
| void traverse(VST &); |
| |
| |
| bool bubble(Rank lo, Rank hi); |
| void bubbleSort(Rank lo, Rank hi); |
| void mergeSort(Rank lo,Rank hi); |
| void merge(Rank lo, Rank mi, Rank hi); |
| |
| }; |
| |
| |
| template <typename T> |
| void Vector<T>::copyFrom(T const *A, Rank lo, Rank hi){ |
| _elem = new T[_capacity = 2 * (hi - lo)]; |
| _size = 0; |
| while (lo < hi){ |
| _elem[_size ++] = A[lo ++]; |
| } |
| } |
| |
| |
| template <typename T> |
| Vector<T> &Vector<T>::operator=(const Vector<T> &V) |
| { |
| delete[] _elem; |
| copyFrom(V._elem, 0, V._size); |
| return *this; |
| } |
| |
| |
| template <typename T> |
| T &Vector<T>::operator[](Rank r) const{ |
| return _elem[r]; |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::expand(){ |
| while (_size == _capacity){ |
| T *oldElem = _elem; |
| _elem = new T[_capacity <<= 1]; |
| for (int i = 0; i < _size; i++){ |
| _elem[i] = oldElem[i]; |
| } |
| delete[] oldElem; |
| } |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::shrink(){ |
| while (_size << 2 < _capacity){ |
| T *oldElem = _elem; |
| _elem = new T[_capacity >>= 1]; |
| for (int i = 0; i < _size; i ++){ |
| _elem[i] = oldElem[i]; |
| } |
| delete[] oldElem; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T> |
| void Vector<T>::unsort(Rank lo, Rank hi){ |
| T *V = _elem + lo; |
| for (Rank i = hi - lo; i > 0; i --){ |
| std::swap(V[i - 1], V[rand() % i]); |
| } |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::reverse(Rank lo, Rank hi){ |
| T *V = _elem; |
| Rank l = lo, r = hi - 1; |
| while(l < r){ |
| std::swap(V[l],V[r]); |
| l ++, r--; |
| } |
| } |
| |
| |
| template <typename T> |
| Rank Vector<T>::find(T const &e, Rank lo, Rank hi) const{ |
| while ((lo < hi --) && (e != _elem[hi])); |
| return hi; |
| } |
| |
| |
| template <typename T> |
| Rank Vector<T>::search(T const &e, Rank lo, Rank hi) const{ |
| T* A = _elem; |
| while ( lo < hi ){ |
| Rank mi = ( lo + hi ) >> 1; |
| ( e < A[mi] ) ? hi = mi : lo = mi + 1; |
| } |
| Rank p = -- lo; |
| if(A[p] == e) return p; |
| return -1; |
| } |
| |
| |
| template <typename T> |
| T Vector<T>::get(Rank r){ |
| T value = _elem[r]; |
| return value; |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::put(Rank r, T const &e){ |
| _elem[r] = e; |
| } |
| |
| |
| template <typename T> |
| Rank Vector<T>::insert(Rank r, T const &e){ |
| expand(); |
| for (int i = _size; i > r;) _elem[i] = _elem[i - 1]; |
| _elem[r] = e, _size ++; |
| return r; |
| } |
| |
| |
| template <typename T> |
| int Vector<T>::remove(Rank lo, Rank hi){ |
| if (lo == hi) return 0; |
| while(hi < _size){ |
| _elem[lo ++] = _elem[hi ++]; |
| } |
| _size = lo; |
| shrink(); |
| return hi - lo; |
| } |
| |
| |
| template <typename T> |
| T Vector<T>::remove(Rank r){ |
| T re_elem = _elem[r]; |
| remove(r, r + 1); |
| return re_elem; |
| } |
| |
| |
| template <typename T> |
| int Vector<T>::deduplicate(){ |
| int oldSize = _size; |
| Rank i = 1; |
| while (i < _size){ |
| (find(_elem[i], 0, i) < 0) ? |
| i ++ : remove(i); |
| } |
| return oldSize - _size; |
| } |
| |
| |
| template <typename T> |
| int Vector<T>::uniquify(){ |
| int i = 0, j = 0; |
| while(++ j < _size){ |
| if(_elem[i] != _elem[j]){ |
| _elem[++i ] = _elem[j]; |
| } |
| } |
| _size = ++ i; shrink(); |
| return j - i; |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::traverse(void (*visit)(T&)){ |
| for (int i = 0; i < _size; i ++){ |
| visit(_elem[i]); |
| } |
| } |
| |
| |
| template <typename T> |
| template <typename VST> |
| void Vector<T>::traverse(VST& visit){ |
| for (int i = 0; i < _size; i ++){ |
| visit(_elem[i]); |
| } |
| } |
| |
| template <typename T, typename VST> |
| void traverse(VST& visit, T& V, Rank lo, Rank hi) { |
| for(Rank i = lo; i <= hi; i ++){ |
| visit(V[i]); |
| } |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::bubbleSort(Rank lo, Rank hi) |
| { while (!bubble(lo,hi --)); } |
| |
| |
| template <typename T> |
| bool Vector<T>::bubble(Rank lo, Rank hi){ |
| bool sorted = true; |
| while (++ lo < hi){ |
| if (_elem[lo - 1] > _elem[lo]){ |
| sorted = false; |
| std::swap(_elem[lo - 1], _elem[lo]); |
| } |
| } |
| return sorted; |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::mergeSort(Rank lo, Rank hi){ |
| if(hi - lo < 2) return; |
| int mi = (lo + hi) >> 1; |
| mergeSort(lo, mi); |
| mergeSort(mi, hi); |
| merge(lo, mi, hi); |
| } |
| |
| |
| template <typename T> |
| void Vector<T>::merge(Rank lo, Rank mi, Rank hi){ |
| T* A = _elem + lo; |
| int lb = mi - lo; T* B = new T[lb]; |
| for(Rank i = 0; i < lb; i ++) B[i] = A[i]; |
| int lc = hi - mi; T* C = _elem + mi; |
| for(Rank i = 0, j = 0, k = 0; (j < lb) || (k < lc);){ |
| if((j < lb) && (!(k < lc) || (B[j] <= C[k]))) A[i ++] = B[j ++]; |
| if((k < lc) && (!(j < lb) || (C[k] < B[j]))) A[i ++] = C[k ++]; |
| } |
| delete [] B; |
| } |
注意:
-
关于 #pragma once
, 是受到绝大多数现代编译器支持的非标准语用。当某个头文件中包含它时,指示编译器只对其分析一次,即使它在同一源文件中(直接或间接)被包含了多次也是如此。
-
阻止同一头文件的多次包含的标准方式是使用包含防护:
| #ifndef LIBRARY_FILENAME_H |
| #define LIBRARY_FILENAME_H |
| |
| #endif |
参考内容:实现定义的行为控制
我们将上述代码封装完毕后,将其文件命名为 Vector.h
,然后置于配置环境文件所属的 include
文件夹下,或编译时默认引用的文件路径下。
例如:D:\mingw64\x86_64-w64-mingw32\include
| #include <iostream> |
| #include "Vector.h" |
| using namespace std; |
| |
| void show(int e){ |
| cout << e << " "; |
| } |
| |
| void test_01(){ |
| |
| Vector<int> a; |
| |
| for(int i = 1; i <= 20; i ++){ |
| a.insert(i); |
| } |
| |
| a.unsort(0, a.size()); |
| traverse(show, a, 0, a.size() - 1); |
| cout << endl; |
| |
| a.bubbleSort(0, a.size()); |
| for(int i = 0; i < a.size(); i ++) cout << a[i] << " "; |
| cout << endl; |
| |
| } |
| |
| int main(){ |
| |
| test_01(); |
| |
| system("pause"); |
| |
| return 0; |
| |
| } |
栈(stack)是存放数据对象的一种特殊容器,其中的数据元素按线性的逻辑次序排列,故也可定义首、末元素。不过,尽管栈结构也支持对象的插入和删除操作,但其操作的范围仅限于栈的某一特定端。也就是说,若约定新的元素只能从某一端插入其中,则反过来也只能从这一端删除已有的元素。禁止操作的另一端,称作盲端。
入栈与出栈:
- 栈中可操作的一端更多也称作栈顶(stack top),而另一无法直接操作的盲端则更多地称作栈底(stack bottom)。
- 作为抽象数据类型,除了引用栈顶的
top()
等操作外,最常用的插入与删除操作分别称作入栈(push)和出栈(pop)。
后进先出:
- 由以上关于栈操作位置的约定和限制不难看出,栈中元素接受操作的次序必然始终遵循所谓"后进先出"(last-in-first-out,LIFO)的规律。
- 从栈结构的整个生命期来看,更晚(早) 出栈的元素应为更早(晚)入栈者;反之,更晚(早)入栈者应更早(晚)出栈。
操作接口 |
功能 |
返回类型 |
size() |
报告栈的规模 |
int |
empty() |
判断栈是否为空 |
bool |
push(e) |
将e 插至栈顶 |
void |
pop() |
删除栈顶对象 |
T |
top() |
引用栈顶对象 |
T& |
| #include "Vector.h" |
| |
| template <typename T> class Stack: public Vector<T> { |
| public: |
| |
| void push ( T const &e ) { this -> insert ( e ); } |
| T pop() { return this -> remove ( this -> size() - 1 ); } |
| T &top() { return ( *this ) [this -> size() - 1]; } |
| |
| }; |
递归算法所需的空间量,主要决定于最大递归深度。在达到这一深度的时刻,同时活跃的递归实例达到最多。
操作系统实现函数(递归)调用、记录调用与被调用函数(递归)实例之间的关系、实现函数(递归)调用的返回、维护同时活跃的所有函数(递归)实例等,都利用了栈的结构。
在 Windows
等大部分操作系统中,每个运行中的二进制程序都配有一个调用栈(call stack)或执行栈(Cexecution stack)。借助调用栈可以跟踪属于同一程序的所有函数,记录它们之闻的相互调用关系,并保证在每一调用实例执行完毕之后,可以准确地返回。
函数调用:
- 调用栈的基本单位是顿(frame)。
- 每次函数调用时,都会相应地创建一帧,记录该函数实例在二进制程序中的返回地址(return address)以及局部变量、传入参数等,并将该帧压入调用栈。
- 若在该函数返回之前又发生新的调用,则同样地要将与新函数对应的一帧压入栈中,成为新的栈顶。
- 函数一旦运行完毕,对应的帧随即弹出,运行控制权将被交还给该函数的上层调用函数,并按照该顿中记录的返回地址确定在二进制程序中继续执行的位置。
- 调用栈中的各帧,依次对应于那些尚未返回的调用实例,亦即当时的活跃函数实例(active function instance)。
- 调用栈中各顿还需存放前一帧的起始地址,以保证其出栈之后前一顿能正确地恢复。
- 特别地,位于栈底的那帧必然对应于入口主函数
main()
, 若它从调用栈中弹出,则意味着整个程序的运行结束,此后控制权将交还给操作系统。
仿照递归跟踪法,程序执行过程出现过的函数实例及其调用关系,也可构成一棵树,称作该程序的运行树。任一时刻的所有活跃函数实例,在调用栈中自底到顶,对应于运行树中从根节点 到最新活跃函数实例的一条调用路径。
递归:
- 作为函数调用的特殊形式,递归也可借助上述调用栈得以实现。对应于上图
funcB()
的自我调用,也会新压入一顿。
- 由此可见,同一函数可能同时拥有多个实例,并在调用栈中,各自占有一帧。这些帧的结构完全相同,但其中同名的参数或变量都是独立的副本。
- 比如在
funcB()
的两个实例中,入口参数 m
和内部变量 i
各有一个副本。
考查如下问题,任给十进制整数 n
,将其转换为 X
进制的表示形式:
nnidini+1=(dm…d2d1d0)(λ)=dm×λm+…d2×λ2+d1×λ1+d0×λ0=(dm…di+1…d1)λ=ni%λ=⌊λni⌋
| void convert ( Stack<char> &S, __int64 n, int base ) { |
| static char digit[] |
| = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
| while (n > 0){ |
| int remainder = (int)(n % base); |
| S.push (digit[remainder]); |
| n /= base; |
| } |
| } |
考查如下问题,给定只含有括号的表达式,判断该括号序列是否合法匹配:
| bool paren(const char s[], int l, int r){ |
| Stack<int> res; |
| for(int i = l; i < r; i ++){ |
| char op = s[i]; |
| if(op == '<' || op == '(' || op == '[' || op == '{') res.push(op); |
| else{ |
| if(!res.empty()){ |
| if(op == '>' && res.top() == '<' || op == ')' && res.top() == '(' || op == ']' && res.top() == '[' || op == '}' && res.top() == '{') res.pop(); |
| else break; |
| } |
| else{ |
| res.push(op); |
| break; |
| } |
| } |
| } |
| return res.empty(); |
| } |
解释:
- 只要将
push()
、pop()
操作分别与左、右括号相对应,则长度为 n
的栈混洗,必然与由 n
对括号组成的合法表达式彼此对应 。
- 按照这一理解,借助栈结构,只需扫描一趟表达式,即可在线性时间内,判定其中的括号是否匹配。
针对这一类问题,运算时要考虑运算符优先级问题,则要把优先级高的数据先处理,故选择栈的结构。
提供针对包含加减乘除及括号的正整数的中缀表达式运算:
| |
| bool pri_cmp(const char a, const char b){ |
| if(a == '*' || a == '/') return true; |
| else if((a == '+' || a == '-') && (b == '+' || b == '-')) return true; |
| else return false; |
| } |
| |
| |
| void eval(Stack<int> &num, Stack<char> &op){ |
| int b = num.pop(), a = num.pop(); |
| char c = op.pop(); |
| int x; |
| if (c == '+') x = a + b; |
| else if (c == '-') x = a - b; |
| else if (c == '*') x = a * b; |
| else x = a / b; |
| num.push(x); |
| } |
| |
| int calculate(const char str[], int n){ |
| Stack<int> num; |
| Stack<char> op; |
| for (int i = 0; i < n; i ++){ |
| auto c = str[i]; |
| if(isdigit(c)){ |
| int x = 0, j = i; |
| while (j < n && isdigit(str[j])) |
| x = x * 10 + str[j ++ ] - '0'; |
| i = j - 1; |
| num.push(x); |
| } |
| else if(c == '(') op.push(c); |
| else if(c == ')'){ |
| while (op.top() != '(') eval(num, op); |
| op.pop(); |
| } |
| else{ |
| while (op.size() && op.top() != '(' && pri_cmp(op.top(), c)) eval(num, op); |
| op.push(c); |
| } |
| } |
| while (op.size()) eval(num, op); |
| return num.pop(); |
| |
| } |
解释:
- 该算法自左向右扫描表达式,并对其中字符逐一做相应的处理。
- 利用双栈,一个操作数栈
num
,一个运算符栈 op
。
- 按照运算符优先级运算,将栈顶运算符和即将入栈的运算符的优先级比较:
- 如果栈顶的运算符优先级低,新运算符直接入栈。
- 如果栈顶的运算符优先级高,先出栈计算子表达式,新运算符再入栈。
-
括号分为两个运算符 (
和 )
:
- 遇到
(
说明之后的运算要先行计算,故只需将 (
压栈。
- 遇到
)
说明括号内的子表达式需要计算,故要逆向计算运算符直至遇到 (
。
整个计算过程只需扫描一遍表达式,时间复杂度为 O(n)。
| #include <iostream> |
| #include "Vector.h" |
| using namespace std; |
| |
| template <typename T> class Stack: public Vector<T> { |
| public: |
| |
| void push ( T const &e ) { this -> insert ( e ); } |
| T pop() { return this -> remove ( this -> size() - 1 ); } |
| T &top() { return ( *this ) [this -> size() - 1]; } |
| |
| }; |
| |
| |
| void convert ( Stack<char> &S, __int64 n, int base ) { |
| static char digit[] |
| = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
| while (n > 0){ |
| int remainder = (int)(n % base); |
| S.push (digit[remainder]); |
| n /= base; |
| } |
| } |
| |
| |
| bool paren(const char s[], int l, int r){ |
| Stack<int> res; |
| for(int i = l; i < r; i ++){ |
| char op = s[i]; |
| if(op == '<' || op == '(' || op == '[' || op == '{') res.push(op); |
| else{ |
| if(!res.empty()){ |
| if(op == '>' && res.top() == '<' || op == ')' && res.top() == '(' || op == ']' && res.top() == '[' || op == '}' && res.top() == '{') res.pop(); |
| else break; |
| } |
| else{ |
| res.push(op); |
| break; |
| } |
| } |
| } |
| return res.empty(); |
| } |
| |
| |
| bool pri_cmp(const char a, const char b){ |
| if(a == '*' || a == '/') return true; |
| else if((a == '+' || a == '-') && (b == '+' || b == '-')) return true; |
| else return false; |
| } |
| |
| |
| void eval(Stack<int> &num, Stack<char> &op){ |
| int b = num.pop(), a = num.pop(); |
| char c = op.pop(); |
| int x; |
| if (c == '+') x = a + b; |
| else if (c == '-') x = a - b; |
| else if (c == '*') x = a * b; |
| else x = a / b; |
| num.push(x); |
| } |
| |
| int calculate(const char str[], int n){ |
| Stack<int> num; |
| Stack<char> op; |
| for (int i = 0; i < n; i ++){ |
| auto c = str[i]; |
| if(isdigit(c)){ |
| int x = 0, j = i; |
| while (j < n && isdigit(str[j])) |
| x = x * 10 + str[j ++ ] - '0'; |
| i = j - 1; |
| num.push(x); |
| } |
| else if(c == '(') op.push(c); |
| else if(c == ')'){ |
| while (op.top() != '(') eval(num, op); |
| op.pop(); |
| } |
| else{ |
| while (op.size() && op.top() != '(' && pri_cmp(op.top(), c)) eval(num, op); |
| op.push(c); |
| } |
| } |
| while (op.size()) eval(num, op); |
| return num.pop(); |
| |
| } |
| |
| void test_01(){ |
| |
| Stack<int> a; |
| Stack<char> b; |
| Stack<double> c; |
| |
| for(int i = 1; i <= 10; i ++){ |
| a.push(i); |
| } |
| |
| cout << "栈的大小为:" << a.size() << endl; |
| cout << "当前栈顶元素为:" << a.top() << endl; |
| |
| for(int i = 0; i < 3; i ++) a.pop(); |
| cout << "弹出3次当前栈顶元素后的栈顶元素为:" << a.top() << endl; |
| |
| if(a.empty()) cout << "栈a为空" << endl; |
| else cout << "栈a不为空" << endl; |
| |
| } |
| |
| void test_02(){ |
| |
| Stack<char> a; |
| |
| int n, X; |
| cout << "输入十进制数n和要转换的目标进制:" << endl; |
| cin >> n >> X; |
| |
| convert(a, n, X); |
| |
| while(!a.empty()){ |
| cout << a.top(); |
| a.pop(); |
| } |
| cout << endl; |
| |
| } |
| |
| void test_03(){ |
| |
| cout << "输入括号序列的长度和该括号序列:" << endl; |
| |
| char a[1010]; |
| |
| int n; |
| |
| cin >> n >> a; |
| |
| if(paren(a, 0, n)) cout << "匹配" << endl; |
| else cout << "不匹配" << endl; |
| |
| } |
| |
| void test_04(){ |
| |
| cout << "输入表达式的长度和该表达式:" << endl; |
| |
| int n; |
| |
| char a[1010]; |
| |
| cin >> n >> a; |
| |
| cout << calculate(a, n) << endl; |
| |
| } |
| |
| int main(){ |
| |
| test_01(); |
| |
| test_02(); |
| |
| test_03(); |
| |
| test_04(); |
| |
| system("pause"); |
| |
| return 0; |
| |
| } |
与栈一样,队列(queue)也是存放数据对象的一种容器,其中的数据对象也按线性的逻辑次序排列。
入队与出队:
- 队列结构同样支持对象的插入和删除,但两种操作的范围分别被限制于队列的两端。 允许取出元素的一端称作队头(front),而允许插入元素的另一端称作队尾(rear)。
- 队列元素的插入与删除也是修改队列结构的两种主要方式,站在被操作对象的角度,分别称作入队(enqueue)和出队(dequeue)操作。
先进先出:
- 由以上的约定和限制不难看出,与栈结构恰好相反,队列中各对象的操作次序遵循所谓”先进先出“(first-in-first-out,FIFO)。
- 从队列结构的整个生命期来看,更早(晚)出队的元素应为更早(晚)入队者,反之,更早(晚)入队者应更早(晚)出队。
操作接口 |
功能 |
返回类型 |
size() |
报告队列的规模 |
int |
empty() |
判断队列是否为空 |
bool |
enqueue(e) |
将e 插至队尾 |
void |
dequeue() |
删除队尾对象 |
T |
front() |
引用队首对象 |
T& |
| #include "List.h" |
| |
| template <typename T> class Queue: public List<T> { |
| public: |
| void enqueue ( T const &e ) { this -> insertAsLast ( e ); } |
| T dequeue() { return this -> remove ( this -> first() ); } |
| T &front() { return this -> first() -> data; } |
| }; |
为在客户(client)群体中共享的某一资源时一套公平且高效的分配规则必不可少,而队列结构则非常适于定义和实现这样的一套分配规则。 具体地,可以借助队列 Q
实现一个资源循环分配器:
| RoundRobin { |
| Queue Q(clients); |
| while(!ServiceClosed()){ |
| e = Q.dequeue(); |
| server(e); |
| Q.enqueue(e); |
| } |
| } |
解释:
- 在以上所谓轮值(round robin)算法中,首先令所有参与资源分配的客户组成一个队列
Q
。
- 接下来是一个反复轮回式的调度过程:
- 取出当前位于队头的客户,将资源交予该客户使用。
- 在经过固定的时间之后,回收资源,并令该客户重新入队。
- 得益于队列"先进先出"的特性,如此既可在所有客户之间达成一种均衡的公平,也可使得资源得以充分利用。
- 深度优先搜索基于栈的数据结构实现。
- 宽度优先搜索基于队列的数据结构实现。
- 以上对于栈和队列的扩展应用参见DFS&BFS,不再重复赘述。