顺序栈的实现
顺序栈:用顺序存储的方式实现栈
顺序栈的缺点:栈的大小不可改变
在做题过程中一定要注意栈顶指针top指向的是栈顶元素的位置还是栈顶元素之后的那个位置。
代码3.1.1
//王道第三章
//顺序栈
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
int data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针
}SqStack; //Sq---Squence 顺序
//初始化栈
void InitStack(SqStack &S){
S.top=-1; //初始化栈顶指针为-1
}
//判断栈是否为空栈
bool StackEmpty(SqStack S){
if(S.top==-1) return true;
else return false;
}
//新元素进栈
bool Push(SqStack &S,int e){
//首先判断是否满栈,如果满栈就报错
if(S.top==MaxSize-1) return false;
S.top++; //指针先加一
S.data[S.top]=e;
return true;
}
//出栈操作
bool Pop(SqStack &S,int &x){
if(S.top==-1) return false; //空栈报错
x=S.data[S.top]; //栈顶元素先出栈
S.top--; //指针-1,在逻辑上删除数据
return true;
}
//读栈顶元素
bool Pop(SqStack &S,int &x){
if(S.top==-1) return false; //空栈报错
x=S.data[S.top]; //x记录栈顶元素
return true;
}
int main(){
SqStack S; //声明一个顺序栈(分配空间)
InitStack(S);
return 0;
}
可以通过共享栈的方式提高一整片分配给栈的空间的利用率
逻辑上实现了两个栈,但物理上他们又是占用了同一片的存储空间,能够提高空间资源的利用率
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
int data[MaxSize]; //静态数组存放栈中元素
int top0; //0号栈顶指针
int top1; //1号栈顶指针
}ShStack; //Sh---Share 共享
//初始化共享栈
void InitStack(ShStack &S){
S.top0=-1; //初始化栈顶指针
S.top1=MaxSize;
return;
}
//判断是否满栈
bool isFull(ShStack S){
if(S.top0+1==S.top1) return true;
else return false;
}
int main(){
ShStack S; //声明一个共享栈(分配空间)
InitStack(S); //初始化共享栈
return 0;
}
链栈
链栈:用链式存储的方式实现栈
(联想单链表的头插法,和在头节点处删除操作。)
类似的方法我们可以建立带头结点的链栈和不带头节点的链栈
代码3.1.2
//王道第三章
//链栈(带头结点)
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct StackNode{
int data; //静态数组存放栈中元素
struct StackNode *next;
}StackNode,*Stack;
//初始化栈
void InitStack(Stack &S){
S->next=NULL; //初始化栈
}
//判断栈是否为空栈
bool StackEmpty(Stack S){
if(S->next==NULL) return true;
else return false;
}
//新元素进栈
bool Push(Stack &S,int e){
StackNode *s=(StackNode *)malloc(sizeof(StackNode));
s->data=e;
s->next=S->next;
S->next=s;
return true;
}
//出栈操作
bool Pop(Stack &S,int &x){
if(S->next==NULL) return false; //空栈报错
x=S->next->data; //x记录栈顶元素
StackNode *s=S->next;
S->next=s->next;
free(s);
return true;
}
//读栈顶元素
bool Pop(Stack S,int &x){
if(S->next==NULL) return false; //空栈报错
x=S->next->data; //x记录栈顶元素
return true;
}
int main(){
Stack S; //声明一个顺序栈
InitStack(S);
StackEmpty(S);
return 0;
}
队列
顺序队列(循环队列)
代码3.1.3
//王道第三章
//顺序队列
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#define MaxSize 10 //定义队列中元素的最大个数
typedef struct{
int data[MaxSize]; //静态数组存放队列中的元素
int front,rear; //队头指针和队尾指针
}SqQueue; //Sq---Squence 顺序
//初始化队列
void InitQueue(SqQueue &Q){
//让队头指针front指向队头
//让队尾指针rear指向队尾的后一个元素
//初始时队头指针和队尾指针都指向0
Q.front=0;
Q.rear=0;
}
//判断队列是否为空队列
bool QueueEmpty(SqQueue Q){
//Q.front==Q.rear是判断空队列的条件
if(Q.front==Q.rear) return true;
else return false;
}
//入队
bool EnQueue(SqQueue &Q,int e){
//首先判断队列是否已满,如果满就报错
if((Q.rear+1)%MaxSize==Q.front) return false;
Q.data[Q.rear]=e;
Q.rear=(Q.rear+1)%MaxSize; //队尾指针加一,取模,利用循环队列
return true;
}
//出队操作(删除第一个队头元素,并用x返回)
bool DeQueue(SqQueue &Q,int &x){
if(Q.rear==Q.front) return false; //空队列报错
x=Q.data[Q.front]; //队头元素出队
Q.front=(Q.front+1)%MaxSize;//指针-1,在逻辑上删除数据
return true;
}
//读栈顶元素
bool Pop(SqQueue &Q,int &x){
if(Q.rear==Q.front) return false; //空队列报错
x=Q.data[Q.front]; //x记录队头元素
return true;
}
int main(){
SqQueue Q; //声明一个顺序队列(顺序存储)
InitQueue(Q);
return 0;
}
↓循环队列↓
考试的时候需要注意题目的队尾指针是指向队尾元素的还是队尾元素的后一个元素的,两者之间进行操作时的处理方法不同
链式队列
链式队列 = 链队列 (带/不带头节点)
代码3.1.4
//王道第三章
//链式队列 = 链队列 (带/不带头节点)
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct LinkNode{//链式队列的结点
int data; //用于存放队列中的元素
struct LinkNode *next;//用于指向队列的下一个结点
}LinkNode;
typedef struct{ //链式队列
LinkNode *front,*rear;//队列的队头指针和队尾指针
}LinkQueue;
//初始化队列(带头结点)
void InitQueue(LinkQueue &Q){
//初始时队头指针和队尾指针都指向头节点
Q.front=Q.rear=(LinkNode *)malloc(sizeof(LinkNode));
Q.front->next=NULL;
}
//判断队列是否为空队列(带头结点)
bool QueueEmpty(LinkQueue Q){
//Q.front->next==NULL是判断链队列是否为空队列的条件
//Q.front==Q.rear也能判断
if(Q.front==Q.rear) return true;
else return false;
}
//入队(带头结点)
bool EnQueue(LinkQueue &Q,int e){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
if(s==NULL) return false;
s->data=e;
s->next=NULL;
Q.rear->next=s; //新节点插入表尾
Q.rear=s; //更新表尾结点
return true;
}
//出队操作(带头结点)(删除第一个队头元素,并用x返回)
bool DeQueue(LinkQueue &Q,int &x){
if(Q.front==Q.rear) return false; //空队列报错
LinkNode *q=Q.front->next;
x=q->data; //x存储要删除结点的数据
Q.front=q->next; //修改队列的队头
if(Q.rear==q) Q.rear=NULL;//此次是最后一个结点出队
free(q); //释放删除结点的空间
return true;
}
//初始化队列(不带头结点)
void NInitQueue(LinkQueue &Q){
//初始时队头指针和队尾指针都指向NULL
Q.front=Q.rear=NULL;
}
//判断队列是否为空队列(不带头结点)
bool NQueueEmpty(LinkQueue Q){
//Q.front==NULL是判断链队列是否为空队列的条件
//如果rear指向的是队尾元素也可以判断rear是否指向NULL
if(Q.front==NULL) return true;
else return false;
}
//入队(不带头结点)
bool NEnQueue(LinkQueue &Q,int e){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
if(s==NULL) return false;
s->data=e;
s->next=NULL;
if(Q.front==NULL){
//如果等于代表是第一个结点
Q.front=s;
Q.rear=s;
}else{
Q.rear->next=s; //新节点插入表尾
Q.rear=s; //更新表尾结点
}
return true;
}
//出队操作(不带头结点)(删除第一个队头元素,并用x返回)
bool NDeQueue(LinkQueue &Q,int &x){
if(Q.front==NULL) return false; //空队列报错
LinkNode *q=Q.front;
x=q->data; //x存储要删除结点的数据
Q.front=q->next; //修改队列的队头
if(Q.rear==q) Q.front=NULL,Q.rear=NULL;//此次是最后一个结点出队
free(q); //释放删除结点的空间
return true;
}
int main(){
LinkQueue Q; //声明一个顺序队列(顺序存储)
InitQueue(Q);
return 0;
}
双端队列
下面的类型在选择题中很常见。
从左到右观察可能输出的序列,第一个数输出时,意味着比他小的数都已经入栈或入队了,应该有一定的排列方式,然后再根据输入受限或者输出首先或者单端输入输出的情况,思考有没有能满足题目输出的插入方式,如果没有的话,该输出序列就是非法的。
栈的应用—括号匹配问题
扫描到匹配的括号,就出栈相对应的匹配括号
一旦扫描到不匹配的括号就可以停下来,就可以判断这组括号是非法的
后缀表达式的特点:后缀表达式的符号排列顺序与中缀表达式中的符号生效的次序相同
注意:上面这张PPT里面的“左操作数”“右操作数”“左优先原则”“右优先原则”是DIY的,王道自己杜撰的,不能真的写在简答题里面。
上面的栈是用来存储当前还不能确定运算顺序的运算符的(初加减乘除外还包括左括号,但不包括右括号,右括号不会出现入栈的情况)
用手写伪代码的方式转换中缀表达式为后缀表达式,手写推理符号进出栈的过程。
注意如果这里的运算过长、过为复杂需要防止用来存储运算符号的栈溢出。
中缀表达式的计算机运算:
二维数组也具有随机存取的特性
普通矩阵
对称矩阵
注意对称矩阵计算线性实际存储的位置时
①明确到底是用上三角区(i>j)外带主对角线还是下三角区(i<j)外带主对角线存储。
如果是上三角区域可以通过对称矩阵的性质计算转化为下三角区域的计算,也可以不转化,直接按照上三角区域的方式计算。
②开辟线性存储区域的角标是从0开始的还是从1开始的。
③是采用行优先原则存储还是列优先原则存储。
三角矩阵
三对角矩阵的压缩存储
三对角矩阵又称带状矩阵
如何通过三角对称矩阵的坐标位置映射到线性的存储位置
如何通过三角对称矩阵的线性的存储位置映射到坐标位置
稀疏矩阵的压缩存储
↑顺序存取(三元组的方法)缺点:失去了随机存取的特性