栈(stack)
- 也是一种线性表,但只能在栈顶进行插入或删除,先进后出LIFO
- 基本操作:压栈push,弹出pop,判空empty,获取大小size,获取栈顶top
- 按照存储方式可以分为顺序栈和链式栈,链式栈(用头插的方式入栈),链式栈可改变容量
//c语言顺序栈
typedef struct {
int data[10];//假设大小为10
int top;
}SqStack;
//共享栈
typedef struct {
int data[10];
int top0;
int top1;
}ShStack;
bool pushStack(SqStack &s, int x) {
if(s.top == 10 - 1)
return false;
s.data[s.top++] = x;
return true;
}
//C++
#include<stack>
stack<int> s;//有模板
s.push(1);//压栈
s.pop();//弹栈,删除栈顶元素
s.empty();//判空,空为1
s.size();//获取大小
s.top();//获取栈顶(不删除栈顶元素)
共享栈:
栈(stack)的应用
- 括号匹配(左括号入栈,遇右括号出栈与之匹配)
- 表达式求值:中缀表达式a+b,后缀表达式ab+(若a+b-c,后缀ab+c-,从左到右入栈),前缀表达式+ab(若a+b-c,前缀-+abc,从右往左入栈),中缀表达式直接求值需要两个栈分别放数和运算符。中缀转后缀适合左优先(a+b-c,先+再-),中缀转前缀适合右优先
- 中缀转后缀:一个栈存储运算符,一个字符串存储后缀。遍历中缀,遇数放入后缀字符串,遇运算符压入栈,保持栈顶的运算符优先级最高,如果遇到的运算符(假设“-”)优先级小于栈顶(假设“/”),则不断弹出优先级高的栈顶,先拿去运算(即放入后缀字符串),直到遇到的“-”可以放入栈为止。遍历中缀后依次弹出栈放入后缀字符串
- 中缀表达式求值:中缀转后缀 + 后缀表达式求值
- 递归的本质是函数调用栈(递归工作栈),函数先进后出
//括号匹配
bool bracketCheck(char str[], int length) {
stack<char> s;
for(int i = 0; i < length; ++i) {
if(str[i] == '(' || str[i] == '[' || str[i] == '{')
s.push(str[i]);
else {
if(s.empty()) //
return false;
char tmp = s.pop();
if(str[i] == ')' && tmp != '(' )
return false;
if(str[i] == ']' && tmp != '[' )
return false;
if(str[i] == '}' && tmp != '{' )
return false;
}
}
return s.empty();
}
//中缀转后缀
string infix2suffix(string& infix) {
stack<char> symb; //存放运算符
string suffix; //存放后缀表达式
int len = infix.length();
for(char c: infix) { //依次遍历中缀字符
if('0' <= c && c <= '9') { //遇数字直接放入后缀
suffix.push_back(c);
}
else if(c == '+' || c == '-'){ //遇运算符入栈,保持栈顶的优先级最高
while(!symb.empty() && symb.top() != '(') {
suffix.push_back(symb.top()); //高优先级的直接参与运算放入后缀
symb.pop();
}
symb.push(c);
}
else if(c == '*' || c == '/'){
while(!symb.empty() && symb.top() != '+' && symb.top() != '-' && symb.top() != '(') {
suffix.push_back(symb.top());
symb.pop();
}
symb.push(c);
}
else if(c == '('){ //括号优先级更高
symb.push(c);
}
else if(c == ')'){ //遇右括号,将括号内的参与运算放入后缀
while(symb.top() != '(') {
suffix.push_back(symb.top());
symb.pop();
}
symb.pop();
}
}
while(!symb.empty()) {
suffix.push_back(symb.top());
symb.pop();
}
return suffix;
}
//计算后缀表达式(示例只适用于个位数的后缀表达式)
int calcuSuffix(string& suffix) {
stack<int> calcu;
int len = suffix.length();
for(char c: suffix) {
if('0' <= c && c <= '9') { //数字
calcu.push(c-'0');
}
else { //运算符
int y = calcu.top(); calcu.pop(); //后弹出的才是左操作数(如被除数)
int x = calcu.top(); calcu.pop();
switch(c) {
case '+': calcu.push(x+y); break;
case '-': calcu.push(x-y); break;
case '*': calcu.push(x*y); break;
case '/': calcu.push(x/y); break;
default : break;
}
}
}
return calcu.top();
}
note:
- 后缀表达式计算时,先弹出的是右操作数(如除数),后弹出的是左操作数(如被除数),应该用后弹出运算先弹出,前缀反之。
- 同样,中缀转后缀中,遍历时若遇到的运算符“-”,则需要弹出栈中优先级 ≥ “-”的运算符,而不仅仅是>。
- 十位以上的中缀转后缀以及表达式计算需要用符号分隔开标记,或者用字符栈存储后缀
队列(queue)
- 也是一种线性表,只在队头出队,队尾入队,先进先出FIFO
- 基本操作:入队push,出队pop,判空empty,获取大小size,获取队头front,获取队尾back
- 双端队列deque,队首和队尾均可以出队和入队,push_front、push_back
//C语言顺序队列
typedef struct {
int data[10]; //假设大小为10
int front, rear; //队首和队尾指针
}SqQueue;
bool pushQueue(SqQueue q, int x){
if((q.rear+1)%10 == q.front)//队尾指针差一点追上队首指针
return false;//队满
q.data[q.rear] = x;
q.rear = (q.rear + 1) % 10;//难点,环状存储空间(循环队列)
}
bool emptyQueue(SqQueue q){
if(q.rear == q.front)
return true;//空
else
return false;
}
bool sizeQueue(SqQueue q){
return (rear + 10 - front) % 10;
}
//满队列存储思路,结构体中建立一个size标志位
//插入时
size++;
//删除时
size--;
//判满
size == 10;
//结构体中建立一个tag标志位
//插入时
tag = 1;
//删除时
tag = 0;
//判空
front == rear && tag = 0;
//判满
front == rear && tag = 1;
//C++
#include<queue>
queue<int> q;
q.push(1);//放入队尾
q.pop();//弹出队头元素
q.empty();//判空,空为1
q.size();//获取元素个数
q.front();//获取队头元素
q.back();//获取队尾元素
//双端队列
#include <deque>
deque<int> dq;
dq.push_front(1);//放入队首
dq.push_back(1);//放入队尾
dq.pop_front();//弹出队首
dq.pop_back();//弹出队尾
dq.empty();//判空,空为1
dq.size();//获取元素个数
dq.front();//获取队头元素
dq.back();//获取队尾元素
队列的环状式存储(循环队列):
note:
- 变为环形的原因,使得插入和删除不用增加额外空间,在固定空间进行。
- 没有size标志位时,判空:队尾指针和队首指针指向同处。判满:队尾指针差一点追上队首指针(中间相差一个位置,因为要与判空有所区别,会浪费一个空间)。
- 队列满空间存放思路:建立size标志位或tag标志位。
- 链队列:许多个包含数据域和next指针的节点结构体,一个包含链表头指针、尾指针和size标志位的结构体,插入更新尾指针,弹出更新头指针,判空,头指针和尾指针指向同一处。不用判满。
队列(queue)的应用
- 广度优先搜索(或树的层次遍历,BFS),处理每个节点时将节点的子节点都放入队尾,依次处理队首的节点。
- 操作系统中分配有限的系统资源,比如先来先服务策略(FCFS)、缓冲区。
矩阵的压缩存储:
- 对称矩阵,可按行优先的原则,用一维数组存储下三角矩阵,映射:a(i,j)=a(j,i) <=> b( i(i-1)/2+j-1 )。三角矩阵类似。
- 三对角矩阵(带状矩阵),行优先一维存储,映射:a(i,j) <=> b( 2i+1-j )
- 稀疏矩阵,顺序存储三元组,将有值的位置记录下来(i, j, v)。另一种方法为十字链表法,有两条分别代表x轴和y轴的链表数组,每个值作为一个节点,内部存储具体值、指向该行下一个具体值的指针以及指向该列下个具体值的指针。
十字链表法: