数据结构笔记(第三章)

第三章:线性结构之栈和队列

1.栈的定义:只允许在一端插入和删除 的线性表。 允许插入和删除的一端称为栈顶 (top),另一端称栈底(bottom) 。

2.栈的特点:后进先出 (LIFO) 。

//栈的抽象数据类型 
template <class T>   //此程序放入stack.h  
class Stack {            //栈的类定义 
public:      
Stack( ){ };                //构造函数      
virtual void Push(T& x) = 0;                //进栈      
virtual bool Pop(T& x) = 0;           //出栈      
virtual bool getTop(T& x)const = 0; //取栈顶      
virtual bool IsEmpty( ) const= 0;      //判栈空      
virtual bool IsFull( ) const= 0;         //判栈满       
virtual int getSize( )const =0; 
}; 栈

顺序栈

基于数组的存储表示

//栈的数组存储表示 — 顺序栈 
#include <assert.h>                            //P89程序3.2 
#include <iostream.h> 
#include “stack.h”                  //P88栈的类定义 
const int stackIncrement=20;
template <class T> 
class SeqStack : public Stack<T> {     //顺序栈类定义 
private:           
T *elements;          //栈元素存放数组      
int top;                        //栈顶指针      
int maxSize;                                 //栈最大容量      
void overflowProcess();             //栈的溢出处理 
public:      
SeqStack(int sz =50);                //构造函数      
~SeqStack() { delete []elements; }   //析构函数      
void Push(const T& x);       
bool Pop(T& x);                        //出栈      
bool getTop(T& x);             //取栈顶内容      
bool IsEmpty() const { return top == -1; }      
bool IsFull() const { return top == maxSize-1; }     
int  getSize( )const {return top+1 ; }    
void makeEmpty( ){top=-1 ; }
}; 

//顺序栈的操作
template <class T> 
void SeqStack<T>::overflowProcess() {   
//私有函数:当栈满则执行扩充栈存储空间处理  
T *newArray = new T[maxSize+stackIncrement];         //创建更大的存储数 
if(newArray==NULL){cerr<<“失败”<<endl; 
exit(1); }  
for (int i = 0; i <= top; i++)           
newArray[i] = elements[i];   
maxSize += stackIncrement ;        
delete [ ]elements;       
elements = newArray;     //改变elements指针 
};  
template <class T> 
void SeqStack<T>::Push(const T& x) {    
//若栈不满, 则将元素x插入该栈栈顶, 否则溢出处理    
if (IsFull() == true) overflowProcess();       //栈满    
elements[++top] = x;                             //栈顶指针先加1, 再进栈
};  
template <class T> 
bool SeqStack<T>::Pop(T& x) { 
//函数退出栈顶元素并返回栈顶元素的值    
if (IsEmpty() == true) return false;   
x = elements[top--];//先取值,然后栈顶指针减1       
return true;                              //退栈成功 
};   
template <class T>
bool SeqStack<T>::getTop(T& x) { //若栈不空则函数返回该栈栈顶元素的地址   
if (IsEmpty() == true) return false;  
x = elements[top];     
return true; 
}; 

双栈共享一个栈空间

 两个栈共享一个数组空间V[maxSize]

 设立栈顶指针数组 t[2] 和栈底指针数组 b[2]

​ t[i]和b[i]分别指示第 i 个栈的栈顶与栈底

 初始 t[0] = b[0] = -1, t[1] = b[1] = maxSize

 栈满条件:t[0]+1 == t[1] //栈顶指针相遇

 栈空条件:t[0] = b[0]或t[1] = b[1] //退到栈底

链式栈

基于链表的存储表示

链式栈不需要附加头结点,因为栈是特殊的线性表,只能在栈顶(即链表头部)插入或删除,所以 不需要附加头结点。

//链式栈 (LinkedStack)类的定义
template <class T> 
class LinkedStack : public Stack<T> { //链式栈类定义    
private:   LinkNode<T> *top;                         //栈顶指针   
void output(ostream& os, StackNode <T> *ptr); //递归输出栈的所有元素 
public:      
LinkedStack( ) : top(NULL) { }               //构造函数;说明链式栈无无附加头结点 
~LinkedStack( ) { makeEmpty( ); }           //析构函数   
void Push(T x);                   //进栈      
bool Pop(T& x);                 //退栈      
bool getTop(T& x) const;             //取栈顶                
bool IsEmpty( ) const { return top == NULL; }      
int getSize( )const;                     //求栈中元素个数      
void makeEmpty();   //清空栈的内容      
friend ostream& operator << (ostream& os,            
LinkedStack<T>& s) { }                          
//输出栈元素的重载操作 <<           
//书上的参数SeqStack应为LinkedStack 
}; 

//链式栈类操作-求个数 
template <class T>  
int LinkedStack<T>::getSize( )const{  
LinkNode<T> *p=top; 
int k=0; 
while(p!=NULL) {k++; p=p->link;} //p94书上的while语句里用的top应为p。 
return k; 
} 

//链式栈类操作-置空 
template <class T> 
void LinkedStack<T>::makeEmpty( ) {  
//逐次删去链式栈中的元素直至栈顶指针为空。     
LinkNode<T> *p;  
while (top != NULL)            //逐个结点释放         
{ p = top;  top = top->link;  delete p; } 
}; 
 
//链式栈类操作-进栈 
template <class T> 
void LinkedStack<T>::Push(const T& x) { 
//将元素值x插入到链式栈的栈顶,即链头。     
top = new StackNode<T> (x, top);     
//创建新结点//同时插入到top前面,然后top指向新 结点即新栈顶   
//类似于P76InsertAfter里link=new Term(c,e,link);      
assert (top != NULL);       //创建失败退出 
//assert断言机制,若参数条件满足继续执行否则 
//出错处理,终止执行 
}; 
 
//链式栈类操作-出栈 
template <class T> 
bool LinkedStack<T>::Pop(T& x) { //删除栈顶结点, 返回被删栈顶元素的值。      
if (IsEmpty() == true) return false;   //栈空返回     
LinkNode<T> *p = top;  //暂存栈顶元素   
top = top->link;       //退栈顶指针   
x = p->data;   
delete p;          //释放结点      
return true;  
}; 

//链式栈类操作-获取栈顶元素值 
template <class T> bool 
LinkedStack<T>::getTop(T& x) const {    
if (IsEmpty() == true) return false;   //栈空返回   
x = top->data;                       //返回栈顶元素的值      
return true;  
}; 
 
//链式栈类操作-输出 
template <class T> 
ostream& operator << (ostream& os, LinkedStack<T>& s) { //从栈顶开始逐个输出 
os<<“栈中元素个数=”<<s.getSize( )<<endl; 
StackNode<T> *p=s.top;         //书上S.top应为s.top 
int i=0;   
while(p!=NULL) {  
os<<++i<<“:”<<p->data<<endl;  
p=p->link;} 
return os; 
}  

//链式栈类操作-递归输出 
template <class T> 
void LinkedStack<T>::output(ostream& os,      
StackNode<T> *ptr) {     
int i=1;     //递归输出栈中所有元素(沿链逆向输出)  
if (ptr != NULL) {     
if (ptr->link != NULL)                
output(os, ptr->link, i++);    
os << i << “ : ” << ptr->data << endl;                    
//递归结束条件ptr->link != NULL,                  
//输出最后一个元素,然后沿链逐个返回输出 
  } 
}; 

栈的应用:括号匹配

如从左向右扫描,则每个右括号将与最近遇到的未匹配的左括号匹配。

把从左向右扫描到的左括号放入栈中,在后续扫描中遇到右括号时,就将它与栈顶的左括号 (如果存在)相匹配,同时在栈顶删除该左括号。
分析可能出现的不匹配的情况

1.到目前左括号少即:到来的是右括号而栈中无左括号在等待(栈是空的)

2.到结束右括号少。即:栈中还有左括弧没等到和它相匹配的右括弧

算法的设计思想

1)凡出现左括弧,则进栈;

2)凡出现右括弧,首先检查栈是否空若栈空,则表明“右括弧”多了,不匹配,否则“左括弧出栈”;

3)表达式检验结束时,若栈空,则匹配正确 否则说明左括弧多了,不匹配。

void PrintMatchedPairs(char* expression){   
Stack<int> s(maxLength);   
int j, length=strlen(expression);   
for ( int i=1; i<length; i++) {      
if ( expression[i-1]==‘(’ )  s.Push(i);   
else if ( expression[i-1]==‘)’ ) {           
if ( s.Pop(j)==true )             
cout<<j<<“与”<<i<<“匹配”<<endl;           
else             
cout<<“没有与”<<i<<“匹配的左括号”<<endl;         
}   
while ( s.IsEmpty( )==false ) {     
s.Pop(j);     
cout <<“没有与”<<j<<“匹配的左括号”<<endl; } 
} 

栈的应用:表达式求值

一个表达式由操作数(亦称运算对象)、操作符(亦称运算符)和分界符组成。

 算术表达式有三种表示:

 中缀(infix)表示 <操作数> <操作符> <操作数>,如 A+B;

 前缀(prefix)表示 <操作符> <操作数> <操作数>,如 +AB;

 后缀(postfix)表示 <操作数> <操作数> <操作符>,如 AB+;

表达式

中缀表达式 a + b * ( c - d ) - e / f

后缀表达式 a b c d - * + e f / -

前缀表达式 - + a * b – c d / e f

结论

1)操作数之间的相对次序不变

2)运算符之间的的相对次序不同

3)前缀式的运算规则为:

连续出现的两个操作数和在它们之前且紧靠它们的运算符 构成一个最小表达式;

4)后缀式的运算规则为:

运算符在式中出现的顺序恰为表达式的运算顺序; 每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小 表达式;

算术表达式运算规则

运算规则:

先左后右,先乘除后加减,先括弧内后括弧外 。

例如:4+2*3-10/5=4+6-10/5=10-10/5= 10-2=8

​ 4 2 3 * + 10 5 / - = 8

实现方法讲解:

  • 中缀表达式直接求值法

    借助于栈:OPND栈和OPTR栈

    操作数入OPND栈,算符入OPTR栈

  • 中缀变后缀表达式求值:

    运算符顺序变化,需存储“等待中” 的运算符

    需将当前运算符与等待中最后一个运算符比较

如何从中缀表达式求得后缀式?

利用堆栈存储“等待中”的运算符实现中缀变后缀表达式,为了实现这种转换,需要考虑各操作符在栈 内和栈外的优先级

 对原表达式中出现的每一个运算符是否即刻 进行运算取决于在它后面出现的运算符

 如果它的优先数“高或等于”后面的运算, 则它的运算先进行,

 否则就得等待在它之后出现的所有优先数高 于它的“运算”都完成之后再进行。

从原表达式求得后缀式的规则为 :

  1. 设立运算符栈;

  2. 设表达式的结束符为“#”,预设运算符栈 的栈底为“#”;

  3. 若当前字符是操作数,则直接发送给后缀式;

  4. 若当前字符为运算符且优先数高于栈顶运算 符,则进栈,否则退出栈顶运算符发送给后 缀式;

  5. 若当前字符是结束符,则自栈顶至栈底依次 将栈中所有运算符发送给后缀式;

注意:

一般作为相同运算符,先出现的比后出现的 优先级高;

先出现的运算符优先级低于“(”,高于“)”;

优先权相等的仅有“(”和“)”、“#”。

 #:作为表达式结束符,通常在表达式之前 加一“#”使之成对,当出现“#”=“#”时,表 明表达式求值结束,“#”的优先级最低。
任意相继出现的算符θ1和θ2,都要比较优先权 关系。 一般任意两个相继出现的两个算符θ1和θ2之间 的优先关系至多有下面三种之一: θ1<θ2 θ2的优先权高于θ1 θ1=θ2 二者优先权相等 θ1>θ2 θ2的优先权低于θ1

中缀式转换为后缀式的算法 :

(isp叫做栈内(in stack priority)优先数; icp叫做栈外(in coming priority)优先数。操作符优先数相等的情况只出现在括号配对或 栈底的“#”号与输入流最后的“#”号配对时。)

操作符栈初始化,将符号‘#’进栈。然后读 入中缀表达式字符流的首字符ch。

 重复执行以下步骤,直到栈为空,停止循 环。

 若ch是操作数直接输出,读入下一个字 符ch。

 若ch是操作符,判断ch的优先级icp和位 于栈顶的操作符op的优先级isp:
 若 icp(ch) > isp(op),令ch进栈,读入下一 个字符ch。

 若 icp(ch) < isp(op),退栈并输出。

 若 icp(ch) == isp(op),退栈但不输出,若 退出的是“(”号读入下一个字符ch。

 算法结束,输出序列即为所需的后缀表达式

void postfix ( expression e ) {   
stack<char> s;   
char ch, op;    
s.Push ( ‘#’ );   
cin.Get ( ch ); 
while ( ! s.IsEmpty( )) // 课本代码有问题  
//循环条件也可以为while(ch!=‘#’||s.getTop()!=‘#’) 
        if ( isdigit ( ch ) )            
        { cout << ch;  cin.Get ( ch ); }        
        else {             
        if ( isp ( s.GetTop( ) ) < icp ( ch ) ) c
             { s.Push ( ch );  cin.Get ( ch ); 
             }           
             else if ( isp ( s.GetTop( ) ) > icp ( ch ) )              
             { s.Pop(op);   
             cout << op; 
             }            
             else { s.Pop(op);                  
             if ( op == ‘(’ ) cin.Get ( ch ); }   
             }
             } 
class Calculator { //模拟一个简单计算器 
public:           
  Calculator(int sz):s(sz){}; //构造函数           
  void Run();             //执行表达式计算           
  void Clear(); 
private:             
  Stack<double> s;      //栈对象定义          
  void  AddOperand(double value) ;  //进操作数栈           
  bool Get2Operands(double& left,double& right);                             
  //从栈中退出两个操作数           v
  oid DoOperator(char op); //形成运算指令,进行计算 
  }; 
void Calculator :: Run ( ) {    
   char ch;   double newoperand;    
   while ( cin >> ch,  ch != ‘#’ ) {        
     switch ( ch ) {         
       case ‘+’ :           
       case ‘-’ :            
       case ‘*’ :           
       case ‘/’ :   
       DoOperator ( ch );                             
       break;   //计算         
      default :  cin.putback ( ch );                              
                                //将字符放回输入流                   
       cin >> newoperand;  //读操作数                        
          AddOperand ( newoperand );       
          }    
       }

栈的应用:递归

递归的定义

若一个对象部分地包含它自己,或用它自己 给自己定义, 则称这个对象是递归的;若 一个过程直接地或间接地调用自己, 则称这 个过程是递归的过程。

函数间的的执行

1 .操作系统中,当一个函数调用另一个函数,需先完成:

  1. 将所有的实在参数、返回地址等信息传递给被调用函数保存

  2. 为被调用函数的局部变量分配存储区;

  3. 将控制转移到被调用函数的入口。

2 .从被调用函数返回调用函数之前,应该完成:

  1. 保存被调函数的计算结果;

  2. 释放被调函数的数据区;

  3. 依照被调函数保存的返回地址将控制转移到调用函数

递归函数的概念

直接或间接地调用自身的函数称为递归函数。 从递归函数的执行过程看,递归函数设计时必须 有一个出口,直接处理, 从而结束对自身的调用。

构成递归的条件

不能无限制地调用本身, 必须有一个出口,直接处理。

递归定义包括两项,一是边界条件:描述递归终止时问题的求解 ;二是递归函数:将问题分解为与原问题性质相同,但 规模较小的问题。只有具备了这两个要素,递归函数才能在有限次计算后得出结果。

递归设计:自顶向下、逐步分解的策略

解决递归问题的策略是把一个规模比较大的 问题分解为一个或若干规模比较小的问题, 子问题应与原问题做同样的事情,且更为简 单;分别对这些比较小的问题求解,再综合 它们的结果,从而得到原问题的解。

以下三种情况常常用到递归方法

 定义是递归的

 数据结构是递归的

 问题的解法是递归的

//含一个递归调用的递归过程的一般形式 
void  p(参数1) 
{    if(数据为递归出口)                    
操作;        
else{                  
操作;                 
p(参数2);                 
操作;         
} 
} 

递归过程改为非递归过程

递归过程简洁、易编、易懂。但递归过程效率低,重复计算多,改为非递 归过程的目的是提高效率 。

 单向递归或尾递归可直接用迭代实现其非递归 过程(尾递归是单项递归的特例)

 其他情形必须借助栈实现非递归过程

//用迭代法实现单向递归 
//斐波那契数列就是单向递归 
long FibIter(long n) {      
if (n <= 1) return n;      
long twoback = 0,  oneback = 1,  current;      
for (int i = 2; i <= n; i++) {          
current = twoback + oneback;          
twoback = oneback;             
oneback = current;   
}   
return current; 
} 
//用迭代法实现尾递归 
//递归调用语句只有一句且在函数尾部
void recfunc(int A[ ],  int n) {      
if (n >= 0) {           
cout << A[n] << “  ”;           
n--;           
recfunc(A, n);     
 } 
} 

void sterfunc(int A[ ], int n) { 
//消除了尾递归的非递归函数     
while (n >= 0) {          
cout << "value " << A[n] << endl;          
n--;     
    } 
}     
递归与回溯

回溯法也常称为试探法。这种方法将问题的 候选解按某种顺序逐一检验,当发现当前解 不可能是解时,可以沿搜索路径回退到前一 结点,沿另一分支继续搜索,直到搜索到问题 的解,或搜完全部分支没有解存在为止。

回溯法常使用递归进行试探,或使用栈帮助向前试探。

迷宫问题

迷宫是一个矩形区域,有一个入口和一个出口,其内 部设置了许多不能穿越的墙壁,对从入口到出口的前 进道路上形成了许多障碍.若能正确的绕过障碍,则从入口到出口存在一条穿越 迷宫的路线。

前进方向:北、东北、东、东南 南、西南、西、西北 .
走步规则:首先从东开始,按照 顺时针方向搜索下一步可 能前进的位置 .

数据结构设计
1 用二维数组Maze[m+2][p+2]表示迷宫

 若元素Maze[i][j]==1,表示该位置上是障碍。

 若元素Maze[i][j]==0,表示该位置上是通路。

2 用二维数组mark[m+2][p+2]标志已走过 的路途,防止走老路。

 初始化时,所有元素都是没走过的,设为0。

 当行进到迷宫的某个位置[i][j]时,就将对应的 mark[i][j]置为1;

 当前点的下一个试探位置 从当前位置Maze[i][j]出发,可能的前进方向有8个,用一 维数组move[8]表示。其数组元素为结构类型数据:

struct offsets {     
int a, b;        //a,b是横,纵方向的偏移量    
char *dir } 

offsets move[8]={
{-1,0,"N"},{-1,1,"NE"}, {0,1,"E"}, { 1,1,"SE"}, {1,0,"S"},{1,-1,"SW" 
{0,-1,"W"},{-1,1,"NW"}
} 
//解决迷宫问题的递归算法 
int SeekPath(int x,int y)
{      
int i,g,h; char *d;   //d,h记录位置信息,d记录方向      
if(x==m && y==n)  return 1;           
for(i=0;i<8;i++)
 {           
   g=x+move[i].a;   h=y+move[i].b;     d=move[i].dir; 
   if(Maze[g][h]==0&&mark[g][h]==0)  
   {                
        mark[g][h]=1;            
        if(SeekPath(g,  h))
        {              
          cout<<“(“<<g<<“,”<<h<<“),”<<“direction”<<move[i].dir<< “,”;       
          return 1;   
        }
   }         
 }//回溯,换方向再试      
if(x==1&&y==1) cout<<“no path in Maze”<<endl;      
Return 0; 
} 

n皇后问题

在 n 行 n 列的国际象棋棋盘上,若两个皇后位于 同一行、同一列、同一对角线上,则称为它们为 互相攻击。n 皇后问题是指找到这 n 个皇后的互 不攻击的布局。

8皇后问题:在8X8格的国际象棋上摆放八个皇后,使 其不能互相攻击,即任意两个皇后都不能处于同一行、 同一列或同一斜线上,问有多少种摆法?

解题思路

安放第 i 行皇后时,需要在列的方向从 0 到 n-1 试探 ( j = 0, …, n-1 )

在第 j 列安放一个皇后: 如果在列、对角线方向有其它皇后,则 出现攻击,撤消在第 j 列安放的皇后。如果没有出现攻击,在第 j 列安放的皇 后不动,递归安放第 i+1行皇后。

设置数组 col [n] :col[i] 标识第 i 列是否安放了皇后

void Queen(int i) {     
if (i>n) 输出棋盘布局;     
else     
for (int j = 1;  j <= n;  j++) {          
 if (第 i 行第 j 列没有攻击) {              
 在第 i 行第 j 列安放皇后;               
 Queen(i+1);             
   }         
  }      
} 

//算法求精 
void queen(int k)    {  
if(k>n) {sum++ ;               
//在这里打印出来 ;              
cout<<"the solution of "<<sum <<endl;              
for(int i=1;i<=n;i++)             
{for(int j=1;j<=n;j++)            
if (x[i]==j) cout<<"* " ;               
else cout<<"0 ";                 
cout<<endl ;    }             
cout<<endl;    
} else  
for(int j=1;j<=n;j++)  
{   x[k]=j;      
if(place(k))    
queen(k+1);   }} 
place(int t)  
//检查 第 t个皇后在第T行的当前位置上是否满足约束条件 
{  
for(int i=1;i<t;i++)    
if((x[i]==x[t])|| (abs(i-t)==abs(x[i]-x[t])) ) return false ;  
return true; 
} 
Int sum=0; 
int main() 
{ 
int n; 
cout<<"请输入皇后的数目"<<endl; 
cin>>n; 
int *x=new int[n+1] ; //记录每行的皇后放在哪一列  
for(int i=0;i<=total;i++) x[i]=0; 
queen(1) ; 
cout<<"the result of nqueen is :"<<sum<<endl; 
delete [] x ; 
return 0; 
} 

队列

队列是只允许在一端删除,在另一端插入 的线性表

允许删除的一端叫做队头(front),允许插 入的一端叫做队尾(rear)。

特性:先进先出(FIFO, First In First Out)

//队列的抽象数据类型 
template <class E> 
class Queue { 
public:      
Queue() { };       //构造函数      
~Queue() { };       //析构函数      
virtual bool EnQueue(const E& x) = 0;  //进队列      
virtual bool DeQueue(E& x) = 0;       //出队列      
virtual bool getFront(E& x) = 0;       //取队头        
virtual bool IsEmpty() const = 0;       //判队列空      
virtual bool IsFull() const = 0;       //判队列满 }; 

//队列的数组存储表示 
template <class E> class SeqQueue : public Queue<E> {    //队列类定义 protected:      
int rear, front;         //队尾与队头指针      
E *elements;         //队列存放数组      
int maxSize;         //队列最大容量 
……. 
// 函数 
}; 

队列的进队和出队原则

 进队时先将新元素按 rear 指示位置加入, 再将队尾指针加一 rear = rear + 1。

 队尾指针指示实际队尾的后一位置。

 出队时按队头指针指示位置取出元素,再将 队头指针进一 front = front + 1,

 队头指针指示实际队头位置。

 队满时再进队将溢出出错(假溢出) ;

 解决假溢出的办法之一:将队列元素存放数 组首尾相接,形成循环(环形)队列。

循环队列

 队列存放数组被当作首尾相接的表处理。

 为了防止队头、队尾指针加1产生假溢出,可用语言 的取模(余数)运算实现。

 队列初始化:front = rear = 0;  队头指针进1: front = (front+1) % maxSize;

 队尾指针进1: rear = (rear+1) % maxSize;

 队空条件:front == rear;

 队满条件:(rear+1) % maxSize == front (看示例)

通过牺牲一个存储空间来实现(课本上的方案)

//队列的数组存储表示----循环队列 
template <class E> class SeqQueue : public Queue<E> {    //队列类定义 protected:      
int rear, front;         //队尾与队头指针      
E *elements;         //队列存放数组      
int maxSize;         //队列最大容量 
public:      
SeqQueue(int sz = 10);    //构造函数 
~SeqQueue() { delete[ ] elements; }  //析构函数      
bool EnQueue(const E &x);         //新元素进队列      
bool DeQueue(E& x);      //退出队头元素      
bool getFront(E& x);       //取队头元素值      
void makeEmpty() { front = rear = 0; }        
bool IsEmpty() const { return front == rear; }       
bool IsFull() const           
{ return ((rear+1)% maxSize == front); }       
int getSize() const           
{ return (rear-front+maxSize) % maxSize; }  
}; 

//循环队列操作的定义 
template <class E> 
SeqQueue<E>::SeqQueue(int sz): front(0), rear(0), maxSize(sz) { //构造函数   
elements = new E[maxSize];         
assert ( elements != NULL ); 
}; 
template <class E> 
bool SeqQueue<E>::EnQueue(const E& x) {
//若队列不满, 则将x插入到该队列队尾, 否则返回      
if (IsFull() == true) return false;         
elements[rear] = x;                    //先存入      
rear = (rear+1) % maxSize;       //尾指针加一      
return true;    
};  
template <class E> 
bool SeqQueue<E>::DeQueue(E& x) {  
//若队列不空则函数退队头元素并返回其值      
if (IsEmpty() == true) return false;
x = elements[front];                  //先取队头      
front = (front+1) % maxSize;   //再队头指针加一      
return true; 
}; 
template <class E> 
bool SeqQueue<E>::getFront(E& x) const { //若队列不空则函数返回该队列队头元素的
if (IsEmpty() == true) return false;    //队列空   
x = elements[front];      //返回队头元素   
return true; 
};  

链式队列

 队头在链头,队尾在链尾。

 链式队列在进队时无队满问题,但有队空问 题。

 队空条件为 front == NULL

//队列的链接存储表示 — 链式队列 
//链式队列类的定义 
template <class E> struct LinkNode {                   //队列结点类定义  private:      
E data;             //队列结点数据     
LinkNode<E> *link;          //结点链指针 
public:     
LinkNode(const E& d , LinkNode<E>*  next =null) : data(d), link(next) { } 
};   
template <class E> 
class LinkedQueue {  
private:       
LinkNode<E> *front, *rear;   //队头、队尾指针 
public:      
LinkedQueue() : rear(NULL), front(NULL) { }     
~LinkedQueue(MakeEmpty());        
bool EnQueue(const E& x);     
bool DeQueue(E& x);        
bool GetFront(E& x);        
void MakeEmpty();                 
bool IsEmpty() const { return front == NULL; } 
}; 
template <class E> 
void LinkedQueue<E>::makeEmpty() {      //析构函数     
LinkNode<E> *p;     
while (front != NULL) {              //逐个结点释放         
p = front;  
front = front->link;  
delete p;     } 
};  
template <class E>  
bool LinkedQueue<E>::EnQueue(const E& x) { //将新元素x插入到队列的队尾 
// 分空表和非空表两种情况。 
if (front == NULL)  {              //创建第一个结点        
front = rear = new LinkNode<E> (x);           
if (front == NULL) return false; } //分配失败       
else {                                      //队列不空, 插入           
rear->link = new LinkNode<E> (x);           
if (rear->link == NULL) return false;           
rear = rear->link;      }      
return true; 
}; 
template <class E> //如果队列不空,将队头结点从链式队列中删去 
bool LinkedQueue<E>::DeQueue(E& x) {      
if (IsEmpty() == true) return false;        //判队空      
LinkNode<E> *p = front;        
x = front->data;  
front = front->link;         
delete p;   
return true;     
}; 
template <class E>  
bool LinkedQueue<E>::GetFront(E& x) { //若队列不空,则函数返回队头元素的值      
if (IsEmpty() == true) return false;       
x = front->data;  
return true; 
}; 

队列的应用:打印杨辉三角形

算法逐行打印二项展开式 (a + b)i 的系数

每行的第i个元素,等于上一行的第i个元素和 它的前驱项相加的结果

//利用队列打印二项展开式系数的算法 
#include <stdio.h> 
#include <iostream.h> 
#include "queue.h" 
void YANGHVI(int n) {      
 Queue q(n+2);                     //队列初始化      
 q.MakeEmpty();      
 q.EnQueue(1);  q.EnQueue(1); 
 int s = 0, t;  
 for (int i = 1; i <= n; i++) {            //逐行计算           
 cout << endl;                
 q.EnQueue(0); //各行间插入一个0               
 for (int j = 1; j <= i+2; j++)        
 {//处理第i行的i+2(包括0)个系数。               
  q.DeQueue(t);              
  q.EnQueue(s + t);                 
  s = t;               
  if (j != i+2) cout << s << ' ';            
     }      
 }}

优先级队列

每次从队列中取出的是具 有最高优先权的元素

//优先级队列的类定义 
#include <assert.h> 
#include <iostream.h> 
#include <stdlib.h> 
template <class E> 
class PQueue { private:     
E *pqelements;        //存放数组     
int count;                  //队列元素计数     
int maxPQSize;                //最大元素个数     
void adjust();                  //调整 
public:      
PQueue(int sz = 50);     
~PQueue() { delete [ ] pqelements; }     
bool Insert(E x);     
bool RemoveMin(E& x);      
bool GetFront(E& x);      
void MakeEmpty() { count = 0; }     
bool IsEmpty() const { return count == 0; }     
bool IsFull() const             
{ return count == maxPQSize; }      
int Length() const { return count; } 
}; 

//优先级队列部分成员函数的实现 
template <class E>  PQueue<E>::PQueue(int sz) {       
maxPQSize = sz;  count = 0;       
pqelements = new E[maxPQSize];       
assert (pqelements != NULL);  } 
template <class E>  
bool PQueue<E>::Insert(E x) {      
if (IsFull() == true) return false;   //判队满断言      
pqelements[count++] = x;              //插入 
adjust();   
return true; 
} 
template <class E>  
void PQueue<E>::adjust() {      
E temp = pqelements[count-1];      //将最后元素暂存再从后向前找插入位置      
for (int j = count-2;  j >= 0;  j--)          
if (pqelements[j] <= temp) break;           
else pqelements[j+1] = pqelements[j];      
pqelements[j+1] = temp; 
} 
template <class E>  
bool PQueue<E>::RemoveMin(E& x) {      
if (IsEmpty()) return false;     
x = pqelements[0];       //取出0号元素      
for (int i = 1; i < count; i++)          
pqelements[i-1] = pqelements[i];           //从后向前逐个移动元素填补空位      
    count--;      
return true; }  
template <class E> bool PQueue<E>::GetFront (E& x) {      
if (IsEmpty() == true) return false;      
x = pqelements[0];        
return true; 
} 
posted @ 2020-05-16 21:03  繁辰一梦  阅读(513)  评论(2编辑  收藏  举报