栈和队列
我有想过要不要把栈和队列和在一起讲,因为他们之间太多相似的地方了,但是我还是决定分开来写,这样复习梳理起来会更加的细致,如有不对的地方也请大家指出,多多包涵。
一、栈
1.栈的基本概念
栈(stack)是只能在尾部添加数据或者删除数据的线性表。表尾,表顶:top,表头,栈底:bottom,栈底bottom在栈中指向第一个数据存储空间,并且保持不变,栈顶指向下一个存储区域的地址,存入一个,栈顶向后移动一下。
栈的存储和拿取数据的顺序是后进先出 LIFO,每次取出数据的时候都是从栈顶取数据,取完后栈顶向前移动一位数。
2.栈的顺序存储结构
栈的顺序存储结构主要也还是开辟一个足够大的空间,如果空间需要增量的需求的话要设定一个增量的值,top表示下标,开始的时候top = 0。栈的顺序表的地址是连续的,数据查找比较方便
3.栈的链式存储结构
链式表主要还是 用指针域指向下一个节点使得整个数据连成一个栈,一个线性表。
#include <stdio.h>
#include <string>
#include <stdlib.h>
#define EMPTY -2
#define ERROR -1
#define SUCESSED 1
#define FULL 2
typedef int Status;
typedef int ElemType, * pElemType;
//节点结构
typedef struct List_Stack {
pElemType data; //数据域
struct List_Stack* next; //指针域
}List_Stack, * pList_Stack;
//栈结构
typedef struct Stack {
int length; //长度
pList_Stack head; //节点
}Stack, * pStack;
//初始化线性链表栈
Status Init_LiStack(pStack& pLStack) {
pLStack = (pStack)malloc(sizeof(Stack));
if (pLStack == NULL) {
return ERROR;
}
pLStack->length = 0;
pLStack->head = NULL; //头节点为空
return SUCESSED;
}
//增加栈元素
Status Insert_Stack(pStack& pLStack, pElemType& elem) {
//判断是否为空
if (pLStack) {
pList_Stack ptemp = pLStack->head;
//创建新节点
pList_Stack newtemp = (pList_Stack)malloc(sizeof(List_Stack)); //开辟新节点空间
newtemp->data = elem;
newtemp->next = ptemp;
pLStack->head = newtemp;
pLStack->length++;
}
return SUCESSED;
}
//弹出栈顶 保存到elem中
Status Pop(pStack& pLStack, pElemType& elem)
{
//栈存在且头节点有数据
if (pLStack && pLStack->head) {
pList_Stack ptemp = pLStack->head;
elem = pLStack->head->data; //变量赋值
pLStack->head = pLStack->head->next; //弹出栈顶
ptemp->next = NULL; //切断联系
free(ptemp);
pLStack->length--;
return SUCESSED;
}
return ERROR;
}
//查看栈是否为空
Status StackEmpty(pStack& pLStack) {
if (pLStack->length == 0)
return EMPTY;
else
return FULL;
}
Status Get_Top(pStack& pLStack, pElemType& elem) {//查看栈顶元素
if (pLStack && pLStack->head) {
elem = pLStack->head->data;
return SUCESSED;
}
return ERROR;
}
Status Clear_Stack(pStack& pLStack) { //清空栈元素
pList_Stack pelem;
while (pLStack->head) {
pelem = pLStack->head;
pLStack->head = pLStack->head->next;
free(pelem);
}
pLStack->length = 0;
return SUCESSED;
}
//销毁栈
Status Destroy_Stack(pStack& pLStack) {
pList_Stack pelem;
while (pLStack->head) {
pelem = pLStack->head;
pLStack->head = pLStack->head->next;
free(pelem);
}
free(pLStack);
return SUCESSED;
}
//获取栈的长度
int Get_Stack_Lenth(pStack& pLStack) {
if (pLStack) {
return pLStack->length;
}
else
return -1;
}
看第四大点1.迷宫算法的引用那个大哥的就是链式存储结构
4.栈的应用
逻辑表达式计算
#include "LiStack.h"
#include <stdio.h> // printf();scanf()
#include <stdlib.h> // exit()
#include <malloc.h> // malloc()
#include <string.h>
//根据优先级判断是否要弹出运算
bool Check_NUM_TOP_NVIC(char op, pStack& OP) {
pElemType temp;
if (Get_Stack_Lenth(OP) == 0)
return false;
Get_Top(OP, temp);
switch (*temp) { //栈顶数据
case '+':
case '-':
if (op == '(') {
return false;
}
if (op == '*' || op == '/') {
return false;
}
return true;
case '*':
case '/':
switch (op) {
case '-':
case '+':
return true;
case '*':
case '/':
return true;
case '(':
return false;
}
case '(':
if (op == ')') {
Pop(OP, temp); //( 和 ) 遇上了(弹出
}
return false;
case ')':
return true;
}
exit(-1);
return false;
}
//执行运算操作
int Do(pStack& OP, pStack& NUM) {
pElemType op;
pElemType num1;
pElemType num2;
Pop(OP, op);
Pop(NUM, num1);
Pop(NUM, num2);
switch (*op) {
case '+':
return ((*num2) + (*num1));
case '-':
return ((*num2) - (*num1));
case '*':
return ((*num2) * (*num1));
case '/':
return ((*num2) / (*num1));
default:
exit(-1);
break;
}
return 0;
}
int main() {
char op_num[] = { "17*(7+7)+2/2*2/(2+2)" };
char num[8] = {};
int i = 0, j = 0;
pStack OP; //运算符号
pStack NUM; //操作数
pElemType op_temp;
pElemType temp;
int res = 0;
Init_LiStack(OP);
Init_LiStack(NUM);
for (j = 0, i = 0; i < strlen(op_num); i++) {
if (op_num[i] >= '0' && op_num[i] <= '9') {
num[j]= op_num[i];
j++;
}
else {
if (j > 0) {
temp = (pElemType)malloc(sizeof(ElemType));
*temp = atoi(num);
Insert_Stack(NUM, temp);
j = 0;
memset(num, 0, sizeof(num));
}
switch (op_num[i]) {
//(:直接存入OP栈中
case '(':
op_temp = (pElemType)malloc(sizeof(ElemType));
*op_temp = '(';
Insert_Stack(OP, op_temp);
break;
//全部出栈直到遇到(将(弹出
case ')':
while (Check_NUM_TOP_NVIC(op_num[i], OP)) {
temp = (pElemType)malloc(sizeof(ElemType));
*temp = Do(OP, NUM);
Insert_Stack(NUM, temp);
}
break;
case '+':
case '-':
op_temp = (pElemType)malloc(sizeof(ElemType));
//判是否为空
if (StackEmpty(OP) != EMPTY) { //不为空
//根据优先级判断
while(Check_NUM_TOP_NVIC(op_num[i], OP)) {
temp = (pElemType)malloc(sizeof(ElemType));
*temp = Do(OP, NUM);
Insert_Stack(NUM, temp);
}
}
*op_temp = op_num[i];
Insert_Stack(OP, op_temp);
break;
case '*':
case '/':
op_temp = (pElemType)malloc(sizeof(ElemType));
//判是否为空
if (StackEmpty(OP) != EMPTY) { //不为空
//根据优先级判断
while (Check_NUM_TOP_NVIC(op_num[i], OP)) {
temp = (pElemType)malloc(sizeof(ElemType));
*temp = Do(OP, NUM);
Insert_Stack(NUM, temp);
}
}
*op_temp = op_num[i];
Insert_Stack(OP, op_temp);
break;
}
}
}
//存入最后一个操作数
if(j > 0){
temp = (pElemType)malloc(sizeof(ElemType));
*temp = atoi(num);
Insert_Stack(NUM, temp);
}
//长度不为1的时候不是结果
while (NUM->length != 1) {
temp = (pElemType)malloc(sizeof(ElemType));
*temp = Do(OP, NUM);
Insert_Stack(NUM, temp);
}
if (OP->length != 0) {
printf("计算式有问题\n");
}
printf("res:=\n", *(NUM->head->data));
printf("%s\n", op_num);
system("pause");
return 0;
}
二、队列
1.队列的基本概念
队列:先进先出
有两个指针front对头指针,rear队尾指针,队尾指针用来入队操作,而front用来出队操作一开始的时候front和rear在同一位置,当然也有rear在front后一位置的操作,看存储顺序要求。当数据存入的时候rear向后移动,front不动,当出队时候front将数据输出,并且向后移动一位。
2.队列的顺序存储结构
顺序存储结构的时候我以前一直在想数据空间存完了,出队数据输出后我的rear怎么跑到前面去存储啊。还是科班的人会研究用了一个%,(rear+1)%MaxSize == 0 的话相当于数据存入后已经到头了,将rear重新等于0,这样就是实现了rear重新到头开始进行数据输出,当然还要判断一下rear等于front这个样子判断是不是数据满了,除了这个还有就是如果rear+1等于front的话那么就不能进行存储因为到时候可能会导致下次存入数据的时候rear等于front,可能会存在判为数据存储为空的情况,这个情况下有两种解决办法
1.定义数据标志位,可以是size表示存储了多少数据,还可以是tag表示上一次操作是增加队列还是删除队列,只有队列是增加才可能是队满,只有是删除队列才可能是队空
2.最后一个存储空间牺牲掉这样,这样存到rear+1==front时不进行存储,返回error。
3.队列的链式存储结构
链式存储结构其实也就比线性表多了一个尾指针而且其实没有太大的差距
4.队列的应用
队列的应用比如像在打印机的时候就是数据先压入缓存区,然后打印完成后从缓存区队列里面拿去数据,符合先进先出的原则。
三、特殊矩阵的压缩存储
特殊矩阵有:对称矩阵,三角矩阵,线性矩阵,稀疏矩阵
特殊矩阵的压缩存储其实是将矩阵的特征提取出来
对称矩阵:是将原本n*n的二维数组,转变成一维数组,数组大小是n!,再设置一个转换的式子,将原本的i,j位置的数据,转变成k位置的数据
三角矩阵:和对称矩阵同理,只是将另外一边的常数c,单独存放到最后去,也要设置一个转换式子去转变位置
线性矩阵同理
稀疏矩阵可以用一个顺序表来存储,顺序表内存放i,j,value,要用的时候顺序查找
还有一种方法是十字链表法,是指每一个i和j设立一个头节点,将数据挂接在链表上,每个数据包涵上下行列的下一节点信息。
四、其他
1.迷宫的"穷举求解":
这个大哥写的很好啊,我没时间写,一起去理解吧,他的实现里面有用到C++的引用’&’,其实在C语言里面也就是把地址传入的意思,会更简洁一些。
比较想讲的是这个地方我也理解了蛮久的了。
// -----栈的链式存储结构----------------------------------
typedef struct SNode {
SElemType data; // 数据域
struct SNode* next; // 指针域
} SNode, * LinkStack;
SNode就不讲了,就是讲struct SNode重命名为SNode,开空间以后是一个有实体的数据元素
LinkStack == *SNode,用来代表指针,这样做的好处就是不会经常要考虑数据指针的变量,代码的可读性更高。
https://blog.csdn.net/qq_41511151/article/details/108047712
2.共享栈
为什么要用共享栈,如果为了一个静态栈开辟大量的存储空间其实很不合理,如果用两个栈开辟一个存储空间,一个栈的top1从下往上初始化top1=-1,一个栈的指针top2从上往下初始化top2=MaxSize,共用同一片存储空间,判断栈满的标志是top1+1=top2
五、总结
队列和栈的其实都是特殊的线性表,只是条件受限,主要是通过封装函数来对其进行限制,双端队列也是如此,顺序存储的话要考虑开辟空间的长度和增量的多开空间操作,链表的话就是指针关系要考虑清楚然后进行操作。free只能释放用malloc,calloc,realloc开辟的空间。