栈和队列都是线性表,只是规定:栈只能在一端进行 插入/删除 操作,队列只能在表的一端(队尾)插入,另一端(队头)删除
按存储(物理)结构:栈分为顺序栈和链式栈,队列分为顺序队和链队
注:在《数据结构与算法分析–C语言描述》这本书中,表 & 栈 & 队列是处于同一章的
文章目录
栈
基本操作–初始化和进/出栈(顺序表 & 非函数)
top 值的设定取决于一个要素:指的是否是当前可存位置,不是就初始化为 -1(理解为指向栈顶元素),是就为 0。
top = -1 时
top = -1; /* 初始化 */
s[++top] = val; /* val入栈 */
val = s[top--]; /* 出栈 */
top = 0 时
top = 0; /* 初始化 */
s[top++] = val; /* 入栈 */
val = s[--top]; /* 出栈 */
结构体定义(顺序表)
typedef struct Stack{
int capacity; /* 存储容量 */
int top;
int *array; /* 定义为指针而非固定大小的数组可以在程序中按实际情况分配,更加具有普遍性 */
}Stack;
基本操作(顺序表)
void Error(char *string);
Stack *createStack(int stackSize);
int isEmpty(Stack *s);
void push(Stack *s, int val);
int pop(Stack *s);
/* 报错(不是基本操作,只是为了下面不重复打代码) */
void Error(char *string){
fprintf(stderr, string); /* 显示错误信息 */
fprintf(stderr, "\n");
abort(); /* 终止程序 */
}
/* 创建空栈 */
Stack *createStack(int stackSize){
Stack *s = (Stack *)malloc(sizeof(Stack));
if (!s) /* 当 s == NULL时,代表内存不足,此时不能对 s 进行初始化操作 */
Error("空间不足");
else{
s->capacity = stackSize;
s->top = -1;
s->array = (int *)malloc(sizeof(int) * stackSize);
if (!s->array)
Error("空间不足");
}
return s;
}
/* 判空 */
int isEmpty(Stack *s){
return s->top == -1;
}
/* 入栈 */
void push(Stack *s, int val){
// if (isEmpty(s))
// Error("栈满");
s->array[++s->top] = val; /* '->' 优先级高于 '++' */
}
/* 出栈 */
int pop(Stack *s){
if (isEmpty(s))
Error("栈已空");
return s->array[s->top--];
}
节点定义(链表)
typedef struct StackNode{
int val;
struct StackNode *next;
}StackNode;
基本操作(链表)
带头节点
/* 创建空栈 */
StackNode *createStack(void){
StackNode *s = (StackNode *)malloc(sizeof(StackNode));
if (!s){ /* 当 s == NULL时,代表内存不足,此时不能对 s 进行初始化操作 */
fprintf(stderr, "空间不足\n"); /* 显示错误信息 */
abort(); /* 终止程序 */
}
s->next = NULL;
return s;
}
/* 判空 */
int isEmpty(StackNode *s){
return s->next == NULL;
}
/* 入栈 */
void push(StackNode *s, int val){
StackNode *p = createStack();
p->val = val;
/* 用头插法即可,这样头节点就是栈的 top 指针 */
p->next = s->next;
s-next = p;
}
/* 出栈 */
int pop(StackNode *s){
StackNode *p;
if (isEmpty(s)){
fprintf(stderr, "栈为空\n");
abort();
}else{
p = s->next;
int val = p->val;
s->next = s->next->next;
free(p);
}
return val;
}
不带头节点
/* 判空 */
int isEmpty(StackNode *s){
return s == NULL;
}
/* 入栈,用法:push(&head, val),head的声明为StackNode *head(因为要修改指针的指向,故传递的应是 head 指针的地址,所以函数中对应形参的声明为指针的指针)*/
void push(StackNode **s, int val){
StackNode *p = createStack();
p->val = val;
/* 头插法 */
p->next = *s;
*s = p; /* 令头指针指向 p */
}
/* Usage: pop(&head, &val) */
int pop(StackNode **s, int *val){
StackNode *p;
if (isEmpty(s)){
return 0;
}else{
p = *s;
*val = p->val;
*s = *s->next;
free(p);
return 1;
}
}
算法题
1. 括号是否有效( LeetCode 20题)
解法1: 遇左入栈,遇右出栈
算法思路:遇到左括号将其入栈,遇到右括号出栈,若不匹配则无效
bool isValid(char * s){
int i, top;
char c;
char stack[strlen(s)];
for (i = 0, top = -1; (c = s[i]) != '\0'; i++){
/* 可以使用 if() */
switch (c){
case '(':
case '[':
case '{':
stack[++top] = c;
break;
case ')':
if (top == -1 || stack[top--] != '(')
goto mismatch;
break;
case ']':
if (top == -1 || stack[top--] != '[')
goto mismatch;
break;
case '}':
if (top == -1 || stack[top--] != '{')
goto mismatch;
break;
default:
goto mismatch;
break;
}
}
if (top == -1)
return 1;
mismatch:
return 0;
}
2. 处理后缀表达式(LeetCode 036题)
解法1: 栈
算法思路:创建一个栈,用以保留数值以及计算结果,遇到运算符弹出两个数,对于“-”和“/”需要区分顺序(注意到tokens是指针数组,指向的是字符串数组对应的位置)
#define NUMS 0
int strToNum(char *str, int *ret);
int evalRPN(char *tokens[], int tokensSize){
/* */
int sum, product, bop, rop;
int i, top, ret, s[tokensSize];
for(i = 0, top = -1; i < tokensSize; i++){
switch (strToNum(tokens[i], &ret)){
/* 需注意的是,通过 switch(ret)先处理运算符再 default: 处理数值是不对的,因为字符也是数,'/' 就是47,当 ret == 47 时,会从 case '/': 开始,导致程序出错 */
case NUMS:
s[++top] = ret;
break;
case '+':
sum = s[top--] + s[top--];
s[++top] = sum;
break;
case '*':
product = s[top--] * s[top--];
s[++top] = product;
break;
case '-':
rop = s[top--];
bop = s[top--];
s[++top] = bop - rop;
break;
case '/':
rop = s[top--];
bop = s[top--];
s[++top] = bop / rop;
break;
}
}
return s[top--];
}
int strToNum(char *str, int *ret){
int i, sign;
int len = strlen(str);
if(len == 1 && !isdigit(str[0])) /* len需等于1,否则会一并处理负数的情况导致错误 */
return str[0];
else {
i = 0;
sign = 1;
if (str[0] == '-'){
++i;
sign = -1;
}
for (*ret = 0; str[i] != '\0'; i++){
*ret = *ret * 10 + (str[i] - '0');
}
}
*ret = sign * *ret;
return NUMS;
}
3. 基本运算器( LeetCode 227题)
(处理整数级的加减乘除,基于中缀转后缀)
思路源于《数据结构与算法分析–C语言描述》P58 中缀到后缀的转换
题目简述:给定一个字符串表达式
s
,请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。算法思路:本题可以拆分为以下四个小步骤
正确处理字符串中的操作数
用双栈分别记录操作数和运算符,以中缀转后缀的思路进行栈的弹出
中转后的思路为:若运算符栈顶元素的优先级不低于准备入栈元素的优先级,那么先出栈进行运算,重复上述行为直到栈空或者遇到优先级更低的元素。
这种出栈的顺序保证了同优先级情况下计算按照从左往右进行,若仅出比入栈优先级低的元素,那么则相反,而且会出现错误。如 2*3/4,正常整数运算结果为 1,若从右到左进行,则为 2 * (3 / 4) = 0。
正确处理弹出优先级
计算
Ps:在LeetCode中选择执行代码可以获取正确结果,但是提交代码会bug,应该又是平台设置原因
#define NUMS 1
#define SYMBOL 0
#define END -1
int getOp(char *s, int *op); /* 处理字符串中的操作数 */
int shouldPop(int s_op, int op); /* 给出弹出信号 */
int getPriority(int op); /* 获取运算符优先级 */
int compute(int lop, int rop, int operator); /* 计算 */
int calculate(char * s){
int op, type, top1, top2;
int op_tmp, lop, rop;
int *operator = (int *)malloc(strlen(s) * sizeof(int));
int *operand = (int *)malloc(strlen(s) * sizeof(int));
top1 = top2 = -1;
while((type = getOp(s, &op)) != END){
switch(type){
case NUMS:
operand[++top2] = op;
break;
default:
while (top1 != -1 && shouldPop(operator[top1], op)){
op_tmp = operator[top1--];
rop = operand[top2--];
lop = operand[top2--];
operand[++top2] = compute(lop, rop, op_tmp);
}
operator[++top1] = op;
}
}
while (top1 != -1){
op_tmp = operator[top1--];
rop = operand[top2--];
lop = operand[top2--];
operand[++top2] = compute(lop, rop, op_tmp);
}
assert(top2 == 0);
return operand[top2];
}
/* 若感兴趣可以验算结果是否正确
int main(void){
printf("%d", calculate("3/2"));
}
*/
/* 返回当前操作数 and 运算符 */
int getOp(char *s, int *op){
static int i = 0;
*op = 0;
if (s[i] == '\0')
return END;
while (isspace(s[i])) /* 跳过空白 */
i++;
if (!isdigit(s[i])){ /* 处理运算符 */
*op = s[i++];
return SYMBOL;
}
while (isdigit(s[i])){ /* 获取正确的操作数 */
*op = *op * 10 + (s[i++] - '0');
}
return NUMS;
}
/* 返回是否应该弹出栈中运算符,原理:比较栈中运算符和入栈运算符之间的优先级,若栈中优先级不低于入栈运算符,则出栈 */
int shouldPop(int s_op, int op){
if (getPriority(s_op) >= getPriority(op)) /* 需要 >=,否则 2*3/4 = 1 的运算顺序会变为2*(3/4) = 0 */
return 1;
else
return 0;
}
/* 获取运算符优先级 */
int getPriority(int op){
if (op == '+' || op == '-')
return 1;
else if (op == '*' || op == '/')
return 2;
else
return 3;
}
/* 根据左右操作数和运算符计算结果并返回 */
int compute(int lop, int rop, int operator){
int ans;
switch(operator){
case '+':
ans = lop + rop;
break;
case '-':
ans = lop - rop;
break;
case '*':
ans = lop * rop;
break;
case '/':
ans = lop / rop;
break;
default:
abort();
break;
}
return ans;
}
队列
队列出/入队会使 front/rear 指针后移,这就导致经过一系列的出入队后,front/rear 可能到达数组的末尾,故将数组优化为循环数组
结构体定义(顺序表)
typedef struct Queue{
int front;
int rear;
int capacity;
int *array;
}Queue;
基本操作(顺序表)
/* 创建空队列 */
Queue *createQueue(int queueSize){
Queue *q = (Queue *)malloc(sizeof(Queue));
if (!q) /* 当 s == NULL时,代表内存不足,此时不能对 s 进行初始化操作 */
Error("空间不足");
else{
q->capacity = queueSize;
q->front = 0;
q->rear = 0;
q->array = (int *)malloc(sizeof(int) * queueSize);
if (!q->array)
Error("空间不足");
}
return q;
}
/* 判空 */
int isEmpty(Queue *q){
return q->front == q->rear;
}
/* 入队 */
int enQueue(Queue *q, int val){
if ((q->rear+1) % q->capacity == q->front)
return 0;
q->array[q->rear] = val;
q->rear = (q->rear+1) % q->capacity;
return 1;
}
/* 出队 */
int deQueue(Queue *q, int *val){
if (q->front == q->rear)
return 0;
*val = q->array[q->front];
q->front = (q->front+1) % q->capacity;
return 1;
}
结构体定义(链表)
typedef struct Node{
int val;
struct Node *next;
}Node;
typedef struct Queue{
Node *front;
Node *rear;
}Queue;
基本操作(链表)
/* 创建空队 */
Queue *createQueue(void){
Queue *q = (Queue *)malloc(sizeof(Queue));
if (!q)
Error("空间不足");
q->rear = q->front = NULL;
return q;
}
/* 根据 val 创建队节点 */
Node *createNode(int val){
Node *p = (Node *)malloc(sizeof(Node));
if (!p)
Error("空间不足");
p->val = val;
p->next = NULL;
return p;
}
/* 判空 */
int isEmpty(Queue *q){
return (q->rear == NULL || q->front == NULL); /* 实际上若空,二者皆 NULL */
}
/* 入队 */
void enQueue(Queue *q, int val){
Node *node;
node = createNode(val);
if (isEmpty(q)) /* 当为空,需处理两个指针 */
q->rear = q->front = node;
else{
q->rear->next = node;
q->rear = node;
}
}
/* 出队 */
int deQueue(Queue *q, int *val){
Node *tmp;
if (isEmpty(q))
return 0;
tmp = q->front;
if (q->front == q->rear) /* 若队列中仅剩一个元素 */
q->front = q->rear = NULL;
else
q->front = q->front->next;
*val = tmp->val;
free(tmp);
return 1;
}