第三章 栈和队列【数据结构与算法】【精致版】

简介: 第三章 栈和队列【数据结构与算法】【精致版】

前言

2023-11-2 22:12:24

以下内容源自《【数据结构与算法】【精致版】》

仅供学习交流使用

第 3 章 栈和队列

第2章介绍了线性表的概念,从数据结构上看,栈和队列也是线性表,只不过是两种特殊的 线性表。栈只允许在表的一端进行插入或删除操作,而队列只允许在表的一端进行插入操作、在另一端进行删除操作。因而,栈和队列也可以被称为操作受限的线性表。从数据类型的角度来看,栈与队列是与线性表不同的抽象数据结构。

3.1 应用实例

应用实例一 迷宫求解问题

这是实验心理学中的一个经典问题,心理学家用一个无顶盖的大盒子做成“迷宫”从然后把一只老鼠从入口处赶进迷宫。迷宫中设置了很多隔断,对老鼠的前进方向构成多处障碍,在迷宫的唯一出口处放置了一块奶酪,吸引老鼠寻找正确的通路以到达出口。

可以用一个mxn的方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计

一个程序,对任意设定的迷宫,求出一条从入口到出口的通路,或得到没有通路的结论。

计算机解迷宫时,通常是采用“穷举求解”的方法,即从入口出发,沿某方向向前探索,若能 走通则继续往前走;否则沿原路返回,换一个方向再继续探索,直至所有可能的通路都探索到为止。为了保证在任何位置上都能沿原路退回,显然要用具有后进先出特性的栈来保存从入口到当前位置的路径。

应用实例二“马”踏棋盘问题

将棋子“马”随机地放在国际象棋8x8棋盘Board[8][8]的某个方格中,“马”按走棋规则进 行移动,要求每个方格只进入一次,走遍棋盘上所有的64个方格。编写(非)递归程序,求出“马” 的行走路线,并按求出的行走路线将数字1,2,…,64依次填入一个8x8的方阵并输出。

求解时可采用栈的数据结构,即将“马”的行走顺序压人栈中。每次在多个可走的位置中选 择其中一个进行试探,其余未曾试探过的可走位置必须用适当的结构妥善管理,以备试探失败时 的“回溯”(悔棋)使用。

以上实例的分析与实现将在3.4节详细介绍。

3.2栈

3.2.1 栈的概念及运算

栈(stack)是一种只允许在一端进行插人和删除操作的线性表,它是一
种操作受限的线性表

在表中允许进行插入和删除的一端称为“栈顶”(top),另一端称为“栈底”(bottom)。

栈的插入操作通常称为“入栈”或“进栈”(push),

而栈的删除操作则称为“出栈”或“退栈”(pop)。

当栈中无数据元素时,称为“空栈”。

栈具有后进先出的特性,因此又称为“后进先出”(Last In First Out,LIFO)表

栈的示意图

栈的基本运算有以下几种。
① InitStack(S) 初始化:初始化一个新的栈。
② Empy(S )栈的非空判断:若栈S不空,则返回TRUE;否则,返回FALSE。
③ Push(S.x) 入楼:在栈S的顶部插入元素x,若栈满,则返回FALSE;否则,返回TRUE
④ Pop(S) 出栈:若栈S不空,则返回栈顶元素,并从栈顶中删除该元素;否则,返回空元素  
⑤ GetTop(S) 取栈顶元素:若栈S不空,则返回栈顶元素;否则返回空元素NULL。
⑥ SetEmpty(S) 置栈空操作:置栈S为空栈。

栈是一种特殊的线性表,因此栈可采用顺序存储结构存储,也可以使用链式存储结构存储。

3.2.2栈的顺序存储结构

1. 顺序栈
利用顺序存储的方式实现的栈称为“顺序栈”。类似于顺序表的定义,栈中的数据元素用一个
预设的足够长度的一维数组来实现:ElemType elem[MAXSIZE]。栈底位置可以设置在数组任
一个端点,而栈顶是随着插人和删除而变化的。用int top来作为栈顶的指针,指明当前栈顶的    位置,同时将elem和top封装在一个结构中。顺序栈的类型描述如下。
#define MAXSIZE<栈最大元素数>
typedef struct{ 
  ElemType elem[MAXSIZE];
  int top;
}SeqStack;

定义一个指向顺序栈的指针:

SeqStack *s;

常将0下标端设为栈底,这样空栈时校顶指针s->top=-1;入栈时,栈顶指针加1,即s->top++;出栈时,栈顶指针减1,即s->top–。

1-顺序栈.h
#include<stdio.h> 
#include<stdlib.h> 
//在使用的使用命名 
//typedef int ElemType;
//定义顺序栈 
#define MAXSIZE 10
typedef int  ElemType; 
typedef struct{ 
  ElemType elem[MAXSIZE];
  int top;
}SeqStack;
//(1)置空栈
//首先建立栈空间,然后初始化栈顶指针。
SeqStack * InitStack(){
  SeqStack *s;
  s=(SeqStack * ) malloc(sizeof( SeqStack));  
  s->top=-1;
  return s;
}
//(2)判空栈
int Empty(SeqStack *s){
  if(s->top==-1) return 1;  //代表空 
  else return 0;
}
//(3)入栈
int Push(SeqStack *s, ElemType x){
  if(s->top==MAXSIZE-1) return 0;//栈满不能入栈,否则将造成“上溢” 
  else {s->top++;
  s->elem[s->top]=x;
  return 1;
  } 
}
//(4)出栈
int Pop( SeqStack *s, ElemType *x){
  if(Empty(s)) return 0;  //栈空不能出栈
  else { 
    *x=s->elem[s->top];//栈顶元素存入*x,返回
    s->top--;
    return 1;
  }
}
//(5)取栈顶元素
ElemType GetTop(SeqStack *s){
  if(Empty(s)) return 0;//栈空
  else return (s->elem[s->top]);
}

1-顺序栈测试.c

typedef int ElemType; 
#include "1-顺序栈.h" 
int main(){
  SeqStack *s=InitStack();
  printf("%d",Empty(s));//1 
  int x1=1;
  Push(s,x1); 
  int x2=2;
  Push(s,x2);
  printf("%d",Empty(s));//0
  int x3;
  Pop(s,&x3);
  printf("%d",x3);//2
  int t=GetTop(s);
  printf("%d",t);//1
}
2. 多栈共享邻接空间

栈的共享中最常见的是两栈共享

假设两个栈共享一维数组elem[MAXSIZE],则可以利用栈的“栈底位置不变,栈顶位置动态变化”特性,两个栈底分别为-1和MAXSIZE,而它们的栈顶都往中间方向延伸。因此,只要整个数组elem[MAXSIZE]未被满,无论哪个栈的人栈都不会发生上溢。

两栈共享的数据结构可定义如下。

typedef struct{
  ElemType elem[ MAXSIZE];
  int lefttop;//左栈栈顶位置指示器
  int righttop;//右栈栈顶位置指示器
} Dupsqstack;

两个栈共享邻接空间的示意图如图3-4所示。左栈入栈时,栈顶指针加1,右栈人栈时,栈顶指针减1。由于两个栈顶均可向中间伸展,互补余缺,因此使得每个栈的最大空间均大于MAXSIZE/2。

为了识别左右栈,必须另外设定标志:

char status;
status ='L';  //左栈
status ='R';//右栈

在进行栈操作时,需指定栈号status=L’为左栈,status='R’为右栈;判断栈满的条件如下:

s->lefttop+1=s->righttop;
2-共享栈.c

勘误:
P63共享栈的初始化,应该是二级指针或者返回一级指针
P64入栈的右栈进栈有误,应为righttop

#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
//定义顺序栈 
#define MAXSIZE 10
typedef int  ElemType; 
typedef struct{
  ElemType elem[MAXSIZE];
  int lefttop;//左栈栈顶位置指示器
  int righttop;//右栈栈顶位置指示器
} Dupsqstack;
//(1)初始化操作
//错误;初始化不了 
//s的初始化,并不能传递到实参
//需要使用二级指针 
//int InitDupStack(Dupsqstack *s){
//  //创建两个共享邻接空间的空栈由指针s指出
//  if ((s=(Dupsqstack * )malloc(sizeof(Dupsqstack)))==NULL)  
//    return FALSE;
//  s->lefttop=-1;
//  s->righttop=MAXSIZE;
//  return TRUE;
//}
//修改 
int InitDupStack(Dupsqstack **s){
  //创建两个共享邻接空间的空栈由指针s指出
  if ((*s=(Dupsqstack * )malloc(sizeof(Dupsqstack)))==NULL)  
    return FALSE;
  (*s)->lefttop=-1;
  (*s)->righttop=MAXSIZE;
  return TRUE;
}
//(2)入栈操作
int PushDupSrack(Dupsqstack *s,char status, ElemType x){
  //把数据元素x。压入左栈或右栈
  if(s->lefttop+1==s->righttop)return FALSE;//栈满
  if(status=='L') s->elem[++s->lefttop]=x;//左栈进栈
  if(status=='R') s->elem[--s->righttop]=x;//右栈进栈
  else return FALSE;//参数错误
  return TRUE;
}
//(3)出栈操作
ElemType PopDupStack(Dupsqstack * s,char status){
  //从左楼(satus='L')或右栈(status='R')退出栈顶元素
  if(status=='L'){    
    if(s->lefttop<0) return 999;//左栈为空
    else return (s->elem[s->lefttop--]);//左栈出栈
  }else if(status=='R'){  
    if(s->righttop>MAXSIZE-1) return 999;//右栈为空
    else return (s->elem[s->righttop++]);//右栈出栈
  }else return 999;//参数错误
}
// 去栈顶元素
ElemType GetDupsqTop(Dupsqstack * s,char status){
  if(status=='L'){    
    if(s->lefttop<0) {return 999;}//左栈为空
    else return (s->elem[s->lefttop]);//取左栈顶元素 
  }else if(status=='R'){  
    if(s->righttop>MAXSIZE-1) return 999;//右栈为空
    else return (s->elem[s->righttop]);//取右栈顶元素 
  }
}
int main(){
  Dupsqstack *d;
  InitDupStack(&d);
  int l0=1;
  int r0=2;
  PushDupSrack(d,'L',l0);
  PushDupSrack(d,'R',r0);
  int l=GetDupsqTop(d,'L');
  int r=GetDupsqTop(d,'R');
  printf("%d\n",l);//1
  printf("%d\n",r);//2
}

3.2.3栈的链式存储结构

栈也可以采用链式存储结构表示,这种结构简称为“链栈”。

在一个链栈中,栈底就是结表的最后一个结点,而栈顶总是链表的第一个结点采用带头结点的单链表实现栈。因为栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针top就作为栈顶指针,top始终指向当前栈顶元素前面的头结点,即top->next为栈顶元素,当top->next==NULL,则代表栈空

1)链栈定义
//链栈的C语言定义如下。
typedef int DataType;
typedef struct Stacknode{
  DataType data;
  struct Stacknode * next;
}slStacktype;
2)链栈操作
3-链栈.h
#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
//链栈的C语言定义如下。
typedef int DataType;
typedef struct Stacknode{
  DataType data;
  struct Stacknode * next;
}slStacktype;
// 初始化
slStacktype* Init(){
  slStacktype *p;
  if((p=(slStacktype * )malloc(sizeof( slStacktype)))==NULL) 
    return NULL;
  return p;
} 
//(1)入栈操作
//将元素x压入链栈top中
int PushLstack(slStacktype * top, DataType x){  
  slStacktype *p;
  //申请一个结点
  if((p=(slStacktype * )malloc(sizeof( slStacktype)))==NULL) 
    return FALSE;
  p->data=x;
  p->next=top->next;
  top->next=p;
  return TRUE;
}
//(2)出栈操作
//从链栈top中删除栈顶元素
DataType PopLstack(slStacktype * top){
  slStacktype * p;
  DataType x;
  if(top->next==NULL){//空栈  
    printf("此栈为空!");  
    return;
  }
  p=top->next;
  top->next=p->next;  
  x=p->data;
  free(p);
  return x;
}
//取栈顶元素
DataType GetLsPop(slStacktype * top){
  if(top->next==NULL){//空栈  
    printf("此栈为空!");  
    return;
  }
  DataType x=top->next->data;
  return x;
}

3-链栈.c

#include"3-链栈.h"
int main(){
  slStacktype *sl=Init();
  int x=1;
  PushLstack(sl,x);
  int y=GetLsPop(sl);
  printf("%d",y);//1 
  int z=PopLstack(sl);
  printf("%d",z);//1
}
(3)多个链栈的操作

可将多个单链栈的栈顶指针放在一个一维数组中统一管理。

设一维数组top[M]:

slStacktype* top[M]

其中top[0] ,top[1],…,top[i],…,top[M-1]指向M个不同的链栈,分别是M个链栈的 栈顶指针,这样只需确定链栈号i,然后以top[i]为栈顶指针进行栈操作,即可实现各种操价。多个链栈示意图如图3-6所示。

多个链栈的基本操作
4-多个链栈.c
#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
#define M 10 
//链栈的C语言定义如下。
typedef int DataType;
typedef struct Stacknode{
  DataType data;
  struct Stacknode * next;
}slStacktype;
// 初始化
void Init(slStacktype * top[M],int i){
  if((top[i]=(slStacktype * )malloc(sizeof( slStacktype)))==NULL) 
    printf("初始化出错");
    return;
} 
//①入栈操作
//将元素x压人链栈top[i]中
int PushDupLs(slStacktype * top[M],int i, DataType x){
  Init(top,i);
  slStacktype * p;
  //申请一个结点
  if((p=(slStacktype *)malloc(sizeof(slStacktype)))==NULL){
    return FALSE;   
  }
  p->data=x;
  p->next=top[i]->next;
  top[i]->next=p;
  return TRUE;
}
//②出栈操作
//从链栈top[i]中删除栈顶元素
DataType PopDupLs(slStacktype * top[M],int i){
  slStacktype *p;
  DataType x;
  if(top[i]->next==NULL){//空栈
    printf("此栈为空!");
    return;
  }
  p=top[i]->next;
  top[i]->next=p->next;
  x=p->data;
  free(p);
  return x;
}
//取栈顶元素
DataType GetTopDupLs(slStacktype * top[M],int i){
  slStacktype *p;
  DataType x;
  if(top[i]->next==NULL){//空栈
    printf("此栈为空!");
    return;
  }
  p=top[i]->next; 
  x=p->data;
  free(p);
  return x;
}
int main(){
  slStacktype * top[M];
  int x1=1;
  PushDupLs(top,1,x1);
  int x11=GetTopDupLs(top,1);
  printf("%d",x11);//1
  int x2=2;
  PushDupLs(top,2,x2);
  int x22=GetTopDupLs(top,2);
  printf("%d",x22);//2
}

在上面的两个算法中,当指定栈号i(0<=i<=M-1)时,则只对第i个链栈操作,不会影响其他链栈。

3.2.4栈的应用

0. 括号匹配

判断都是括号所构成的字符串是否括号都是一一对应的

5-括号匹配.c
typedef char ElemType; 
#include "1-顺序栈.h"
int Match(char x,char y){
  if(x=='('){
    if(y==')'){
      return 1;
    }
  }
  if(x=='['){
    if(y==']'){
      return 1;
    }
  }
  if(x=='{'){
    if(y=='}'){
      return 1;
    }
  }
  return 0;
}
void BracketMatch(char *str){ 
  SeqStack* _S=InitStack();
  SeqStack S=*_S;
  int i;
  char ch;
  for(i=0;str[i]!='\0';i++){
    switch(str[i]){
      case'(':
      case'[':
      case'{': 
        Push(&S,str[i]);
        break;  
      case ')':
      case ']':
      case '}': 
        if(Empty(&S)){
          printf("右括号多余!");  
          return;
        }else{
          ch=GetTop(&S);
          if(Match(ch,str[i])){
            Pop(&S,&ch);
          }else{
            printf("对应的左右括号不同类!");  
            return;
          }
        } 
    }
  }
  if(Empty(&S))
    printf("括号匹配!");
  else
    printf("左括号多余!");
}
int main(){
  char str[10];
  printf("输入括号字符串\n"); 
  gets(str);
  printf("%s",str);
  BracketMatch(str);
}

运行结果如下

1. 算术表达式求值(上机)

表达式求值是程序设计语言编译中的一个最基本问题,它的实现方法是栈的一个典型的应用实例,

在计算机中,任何一个表达式都是由操作数(operand)、运算符(operalor)和界限符(delimiter)组成的。其中操作数可以是常数,也可以是变量或常量的标识符;运算符可以是算术运算符、关系运算符和逻辑运算值;界限符为左右括号和标识表达式结束的结束符。在本节中,仅讨论简单算术表达式的求值问题。假设在这种表达式中只含加、减、乘、除四则运算,所有的运算对象均为整型常数,表达式的结束符为“#”,即仅含符号+、一、*、/、(、)和#。

算术四则运算的规则如下。

①先乘除、后加减。

②同级运算时先左后右。

③先括号内,后括号外。

3.3 队列

3.3.1队列的概念及其运算

队列(queue)是另一种限定性线性表,它只允许插人在表的一端进行,而删除在表的另一端进行,允许插人的一端叫队尾(rear),而允许删除的一端叫队头(front)。

队列的插人操作通常为“入队”或“进队”,而队列的删除操作则称为“出队”或“退队”。当队列中无数据元素时,所“空队列”。

根据队列的定义可知,队头元素总是最先进队列,也总是最先出队列;队尾元素总最后进队列,因而也是最后出队列。这种表是按照先进先出(First In First Out,FIFO)的原则组织数据的,因此,队列也被称为“先进先出”表。

在队列上进行的基本操作如下。

①队列初始化:InitQutoe(ql
相始条件:队列q不存在,
操作结果:构造了一个空队列:
②人队操作:InQuene(q.x)
初始条件:队列q存在。
操作结果:对已存在的队列q,插人一个元素x到队尾。操作成功,返回值为TRUE,否则返回值为FALSE
③出队操作:OutQueue(q.x)
初始条件:队列g存在且非空。
操作结果:删除队首元素,并返回其值。操作成功,返回值为TRUE,否则返回值为FALSE
④读队头元素:FrontQueue(q,x)
初始条件:队列q存在且非空。
操作结果:读队头元素,并返回其值,队列不变。操作成功,返回值为TRUE,否则返回值为FALSE.
⑤判队空操作:EmptyQueue(q)
初始条件:队列q存在
操作结果:若q为空队列则返回为TRUE,否则返回为FALSE.

队列的顺序存储结构可以 简称为“顺序队列”,另外再设立两个指示器:一个为指向队头元素位置的指示器front,另一个为指向队尾元素位置的指示器rear

C语言中,数组的下标是从0开始的,因此为了算法设计的方便,在此约定:初始化队列时,空队列的front=rear=-1,当插人新的数据元素时,尾指示器rear加1,而当队头元素出队列时,头 指示器front加1。另外还约定,在非空队列中,头指示器front总是指向队列中实际队头元素的 前面一个位置,而尾指示器rear总是指向队尾元素(这样的设置是为了某些运算的方便,并不是唯一的方法)。

顺序队列的类型定义如下

define MAXSIZE<最大元素数>//队列的最大容量
vypedef struct{
  ElemType elem[MAXSIZE];//队列元素的存储空间
  int rear,front;//队尾、队头指针
}SeQueue;

定义一个指向队列的指针变量:SeQueue *sq;

申请一个顺序队列的存储空间:sq=(SeQueue*)malloc(sizeof(SeQueue));

存在“假溢出”的问题

3.3.2循环队列

解决假溢出的方法之一是将队列的数据区elem[0~MAXSIZE-1]看成头、尾相接的循环结构,即规定最后一个单元的后维为第个单元,这样整个数据区就像一个环,我们形象地称之为循环队列

入队时队尾指针加1操作修改为

sq->rear=(sq->rear+1)%MAXSIZE

出队时队尾指针加1操作修改为

sq->rear=(sq->front+1)%MAXSIZE

又产生一个新问题队空和队满的条件相同都为front==rear

解决的方法之一是少用一个元素空间,把如图所示视为队满。

队满的条件是(rear+1)%MAXSIZE==front

就能和队空区别开。

另外一种方法是附设一个存储队列中元素个数的变量,如num,当num == 0时队空,当num==MAXSIZE时队满

还有一种在习题(8)中

下面的循环队列及操作依据少用一个元素空间来实现的。

循环队列的类型定义
#define MAXSIZE 10
//下面的循环以列及操作依据少用个元素空间来实现
//循环队列的类型定义及基本运算如下。
typedef int ElemType;
typedef struct{ 
  ElemType elem [MAXSIZE];//队列的存储区
  //队头队尾指针
  int front, rear;  
}CSeQueue;//循环队列
循环队列的基本运算
6-循环队列.c
#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
#define MAXSIZE 10
//下面的循环以列及操作依据少用个元素空间来实现
//循环队列的类型定义及基本运算如下。
typedef int ElemType;
typedef struct{ 
  ElemType elem [MAXSIZE];//队列的存储区
  //队头队尾指针
  int front, rear;  
}CSeQueue;//循环队列
//(1)置空队
CSeQueue * IniseQueue(){
  CSeQueue * q=(CSeQueue *)malloc(sizeof(CSeQueue));
  q->front=q->rear=MAXSIZE-1;
  return q;
}
//(2)入队
int InSeQueue( CSeQueue * q,ElemType x){
  if((q->rear+1)%MAXSIZE==q->front){//队满不能人队
    printf("队满");
    return FALSE;
  }else{
    q->rear=(q->rear+1)%MAXSIZE;
    q->elem[q->rear]=x;
    return TRUE;//人队完成
  }
}
//(3)出队
int OutSeQueue( CSeQueue *q , ElemType *x){
  if(q->front==q->rear){
    printf("队空");
    return FALSE;
  }else{
    q->front=(q->front+1)%MAXSIZE;
    *x=q->elem[q->front];//读出队头元素
    return TRUE;//出队完成
  }
}
//(4) 判断队空
int EmptySeQueue(CSeQueue *q){
  if(q->front==q->rear) return TRUE;
  else return FALSE;
}
int main(){
  CSeQueue *cs=IniseQueue();
  int x=1;
  InSeQueue(cs,x);
  printf("%d",EmptySeQueue(cs));//0
  int x0;
  OutSeQueue(cs,&x0);
  printf("%d",x0);//1
  printf("%d",EmptySeQueue(cs));//1
}

3.3.3 链队列

链式存储的队列称为链队列

可以采用带头结点的单链表表示队列。

头指针始终指向头结点,尾指针指向当前最后一个元素结点

//链队列的数据类型描述如下。
typedef struct node{ 
  DataType data;
  struct node * next;
}QNode;
//链队列结点的类型
typedef struct{  
  QNode * front;
  QNode * rear;
} LQueue;//将头尾指针封装在一起的链队列
链队列的基本运算
7-链队列.c
#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
#define MAXSIZE 10
typedef int  DataType; 
//链队列的数据类型描述如下。
typedef struct node{ 
  DataType data;
  struct node * next;
}QNode;
//链队列结点的类型
typedef struct{  
  QNode * front;
  QNode * rear;
} LQueue;//将头尾指针封装在一起的链队列
//(1)创建一个带头结点的空队
LQueue * Init_LQueue(){
  LQueue *q; QNode*p;
  q=(LQueue*)malloc( sizeof(LQueue));//申请头尾指针结点  
  p=(QNode*)malloc( sizeof(QNode));//申请链队列头结点  
  p->next=NULL;
  q->front=q->rear=p;
  return q;
}
//(2)入队
void InLQueue(LQueue *q , DataType x){ 
  QNode *p;
  p=(QNode*)malloc(sizeof(QNode));//申请新结点  
  p->data=x;
  p->next=NULL; 
  q->rear->next=p;
  q->rear=p;
}
//(3)判队空
int Empty_LQueue(LQueue *q){
  if(q->front==q->rear) return 1;//代表空 
  else return 0;
}
//(4)出队
int Out_LQueue(LQueue *q, DataType *x){
  QNode *p;
  if(Empty_LQueue(q)){
    printf("队空");
    return FALSE;
  }
  else{ 
    p=q->front->next;
    q->front->next=p->next;
    *x=p->data;//队头元素放x中
    free(p);
    if(q->front->next==NULL)//只有一个元素时,出队后队空,修改队尾指针
      q->rear=q->front;
    return TRUE;
  }
}
int main(){
  LQueue *lq=Init_LQueue();
  printf("%d",Empty_LQueue(lq));//1
  int x=1;
  InLQueue(lq,x);
  printf("%d",Empty_LQueue(lq));//0
  int x0;
  Out_LQueue(lq,&x0);
  printf("%d",x0);//1 
  printf("%d",Empty_LQueue(lq));//0
}

3.4 实例分析与实现

应用实例一 迷宫求解

应用实例二 马踏棋盘

应用实例三 计算器

3.5 算法总结 ——递归与分治 算法

实验

应用实例一 迷宫求解

应用实例二 马踏棋盘

应用实例三 计算器

习题3

1.单项选择题

(1)(2010考研真题)若元素a,b,e,d,e,f依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次进行退栈操作,则不可能得到的出栈序列是____。 D

A. d,c,e,b,f,a

B. c,b,d,a,e,f

C. b.c,a,e,f,d

D. a,f,e,d,c,b

(2)若某堆栈的输人序列为1,2,3,n-l,n,输出序列的第1个元素为n,则第i个输出元素为____。 A

A. n-i+1

B. n-1

C. i

D.哪个元素都有可能

(3)以数组Q[0…m-1]存放循环队列中的元素,变量rear和qulen分别指示循环队列中队尾元素的实际位置和当前队列中元素的个数,队列第一个元素的实际位置是___。 D

A. rear-qulen

B. rear-qulen+m

C. m-qulen

D. (1+(rear-qulentm))mod m

(4)设输人元素为1,2,3,A,B,输人次序为123AB,元素经过栈后到达输出序列,当所有元素均到达输出序列后,序列____是不可能的。 C

A. BA321

B. A3B21

C. B32A1

D. AB321

(5)(2012年考研真题)已知操作符包括“+”“一”“”“/”“(”和“)”。将中缀表达式a+b-a((c+d)/e-)+g转换为等价的后缀表达式ab+acd+e/f-*-g+时,用栈来存放暂时还不能确定运算次序的操作符。若栈初始时为空,则转换过程中同时保存在栈中的操作符的最大个数是___。 A

A. 5

B. 7

C. 8

D.11

2.综合题

(2) 回文是指正读反读均相同的字符序列,如“abba”和“abdba"均是回文,但“good"不是回文。试写一个算法判定给定的字符串是否为回文。

2.c

#include<string.h>
typedef char ElemType; 
#include "1-顺序栈.h" 
int Match(char x,char y){
  if(x==y){   
    return 1;
  }
  return 0;
}
void BracketMatch(char *str){ 
  SeqStack* _S=InitStack();
  SeqStack S=*_S;
  int i;
  char ch;
    int len=strlen(str); 
    int mid=len/2;
  for(i=0;str[i]!='\0';i++){
    if(i==mid&&len%2==1){
      continue;
    }
    if(i<mid){      
      Push(&S,str[i]); 
    }else{
      if(Empty(&S)){
        printf("右字符多余!");  
        return;
      }else{
        ch=GetTop(&S);
        if(Match(ch,str[i])){
          Pop(&S,&ch);
        }else{
          printf("对应的左右字符不同!");  
          return;
        }
      } 
    }
  }
  if(Empty(&S))
    printf("回文!");
  else
    printf("左字符多余!");
}
int main(){
  char str[10];
  printf("输入字符串\n");
  gets(str);
  printf("%s",str);
  BracketMatch(str);
}

运行结果如下

(6) 假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素结点,试编写相应的置空队、判队空、入队和出队算法。

6.c

#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
#define MAXSIZE 10
typedef int  DataType; 
//链队列的数据类型描述如下。
typedef struct node{ 
  DataType data;
  struct node * next;
}QNode;
//链队列结点的类型
typedef struct{  
  QNode * rear;
} XQueue;//将尾指针封装在一起的链队列
//(1)创建一个带头结点的空队
XQueue * Init_XQueue(){
  XQueue *q; QNode*p;
  q=(XQueue*)malloc(sizeof(XQueue));//申请尾指针结点  
  p=(QNode*)malloc(sizeof(QNode));//申请循环队列头结点  
  p->next=p;
  q->rear=p;
  return q;
}
//(2)入队
void InXQueue(XQueue *q , DataType x){ 
  QNode *p;
  p=(QNode*)malloc(sizeof(QNode));//申请新结点  
  p->data=x;
  p->next=q->rear->next; 
  q->rear->next=p;
  q->rear=p;
}
//(3)判队空
int Empty_XQueue(XQueue *q){
  if(q->rear->next==q->rear) return 1;//代表空 
  else return 0;
}
//(4)出队
int Out_XQueue(XQueue *q, DataType *x){
  QNode *p;
  if(Empty_XQueue(q)){
    printf("队空");
    return FALSE;
  }
  else{ 
    p=q->rear->next->next;//q->rear->next相当于q->front 
    q->rear->next->next=p->next;
    *x=p->data;//队头元素放x中
    free(p);
    if(q->rear->next->next==NULL)//只有一个元素时,出队后队空,修改队尾指针
      q->rear=q->rear->next;
    return TRUE;
  }
}
int main(){
  XQueue *lq=Init_XQueue();
  printf("%d",Empty_XQueue(lq));//1
  int x1=1;
  InXQueue(lq,x1);
  printf("%d",Empty_XQueue(lq));//0
  int x2=2;
  InXQueue(lq,x2);
  int y1;
  Out_XQueue(lq,&y1);
  printf("%d",x1);//1 
  int y2;
  Out_XQueue(lq,&y2);
  printf("%d",y2);//2
  printf("%d",Empty_XQueue(lq));//0
}

(8) 在循环队列中,可以设置一个标志域tag,以区分当尾指针和头指针相等时,队列状态是“空”还是“满”(tag的值为0表示“空”,tag的值为1表示“满”),编写此结构相应的队列初始化、入队、出队算法。

8.c

#include<stdio.h>
#include<stdlib.h> 
#define FALSE 0
#define TRUE 1
#define MAXSIZE 2
//下面的循环以列及操作依据少用个元素空间来实现
//循环队列的类型定义及基本运算如下。
typedef int ElemType;
typedef struct{ 
  ElemType elem [MAXSIZE];//队列的存储区
  //队头队尾指针
  int front, rear;
  int flag; 
}CSeQueue;//循环队列
//先声明一下,就不会有Warning 
int EmptySeQueue(CSeQueue *q);
int ManSeQueue(CSeQueue *q);
//(1)置空队
CSeQueue * IniseQueue(){
  CSeQueue * q=(CSeQueue *)malloc(sizeof(CSeQueue));
  q->front=q->rear=MAXSIZE-1;
  q->flag=0;
  return q;
}
//(2)入队
int InSeQueue( CSeQueue * q,ElemType x){
  if(ManSeQueue(q)){//队满不能人队
    printf("队满");
    return FALSE;
  }else{
    q->rear=(q->rear+1)%MAXSIZE;
    q->elem[q->rear]=x;
    q->flag=1;
    return TRUE;//人队完成
  }
}
//(3)出队
int OutSeQueue( CSeQueue *q , ElemType *x){
  if(EmptySeQueue(q)){
    printf("队空");
    return FALSE;
  }else{
    q->front=(q->front+1)%MAXSIZE;
    *x=q->elem[q->front];//读出队头元素
    q->flag=0;
    return TRUE;//出队完成
  }
}
//(4) 判断队空
int EmptySeQueue(CSeQueue *q){
  if(q->front==q->rear&&q->flag==0) return TRUE;
  else return FALSE;
} 
//(5)判断队满
int ManSeQueue(CSeQueue *q){
  if(q->front==q->rear&&q->flag==1) return TRUE;
  else return FALSE;
}
int main(){
  CSeQueue *cs=IniseQueue();
  printf("%d",EmptySeQueue(cs));//1
  int x1=1;
  InSeQueue(cs,x1);
  int x2=2;
  InSeQueue(cs,x2);
  printf("%d",ManSeQueue(cs));//1
  int y1;
  OutSeQueue(cs,&y1);
  printf("%d",y1);//1
  printf("%d",EmptySeQueue(cs));//0
  int y2;
  OutSeQueue(cs,&y2);
  printf("%d",y2);//2
  printf("%d",EmptySeQueue(cs));//1
}

最后

2023-11-2 23:24:48

我们都有光明的未来

不必感谢我,也不必记得我

祝大家考研上岸

祝大家工作顺利

祝大家得偿所愿

祝大家如愿以偿

点赞收藏关注哦

相关文章
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
207 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
35 1
|
27天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
53 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
49 0
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
93 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
43 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍