栈和队列存储结构及简单应用

目录

一、栈和队列的基本介绍

1.栈--先进后出

2.队列--先进先出

二、顺序栈队列(c语言)的完整实现

1.代码部分

2.解说部分

三、顺序栈(c语言)的代码实现

四、简单应用

1.四则运算计算器

完整代码实现(c++):

2.括号配对问题


一、栈和队列的基本介绍

1.栈--先进后出

        顺序栈:类似动态线性表

        链式栈:以链表头作为栈尾

2.队列--先进先出

(本质就是特殊的单链表,有头尾两指针)

        链式队列:结点+结点类型的头指针和尾指针

        循环队列:一维数组

 

二、顺序栈队列(c语言)的完整实现

在栈和队列中进行插入删除时,注意:

        没有结点,只有一个结点,有很多个结点,这三种情况

1.代码部分

头文件和具体实现部分:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//包含bool类型


typedef int QDataType;

//节点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QueueNode;

//再在队列中建立2个指针
typedef struct Queue
{
	//指针的类型是指针指向变量的类型,而不是指针变量自身的类型
	QueueNode* head;//头指针
	QueueNode* tail;//尾指针
}Queue;


void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
#define _CRT_SECURE_NO_WARNINGS 1

#include "queue.h"
void QueueInit(Queue* pq)
{
	assert(pq);//结构不能为空,但此时指针可以为空
	pq->head = NULL;
	pq->tail = NULL;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		QueueNode* nnext = cur->next;
		free(cur);
		cur = nnext;
	}
	pq->head = pq->tail = NULL;
}
//入队列--形参为结构体的地址,pq代表结构体的指针
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));//节点类型指针
	newnode->data = x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;//因为head,tail这些都是指针newnode,所以可以直接赋值
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}
//出队列
void QueuePop(Queue* pq)
{
	//队头出
	assert(pq);

	assert(!QueueEmpty(pq));
	QueueNode* nnext = pq->head->next;
	free(pq->head);
	//free函数释放由malloc函数申请到的整块内存空间,参数是指向要释放空间的首地址
	pq->head = nnext;
	if (QueueEmpty(pq))
	{
		pq->tail = NULL;
	}

}
//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}
//队列元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	
	int n = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		cur = cur->next;
		n++;
	}

	return n;

}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head==NULL;
}

测试用例:

void TestQueueq()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);

	QueuePop(&q);
	QueuePop(&q);
	QueuePop(&q);
	QueuePop(&q);

	printf("%d\n", QueueBack(&q));//取队尾数据

	QueuePush(&q, 10);
	QueuePush(&q, 10);

	QueueDestroy(&q);


}
int main()
{
	TestQueueq();
	return 0;
}

2.解说部分

队列的基本操作函数:(动态链表相似)(函数都是传的地址)

1.初始化操作:将头尾指针赋空值防产生野指针

2.销毁:另建一个指针,从头开始销毁,注意结点间联系不要断

3.入队列:建立一个新的结点存入的数据,用指针指向这个新节点,将结点插入队列后面

注意:

1.当队列是空时,将新节点指针都赋值给队列头尾指针

2.当队列有元素时,插入新节点后还要移动队列里的尾指针

注意:

4.出队列:从头出,传入队列的地址(指针),

        (1)删除前先判断此时结点个数,可调用专门判断结点是否为空的函数查看。

        (2)函数里另新建指针帮助头结点后移一位和用free释放原结点空间,

        (3)注意结点为空时队尾的指针也要置空

5.取队头数据

        判断形参(队列的地址)是否为空,再判断队列的结点个数是否为空,都不为空就用队列的头指针返回第一个结点的数据

6.取队尾数据

        和5高度相似,用尾指针掉尾数据

7.返回队列元素个数

(1)判断形参(队列的地址)是否为空,

(2)新建一个队列结点类型指针,将头指针赋值给它,循环计数直到该新建指针指向空(也就是遍历完了队列)

8.判断队列是否为空

该函数返回值类型可以是bool类型,(要添加<stdbool.h>的头文件,且还要申明使用了bool类型的函数)

返回值是:队列的头指针==空值NULL

三、顺序栈(c语言)的代码实现

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once//没有这句会有struct类型重定义的报错

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//包含bool类型


typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//数组首地址
	int top;//数组下标(数组元素个数)
	int capacity;//此时空间里,数组元素个数可达到的最大值
}ST;

bool StackEmpty(ST* ps);
void StackInit(ST* ps);
void StackDestory(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool isValid(char* s);
void p(char* s);
#define _CRT_SECURE_NO_WARNINGS 1

#include "stack.h"


bool StackEmpty(ST* ps);//使用bool类型时还要再前面申明

//初始化
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	//top给0,意思是它指向栈顶数据的下一个,给-1,意思是它指向栈顶数据
	ps->capacity = 0;
}

//销毁栈
void StackDestory(ST* ps)
{
	free(ps->a);//void free(void *ptr)释放有动态存储分配函数申请到的整块内存空间,ptr是指向要释放空间的首地址
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

//压栈
void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	//注意空间不够的情况
	if (ps->top==ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);//??
		//sizeof() 功能是返回一个变量或者类型的大小(以字节为单位)

		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}


//出栈--删除数据
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

//取栈顶的数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return  ps->a[ps->top - 1];
}


//栈里面总共有多少数据
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;//top是从0开始的吗?
}


//判断栈是否为空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

测试用例:

void TestStack1()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);

	StackPop(&st);
	StackPop(&st);
	StackPop(&st);
	StackPop(&st);

	StackDestory(&st);//尽管前面已经将栈里的内容都删除了,但是为它分配的空间还在
}


void TestStack2()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);

	printf("%d ", StackTop(&st));//先取栈顶的数据
	StackPop(&st);
	printf("%d ", StackTop(&st));//先取栈顶的数据
	StackPop(&st);

	StackPush(&st, 5);
	StackPush(&st, 6);

	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));//先取栈顶的数据
		StackPop(&st);
	}

	//定义一个栈后,就养成先初始化再销毁的习惯
	StackDestory(&st);
}

void TestStack3()
{
	char s[7] = "()[]{}";
	isValid(&s);
	//p(s);
}


int main()
{
	MyStack* st = myStackCreate();
	myStackPush(st, 1);
	myStackPush(st, 2);
	myStackPush(st, 3);
	myStackPop(st);

	myStackFree(st);
	return 0;
}

四、简单应用

1.四则运算计算器

        原理:

初始化一个栈,用于保存暂时还不能确定运算顺序的运算符,从左到右处理各个元素,指导末尾,可能遇到三种情况:

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

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

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

其中,

中缀表达式:运算符在两个操作数中间

后缀表达式(也叫逆波兰表达式):运算符在两个操作数后面

前缀表达式(也叫波兰表达式):运算符在两个操作数前面

 

 

 

关于前缀、中缀和后缀三者的具体转换可以看b站王道考研的视频

完整代码实现(c++):

#pragma once
#ifndef _STACK_H_
#define _STACK_H_

#include <iostream>
#include <assert.h>

using namespace std;
const int MaxSize = 100;

template<class STDataType>
class Stack
{
public:
	 STDataType* data;//动态数组首元素地址
	int top;//数组下标(数组元素个数)

	//判空
	bool StackEmpty()
	{
		return top == 0;
	}
	//构函--初始化
	Stack()
	{
		data = new STDataType[MaxSize];
		top = 0;
		
	}
	//销栈
	virtual ~Stack()
	{
		delete[] data;
		top = 0;
	}

	//入栈
	void StackPush(STDataType x)
	{
		//要先判断空间够不够
		data[top] = x;
		top++;
	}
	//出栈
	void StackPop()
	{
		assert(!StackEmpty());
		top--;
	}
	//取栈顶元素
	STDataType StackTop()
	{
		assert(!StackEmpty());
		STDataType e = data[top - 1];
		return e;//因为数组下标从0开始
	}
	//栈里数据个数
	int StackSize()
	{
		return top;
	}

	//计算后缀
	STDataType cmpvalue(char* postexp)
	{
		STDataType a, b, c, d;

		while (*postexp != '\0')
		{
			switch (*postexp)
			{
			case '+':
			{
				//将操作数栈里的元素出栈
				a = StackTop();
				StackPop();
				b = StackTop();
				StackPop();
				c = b + a;
				//入栈
				StackPush(c);
				break;
			}
			case '-':
			{
				a = StackTop();
				StackPop();
				b = StackTop();
				StackPop();
				c = b - a;
				//入栈
				StackPush(c);
				break;
			}
			case '*':
			{
				a = StackTop();
				StackPop();
				b = StackTop();
				StackPop();
				c = b * a;
				//入栈
				StackPush(c);
				break;
			}
			case '/':
			{
				a = StackTop();
				StackPop();
				b = StackTop();
				StackPop();
				//要判断除数是否为0
				if (a != 0)
				{
					c = b / a;
					//入栈
					StackPush(c);
					break;
				}
				else
				{
					printf("\n\t除0,错误\n");
					exit(0);
				}
				break;
			}
			default:
			{
				d = 0;
				while (*postexp >= '0' && *postexp <= '9')
				{
					d = d * 10 + *postexp - '0';
					postexp++;
				}
				StackPush(d);
				break;
			}
			}
			postexp++;//指针后移
		}

		a = StackTop();
		return a;
	}

	//将中缀表达式转换成后缀表达式
	void trans(char* exp, char postexp[])
	{
		char e;
		int i = 0;//i作为postexp的下标
		//exp表达式未扫描完时循环
		while (*exp != '=' && *exp != '\0')
		{
			switch (*exp)
			{
				case '(':
				{
					//遇左括号直接进栈
					StackPush('(');
					exp++;
					break;
				}
				
				case ')':
				{
					e = StackTop();
					while (e != '(')
					{
						postexp[i++] = e;//将栈里元素赋值给后缀集
						StackPop();//出栈StackPop(&OPND);
						e = StackTop();
					}
					//退出循环时说明扫描到了'(',直接将'(’出栈
					StackPop();
					exp++;//继续扫描其他字符
					break;
				}
				case '+':
					/*遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,
					并加入后缀表达式*/
				case '-'://加和减优先级相同,一起操作(因为case '+'没有break,直接执行后一个case的代码,不同判断
				{
					while (!StackEmpty())
					{
						e = StackTop();
						if (e != '(')
						{
							postexp[i++] = e;
							StackPop();//出栈元素e--因为优先级低,直接出栈入后缀里
							e = StackTop();
						}
						else
							break;//一旦有一个'('或者栈空了就退出循环
					}
					//当前符号进栈
					StackPush(*exp);
					exp++;
					break;
				}
				case '*':
				case '/':
				{
							while (!StackEmpty())
							{
								
								if (((e = StackTop()) == '*' || e == '/')&& !StackEmpty())//优先级很高了,只有栈里同级的才能连续出栈
								{
									postexp[i++] = e;
									StackPop();
									//e = StackTop();
								}
								else
									break;
							
					}
					//当前符号进栈
					StackPush(*exp);
					exp++;
					break;
				}
				default://数字直接进数组
				{
					while (*exp >= '0' && *exp <= '9')
					{
						postexp[i++] = *exp;
						exp++;
					}
					postexp[i++] =' ';//用用#标识一个数值串结束,以便能够处理数组大于9的数
					break;
				}
			}

		}
		//此时exp扫描完毕,栈不空时循环
		while (!StackEmpty())
		{
			e = StackTop();
			postexp[i++] = e;
			StackPop();
		}
		//给postexp表达式添加结束标识
		postexp[i] = '\0';
	}

};

#endif
#include "stack.h"

int main() {
	cout << "简易计算器" << endl;
	cout << "请输入一个无空格的算式:" << endl;

	char exp[MaxSize];
	cin >> exp;
	//cout << "中缀表达式" << exp << endl;
	char postexp[MaxSize];
	Stack<int> OPND; //数字栈
	Stack<char> Optr;//操作符栈
	Optr.trans(exp, postexp);//将中缀改为前缀--操作符栈

	//cout << "中缀表达式" << exp << endl;
	cout << "后缀表达式" << postexp << endl;
	cout << "表达式的值" << OPND.cmpvalue(postexp)<< endl;
	
	return 0;
}

2.括号配对问题

        可结合上面栈的实现来看(c语言)

bool isValid(char* s) {
	ST st;
	StackInit(&st);
	///*s表示是存的值,s是值的地址
	while (*s)//数组字符串末尾是\0,ASCII码值为0
	{
		if (*s == '(' || *s == '[' || *s == '{')
		{
			StackPush(&st, *s);
			printf("%c", *s);
			s++;
		}
		else
		{
			//判断字符串一开始就是右括号
			if (StackEmpty(&st))
			{
				//StackDestory(&st);
				return false;
			}

			STDataType t = StackTop(&st);
			StackPop(&st);
			if ((*s == ')' && t != '(')
				|| (*s == ']' && t != '[')
				|| (*s == '}' && t != '{')
				)
			{
				StackDestory(&st);
				return false;
			}
			else
			{
				s++;
			}
		}
	}

	bool ret = StackEmpty(&st);
	StackDestory(&st);
	return ret;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值