数据结构学习笔记(3栈和队列)

栈和队列

1.栈

(1)定义

只允许在一端进行插入或删除操作的线性表。

特点:后进先出 Last In First Out

n个不同元素进栈,出栈元素不同排列的个数为 1 n + 1 C 2 n n \frac{1}{n + 1}C_{2n}^{n} n+11C2nn(卡特兰数)。

(2)顺序栈的定义和基本操作
定义
#define MaxSize 10
typedef struct
{
    ElemType data[MaxSize];  //静态数组存放栈中元素
    int top;
}SqStack;

初始化栈时,将初始化栈顶指针赋为-1。

进栈
bool Push(SqStack &S,ElemType x)
{
    if(S.top==MaxSize-1)   //栈满,报错
        return false;
    S.top=S.top+1;
    S.data[S.top]=x; 	   //新元素入栈
    return true;
}
出栈
bool Pop(SqStack &S,ElemType &x)
{
    if(S.top==-1)      //栈空,报错
        return false;
    x=S.data[S.top];   //栈顶元素先出栈
    S.top=S.top-1;     //指针再-1
}
(3)共享栈
定义

两个栈共享同一片空间。

#define MaxSize 10
typedef struct
{
    ElemType data[MaxSize];
    int top0;		//0号栈栈顶指针
    int top1;		//1号栈栈顶指针
}ShStack;
初始化栈操作
void InitStack(ShStack &s)
{
    S.top0=-1;
    S.top1=MaxSize;
}

栈满的条件:top0+1==top1

(4)栈的链式存储结构
typedef struct Linknode
{
    ElemType data;			//数据域
    struct Linknode *next;	//指针域
}*LiStack;

2.队列

(1)定义

只允许在一端进行插入,在另一端删除的线性表。

队列的特点:先进先出(FIFO)

重要术语:队头、队尾、空队列

(2)顺序存储结构

分析思路:分析frontrear指针的指向;确定判空、判满的方法。

初始化
#define MaxSize 10		//定义队列中元素的最大个数
typedef struct
{
    ElemType data[MaxSize];
    int front,rear;	    //队头指针和队尾指针
}SqQueue;  //sequence顺序

初始化时,可将front指向队头元素,rear指向队尾元素的后一个位置(下一个应该插入的位置)。

void InitQueue(SqQueue &Q)
{
    //初始时,队头、队尾指针指向0
    Q.rear=Q.front=0;
}

判断队列是否为空,可用Q.rear==Q.front来判断。

入队

只能从队尾入队(插入)。(循环队列

image-20220116213719080
bool EnQueue(SqQueue &Q,ElemType x)
{
    if()
        return false;
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;	//队尾指针+1取模
    return true;
}
出队
//删除一个队头元素,并用x返回
bool DeQueue(SqQueue &Q,ElemType &x)
{
    if(Q.rear==Q.front)		//判断队空
        return false;
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;
    return true;
}

获得队头元素的值的操作:

//获得队头元素的值,用x返回
bool GetHead(SqQueue &Q,ElemType &x)
{
    if(Q.rear==Q.front)
		return false;    //队空则报错
    x=Q.data[Q.front];
    return true;
}

队列已满的条件:队尾指针的再下一个位置是队头,即(Q.rear+1)%MaxSize==Q.front

队列的元素个数=(rear-front+MaxSize)%MaxSize

对于判空、判满的方法,另有下列2个方案:

方案2:引入一个变量int size作为保存队列的当前长度,当size==MaxSize时,队列满。

方案3:判断队列已满/已空

每次删除操作成功时,都令tag=0;每次插入操作成功时,都令tag=1

逻辑:只有删除操作,才可能导致队空;只有插入操作,才可能导致队满。

队空条件:front==rear&&tag==0

队满条件:front==rear&&tag==1

(3)链式存储结构
typedef struct LinkNode
{
    ElemType data;
    struct LinkNode *next;
}LinkNode;
typedef struct
{
    LinkNode *front,*rear;		//队列的队头和队尾指针
}LinkQueue;
入队

新元素连接到的是队列尾部。

带头结点:

void EnQueue(LinkQueue &Q,ElemType x)
{
    LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;
    Q.rear->next=s;		//新结点插入到rear之后
    Q.rear=s;			//修改表尾指针
}

不带头结点:

void EnQueue(LinkQueue &Q,ElemType x)
{
    LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;
    if(Q.front==NULL)	//修改front和rear的指向
    {
        Q.front=s;
        Q.rear=s;
    }
    else
    {
        Q.rear->next=s;
        Q.rear=s;
    }
}
出队

带头结点:

bool DeQueue(LinkQueue &Q, ElemType &x)
{
	if (Q.front == Q.rear)
		return false;
	LinkNode *p = Q.front->next;	//用变量x返回队头元素
	x = p->data;
	Q.front->next = p->next;		//修改头结点的next指针
	if (Q.rear == p)				//本次是最后一个结点出队
		Q.rear = Q.front;
	free(p);
	return true;
}

不带头结点:则LinkNode *p=Q.front;,即p是队列的队头。

3.栈的应用

(1)栈在括号匹配中的应用
原则

遇到左括号就入栈;遇到右括号,就“消耗”一个左括号。当发现当前扫描到的右括号与栈顶左括号不匹配,则本次扫描失败。

算法实现

需要用到的函数如下:

//初始化栈
void InitStack(SqStack &S);
//判断栈是否为空
bool StackEmpty(SqStack S);
//新元素入栈
bool Push(SqStack &S, char x);
//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x);

代码如下:

#define MaxSize 10
typedef struct
{
	char data[MaxSize];
	int top;
}SqStack;
bool bracketCheck(char str[], int length)
{
	SqStack S;
	InitStack(S);		//初始化一个栈
	for (int i = 0;i < length;i++)
	{
		if (str[i] == '(' || str[i] == '[' || str[i] == '{')
			Push(S, str[i]);	//扫描到左括号,入栈
		else
		{
			if (StackEmpty(S))	//扫描到右括号,且当前栈空
				return false;
			char topElem;
			Pop(S, topElem);	//栈顶元素出栈
			if (str[i] == ')'&&topElem != '(')
				return false;
			if (str[i] == ']'&&topElem != '[')
				return false;
			if (str[i] == '}'&&topElem != '{')
				return false;
		}
	}
	return StackEmpty(S);	//检索完全部括号后,栈空则说明匹配成功
}
(2)栈在表达式求值中的应用
一些表达式的定义

波兰表达式(前缀表达式)、逆波兰表达式(后缀表达式)。

前缀表达式:运算符在两个操作数前面,如+ab,-+abc,-+ab*cd

中缀表达式:运算符在两个操作数之间,如a+b,a+b-c,a+b-c*d

后缀表达式:运算符在两个操作数后面,如ab+,ab+c-(或abc-+),ab+cd*-

中缀表达式转后缀表达式
手算

(1)确定中缀表达式中各个运算符的运算顺序;

(2)选择下一个运算符,按照【左操作数 右操作数 运算符】的方式组合成一个新的操作数;

(3)如果还有运算符没被处理,就继续步骤2。

【注】”左优先”原则:只要左边的运算符能先计算,就优先算左边的。(可保证运算顺序唯一)

机算

从左到右出理各个元素,直到末尾。可能遇到以下三种情况:

(1)遇到操作数:直接加入后缀表达式。

(2)遇到界限符:遇到"(“直接入栈;遇到”)“则依次弹出栈内运算符并加入后缀表达式,直到弹出”(“为止。(注:”("不加入后缀表达式)

(3)遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到"("或栈空则停止。之后再把当前运算符入栈。

用栈实现表达式的计算
后缀表达式的计算

(1)从左到右扫描下一个元素,直到处理完所有元素;

(2)若扫描到操作数则压入栈,并回到(1),否则执行(3);

(3)若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到(1)。

中缀表达式的计算

初始化两个栈,操作数栈运算符栈

若扫描到操作数,压入操作数栈;

若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)。

(3)栈在递归中的应用

函数调用时,需要用一个栈存储调用返回地址、实参、局部变量

4.队列的应用

树的层次遍历、图的广度优先遍历、队伍在操作系统中的应用。

队伍在操作系统中的应用:

多个进程争抢着使用有限的系统资源时,FCFS(First Come First Service,先来先服务)是一种常用的策略。

例:CPU资源的分配,一台打印机打印论文(使用缓冲区,可用“队列”组织打印数据,可缓解主机与打印机速度不匹配的问题)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值