线性表 -- 栈和队列

栈和队列都是线性表,只是规定:栈只能在一端进行 插入/删除 操作,队列只能在表的一端(队尾)插入,另一端(队头)删除

按存储(物理)结构:栈分为顺序栈和链式栈,队列分为顺序队和链队

注:在《数据结构与算法分析–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 ,请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。

算法思路:本题可以拆分为以下四个小步骤

  1. 正确处理字符串中的操作数

  2. 用双栈分别记录操作数和运算符,以中缀转后缀的思路进行栈的弹出

    中转后的思路为:若运算符栈顶元素的优先级不低于准备入栈元素的优先级,那么先出栈进行运算,重复上述行为直到栈空或者遇到优先级更低的元素。

    这种出栈的顺序保证了同优先级情况下计算按照从左往右进行,若仅出比入栈优先级低的元素,那么则相反,而且会出现错误。如 2*3/4,正常整数运算结果为 1,若从右到左进行,则为 2 * (3 / 4) = 0。

  3. 正确处理弹出优先级

  4. 计算

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hoper.J

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值