通常称,栈和队列是限定插入和
删除 只能在表的 “ 端点 ” 进行的线性表。

  线性表                  栈               队列
Insert(L, i, x)   Insert(S, n+1, x)   Insert(Q, n+1, x)
 1≤i≤n+1
 Delete(L, i)      Delete(S, n)         Delete(Q, 1)
  1≤i≤n

栈和队列是两种常用的数据类型
第三章 栈和队列
 3.1 栈 (stack)
 3.2 栈的应用举例
 3.4 队列
 (Queue)
学习提要:
1. 掌握栈和队列这两种抽象数据类型的特
点,
    并能在相应的应用问题中正确选用它们。
2. 熟练掌握栈类型的两种实现方法,即两
种存
    储结构表示时的基本操作实现算法,特别
应
 注意栈满和栈空的条件以及它们的描述方
法。
3. 熟练掌握循环队列和链队列的基本操作
实现
§3.1 栈( stack )
3.1.1 栈的类型定义

3.1.2 栈的表示和实现
3.1.1 栈的类型定义
 栈的定义和特点
  定义:限定仅在表尾进行插入或删
   除操作的线性表,表尾 — 栈顶,表头
   — 栈底,不含元素的空表称空栈。
     进栈           出栈
    栈     ...
          an
    顶
          ……...


                   栈 s=(a1,a2,……,an)
          a2
   栈底     a1

特点:先进后出( FILO )或后进先
出( LIFO )
 栈的类型定义
ADT Stack {
  数据对象:
  D = { ai | ai ∈ElemSet, i=1,2,...,n, n≥0 }
   数据关系:
   R1 = { <ai-1, ai >| ai-1, ai∈D, i=2,...,n }
               约定 an 端为栈顶, a1 端为栈底。
     基本操作:
    } ADT Stack
InitStack(&S)
                 DestroyStack(&S)
StackLength(S)
                 StackEmpty(s)
GetTop(S, &e)
                 ClearStack(&S)
Push(&S, e)
                 Pop(&S, &e)
StackTravers(S, visit())
3.1.2 栈的表示和实现
    顺序栈
          类似于线性表的顺序映象实
      现,指向表尾的指针可以作为栈
      顶指针。
//----- 栈的顺序存储表示 -----
#define STACK_INIT_SIZE 100; // 存储空间初始分配量
#define STACKINCREMENT 10;// 存储空间分配增量
typedef struct {
  SElemType *base; // 栈底指针
  SElemType *top; // 栈顶指针
  int stacksize; // 栈的当前可使用的最大容量
} SqStack;
实现:一维数组 s[M]
                                 栈满          栈空
                top
            5   top      F   5               5
            4   top      E   4               4
            3   top      D   3               3
                                  top        2
            2   top      C   2
                                  top   B    1
            1   top      B   1
top                               top   A    0
base        0   top      A   0   base
                base                    出栈
       空栈               进栈
栈底指针 b a s e , 始               设数组维数为 M
终指向栈底位置;               to p =b a s e , 栈空,此时出栈,
栈顶指针 to p , 其初              则下溢( und e rflo w)
值指向栈底,始终
在栈顶元素的下一
                        to p =M, 栈满,此时入栈,则
个位置                           上溢( o ve rflo w)
Status InitStack (SqStack &S)
{// 构造一个空栈 S
S.base=(SElemType*)malloc(STACK_INIT_SIZ
                       E *sizeof(SElemType));
  if (!S.base) exit (OVERFLOW); // 存储分配失败
  S.top = S.base;
  S.stacksize = STACK_INIT_SIZE;
  return OK;
}
Status Push (SqStack &S, SElemType e) {
 if (S.top - S.base >= S.stacksize) {// 栈满,追加存储空
间
     S.base = (SElemType *) realloc ( S.base,
            (S.stacksize + STACKINCREMENT) *
                              sizeof (SElemType));
     if (!S.base) exit (OVERFLOW); // 存储分配失
败
      S.top = S.base + S.stacksize;
      S.stacksize += STACKINCREMENT;
    }
    *S.top++ = e;
Status Pop (SqStack &S, SElemType &e) {
   // 若栈不空,则删除 S 的栈顶元素,
   // 用 e 返回其值,并返回 OK ;
   // 否则返回 ERROR
  if (S.top == S.base) return ERROR;
  e = *--S.top;
  return OK;
}
链栈
    栈的链式存储结构。栈顶指针
 就是链表的头指针。
栈顶指针
       an   an-1   a1 ∧


 注意 : 链栈
 中指针的方
 向
 入栈操
       作
       top top
                                 栈底
p      x                  …...    ^


    p->next=top ; top=p
       出栈操
       作
       q  top
                                 栈底
top                       …...    ^

q=top ; top=top->next; //q 返回了出栈的元素
§3.2 栈的应用
3.2.1   数制转换
3.2.2   括号匹配的检验
3.2.3   行编辑程序问题
3.2.4   迷宫求解
3.2.5   表达式求值
3.2.1 数制转换

十进制 N 和其他 d 进制数的转换
原理 :

 N=( N div d )*d + N mod d

其中: div 为整除运算, mod 为
求余运算
例如: (1348)10=(2504)8 ,其运算过程如
下:
   N     N div 8       N mod 输
                             8
计
算 1348                       出
             168        4
顺                            顺
序 168        21         0    序

   21         2         5                  top
                                 top   2
   2          0        top
                         2   5         5
             top   0         0
   top   4         4                   0
                             4         4
void conversion( ) {
     initstack(S); // 构造空栈
     scanf (“%d”,N);
     while(N){
        push(S , N%8);
        N=N/8; }
     while(! Stackempty(s)){
             pop(S,e);
             printf(“%d”,e);   }
}//conversion
3.3.2 括号匹配的检验
 假设在表达式中
 ([]())或[([ ][ ])]
 等为正确的格式,
 [( ])或([( ))或 (()
 ] ) 均为不正确的格式。

    则 检验括号是否匹配的方法可用
“ 期待的急迫程度 ” 这个概念来描述。
例如:考虑下列括号序列:
   [ ( [ ] [ ] ) ]
   1 2 34 5 6 7 8
分析可能出现的不匹配的情况 :
到来的右括弧 并非是所 “ 期待
 ” 的;
直到结束,也没有到来所
“ 期待 ” 的括弧。
算法的设计思想:
1 )凡出现左括弧,则进栈;
2 )凡出现右括弧,首先检查栈是否空
   若栈空,则表明该“ 右括弧” 多余,
   否则和栈顶元素比较,
     若相匹配,则“ 左括弧出栈” ,
     否则表明不匹配。
3 )表达式检验结束时,
   若栈空,则表明表达式中匹配正确,
   否则表明“ 左括弧” 有余。
3.2.3 行编辑程序问题
                不恰
 如何实现?          当!
“ 每接受一个字符即存入存储器 ”    ?
合理的作法是:
   设立一个输入缓冲区,用以接
受用户输入的一行字符,然后逐行
存入用户数据区,并假设 “ #” 为退
格符, “ @” 为退行符。
假设从终端接受了这样两行字符:
  whli##ilr#e ( s#*s)
   outcha@putchar(*s=#++);
则实际有效的是下列两行:
  while (*s)
  putchar(*s++);
whli##ilr#e ( s#*s)
                 #    #       #

                          r   r
      i          i        l   l
      l          l    l   i   i
      h          h    h   h   h
      w          w    w   w   w
whli##ilr#e ( s#*s)
                          EOF
                 #    )    )
                      s    s
     s            s   *    *
     (           (    (    (
     e           e    e    e
     l           l    l    l
     i           i    i    i
     h           h    h    h
     w           w    w    w
Void LineEdit( ){
// 利用字符栈 S, 从终端接收一行并传送至调用过程的数据
   区
  InitStack(S); // 构造空栈
  ch = getchar(); // 从终端接受一个字符
  While(ch!= EOF){ //EOF 为全文结束符
  ... // 对输入的字符的判断与处理
ClearStack(S);     // 重置 S 为空栈
if (ch != EOF) ch = getchar();}
DestroyStack(S);
} //LineEdid
while (ch != EOF) { //EOF 为全文结束符
 while (ch != EOF && ch != ‘n’) { // ‘ n ’ 为换行符
   switch (ch) {
    case '#' : Pop(S, c); break;
    case '@': ClearStack(S); break;// 重置 S 为空栈
    default : Push(S, ch); break;
   }
   ch = getchar(); // 从终端接收下一个字符
 }
将从栈底到栈顶的字符传送至调用过程的
数据区;
3.2.4 迷宫求解
通常用的是“ 穷举求解 ” 的方法
   #   # # # # # # # #   #
   #   →↓ # $ $ $ #      #
   #     ↓ # $ $ $ #     #
   #   ↓ ←$ $ # #        #
   #   ↓ # # #       #   #
   #   →→↓ #         #   #
   #     # →→↓ #         #
   #   # # # # ↓ # #     #
   #           →→→Θ      #
   #   # # # # # # # #   #
求迷宫路径算法的基本思想是
:
若当前位置“ 可通 ” ,则纳入
 路径,继续前进 ;
若当前位置“ 不可通 ” ,则后
 退,换方向继续探索 ;
若四周“ 均无通路 ” ,则将当前
 位 置从路径中删除出去。
“ 下一个位置 ” 指 “ 当前位置 ” 四周 “
 东南西北 ” 四个方向上相临的通道块
 。

         北

     西       东

         南
             当前位置
根据算法思想 , 为了保证在任何
位置上都能沿原路退回 , 需要用一个后
进先出的结构来保存从入口到当前位置
的路径 .
     假设以栈 S 记录 “ 当前路径 ” ,则
栈顶中存放的是 “ 当前路径上最后一个
通道块 ” 。由此,“ 纳入路径 ” 的操作即
为 “ 当前位置入栈 ” ;“ 从当前路径上删
除前一通道块 ” 的操作为 “ 出栈 ” 。
算法描述
设定当前位置的初值为入口位置
do {
     若当前位置可通
     则 { 将当前位置插入栈顶 ; // 纳入路径
        若该位置是出口位置 , 则结束 ; // 求得路径存
 放在栈中
        否则切换当前位置的东相邻方块为新的当前
 位置 ;
    }
                出口         东

   当前位置       当前位置          当前位置
算法描述
否则  // 不通
若栈不空且栈顶位置尚有其他方向未探索 ,
  则设定新的当前位置为沿顺时针方向旋转找
 的栈顶位置的下一相邻块 ;




       栈顶位       当前位
        置         置
算法描述
若栈不空但栈顶位置的四周均不可通
则 { 删去栈顶位置 ;
         若栈不空 , 则重新测试新的栈顶位
      置,
         直到找到一个可通的相邻块或出栈
      至栈空 ;
   }
}while( 栈不空 );
                    当前位
                     置
算法描述
说明 : 当前位置可通 , 指未曾走到过的通道
块 , 即要求该通道块既不在当前路径上 , 也
不是曾经纳入过路径的通道块 .

     a
起点                    a-b-c-f-g ( 简单路径 )
                 b
d        c             a-b-c-d-e-c-f-g ( 非简单路径
                     f ) 若 b 已经纳入过路径 , 则在当
e
                         前位置为 f 时 , 将不作为可通
    终点       g           位置考虑 , 否则 , 将形成死循
                         环.
通道块的数据描述
Type struct {
int ord; // 通道块在路径上的 “ 序号 ”
PosType seat; // 通道块在迷宫中的 “ 坐标
 ”
int   di;   // 从此通道块走向下一通道块的 “ 方
 向”
} SElemType;   // 栈的元素类型
迷宫问题算法
Status MazePath(MazeType maze,PosType start, PosType
end){
// 若迷宫 maze 中存在从 start 到 end 的通道,则求得一
条存放在栈中,并返回 true ;否则返回 false
InitStack(S); curpos = start; // 设定当前位置为 “ 入口位置
”
curstep = 1; // 探索第一步
do {
        ...
    } while (!StackEmpty(s));
return (false);
} //MazePath
迷宫问题算法
do {
       if (Pass(curpos)) { // 当前位置可通
            FootPrint(curpos); // 留下足迹
            e = ( curstep,curpos,1);
            Push(S,e); // 入栈,加入足迹
            if (curpos == end) return (ture); // 到达出口
            curpos = NextPos(curpos,1);
                                // 下一个位置为当前位置的东邻
          curstep++; // 探索下一个位置
        }// if
        ...
迷宫问题算法
else { // 当前位置不能通过
If ( !StackEmpty(s)) {
      Pop(S,e) ; // 出栈,弹出当前的栈顶位置至 e
      while(e.di == 4 && !StackEmpty(s)) {
      MarkPrint(e.seat); Pop(S,e); // 若该位置无方向可寻,留
下不能通过的标记,并退回一步
      } //while
      if (e.di < 4) { // 表示仍有方向可寻
          e.di++; Push(S,e); // 换下一方向试探
          curpos = NextPos(e.seat, e.di);
       } //end if
}//end else
迷宫问题演示




  成功     不成功
3.2.5 表达式求值
限于二元运算符的表达式定义 :

 Exp = S1 OP S2
操作数 : 变量、常量、表达式
运算符 : 算术运算符、关系运算符、
          逻辑运算符
界限符:括号、结束符
算法思想:
      设置两个工作栈, OPTR 存运算符
, OPND 存操作数及运算结果。算法运算
思想:
1. 首先置 OPND 栈为空, “ #” 为运算符
栈底元素。

2. 依次读入表达式中的每个字符,若是
操作数进 OPND 栈 , 若是运算符则和
OPTR 栈顶元素比较优先权后作相应操
作,直至整个表达式求值完毕。
例: 3 * ( 7 – 2 ) #
    C C C C C C C   C




                          -

          2               (      7-2
          5
          7               *
                                 3*5
          15
          3               #


        OPND 栈          OPTR 栈
例: 3 * ( 7 – 2 )
 OPTR 栈    OPND 栈        输入                     操作
1 #                 3*(7–2)#   PUSH( OPND, ‘3’ )
2 #        3         *(7–2)#    PUSH( OPTR, ‘*’ )
3 #*       3          (7–2)#   PUSH( OPTR, ‘(’ )
4 #*(      3           7–2)#   PUSH( OPND, ‘7’ )
5 #*(      3 7          –2)#   PUSH( OPTR, ‘–’ )
6 # * (–   3 7           2)#   PHSH( OPND, ‘2’ )
7 # * (–   3 7 2          )#   operate( ‘7’,’-’,’2’ )
8 #*(      3 5            )#   POP( OPTR )
9 #*       3 5             #   operate( ‘3’, ‘*’, ‘5’ )
10 #        15             #   return GetTop( OPND )

“(” < “*” , 因此“ ( ” 入操作符栈 OPTR
例: 3 * ( 7 – 2 )
 OPTR 栈    OPND 栈        输入                     操作
1 #                 3*(7–2)#   PUSH( OPND, ‘3’ )
2 #        3         *(7–2)#    PUSH( OPTR, ‘*’ )
3 #*       3          (7–2)#   PUSH( OPTR, ‘(’ )
4 #*(      3           7–2)#   PUSH( OPND, ‘7’ )
5 #*(      3 7          –2)#   PUSH( OPTR, ‘–’ )
6 # * (–   3 7           2)#   PHSH( OPND, ‘2’ )
7 # * (–   3 7 2          )#   operate( ‘7’,’-’,’2’ )
8 #*(      3 5            )#   POP( OPTR )
9 #*       3 5             #   operate( ‘3’, ‘*’, ‘5’ )
10 #        15             #   return GetTop( OPND )

“-” >“ )” , 因此执行 7-2 操作,同时
OPTR 栈弹出“ -” , OPND 栈弹出“ 2” 和
例: 3 * ( 7 – 2 )
 OPTR 栈    OPND 栈        输入                     操作
1 #                 3*(7–2)#   PUSH( OPND, ‘3’ )
2 #        3         *(7–2)#    PUSH( OPTR, ‘*’ )
3 #*       3          (7–2)#   PUSH( OPTR, ‘(’ )
4 #*(      3           7–2)#   PUSH( OPND, ‘7’ )
5 #*(      3 7          –2)#   PUSH( OPTR, ‘–’ )
6 # * (–   3 7           2)#   PHSH( OPND, ‘2’ )
7 # * (–   3 7 2          )#   operate( ‘7’,’-’,’2’ )
8 #*(      3 5            )#   POP( OPTR )
9 #*       3 5             #   operate( ‘3’, ‘*’, ‘5’ )
10 #        15             #   return GetTop( OPND )

“)” =“(” , 操作符的优先权相等,弹出“ (”,
脱括号并接收下一字符 .
OperandType EvaluateExpression() {
 // 设 OPTR 和 OPND 分别为运算符栈和运算数栈, OP 为运算符集合。

 InitStack (OPTR); Push (OPTR, '#');
 initStack (OPND); c = getchar();
 while (c!= '#' || GetTop(OPTR)!= '#') {
  if (!In(c, OP)) { Push((OPND, c); c = getchar(); }
                    // 不是运算符则进栈
  else
       ……                 O P 为运算符集合,若 c 不
                          是运算符,则加入到运算
  } // while
                          数栈中
  return GetTop(OPND);
} // EvaluateExpression
switch ( precede(GetTop(OPTR), c) {
     case '<': // 栈顶元素优先权低
          Push(OPTR, c); c = getchar();
          break;
     case '=': // 脱括号并接收下一字符
          Pop(OPTR, x); c = getchar();
          break;
     case '> ': // 退栈并将运算结果入栈
          Pop(OPTR, theta);
          Pop(OPND, b); Pop(OPND, a);
          Push(OPND, Operate(a, theta, b));
          break;
    } // switch
算法演示
( 3+5 ) * 2- 12 + 6 / 3 #
第三章作业
3.1 设将整数 1 、 2 、 3 、 4 依次进栈,但只要
出栈时栈非空,则可将出栈操作按任何次序夹
入其中,请回答下列问题:
     ( 1 )若入栈次序为
push(1) , pop() , push(2 ), push(3) , pop()
, pop( ) , push(4) , pop( ) ,则出栈的数字序
列为什么?
3.2 写出检验括号匹配的算法。
§3.4 队列
3.4.1 队列的类型定义
3.4.2 链队列
3.4.3 循环队列
3.4.1 队列的类型定义
队列是限定只能在表的一端进行插入,
 在表的另一端进行删除的线性表。
出
      a1     a2   a3…………………….an      入队
队
     front                    rear
             队列 Q=(a1,a2,……,an)
    队尾 (rear)—— 允许插入的一端
    队头 (front)—— 允许删除的一端
    队列特点:先进先出 ( F IF O )
队列的类型定义
ADT Queue {
  数据对象:
      D = {ai | ai∈ ElemSet, i=1,2,...,n, n≥0}
   数据关系:
     R1 = { <a i-1,ai > | ai-1, ai ∈ D, i=2,...,n}
      约定其中 a1 端为队列头 , an 端为队列尾
     基本操作:
   } ADT Queue
队列的基本操作:
InitQueue(&Q)     DestroyQueue(&Q)
QueueEmpty(Q)     QueueLength(Q)
GetHead(Q, &e) ClearQueue(&Q)
EnQueue(&Q, e) DeQueue(&Q, &e)
QueueTravers(Q, visit())
3.4.2 链队列-队列的链式表示和
                实现
   typedef struct QNode{// 结点类型
   QElemType     data ;
   struct QNode *next ;
  }QNode, *QueuePtr;
 typedef struct{ // 链队列类型
   QueuePtr front ; // 队头指针
   QueuePtr rear ; // 队尾指针
 } LinkQueue;
Q.front             a1       …   an   ∧

Q.rear

          Q.front
  空队                     ∧
          Q.rear
  列
空
 队               ^               Q.rear -> next=p
        front rear       p          Q.rear=p
x 入队                 x       ^
    front            rear                 p
y 入队                  x               y       ^
       front                          rear
                         p
x 出队                 x            y       ^
     front                     rear
y 出队         ^           p= Q.front -> next
                     Q.front -> next = p -> next
     front ear
         r
Status InitQueue (LinkQueue &Q) {
    // 构造一个空队列 Q
    Q.front = Q.rear =
             (QueuePtr)malloc(sizeof(QNode));
    if (!Q.front) exit (OVERFLOW);
                         // 存储分配失败
    Q.front->next = NULL;
    return OK;
}
Status EnQueue (LinkQueue &Q,
                            QElemType e) {
   // 插入元素 e 为 Q 的新的队尾元素
   p = (QueuePtr) malloc (sizeof (QNode));
   if (!p) exit (OVERFLOW); // 存储分配失败
   p->data = e; p->next = NULL;
   Q.rear->next = p; Q.rear = p;
   return OK;
}
EnQueue                          p
            ...      ∧      e        ∧


  Q.front         Q.rear    Q.rear


 p->data = e; p->next = NULL;
 Q.rear->next = p; Q.rear = p;
Status DeQueue (LinkQueue &Q,
                       QElemType &e) {
 // 若队列不空,则删除 Q 的队头元素,
 // 用 e 返回其值,并返回 OK ;否则返回
ERROR
 if (Q.front == Q.rear) return ERROR;
 p = Q.front->next; e = p->data;
 Q.front->next = p->next;
 if (Q.rear == p) Q.rear = Q.front;
 free (p); return OK;
DeQueue
       p
   e          ...                         ∧


  Q.front                             Q.rear


 p = Q.front->next; e = p->data;
 Q.front->next = p->next;
 if (Q.rear == p) Q.rear = Q.front;
 free (p);
3.4.3 循环队列-队列的顺序表示和实
现
#define MAXQSIZE 100 // 最大队列长度
typedef struct {
  QElemType *base; // 动态分配存储空间
  int front; // 头指针,若队列不空,
            // 指向队列头元素
  int rear;     // 尾指针,若队列不空,指向
               // 队列尾元素 的下一个位置
} SqQueue;
实现:用一维数组实现 sq[M]
                                               rear
           5                5              5             J6        5
           4                4              4             J5        4
               rear            rear                      J4
           3                3              3                       3
               rear
           2           J3   2 front   J3   2 front                 2
               rear           front   J2
           1           J2   1 front        1                       1
rear=0         rear
           0   front   J1   0 front   J1   0                       0
front=0                                               J4,J5,J6 入
        空队列    J1,J1,J3 入队 J1,J2,J3 出队                    队

存在问题:
当 front=0,rear=M 时再有元素入队发生溢出 — — 真
溢出
当 front≠0,rear=M 时再有元素入队发生溢出 — — 假
解决方案 1
  队首固定,每次出队剩余元素向下移
  动 , 避免发生假溢出 .
Rear         Rear
        J6           J6
        J5           J5   Rear    J6
        J4           J4           J5
        J3           J3           J4
front   J2   front        front   J3


方法可行 , 但将花费太多的时间
解决方案 2 循环队列
         基本思想:把队列设想成环形,让
        sq[0] 接在 sq[M-1] 之后,若 rear+1==M,
        则令 rear=0;
M-1       J6      Rear    M-1    J6
 ...      ...              ...   ...
  2       J5                2    J5
  1       J4      Front     1    J4      Front
  0                         0    J7      Rear


       rear+1 = M, 于是令 rear = 0, “J7” 入队 ,
       有 sq[rear] = J7. 能够避免假溢出 .
解决方案 2
循环队列
 实现: 利用 “ 模 ” 运算,实现若 rear+1==M,
 则 rear=0 的设定 ;

             入队:        sq[rear]=x;
              rear=(rear+1)%M;
             出队: x=sq[front];
              front=(front+1)%M;
             队满、队空判定条件
front
  队空: fro nt==re a r                               rear
  队满: fro nt==re a r
                                        4 5 0
                                        3
                                          2 1
                    rear
             J6
        J5 4 5 0
           3        J4,J5 ,J6 出 队           J6
             2 1
        J4         J7,J8
                         ,J9 入         J5 4 5 0 J7
front
                               队          3 2 1 J8
                                       J4
        初始状态                front          J9
                                rear

             队满队空如何区分判定 ?
解决方案:
1. 另外设一个标志位以区别队空、队满
2. 少用一个元素空间:
      队空: front==rear 队满:
(rear+1)%M==front
              front
                rear            J6
                           J5 4 5 0 J7
        4 5 0                 3
        3                  J4 2 1 J8
          2 1        front
                                    rear
3. 使用一个计数器记录队列中元素的总数
当 count = Sq.length 时为队满 .
分析可见 , 在 C 语言中不能用
动态分配的一维数组来实现循环队
列 . 如果用户的应用程序中设有循环
队列 , 则必须为它设定一个最大队列
长度 ; 若用户无法预估所用队列的
长度 , 则宜用链队列 .
Status InitQueue (SqQueue &Q) {
  // 构造一个空队列 Q
  Q.base = (QElemType *) malloc
        (MAXQSIZE *sizeof (QElemType));
   if (!Q.base) exit (OVERFLOW);
                          // 存储分配失败
   Q.front = Q.rear = 0;
    return OK;
}
Status EnQueue (SqQueue &Q, QElemType
e) { // 插入元素 e 为 Q 的新的队尾元素
  if ((Q.rear+1) % MAXQSIZE == Q.front)
      return ERROR; // 队列满
  Q.base[Q.rear] = e;
   Q.rear = (Q.rear+1) % MAXQSIZE;
   return OK;
}
Status DeQueue (SqQueue &Q, QElemType &e)
{ // 若队列不空,则删除 Q 的队头元素,
    // 用 e 返回其值,并返回 OK; 否则返回 ERROR
    if (Q.front == Q.rear) return ERROR; // 队空
    e = Q.base[Q.front];
    Q.front = (Q.front+1) % MAXQSIZE;
    return OK;
}
例 . 写出以下程序段的输出结果(队列中的
元素
         类型 QElemType 为 char )。
Void main( ){
  Queue Q; InitQueue(Q);
  Char x=‘e’, y=‘c’;
  EnQueue(Q, ‘h’); EnQueue(Q, ‘r’);
  EnQueue(Q, y);
  DeQueue(Q, x); EnQueue(Q, x);
  DeQueue(Q, x); EnQueue(Q, ‘a’);
  While ( !QueueEmpty(Q) ){
         DeQueue(Q, y); Printf(y);
  }
  Printf(x);
}
括号匹配的检验
1 )凡出现左括弧,则进栈;
2 )凡出现右括弧,首先检查栈是否空
   若栈空,则表明该“ 右括弧” 多余,
   否则和栈顶元素比较,
   若相匹配,则“ 左括弧出栈” ,
   否则表明不匹配。
3 )表达式检验结束时,
   若栈空,则表明表达式中匹配正确
,
   否则表明“ 左括弧” 有余。
括号匹配的检验
Status check( ) { // 对于输入的任意一个字符串 , 检
验括号是否配对
SqStack s;
SElemType ch[80], *p,e; //c h 存放字符串

InitStack(s); // 初始化栈成功
Gets(ch); // 输入字符串
p = ch; //p 指向字符串的首字符

... // 输入字符串中括号的检验
括号匹配的检验
while(*p) // 没有到串尾
Switch(*p){
case ‘(’, ‘[’ , ‘{’ :
           Push(s,*p++); break; // 左括号入栈且 p++

case ‘)’, ‘]’ , ‘}’ :
           if (!StackEmpty(s)) { // 若栈不空
               Pop(s,e); // 弹出栈顶元素
           if (!(e==‘(’&& *p ==‘)’ ||
       e==‘[’&& *p ==‘]’|| e==‘{’&& *p ==‘} ’) // 出现
了 3 种匹配情况之外的情况
括号匹配的检验
{ printf(“ 左右括号不匹配 n”);
    return ERROR;
}
p++;
}
else {// 栈空
       printf( “ 缺乏左括号 n”);
       return ERROR;
} //end if
default: p++; // 其它字符不处理,指针向后移
} //end switch
括号匹配的检验
if (StackEmpty(s)) // 字符串结束时栈空
  printf(“ 括号匹配 n”);
else printf(“ 缺少右括号 n”);
}//end check

第三章 栈和队列(新)

  • 1.
    通常称,栈和队列是限定插入和 删除 只能在表的 “端点 ” 进行的线性表。 线性表 栈 队列 Insert(L, i, x) Insert(S, n+1, x) Insert(Q, n+1, x) 1≤i≤n+1 Delete(L, i) Delete(S, n) Delete(Q, 1) 1≤i≤n 栈和队列是两种常用的数据类型
  • 2.
    第三章 栈和队列 3.1栈 (stack) 3.2 栈的应用举例 3.4 队列 (Queue)
  • 3.
    学习提要: 1. 掌握栈和队列这两种抽象数据类型的特 点, 并能在相应的应用问题中正确选用它们。 2. 熟练掌握栈类型的两种实现方法,即两 种存 储结构表示时的基本操作实现算法,特别 应 注意栈满和栈空的条件以及它们的描述方 法。 3. 熟练掌握循环队列和链队列的基本操作 实现
  • 4.
    §3.1 栈( stack) 3.1.1 栈的类型定义 3.1.2 栈的表示和实现
  • 5.
    3.1.1 栈的类型定义 栈的定义和特点 定义:限定仅在表尾进行插入或删 除操作的线性表,表尾 — 栈顶,表头 — 栈底,不含元素的空表称空栈。 进栈 出栈 栈 ... an 顶 ……... 栈 s=(a1,a2,……,an) a2 栈底 a1 特点:先进后出( FILO )或后进先 出( LIFO )
  • 6.
     栈的类型定义 ADT Stack{ 数据对象: D = { ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 数据关系: R1 = { <ai-1, ai >| ai-1, ai∈D, i=2,...,n } 约定 an 端为栈顶, a1 端为栈底。 基本操作: } ADT Stack
  • 7.
    InitStack(&S) DestroyStack(&S) StackLength(S) StackEmpty(s) GetTop(S, &e) ClearStack(&S) Push(&S, e) Pop(&S, &e) StackTravers(S, visit())
  • 8.
    3.1.2 栈的表示和实现 顺序栈 类似于线性表的顺序映象实 现,指向表尾的指针可以作为栈 顶指针。 //----- 栈的顺序存储表示 ----- #define STACK_INIT_SIZE 100; // 存储空间初始分配量 #define STACKINCREMENT 10;// 存储空间分配增量 typedef struct { SElemType *base; // 栈底指针 SElemType *top; // 栈顶指针 int stacksize; // 栈的当前可使用的最大容量 } SqStack;
  • 9.
    实现:一维数组 s[M] 栈满 栈空 top 5 top F 5 5 4 top E 4 4 3 top D 3 3 top 2 2 top C 2 top B 1 1 top B 1 top top A 0 base 0 top A 0 base base 出栈 空栈 进栈 栈底指针 b a s e , 始 设数组维数为 M 终指向栈底位置; to p =b a s e , 栈空,此时出栈, 栈顶指针 to p , 其初 则下溢( und e rflo w) 值指向栈底,始终 在栈顶元素的下一 to p =M, 栈满,此时入栈,则 个位置 上溢( o ve rflo w)
  • 10.
    Status InitStack (SqStack&S) {// 构造一个空栈 S S.base=(SElemType*)malloc(STACK_INIT_SIZ E *sizeof(SElemType)); if (!S.base) exit (OVERFLOW); // 存储分配失败 S.top = S.base; S.stacksize = STACK_INIT_SIZE; return OK; }
  • 11.
    Status Push (SqStack&S, SElemType e) { if (S.top - S.base >= S.stacksize) {// 栈满,追加存储空 间 S.base = (SElemType *) realloc ( S.base, (S.stacksize + STACKINCREMENT) * sizeof (SElemType)); if (!S.base) exit (OVERFLOW); // 存储分配失 败 S.top = S.base + S.stacksize; S.stacksize += STACKINCREMENT; } *S.top++ = e;
  • 12.
    Status Pop (SqStack&S, SElemType &e) { // 若栈不空,则删除 S 的栈顶元素, // 用 e 返回其值,并返回 OK ; // 否则返回 ERROR if (S.top == S.base) return ERROR; e = *--S.top; return OK; }
  • 13.
    链栈 栈的链式存储结构。栈顶指针 就是链表的头指针。 栈顶指针 an an-1 a1 ∧ 注意 : 链栈 中指针的方 向
  • 14.
     入栈操 作 top top 栈底 p x …... ^ p->next=top ; top=p  出栈操 作 q top 栈底 top …... ^ q=top ; top=top->next; //q 返回了出栈的元素
  • 15.
    §3.2 栈的应用 3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 行编辑程序问题 3.2.4 迷宫求解 3.2.5 表达式求值
  • 16.
    3.2.1 数制转换 十进制 N和其他 d 进制数的转换 原理 : N=( N div d )*d + N mod d 其中: div 为整除运算, mod 为 求余运算
  • 17.
    例如: (1348)10=(2504)8 ,其运算过程如 下: N N div 8 N mod 输 8 计 算 1348 出 168 4 顺 顺 序 168 21 0 序 21 2 5 top top 2 2 0 top 2 5 5 top 0 0 top 4 4 0 4 4
  • 18.
    void conversion( ){ initstack(S); // 构造空栈 scanf (“%d”,N); while(N){ push(S , N%8); N=N/8; } while(! Stackempty(s)){ pop(S,e); printf(“%d”,e); } }//conversion
  • 19.
    3.3.2 括号匹配的检验 假设在表达式中 ([]())或[([ ][ ])] 等为正确的格式, [( ])或([( ))或 (() ] ) 均为不正确的格式。 则 检验括号是否匹配的方法可用 “ 期待的急迫程度 ” 这个概念来描述。
  • 20.
    例如:考虑下列括号序列: [ ( [ ] [ ] ) ] 1 2 34 5 6 7 8 分析可能出现的不匹配的情况 : 到来的右括弧 并非是所 “ 期待 ” 的; 直到结束,也没有到来所 “ 期待 ” 的括弧。
  • 21.
    算法的设计思想: 1 )凡出现左括弧,则进栈; 2 )凡出现右括弧,首先检查栈是否空 若栈空,则表明该“ 右括弧” 多余, 否则和栈顶元素比较, 若相匹配,则“ 左括弧出栈” , 否则表明不匹配。 3 )表达式检验结束时, 若栈空,则表明表达式中匹配正确, 否则表明“ 左括弧” 有余。
  • 22.
    3.2.3 行编辑程序问题 不恰 如何实现? 当! “ 每接受一个字符即存入存储器 ” ? 合理的作法是: 设立一个输入缓冲区,用以接 受用户输入的一行字符,然后逐行 存入用户数据区,并假设 “ #” 为退 格符, “ @” 为退行符。
  • 23.
    假设从终端接受了这样两行字符: whli##ilr#e( s#*s) outcha@putchar(*s=#++); 则实际有效的是下列两行: while (*s) putchar(*s++);
  • 24.
    whli##ilr#e ( s#*s) # # # r r i i l l l l l i i h h h h h w w w w w
  • 25.
    whli##ilr#e ( s#*s) EOF # ) ) s s s s * * ( ( ( ( e e e e l l l l i i i i h h h h w w w w
  • 26.
    Void LineEdit( ){ //利用字符栈 S, 从终端接收一行并传送至调用过程的数据 区 InitStack(S); // 构造空栈 ch = getchar(); // 从终端接受一个字符 While(ch!= EOF){ //EOF 为全文结束符 ... // 对输入的字符的判断与处理 ClearStack(S); // 重置 S 为空栈 if (ch != EOF) ch = getchar();} DestroyStack(S); } //LineEdid
  • 27.
    while (ch !=EOF) { //EOF 为全文结束符 while (ch != EOF && ch != ‘n’) { // ‘ n ’ 为换行符 switch (ch) { case '#' : Pop(S, c); break; case '@': ClearStack(S); break;// 重置 S 为空栈 default : Push(S, ch); break; } ch = getchar(); // 从终端接收下一个字符 } 将从栈底到栈顶的字符传送至调用过程的 数据区;
  • 28.
    3.2.4 迷宫求解 通常用的是“ 穷举求解” 的方法 # # # # # # # # # # # →↓ # $ $ $ # # # ↓ # $ $ $ # # # ↓ ←$ $ # # # # ↓ # # # # # # →→↓ # # # # # →→↓ # # # # # # # ↓ # # # # →→→Θ # # # # # # # # # # #
  • 29.
    求迷宫路径算法的基本思想是 : 若当前位置“ 可通 ”,则纳入 路径,继续前进 ; 若当前位置“ 不可通 ” ,则后 退,换方向继续探索 ; 若四周“ 均无通路 ” ,则将当前 位 置从路径中删除出去。
  • 30.
    “ 下一个位置 ”指 “ 当前位置 ” 四周 “ 东南西北 ” 四个方向上相临的通道块 。 北 西 东 南 当前位置
  • 31.
    根据算法思想 , 为了保证在任何 位置上都能沿原路退回, 需要用一个后 进先出的结构来保存从入口到当前位置 的路径 . 假设以栈 S 记录 “ 当前路径 ” ,则 栈顶中存放的是 “ 当前路径上最后一个 通道块 ” 。由此,“ 纳入路径 ” 的操作即 为 “ 当前位置入栈 ” ;“ 从当前路径上删 除前一通道块 ” 的操作为 “ 出栈 ” 。
  • 32.
    算法描述 设定当前位置的初值为入口位置 do { 若当前位置可通 则 { 将当前位置插入栈顶 ; // 纳入路径 若该位置是出口位置 , 则结束 ; // 求得路径存 放在栈中 否则切换当前位置的东相邻方块为新的当前 位置 ; } 出口 东 当前位置 当前位置 当前位置
  • 33.
    算法描述 否则 //不通 若栈不空且栈顶位置尚有其他方向未探索 , 则设定新的当前位置为沿顺时针方向旋转找 的栈顶位置的下一相邻块 ; 栈顶位 当前位 置 置
  • 34.
    算法描述 若栈不空但栈顶位置的四周均不可通 则 { 删去栈顶位置; 若栈不空 , 则重新测试新的栈顶位 置, 直到找到一个可通的相邻块或出栈 至栈空 ; } }while( 栈不空 ); 当前位 置
  • 35.
    算法描述 说明 : 当前位置可通, 指未曾走到过的通道 块 , 即要求该通道块既不在当前路径上 , 也 不是曾经纳入过路径的通道块 . a 起点 a-b-c-f-g ( 简单路径 ) b d c a-b-c-d-e-c-f-g ( 非简单路径 f ) 若 b 已经纳入过路径 , 则在当 e 前位置为 f 时 , 将不作为可通 终点 g 位置考虑 , 否则 , 将形成死循 环.
  • 36.
    通道块的数据描述 Type struct { intord; // 通道块在路径上的 “ 序号 ” PosType seat; // 通道块在迷宫中的 “ 坐标 ” int di; // 从此通道块走向下一通道块的 “ 方 向” } SElemType; // 栈的元素类型
  • 37.
    迷宫问题算法 Status MazePath(MazeType maze,PosTypestart, PosType end){ // 若迷宫 maze 中存在从 start 到 end 的通道,则求得一 条存放在栈中,并返回 true ;否则返回 false InitStack(S); curpos = start; // 设定当前位置为 “ 入口位置 ” curstep = 1; // 探索第一步 do { ... } while (!StackEmpty(s)); return (false); } //MazePath
  • 38.
    迷宫问题算法 do { if (Pass(curpos)) { // 当前位置可通 FootPrint(curpos); // 留下足迹 e = ( curstep,curpos,1); Push(S,e); // 入栈,加入足迹 if (curpos == end) return (ture); // 到达出口 curpos = NextPos(curpos,1); // 下一个位置为当前位置的东邻 curstep++; // 探索下一个位置 }// if ...
  • 39.
    迷宫问题算法 else { //当前位置不能通过 If ( !StackEmpty(s)) { Pop(S,e) ; // 出栈,弹出当前的栈顶位置至 e while(e.di == 4 && !StackEmpty(s)) { MarkPrint(e.seat); Pop(S,e); // 若该位置无方向可寻,留 下不能通过的标记,并退回一步 } //while if (e.di < 4) { // 表示仍有方向可寻 e.di++; Push(S,e); // 换下一方向试探 curpos = NextPos(e.seat, e.di); } //end if }//end else
  • 40.
  • 41.
    3.2.5 表达式求值 限于二元运算符的表达式定义 : Exp = S1 OP S2 操作数 : 变量、常量、表达式 运算符 : 算术运算符、关系运算符、 逻辑运算符 界限符:括号、结束符
  • 42.
    算法思想: 设置两个工作栈, OPTR 存运算符 , OPND 存操作数及运算结果。算法运算 思想: 1. 首先置 OPND 栈为空, “ #” 为运算符 栈底元素。 2. 依次读入表达式中的每个字符,若是 操作数进 OPND 栈 , 若是运算符则和 OPTR 栈顶元素比较优先权后作相应操 作,直至整个表达式求值完毕。
  • 43.
    例: 3 *( 7 – 2 ) # C C C C C C C C - 2 ( 7-2 5 7 * 3*5 15 3 # OPND 栈 OPTR 栈
  • 44.
    例: 3 *( 7 – 2 ) OPTR 栈 OPND 栈 输入 操作 1 # 3*(7–2)# PUSH( OPND, ‘3’ ) 2 # 3 *(7–2)# PUSH( OPTR, ‘*’ ) 3 #* 3 (7–2)# PUSH( OPTR, ‘(’ ) 4 #*( 3 7–2)# PUSH( OPND, ‘7’ ) 5 #*( 3 7 –2)# PUSH( OPTR, ‘–’ ) 6 # * (– 3 7 2)# PHSH( OPND, ‘2’ ) 7 # * (– 3 7 2 )# operate( ‘7’,’-’,’2’ ) 8 #*( 3 5 )# POP( OPTR ) 9 #* 3 5 # operate( ‘3’, ‘*’, ‘5’ ) 10 # 15 # return GetTop( OPND ) “(” < “*” , 因此“ ( ” 入操作符栈 OPTR
  • 45.
    例: 3 *( 7 – 2 ) OPTR 栈 OPND 栈 输入 操作 1 # 3*(7–2)# PUSH( OPND, ‘3’ ) 2 # 3 *(7–2)# PUSH( OPTR, ‘*’ ) 3 #* 3 (7–2)# PUSH( OPTR, ‘(’ ) 4 #*( 3 7–2)# PUSH( OPND, ‘7’ ) 5 #*( 3 7 –2)# PUSH( OPTR, ‘–’ ) 6 # * (– 3 7 2)# PHSH( OPND, ‘2’ ) 7 # * (– 3 7 2 )# operate( ‘7’,’-’,’2’ ) 8 #*( 3 5 )# POP( OPTR ) 9 #* 3 5 # operate( ‘3’, ‘*’, ‘5’ ) 10 # 15 # return GetTop( OPND ) “-” >“ )” , 因此执行 7-2 操作,同时 OPTR 栈弹出“ -” , OPND 栈弹出“ 2” 和
  • 46.
    例: 3 *( 7 – 2 ) OPTR 栈 OPND 栈 输入 操作 1 # 3*(7–2)# PUSH( OPND, ‘3’ ) 2 # 3 *(7–2)# PUSH( OPTR, ‘*’ ) 3 #* 3 (7–2)# PUSH( OPTR, ‘(’ ) 4 #*( 3 7–2)# PUSH( OPND, ‘7’ ) 5 #*( 3 7 –2)# PUSH( OPTR, ‘–’ ) 6 # * (– 3 7 2)# PHSH( OPND, ‘2’ ) 7 # * (– 3 7 2 )# operate( ‘7’,’-’,’2’ ) 8 #*( 3 5 )# POP( OPTR ) 9 #* 3 5 # operate( ‘3’, ‘*’, ‘5’ ) 10 # 15 # return GetTop( OPND ) “)” =“(” , 操作符的优先权相等,弹出“ (”, 脱括号并接收下一字符 .
  • 47.
    OperandType EvaluateExpression() { // 设 OPTR 和 OPND 分别为运算符栈和运算数栈, OP 为运算符集合。 InitStack (OPTR); Push (OPTR, '#'); initStack (OPND); c = getchar(); while (c!= '#' || GetTop(OPTR)!= '#') { if (!In(c, OP)) { Push((OPND, c); c = getchar(); } // 不是运算符则进栈 else …… O P 为运算符集合,若 c 不 是运算符,则加入到运算 } // while 数栈中 return GetTop(OPND); } // EvaluateExpression
  • 48.
    switch ( precede(GetTop(OPTR),c) { case '<': // 栈顶元素优先权低 Push(OPTR, c); c = getchar(); break; case '=': // 脱括号并接收下一字符 Pop(OPTR, x); c = getchar(); break; case '> ': // 退栈并将运算结果入栈 Pop(OPTR, theta); Pop(OPND, b); Pop(OPND, a); Push(OPND, Operate(a, theta, b)); break; } // switch
  • 49.
    算法演示 ( 3+5 )* 2- 12 + 6 / 3 #
  • 50.
    第三章作业 3.1 设将整数 1、 2 、 3 、 4 依次进栈,但只要 出栈时栈非空,则可将出栈操作按任何次序夹 入其中,请回答下列问题: ( 1 )若入栈次序为 push(1) , pop() , push(2 ), push(3) , pop() , pop( ) , push(4) , pop( ) ,则出栈的数字序 列为什么? 3.2 写出检验括号匹配的算法。
  • 51.
  • 52.
    3.4.1 队列的类型定义 队列是限定只能在表的一端进行插入, 在表的另一端进行删除的线性表。 出 a1 a2 a3…………………….an 入队 队 front rear 队列 Q=(a1,a2,……,an) 队尾 (rear)—— 允许插入的一端 队头 (front)—— 允许删除的一端 队列特点:先进先出 ( F IF O )
  • 53.
    队列的类型定义 ADT Queue { 数据对象: D = {ai | ai∈ ElemSet, i=1,2,...,n, n≥0} 数据关系: R1 = { <a i-1,ai > | ai-1, ai ∈ D, i=2,...,n} 约定其中 a1 端为队列头 , an 端为队列尾 基本操作: } ADT Queue
  • 54.
    队列的基本操作: InitQueue(&Q) DestroyQueue(&Q) QueueEmpty(Q) QueueLength(Q) GetHead(Q, &e) ClearQueue(&Q) EnQueue(&Q, e) DeQueue(&Q, &e) QueueTravers(Q, visit())
  • 55.
    3.4.2 链队列-队列的链式表示和 实现 typedef struct QNode{// 结点类型 QElemType data ; struct QNode *next ; }QNode, *QueuePtr; typedef struct{ // 链队列类型 QueuePtr front ; // 队头指针 QueuePtr rear ; // 队尾指针 } LinkQueue;
  • 56.
    Q.front a1 … an ∧ Q.rear Q.front 空队 ∧ Q.rear 列
  • 57.
    空 队 ^ Q.rear -> next=p front rear p Q.rear=p x 入队 x ^ front rear p y 入队 x y ^ front rear p x 出队 x y ^ front rear y 出队 ^ p= Q.front -> next Q.front -> next = p -> next front ear r
  • 58.
    Status InitQueue (LinkQueue&Q) { // 构造一个空队列 Q Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode)); if (!Q.front) exit (OVERFLOW); // 存储分配失败 Q.front->next = NULL; return OK; }
  • 59.
    Status EnQueue (LinkQueue&Q, QElemType e) { // 插入元素 e 为 Q 的新的队尾元素 p = (QueuePtr) malloc (sizeof (QNode)); if (!p) exit (OVERFLOW); // 存储分配失败 p->data = e; p->next = NULL; Q.rear->next = p; Q.rear = p; return OK; }
  • 60.
    EnQueue p ... ∧ e ∧ Q.front Q.rear Q.rear p->data = e; p->next = NULL; Q.rear->next = p; Q.rear = p;
  • 61.
    Status DeQueue (LinkQueue&Q, QElemType &e) { // 若队列不空,则删除 Q 的队头元素, // 用 e 返回其值,并返回 OK ;否则返回 ERROR if (Q.front == Q.rear) return ERROR; p = Q.front->next; e = p->data; Q.front->next = p->next; if (Q.rear == p) Q.rear = Q.front; free (p); return OK;
  • 62.
    DeQueue p e ... ∧ Q.front Q.rear p = Q.front->next; e = p->data; Q.front->next = p->next; if (Q.rear == p) Q.rear = Q.front; free (p);
  • 63.
    3.4.3 循环队列-队列的顺序表示和实 现 #define MAXQSIZE100 // 最大队列长度 typedef struct { QElemType *base; // 动态分配存储空间 int front; // 头指针,若队列不空, // 指向队列头元素 int rear; // 尾指针,若队列不空,指向 // 队列尾元素 的下一个位置 } SqQueue;
  • 64.
    实现:用一维数组实现 sq[M] rear 5 5 5 J6 5 4 4 4 J5 4 rear rear J4 3 3 3 3 rear 2 J3 2 front J3 2 front 2 rear front J2 1 J2 1 front 1 1 rear=0 rear 0 front J1 0 front J1 0 0 front=0 J4,J5,J6 入 空队列 J1,J1,J3 入队 J1,J2,J3 出队 队 存在问题: 当 front=0,rear=M 时再有元素入队发生溢出 — — 真 溢出 当 front≠0,rear=M 时再有元素入队发生溢出 — — 假
  • 65.
    解决方案 1 队首固定,每次出队剩余元素向下移 动 , 避免发生假溢出 . Rear Rear J6 J6 J5 J5 Rear J6 J4 J4 J5 J3 J3 J4 front J2 front front J3 方法可行 , 但将花费太多的时间
  • 66.
    解决方案 2 循环队列  基本思想:把队列设想成环形,让 sq[0] 接在 sq[M-1] 之后,若 rear+1==M, 则令 rear=0; M-1 J6 Rear M-1 J6 ... ... ... ... 2 J5 2 J5 1 J4 Front 1 J4 Front 0 0 J7 Rear rear+1 = M, 于是令 rear = 0, “J7” 入队 , 有 sq[rear] = J7. 能够避免假溢出 .
  • 67.
    解决方案 2 循环队列 实现:利用 “ 模 ” 运算,实现若 rear+1==M, 则 rear=0 的设定 ; 入队: sq[rear]=x; rear=(rear+1)%M; 出队: x=sq[front]; front=(front+1)%M; 队满、队空判定条件
  • 68.
    front 队空:fro nt==re a r rear 队满: fro nt==re a r 4 5 0 3 2 1 rear J6 J5 4 5 0 3 J4,J5 ,J6 出 队 J6 2 1 J4 J7,J8 ,J9 入 J5 4 5 0 J7 front 队 3 2 1 J8 J4 初始状态 front J9 rear 队满队空如何区分判定 ?
  • 69.
    解决方案: 1. 另外设一个标志位以区别队空、队满 2. 少用一个元素空间: 队空: front==rear 队满: (rear+1)%M==front front rear J6 J5 4 5 0 J7 4 5 0 3 3 J4 2 1 J8 2 1 front rear 3. 使用一个计数器记录队列中元素的总数 当 count = Sq.length 时为队满 .
  • 70.
    分析可见 , 在C 语言中不能用 动态分配的一维数组来实现循环队 列 . 如果用户的应用程序中设有循环 队列 , 则必须为它设定一个最大队列 长度 ; 若用户无法预估所用队列的 长度 , 则宜用链队列 .
  • 71.
    Status InitQueue (SqQueue&Q) { // 构造一个空队列 Q Q.base = (QElemType *) malloc (MAXQSIZE *sizeof (QElemType)); if (!Q.base) exit (OVERFLOW); // 存储分配失败 Q.front = Q.rear = 0; return OK; }
  • 72.
    Status EnQueue (SqQueue&Q, QElemType e) { // 插入元素 e 为 Q 的新的队尾元素 if ((Q.rear+1) % MAXQSIZE == Q.front) return ERROR; // 队列满 Q.base[Q.rear] = e; Q.rear = (Q.rear+1) % MAXQSIZE; return OK; }
  • 73.
    Status DeQueue (SqQueue&Q, QElemType &e) { // 若队列不空,则删除 Q 的队头元素, // 用 e 返回其值,并返回 OK; 否则返回 ERROR if (Q.front == Q.rear) return ERROR; // 队空 e = Q.base[Q.front]; Q.front = (Q.front+1) % MAXQSIZE; return OK; }
  • 74.
    例 . 写出以下程序段的输出结果(队列中的 元素 类型 QElemType 为 char )。 Void main( ){ Queue Q; InitQueue(Q); Char x=‘e’, y=‘c’; EnQueue(Q, ‘h’); EnQueue(Q, ‘r’); EnQueue(Q, y); DeQueue(Q, x); EnQueue(Q, x); DeQueue(Q, x); EnQueue(Q, ‘a’); While ( !QueueEmpty(Q) ){ DeQueue(Q, y); Printf(y); } Printf(x); }
  • 75.
    括号匹配的检验 1 )凡出现左括弧,则进栈; 2 )凡出现右括弧,首先检查栈是否空 若栈空,则表明该“ 右括弧” 多余, 否则和栈顶元素比较, 若相匹配,则“ 左括弧出栈” , 否则表明不匹配。 3 )表达式检验结束时, 若栈空,则表明表达式中匹配正确 , 否则表明“ 左括弧” 有余。
  • 76.
    括号匹配的检验 Status check( ){ // 对于输入的任意一个字符串 , 检 验括号是否配对 SqStack s; SElemType ch[80], *p,e; //c h 存放字符串 InitStack(s); // 初始化栈成功 Gets(ch); // 输入字符串 p = ch; //p 指向字符串的首字符 ... // 输入字符串中括号的检验
  • 77.
    括号匹配的检验 while(*p) // 没有到串尾 Switch(*p){ case‘(’, ‘[’ , ‘{’ : Push(s,*p++); break; // 左括号入栈且 p++ case ‘)’, ‘]’ , ‘}’ : if (!StackEmpty(s)) { // 若栈不空 Pop(s,e); // 弹出栈顶元素 if (!(e==‘(’&& *p ==‘)’ || e==‘[’&& *p ==‘]’|| e==‘{’&& *p ==‘} ’) // 出现 了 3 种匹配情况之外的情况
  • 78.
    括号匹配的检验 { printf(“ 左右括号不匹配n”); return ERROR; } p++; } else {// 栈空 printf( “ 缺乏左括号 n”); return ERROR; } //end if default: p++; // 其它字符不处理,指针向后移 } //end switch
  • 79.
    括号匹配的检验 if (StackEmpty(s)) //字符串结束时栈空 printf(“ 括号匹配 n”); else printf(“ 缺少右括号 n”); }//end check