数据结构 栈和队列

目录

基本概念

特点

抽象数据类型定义

顺序表示

存储方式

特点

操作

链式表示

存储方式

操作

递归

定义

常用的递归方法

分治法求解

函数调用过程

多个函数构成嵌套调用

优缺点

案例

进制转换

括号匹配的检验

表达式求值

队列

基本概念

特点

抽象数据类型定义

顺序表示

存储方式

操作

链式表示

存储方式

操作

案例

舞伴问题

练习题


基本概念

栈:限定仅在表尾进行插入和删除操作的线性表。

栈顶:表尾端。

栈底:表头端。

入栈:插入元素到栈顶。

出栈:从栈顶删除最后一个元素。

特点

        后进先出

抽象数据类型定义

ADT Stack{ 

     数据对象:D={ a_{i} | a_{i} ∈ ElemSet,i=1,2,...,n,n≥0 }

     数据关系:R1={ <a_{i-1},a_{i}> |  ,a_{i-1},a_{i} ∈ D,i=2,3,...,n }

                       约定 a_{n} 端为栈顶,a_{1} 端为栈底

     基本操作:

        InitStack(&S)

            操作结果:构造一个空栈 S

        DestroyStack(&S)

            初始条件:栈 S 已存在

            操作结果:栈 S 被销毁

        ClearStack(&S)

            初始条件:栈 S 已存在

            操作结果:将 S 清为空栈

        StackEmpty(S)

            初始条件:栈 S 已存在

            操作结果:若栈 S 为空栈,则返回 true,否则返回 false

       StackLength(L)

            初始条件:栈 S 已存在

            操作结果:返回 S 的元素个数,即栈的长度

       GetTop(S,&e)

            初始条件:栈 S 已存在且非空

            操作结果:用 e 返回 S 的栈顶元素

       Push(&S,e)

            初始条件:栈 S 已存在

            操作结果:插入元素 e 为新的栈顶元素

       Pop(&S,&e)

            初始条件:栈 S 已存在且非空

            操作结果:删除 S 的栈顶元素,并用 e 返回其值

       StackTraverse(S,visit())

            初始条件:栈 S 已存在且非空

            操作结果:从栈底到栈顶依次对 S 的每个数据元素调用函数 visit() 。一旦 visit() 失败,则操作失败

}ADT Stack

顺序表示

存储方式

       同一般线性表的顺序存储结构完全相同,利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。

       设 top 指针,指示栈顶元素在顺序栈中的位置。为了操作方便,通常 top 指示真正的栈顶元素之上的下标地址。

       设 base 指针,指示栈底元素在顺序栈中的位置。

       用 stacksize 表示栈可使用的最大容量。

       空栈:top==base

       栈满:top-base==stacksize

栈满时的处理方法:

1. 报错,返回操作系统

2. 分配更大的空间,作为栈的存储空间,将原栈的内容移入新栈

     下溢:栈已经满,又要压入元素

     上溢:栈已经空,又要弹出元素

     上溢是一种错误,使问题的处理无法进行;而下溢一般认为是一种结束条件,即问题处理结束。

       当两个栈共享一存储区时,栈利用一维数组 stac(1,n) 表示,两栈顶指针为 top[1] 和 top[2] ,当栈 1 空时,top[1] 为   0  ,栈 2 为空时,top[2] 为   n+1  ,栈满时为   top[1]+1=top[2]  (两个栈的栈顶在栈空间的某一位置相遇)。

特点

使用数组作为顺序栈存储方式的特点:简单、方便,但易产生溢出(数组大小固定)

操作

顺序栈类型定义

typedef struct{
    SElemType *base;//栈底指针
    SElemType *top;//栈顶指针
    int stacksize;//栈可用最大容量
}SqStack;

初始化

1. 申请一个数组空间,用于存放数据元素,若申请失败,则初始化失败,退出函数

2. 栈底指针 base 指向该数组,此时 base 指向数组首元素

3. 初始化栈顶指针 top,使其与栈底指针 base 指向相同的地址

4. 初始化栈的可用最大容量 stacksize

bool InitStack(SqStack &S){//构造一个空栈
    S.base=new SElemType[MAXSIZE];
    //S.base=(SElemType*)malloc(MAXSIZE*sizeof(SElemType));
    if(!S.base)
        return false;//存储分配失败
    S.top=S.base;//栈顶指针等于栈底指针
    S.stacksize=MAXSIZE;
    return true;
}

判断栈是否为空

判断栈是否为空就是判断栈顶指针 top 与栈底指针 base 是否相等 

bool StackEmpty(SqStack S){若栈为空,返回true,反之返回false
    if(S.top==S.base)
       return true;
    else
       return false;
}

求顺序栈的长度

length=top-base

int StackLength(SqStack S){
    return S.top-S.base;
}

清空顺序栈

若栈存在,直接改变栈顶指针 top 的位置,将其指向栈底指针 base 所指位置

void ClearStack(SqStack& S) {
	if (S.base)
		S.top == S.base;
}

销毁顺序栈

       若栈存在,释放数组空间,将栈可用最大容量 stacksize 置为 0,并改变栈顶指针 top 和栈底指针 base 所指向的位置,将其指向置为空

void DestroyStack(SqStack& S) {
	if (S.base) {
		delete S.base;
		S.stacksize = 0;
		S.base = S.top = NULL;
	}
}

入栈

1. 判断是否栈满,若栈满,则无法将新元素入栈

2. 将新元素存入目前 top 指针所指向的位置,然后令 top 指针指向下一个位置

bool Push(SqStack& S, SElemType e) {
	if (S.top - S.base == S.stacksize)//栈满
		return false;
	*S.top = e;
	S.top++;
	return true;
}

出栈

1. 判断栈是否为空,若栈空,则无法进行出栈操作;若栈不空,删除栈顶元素

2. 获取栈顶元素 e

3. 栈顶指针减 1

bool Pop(SqStack &S,SElemType &e){
    //若栈不空,则删除S的栈顶元素,用e返回其值
    if(S.top==S.base)//if(StackEmpty(S))
        return false;
    --S.top;
    e=*S.top;
    return true;
}

完整代码

#include<iostream>
using namespace std;
#define MAXSIZE 100
typedef int SElemType;
//顺序栈类型定义
typedef struct {
	SElemType* base;
	SElemType* top;
	int stacksize;
}SqStack;
//初始化
bool InitStack(SqStack& S) {
	S.base = new SElemType[MAXSIZE];
	if (!S.base)
		return false;
	S.top = S.base;
	S.stacksize = MAXSIZE;
	return true;
}
//判断栈是否为空
bool StackEmpty(SqStack S) {
	if (S.top == S.base)
		return true;
	else
		return false;
}
//求顺序栈的长度
int StackLength(SqStack S) {
	return S.top - S.base;
}
//清空顺序栈
void ClearStack(SqStack& S) {
	if (S.base)
		S.top == S.base;
}
//销毁顺序栈
void DestroyStack(SqStack& S) {
	if (S.base) {
		delete S.base;
		S.stacksize = 0;
		S.base = S.top = NULL;
	}
}
//入栈
bool Push(SqStack& S, SElemType e) {
	if (S.top - S.base == S.stacksize)
		return false;
	*S.top = e;
	S.top++;
	return true;
}
//出栈
bool Pop(SqStack& S, SElemType& e) {
	if (S.top == S.base)
		return false;
	S.top--;
	e = *S.top;
	return true;
}
int main() {
	SqStack S;
	InitStack(S);
	for (int i = 0;i < 5;i++) {
		Push(S, i);
		cout << i << "入栈" << endl;
	}
	cout << "栈是否为空?" << endl;
	if (StackEmpty(S))
		cout << "YES" << endl;
	else {
		cout << "NO" << endl;
		int size = StackLength(S);
		cout << "栈中有" << size << "个数据" << endl;
		for (int i = 0;i < size;i++) {
			SElemType e;
			Pop(S, e);
			cout << e << "弹栈" << endl;
		}
	}
	return 0;
}

链式表示

链栈是运算受限的单链表,只能在链表头部进行操作。

存储方式

  • 链表的头指针就是栈顶
  • 不需要头结点
  • 基本不存在栈满的情况
  • 空栈相当于头指针指向空
  • 插入和删除仅在栈顶处执行

操作

链栈类型定义

与链表的类型定义一致

typedef struct StackNode {
	SElemType data;
	struct StackNode* next;
}StackNode,*LinkStack;

初始化

构造一个空栈,栈顶指针置为空

void InitStack(LinkStack &S){
    //构造一个空栈,栈顶指针置为空
    S=NULL;
}

判断链栈是否为空

若栈顶指针指向为空,则说明链栈为空

bool StackEmpty(LinkStack S) {
	if (S == NULL)
		return true;
	else
		return false;
}

入栈

相当于使用头插法将新产生的结点插入链表

void Push(LinkStack &S,SElemType e){
    LinkStack p=new StackNode;//生成新结点p
    p->data=e;
    p->next=S;//将新结点插入到栈顶
    S=p;//修改栈顶指针
}

出栈

相当于删除链表的首元结点,但是需要判断栈是否为空

bool Pop(LinkStack &S,SElemType &e){
    if(S==NULL)
        return false;
    e=S->data;
    LinkStack p=S;
    S=S->next;
    delete p;
    return true;
}

取栈顶元素

若栈非空,直接返回栈顶指针所指结点数据域存储的数据

SElemType GetTop(LinkStack S) {
	if (S != NULL)
		return S->data;
}

完整代码

#include<iostream>
using namespace std;
typedef int SElemType;
//链栈类型定义
typedef struct StackNode {
	SElemType data;
	struct StackNode* next;
}StackNode,*LinkStack;
//初始化
void InitStack(LinkStack& S) {
	S = NULL;
}
//判断链栈是否为空
bool StackEmpty(LinkStack S) {
	if (S == NULL)
		return true;
	else
		return false;
}
//入栈
void Push(LinkStack& S, SElemType e) {
	LinkStack p = new StackNode;
	p->data = e;
	p->next = S;
	S = p;
 }
//出栈
bool Pop(LinkStack& S, SElemType& e) {
	if (S == NULL)
		return false;
	e = S->data;
	LinkStack p = S;
	S = S->next;
	delete p;
	return true;
}
//取栈顶元素
SElemType GetTop(LinkStack S) {
	if (S != NULL)
		return S->data;
}
int main() {
	LinkStack S;
	InitStack(S);
	for (int i = 0;i < 5;i++) {
		Push(S, i);
		cout << i << "入栈" << endl;
	}
	cout << "栈是否为空?" << endl;
	if (StackEmpty(S))
		cout << "YES" << endl;
	else {
		cout << "NO" << endl;
		SElemType e;
		cout << "栈顶元素是:" << GetTop(S) << endl;
		cout << "弹出栈中元素" << endl;
		while (Pop(S, e))
			cout << e << " ";
		cout << endl;
	}
	return 0;
}

递归

一个递归算法必须包括终止条件和递归部分。

定义

若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的。

若一个过程直接地或间接地调用自己,则称这个过程是递归过程。

常用的递归方法

  • 递归定义的数学函数
  • 具有递归特性的数据结构
  • 可递归求解的问题

分治法求解

分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题求解

必备条件

  • 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
  • 可以通过上述转化而使问题简化
  • 必须有一个明确的递归出口,或称递归的边界

算法一般形式

void p(参数表){

      if(递归结束条件)

           可直接求解步骤; ——基本项

      else

           p(较小的参数); ——归纳项

}

函数调用过程

调用前,系统完成

  1. 将实参、返回地址等传递给被调用函数
  2. 为被调用函数的局部变量分配存储区
  3. 将控制转移到被调用函数的入口

调用后,系统完成

  1. 保存被调用函数的计算结果
  2. 释放被调用函数的数据区
  3. 依照被调用函数保存的返回地址将控制转移到调用函数

多个函数构成嵌套调用

优缺点

优点

       结构清晰,程序易读

缺点

       每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。

  递归——>非递归

  • 尾递归、单向递归——>循环结构
  • 自己用栈模拟系统的运行时栈

     单向递归—>循环

       单向递归虽然有一处以上的递归调用语句,但各次递归语句的参数只和主调函数有关,相互之间参数无关,并且这些递归调用语句处于算法的最后。

     借助栈改写递归

  • 递归程序在执行时需要系统提供栈来实现
  • 仿造递归算法执行过程中递归工作栈的状态变化可写出相应的非递归程序
  • 改写后的非递归算法与原来的递归算法相比,结构不够清晰,可读性较差,有的需经过一系列的优化

   借助栈改写递归的方法:

  1. 设置一个工作栈存放递归工作记录(包括实参、返回地址及局部变量……)
  2. 进入非递归调用入口(即被调用程序开始处)将调用程序传来的实在参数和返回地址入栈(递归程序不可以作为主程序,因而可认为初始是被某个调用程序调用)
  3. 进入递归调用入口:当不满足递归结束条件时,逐层递归,将实参、返回地址及局部变量入栈,这一过程可用循环语句实现——模拟递归分解的过程
  4. 递归结束条件满足,将到达递归出口的给定常数作为当前的函数值
  5. 返回处理:在栈不空的情况下,反复退出栈顶记录,根据记录中的返回地址进行题意规定的操作,即逐层计算当前函数值,直至栈空为止——模拟递归求值过程

案例

进制转换

问题描述

       十进制整数 N 向其它进制数 d(二、八、十六)的转换

算法思路

        转化法则:除以 d 倒取余

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

div:整除运算 mod:取余运算

        先将每次计算得到的余数进栈,直到商为 0 时停止,输出是,依次取出栈中元素。

括号匹配的检验

问题描述

        假设表达式中允许包含两种括号:圆括号和方括号

        其嵌套的顺序随意,即:

      ([]())[([][])]为正确格式

      [(])为错误格式【数量是正确的,但是不匹配】

      ([()](()])为错误格式【数量不正确】

算法思路

       遇到左括号进栈,遇到右括号时,将此括号与栈顶元素进行匹配,若匹配,将栈顶元素出栈,不匹配则说明括号是不匹配【先入栈的括号后匹配,或入栈的括号先匹配】。

不匹配的情况:

  • 当遇到某一个右括号时,栈已空,说明到目前为止,右括号多余左括号
  • 从栈中弹出的左括号与当前检验的右括号类型不同,说明出现了括号交叉情况
  • 算术表达式输入完毕,但栈中还有没有匹配的左括号,说明左括号多余右括号

表达式求值

问题描述

        该算法是由运算符优先级确定运算顺序的对表达式求值算法——算符优先算法

算法思路

       表达式的组成:

          操作数:常数、变量

          运算符:算术运算符、关系运算符和逻辑运算符

          界限符:左右括弧和表达式结束符

       任何一个算术表达式都由操作数(常数、变量)、算术运算符(+、-、*、/)和界限符(括号、表达式结束符#、虚设的表达式起始符#)组成,后两者统称为算符。

       需设置两个栈:一个是算符栈 OPTR,用于寄存运算符;另一个是操作数栈 OPND,用于寄存运算数和运算结果。

求值过程——自左至右扫描表达式的每一个字符:

   1. 当扫描到的是运算数时,将其压入栈 OPND

   2. 当扫描到的是运算符时,

         a. 若这个运算符比 OPTR 栈顶运算符的优先级高,则入栈 OPTR,继续向后处理

         b. 若这个运算符比 OPTR 栈顶运算符的优先级低,则从 OPND 栈中弹出两个运算数,从栈 OPTR 中弹出栈顶运算符进行运算,并将运算结果压入栈 OPND

     3. 继续处理当前字符,直到遇到结束符为止

队列

基本概念

队列:仅在表尾进行插入操作,在表头进行删除操作的线性表

队头:表头,即 a1 端

队尾:表尾,即 an 端

入队:插入元素到队尾

出队:删除队头元素

特点

       先进先出

抽象数据类型定义

ADT Queue{ 

     数据对象:D={ a_{i} | a_{i} ∈ ElemSet,i=1,2,...,n,n≥0 }

     数据关系:R1={ <a_{i-1},a_{i}> |  ,a_{i-1},a_{i} ∈ D,i=2,3,...,n }

                       约定 a_{n} 端为队列尾,a_{1} 端为队列头

     基本操作:

        InitQueue(&Q)

            操作结果:构造一个空队列 Q

        DestroyQueue(&Q)

            初始条件:队列 Q 已存在

            操作结果:队列 Q 被销毁,不再存在

        ClearQueue(&Q)

            初始条件:队列 Q 已存在

            操作结果:将 Q 清为空队列

        QueueEmpty(Q)

            初始条件:队列 Q 已存在

            操作结果:若 Q 为空队列,则返回 true,否则返回 false

       QueueLength(Q)

            初始条件:队列 Q 已存在

            操作结果:返回 Q 的元素个数,即队列的长度

       GetHead(Q,&e)

            初始条件:Q 为非空队列

            操作结果:用 e 返回 Q 的队头元素

       EnQueue(&Q,e)

            初始条件:队列 Q 已存在

            操作结果:插入元素 e 为 Q 的新的队尾元素

       DeQueue(&Q,&e)

            初始条件:Q 为非空队列

            操作结果:删除 Q 的队头元素,并用 e 返回其值

       QueueTraverse(Q,visit())

            初始条件:Q 已存在且非空

            操作结果:从队头到队尾,依次对 Q 的每个数据元素调用函数 visit() 。一旦 visit() 失败,则操作失败

}ADT Queue

顺序表示

存储方式

用一维数组 base[MAXSIZE] 表示 。

解决假上溢的方法

 1. 将队中元素依次向队头方向移动

         缺点:浪费时间,每移动一次,队中元素都要移动 

 2. 循环队列

         将队空间设想成一个循环的表,即分配给队列的 m 个存储单元可以循环使用,当 rear 为 maxqsize 时, 若队列的开始端空着,又可从头使用空着的空间。当 front 为 maxqsize 时,也一样。

         base[0] 接在 base[MAXQSIZE-1] 之后,若 rear+1==M,则令 rear=0 。

实现方法:利用模运算

插入元素:  Q.base[Q.rear]=x;

       Q.rear=(Q.rear+1)%MAXQSIZE;

删除元素: x=Q.base[Q.front];

       Q.front=(Q.front+1)%MAXQSIZE;

循环队列:循环使用为队列分配的存储空间

队空:front==rear

队满:front==rear

解决方案:

  1. 另设一个标志以区别队空、队满
  2. 另设一个变量,记录元素个数
  3. 少用一个元素空间

循环队列解决队满时判断方法:少用一个元素空间

操作

循环队列类型定义

typedef struct{
   QElemType *base;//动态分配存储空间
   int front;//头指针,若队列不空,指向队列头元素
   int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;

初始化

1. 申请一个数组空间,用于存放数据元素,若申请失败,则初始化失败,退出函数

2.此时队列为空,故尾指针和头指针均置为 0

bool InitQueue(SqQueue &Q){
    Q.base=new QElemType[MAXQSIZE];//分配数组空间
    //Q.base=(QElemType*)malloc(MAXQSIZE*sizeof(QElemType));
    if(!Q.base) 
        return false;//存储分配失败
    Q.front=Q.rear=0;//头指针尾指针置为0,队列为空
    return true;
}

求队长

 length=(Q.rear-Q,front+MAXQSIZE)%MAXQSIZE

int QueueLength(SqQueue Q) {
	return (Q.rear - Q.front + MAXQSIZE) % MAXSIZE;
}

入队

1. 判断是否队满,若队满,则无法将新元素入队

2. 将新元素存入目前 rear 指针所指向的位置,然后令 rear 指针指向下一个位置

   Q.rear=(Q.rear+1)%MAXQSIZE

bool EnQueue(SqQueue &Q,QElemType e){
    if((Q.rear+1)%MAXQSIZE==Q.front)
        return false;//队满
    Q.base[Q.rear]=e;//新元素加入队尾
    Q.rear=(Q.rear+1)%MAXQSIZE;//队尾指针+1
    return true;
}

出队

1. 判断队列是否为空,若队空,则无法进行出栈操作;若队不空,删除队头元素

2. 获取队头元素 e

bool DeQueue(SqQueue &Q,QElemType &e){
    if(Q.front==Q.rear)
        return false;//队空
    e=Q.base[Q.front];//保存队头元素
    Q.front=(Q.front+1)%MAXQSIZE;//队头指针+1
    return true;
}

取队头元素

若队列不空,直接返回队头指针元素的值

QElemType GetHead(SqQueue Q){
    if(Q.front!=Q.rear)//队列不为空
        return Q.base[Q.front];//返回队头指针元素的值,队头指针不变
}

完整代码

#include<iostream>
using namespace std;
#define MAXQSIZE 100
typedef int QElemType;
//循环队列类型定义
typedef struct {
	QElemType* base;
	int front;
	int rear;
}SqQueue;
//初始化
bool InitQueue(SqQueue& Q) {
	Q.base = new QElemType[MAXQSIZE];
	if (!Q.base)
		return false;
	Q.front = Q.rear = 0;
	return true;
}
//求队长
int QueueLength(SqQueue Q) {
	return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
//入队
bool EnQueue(SqQueue& Q, QElemType e) {
	if ((Q.rear + 1) % MAXQSIZE == Q.front)
		return false;
	Q.base[Q.rear] = e;
	Q.rear = (Q.rear + 1) % MAXQSIZE;
	return true;
}
//出队
bool DeQueue(SqQueue& Q, QElemType& e) {
	if (Q.rear == Q.front)
		return false;
	e = Q.base[Q.front];
	Q.front = (Q.front + 1) % MAXQSIZE;
	return true;
}
//取队头元素
QElemType GetHead(SqQueue Q) {
	if (Q.front != Q.rear)
		return Q.base[Q.front];
}
int main() {
	SqQueue Q;
	InitQueue(Q);
	cout << "队长为:" << QueueLength(Q) << endl;
	for (int i = 0;i < 5;i++) {
		cout << i << "入队" << endl;
		EnQueue(Q, i);
	}
	cout << "队长为:" << QueueLength(Q) << endl;
	cout << "队头元素为:" << GetHead(Q) << endl;
	QElemType e;
	while (DeQueue(Q, e))
		cout << e << "出队" << endl;
	cout << endl;
	return 0;
}

链式表示

链队是运算受限的单链表,只能在链表头部和尾部进行操作。

若无法估计所用队列的长度,宜采用链队

存储方式

操作

链队类型定义

与链表的类型定义一致

typedef struct Qnode{
    QElemType data;
    struct Qnode *next;
}Qnode,*QueuePtr;
typedef struct{
    QueuePtr front;//队头指针
    QueuePtr rear;//队尾指针
}LinkQueue;

初始化

       构造一个空队,申请一个空间作为头结点,队头指针和队尾指针均指向头结点,头结点指针域指针的指向为空。

bool InitQueue(LinkQueue &Q){
    Q.front=Q.rear=(QueuePtr)malloc(sizeof(Qnode));
    if(!Q.front)
        return false;
    Q.front->next=NULL;
    return true;
}

 销毁

从队头开始,依次释放链队中的每一个结点

void DestroyQueue(LinkQueue& Q) {
	while (Q.front) {
		QueuePtr p = Q.front->next;
		free(Q.front);
		Q.front = p;
	}
}

入队

相当于使用尾插法将新产生的结点插入链表

bool EnQueue(LinkQueue& Q, QElemType e) {
	QueuePtr p = (QueuePtr)malloc(sizeof(Qnode));
	if (!p)
		return false;
	p->data = e;
	p->next = NULL;
	Q.rear->next = p;
	Q.rear = p;
	return true;
}

出队

相当于删除链表的首元结点,但是需要判断队是否为空

注意:如果头结点的下一个元素本身就是尾结点,即要删除的恰好是尾结点,需修改尾指针,使尾指针和头指针均指向头结点

bool DeQueue(LinkQueue &Q,QElemType &e){
    if(Q.front==Q.rear)
        return false;
    QueuePtr p=Q.front->next;
    e=p->data;
    Q.front->next=p->next;
    if(Q.rear==p)
        Q.rear=Q.front;//如果头结点的下一个元素本身就是尾结点,即要删除的恰好是尾结点,需修改尾指针,使尾指针和头指针均指向头结点
    delete p;
    return true;
}

求链队的队头元素

判断队是否为空,若不为空,返回首元结点的数据域

bool GetHead(LinkQueue Q, QElemType& e) {
	if (Q.front == Q.rear)
		return false;
	e = Q.front->next->data;
	return true;
}

完整代码

#include<iostream>
using namespace std;
#define MAXQSIZE 100
typedef int QElemType;
//链队类型定义
typedef struct Qnode {
	QElemType data;
	struct Qnode* next;
}Qnode,*QueuePtr;
typedef struct {
	QueuePtr front;
	QueuePtr rear;
}LinkQueue;
//初始化
bool InitQueue(LinkQueue& Q) {
	Q.front = Q.rear = (QueuePtr)malloc(sizeof(Qnode));
	if (!Q.front)
		return false;
	Q.front->next = NULL;
	return true;
}
//销毁
void DestroyQueue(LinkQueue& Q) {
	while (Q.front) {
		QueuePtr p = Q.front->next;
		free(Q.front);
		Q.front = p;
	}
}
//入队
bool EnQueue(LinkQueue& Q, QElemType e) {
	QueuePtr p = (QueuePtr)malloc(sizeof(Qnode));
	if (!p)
		return false;
	p->data = e;
	p->next = NULL;
	Q.rear->next = p;
	Q.rear = p;
	return true;
}
//出队
bool DeQueue(LinkQueue& Q, QElemType& e) {
	if (Q.front == Q.rear)
		return false;
	QueuePtr p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;
	if (Q.rear == p)
		Q.rear = Q.front;
	delete p;
	return true;
}
//求链队的队头元素
bool GetHead(LinkQueue Q, QElemType& e) {
	if (Q.front == Q.rear)
		return false;
	e = Q.front->next->data;
	return true;
}
int main() {
	LinkQueue Q;
	InitQueue(Q);
	QElemType e;
	if (GetHead(Q, e))
		cout << e << endl;
	else
		cout << "队列为空" << endl;
	for (int i = 0;i < 5;i++) {
		EnQueue(Q, i);
		cout << i << "入队" << endl;
	}
	if (GetHead(Q, e))
		cout << "队头元素为:" << e << endl;
	else
		cout << "队列为空" << endl;
	QElemType h;
	while (DeQueue(Q, h)) {
		cout << h << "出队" << endl;
	}
	return 0;
}

案例

舞伴问题

问题描述

       假设在舞会上,男士和女士各自排成一队。舞会开始时,依次从男队和女队的队头各出一人配成舞伴。如果两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。

算法思路

  • 首先构造两个队列
  • 依次将队头元素出队配成舞伴
  • 某队为空,则另外一队等待,其是下一舞曲最先获得舞伴的人

练习题

  选择题

1. 对于栈操作数据的原则是(   )。

A. 先进先出    B. 后进先出    C. 后进后出     D. 不分顺序

2. 在作进栈运算时,应先判别栈是否  ),在作退栈运算时应先判别栈是否(    )。当栈中元素为n,作进栈运算时发生上溢,则说明该栈的最大容量为  )

为了增加内存空间的利用率和减少溢出的可能性,由两个栈共享一片连续的内存空间时,应将两栈的 (    )分别设在这片内存空间的两端,这样,  )时,才产生上溢。 

, : A.          B.           C. 上溢        D. 下溢     

    : A. n-1        B. n           C. n+1         D.  n/2

    : A. 长度       B. 深度        C. 栈顶        D. 栈底

    : A. 两个栈的栈顶同时到达栈空间的中心点.

         B. 其中一个栈的栈顶到达栈空间的中心点.

         C. 两个栈的栈顶在栈空间的某一位置相遇.

         D. 两个栈均不空,且一个栈的栈顶到达另一个栈的栈底.

3. 若一个栈的输入序列为1,2,3,,n,输出序列的第一个元素是i,则第j个输出元素是(     )。

 A. i-j-1          B. i-j            C. j-i+1      D. 不确定的

4. 某堆栈的输入序列为a, bc d,下面的四个序列中,不可能是它的输出序列的是(    )。

A. acbd         B. b, cda    C. c, db, a         D. d, cab

5. 输入序列为ABC,可以变为CBA时,经过的栈操作为(   

A. push,pop,push,pop,push,pop        B. push,push,push,pop,pop,pop

    C. push,push,pop,pop,push,pop        D. push,pop,push,push,pop,pop

6. 若一个栈以向量V[1..n]存储,初始栈顶指针topn+1,则下面x进栈的正确操作是(    )

Atop=top+1;  V [top]=x            B.  V [top]=x; top=top+1   

C. top=top-1;  V [top]=x            D.  V [top]=x; top=top-1

7. 若栈采用顺序存储方式存储,现两栈共享空间V[1..m]top[i]代表第i个栈( i =1,2)栈顶,栈1的底在v[1],栈2的底在V[m],则栈满的条件是(    )。

A. |top[2]-top[1]|=0  B. top[1]+1=top[2] C. top[1]+top[2]=m  D. top[1]=top[2]

8. 栈在(    )中应用。

A. 递归调用        B. 子程序调用       C. 表达式求值    D. A,B,C

9. 一个递归算法必须包括(    )。

A. 递归部分   B. 终止条件和递归部分   C. 迭代部分    D.终止条件和迭代部分

10. 执行完下列语句段后,i值为:(   

     int   f(int x)

     { return  ((x>0) ? x* f(x-1):2);}

      int i  ;

      i =f(f(1));

A2        B. 4       C. 8           D. 无限递归

11. 表达式a*(b+c)-d的后缀表达式是(    )

Aabcd*+-     B. abc+*d-    C. abc*+d-     D. -+*abcd

12. 设计一个判别表达式中左,右括号是否配对出现的算法,采用(    )数据结构最佳。

A.线性表的顺序存储结构       B. 队列     C. 线性表的链式存储结构      D.

13. 用不带头结点的单链表存储队列时,其队头指针指向队头结点,其队尾指针指向队尾结点,则在进行删除操作时(     )

A.仅修改队头指针          B. 仅修改队尾指针 

C. 队头、队尾指针都要修改  D. 队头,队尾指针都可能要修改

14. 递归过程或函数调用时,处理参数及返回地址,要用一种称为(    )的数据结构。

A.队列             B.多维数组           C.栈             D. 线性表

15. 假设以数组A[m]存放循环队列的元素,其头尾指针分别为frontrear,则当前队列中的元素个数为(    )。

A(rear-front+m)%m  Brear-front+1  C(front-rear+m)%m   D(rear-front)%m

16. 若用一个大小为6的数组来实现循环队列,且当前rearfront的值分别为03,当从队列中删除一个元素,再加入两个元素后,rearfront的值分别为多少?(  )

A. 1 5         B. 24          C. 42         D. 5

17. 已知输入序列为abcd 经过输出受限的双向队列后能得到的输出序列有(    )。

    A. dacb     B. cadb      C. dbca       D. bdac     E. 以上答案都不对 

18. 最大容量为n的循环队列,队尾指针是rear,队头是front,则队空的条件是      )。

     A. (rear+1) MOD n=front                   B. rear=front                                                          

Crear+1=front                           D. (rear-l) MOD n=front

19. 栈和队列的共同点是(    )。

A. 都是先进先出                        B. 都是先进后出  

C. 只允许在端点处插入和删除元素        D. 没有共同点

20. 栈和队都是(   

A.顺序存储的线性结构       B. 链式存储的非线性结构

C. 限制存取点的线性结构     D. 限制存取点的非线性结构

21. 设栈S和队列Q的初始状态为空,元素e1e2e3e4,e5e6依次通过栈S,一个元素出栈后即进队列Q,若6个元素出队的序列是e2e4e3,e6,e5,e1则栈S的容量至少应该是(    )

A 6          B. 4          C. 3          D. 2

  填空题 

1.栈是_______的线性表,其运算遵循_______的原则。

2_______是限定仅在表尾进行插入或删除操作的线性表。

3. 设有一个空栈,栈顶指针为1000H(十六进制),现有输入序列为12345,经过PUSH,PUSH,POP,PUSH,POP,PUSH,PUSH之后,输出序列是_______,而栈顶指针值是_______H。设栈为顺序栈,每个元素占4个字节。

4. 当两个栈共享一存储区时,栈利用一维数组stack(1,n)表示,两栈顶指针为top[1]top[2],则当栈1空时,top[1]_______,栈2空时 top[2]_______,栈满时为_______

5.两个栈共享空间时栈满的条件_______

6.在作进栈运算时应先判别栈是否_(1)_;在作退栈运算时应先判别栈是否_(2)_;当栈中元素为n个,作进栈运算时发生上溢,则说明该栈的最大容量为_(3)_。为了增加内存空间的利用率和减少溢出的可能性,由两个栈共享一片连续的空间时,应将两栈的_(4)_分别设在内存空间的两端,这样只有当_(5)_时才产生溢出。

7. 多个栈共存时,最好用_______作为存储结构。

8.表达式23+((12*3-2)/4+34*5/7)+108/9的后缀表达式是_______

9. 循环队列的引入,目的是为了克服_______                                 

10. 已知链队列的头尾指针分别是fr,则将值x入队的操作序列是_______

11.区分循环队列的满与空,只有两种方法,它们是____________

12. 设循环队列存放在向量sq.data[0:M]中,则队头指针sq.front在循环意义下的出队操作可表示为_______,若用牺牲一个单元的办法来区分队满和队空(设队尾指针sq.rear,则队满的条件为_______

13.表达式求值是_______应用的一个典型例子。      

  应用与算法设计题

  应用题:

1.1 什么是递归程序?

   2 递归程序的优、缺点是什么?

   3 递归程序在执行时,应借助于什么来完成?

4 递归程序的入口语句、出口语句一般用什么语句实现? 

2. 当过程P递归调用自身时,过程P内部定义的局部变量在P2次调用期间是否占用同一数据区?为什么? 

3. 试推导出当总盘数为nHanoi塔的移动次数。

4. 用栈实现将中缀表达式8-(3+5)*(5-6/2)转换成后缀表达式,画出栈的变化过程图。

5. 在一个算法中需要建立多个堆栈时可以选用下列三种方案之一,试问:这三种方案之

间相比较各有什么优缺点?

1)分别用多个顺序存储空间建立多个独立的堆栈;

2)多个堆栈共享一个顺序存储空间;

3)分别建立多个独立的链接堆栈。

6. 举例说明顺序队的“假溢出”现象,并给出解决方案。

算法设计题:

1. 设有两个栈S1,S2都采用顺序栈方式,并且共享一个存储区[0..maxsize-1],为了尽量利用空间,减少溢出的可能,可采用栈顶相向,迎面增长的存储方式。试设计S1,S2有关入栈和出栈的操作算法。

2. 设从键盘输入一整数的序列:a1, a2, a3,…,an,试编写算法实现:用栈结构存储输入的整数,当ai-1时,将ai进栈;当ai=-1时,输出栈顶整数并出栈。算法应对异常情况(入栈满等)给出相应的信息。

3. 设表达式以字符形式已存入数组E[n]中,‘#’为表达式的结束符,试写出判断表达式中括号(‘(’和‘)’)是否配对的C语言描述算法:EXYX(E); (注:算法中可调用栈操作的基本算法。)

4. 如果允许在循环队列的两端都可以进行插入和删除操作。要求:

1)写出循环队列的类型定义;

2)写出“从队尾删除”和“从队头插入”的算法。

5.线性表中元素存放在向量A1,,n)中,元素是整型数。试写出递归算法求出A中的最大和最小元素。

int FindMaxMin(int a[],int m,int n,int &max,int &min)

{

    if(m>n) return 0;

    if(m==n){max=min=a[m];return 1;}

    FindMaxMin(a,m+1,n,max,min);

    max=a[m]>max?a[m]:max;

    min=a[m]<min?a[m]:min;

    return 1;

}

6. 已知求两个正整数mn的最大公因子的过程用自然语言可以表述为反复执行如下动作:第一步:若n等于零,则返回m;第二步:若m小于n,则mn相互交换;否则,保存m,然后将nm,将保存的m除以n的余数送n 

1)将上述过程用递归函数表达出来(设求x除以y的余数可以用x MOD y 形式表示)。

int GCD(int m,int n)

{

    if(n==0) return m;

    if(m<n){int t=m;m=n;n=t;}

    return GCD(n,m%n);

}

2)写出求解该递归函数的非递归算法。

7.设计算法以求解从集合{1..n}中选取kk<=n)个元素的所有组合。例如,从集合{1..4}中选取2个元素的所有组合的输出结果为:1  21  31  42  3 2  43  4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何hyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值