数据结构(七) -- C语言版 -- 栈和队列 - 栈的应用解析

零、读前说明

  • 本文中没有涉及到很多的相关理论知识,也没有做深入的了解,所以,您如果是想要系统的学习、想要多学习关于理论的知识等,那么本文可能并不合适您。
  • 本文中所有设计的代码均通过测试,并且在功能性方面均实现应有的功能。
  • 设计的代码并非全部公开,部分无关紧要代码并没有贴出来。
  • 如果你也对此感兴趣、也想测试源码的话,可以私聊我,非常欢迎一起探讨学习。
  • 由于时间、水平、精力有限,文中难免会出现不准确、甚至错误的地方,也很欢迎大佬看见的话批评指正。
  • 嘻嘻。。。。 。。。。。。。。收!

一、概述

  日常我们搬砖使用的编译器,几乎都具有括弧的检测功能、特殊字符对的检测功能,当然也有撤销、后退的功能,细细想一下其实这些功能和栈的使用很契合。
  下面通过介个简单的例子简单说明并测试一下栈的几个经典的应用场合。

二、栈的应用 - 就近匹配

  当需要检测成对出现但又互不相邻的事物时候,那么栈的先进后出的特性非常适合。

2.1、说明

  一般我们在搬砖的过程中,使用到的括弧基本上都是成对出现的,基本最常用的也就这几个:

  • 圆括弧 ( )
  • 花括弧 { }
  • 方括弧 [ ]
  • 尖括弧 < >
  • 单引号 ’ ’
  • 双引号 " "

  就近匹配原则使用的最经典的一个场合就是括弧的匹配,也就是最迟出现的左括号必须与最早出现的同类右括号匹配。

2.2、算法解析

  首先给定字符串,然后从第一个字符串开始一个字符一个字符扫描字符串。
  1、如果为普通字符串,那么直接忽略
  2、如果为左边符号时,入栈
  3、如果为右边符号时,出栈,并将出栈的元素与当前的这个右符号进行匹配判断。
    1)如果匹配成功,则继续向后扫描字符串
    2)如果匹配失败,那最简单的方式就是立即停止并退出
  4、当成功扫描到字符串的末尾之时。
  5、如果栈为空,那么说明陪完成并匹配成功
  6、如果栈不为空,则说明有落单的括弧,肯定是匹配失败啦

2.3、算法实现

2.3.1、算法实现的目录结构

  屁话不多说,就Cmake。没有其他的。

stackapp/
├── CMakeLists.txt
├── README.md
├── build
├── main
│   └── main.c
├── runtime
└── src
    ├── linklist
    │   ├── linklist.c
    │   └── linklist.h
    ├── linkstack
    │   ├── linkstack.c
    │   └── linkstack.h
    └── stackapp
        ├── stackapp.c
        └── stackapp.h

7 directories, 9 files
2.3.2、stackapp.h文件

  stackapp.h文件为栈的应用程序变量和函数的声明及定义的文件,其内容如下。

#ifndef __STACKAPP_H__
#define __STACKAPP_H__

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "../linkstack/linkstack.h"

#define null NULL
#define true 0
#define false !true
#define bool int

typedef struct func_StackApp
{
    int (*run)(const char *str);
} func_StackApp;

#endif

2.3.3、stackapp.c文件

  stackapp.c文件为栈的应用程序实现文件,其内容如下。

#include "stackapp.h"

/**
 * 功 能:
 *      判断是否为 左 符号
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_isLeft(char ch)
{
    bool ret = false;

    switch (ch)
    {
    case '<':
    case '(':
    case '[':
    case '{':
        ret = true;
        break;
    default:
        break;
    }
    return ret;
}
/**
 * 功 能:
 *      判断是否为 右 符号
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_isRight(char ch)
{
    bool ret = false;

    switch (ch)
    {
    case '>':
    case ')':
    case ']':
    case '}':
        ret = true;
        break;
    default:
        break;
    }

    return ret;
}
/**
 * 功 能:
 *      判断是否左符号和右符号是否匹配
 * 参 数:
 *      lch:要判断的左字符
 *      rch:要判断的右字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_Match(char lch, char rch)
{
    bool ret = false;

    switch (lch)
    {
    case '<':
        ret = (rch == '>');
        break;
    case '(':
        ret = (rch == ')');
        break;
    case '[':
        ret = (rch == ']');
        break;
    case '{':
        ret = (rch == '}');
        break;
    default:
        break;
    }

    return !ret;
}

int StackApp_Run(const char *str)
{
    char *tmp = (char *)str;
    int ret = false;

    LinkStack *stack = fun_LinkStack.create();

    while (*tmp != '\0')
    {
        if (StackApp_isLeft(*tmp) == true)
        {
            fun_LinkStack.push(stack, (LinkStackNode *)(tmp));
        }
        else if (StackApp_isRight(*tmp) == true)
        {
            char *ch = (char *)fun_LinkStack.pop(stack);
            if (ch == NULL || StackApp_Match(*ch, *tmp) == false)
            {
                printf("%c does not matched!\n", *tmp);
                ret = false;
            }
        }
        tmp++;
    }

    if (fun_LinkStack.length(stack) == 0 && *tmp == '\0' && ret == true)
    {
        printf("successed\n");
        ret = true;
    }
    else
    {
        printf("Invalid!\n");
        ret = false;
    }

    fun_LinkStack.destroy(stack);

    return ret;
}

func_StackApp fun_StackApp = {
    StackApp_Run
};


2.4、测试案例

  也就是main.c文件的内容。

#include "../src/stackapp/stackapp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern func_StackApp fun_StackApp;

int main(int argc, const char *argv[])
{
    const char *str1 = "abcd<h{rt[tc]}.,123>"; // <{[]}> 匹配
    const char *str2 = "abcd<h{}v>{";          // <{}>{ 缺少 } ,不匹配
    const char *str3 = "abcd}";                // },  缺少 { ,不匹配

    printf("\nstr1 : ");
    fun_StackApp.run(str1);
    printf("\nstr2 : ");
    fun_StackApp.run(str2);
    printf("\nstr3 : ");
    fun_StackApp.run(str3);

    printf("\n");

    return 0;
}

2.4.1、测试案例构建编译

  啪啪啪一顿乱按,就生成了可执行的程序,so easy。。。。
在这里插入图片描述

2.4.2、测试案例测试结果

  还是一顿乱按,就运行了,不过如此。。。
在这里插入图片描述
  好吧,就到这个地方吧。

三、栈的应用 - 中缀转后缀、基于后缀的表达式计算

3.1、说明

  中缀表达式,又称中缀记法,是一个通用的算术或逻辑公式表示方法。操作符以中缀形式处于操作数的中间。中缀表达式是人们常用的算术表示方法。形式为:3+4。而且中缀记法中括号是必需的。计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。
  后缀表达式,又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
  示例:
  中缀表达式: 8+4-6*2
  对应的后缀表达式:8 4 + 6 2 * -

  中缀表达式:1+2*3
  对应的后缀表达式: 1 2 3 * +

  综上所述呢,其实就是:
    中缀表达式符合人类的阅读和思维习惯
    后缀表达式符合计算机的“运算习惯”

3.2、算法解析

3.2.1、中缀转后缀算法分析

  想当然,肯定需要从头到尾遍历表达式。
  1、对于表达式中的数字,那就直接输出;
  2、对于表达式中的符号和运算符
    1)如果是左括号,入栈
    2)如果是运算符,与栈顶符号进行运算优先级的比较
      ①如果栈顶符号的运算优先级低,入栈
      ②如果栈顶符号的运算优先级高,将栈顶符号出栈并输出,然后入栈此运算符
    3)如果是右括号,将栈顶符号出栈并输出,直到匹配到左括号
  3、遍历结束,则将栈中的所有符号依次出栈并输出

3.2.2、基于后缀的表达式计算分析

  想当然,遍历是必须的也是第一步的。
  1、对于表达式中的数字,直接入栈;
  2、对于表达式中的运算符
    1)出栈,即为右边操作数
    2)再出栈,即为左边操作数
    3)根据当前的运算符进行计算
    4)将计算结果入栈
  3、遍历结束
    1)如果栈的长度为1,则栈中的数据即为计算结果
    2)如果栈的长度不为1,那么就是错误表达式

3.3、算法实现

3.3.1、算法实现的目录结构

  屁话不多说,就Cmake。感觉还是屁话多了,算了就这样吧。

stackapp/
├── CMakeLists.txt
├── README.md
├── build
├── main
│   └── main.c
├── runtime
└── src
    ├── linklist
    │   ├── linklist.c
    │   └── linklist.h
    ├── linkstack
    │   ├── linkstack.c
    │   └── linkstack.h
    └── stackapp
        ├── stackapp.c
        └── stackapp.h

7 directories, 9 files

3.3.2、stackapp.h文件

  stackapp.h文件为算法实现中需要的一些用户自定义数据和申明,主要内容如下。

#ifndef __STACKAPP_H__
#define __STACKAPP_H__

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "../linkstack/linkstack.h"

#define null NULL
#define true 0
#define false !true
#define bool int

typedef struct func_StackApp
{
    int (*computer)(const char *, int *);
    int (*transform)(const char *, char *);
} func_StackApp;

#endif


3.3.3、stackapp.c文件

  stackapp.c文件为算法实现的源码,主要内容如下。

#include "stackapp.h"

extern func_LinkStack fun_LinkStack;

/**
 * 功 能:
 *      判断是否为数字
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_isNumber(char ch)
{
    return !(('0' <= ch && '9' >= ch));
}

/**
 * 功 能:
 *      判断是否为运算符
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_isOperator(char ch)
{
    return !(('+' == ch || '-' == ch || '*' == ch || '/' == ch));
}

/**
 * 功 能:
 *      判断是否为左括号
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_isLeft(char ch)
{
    return !(('(' == ch));
}
/**
 * 功 能:
 *      判断是否为有括号
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      成功:true
 *      失败:false
 **/
bool StackApp_isRight(char ch)
{
    return !((')' == ch));
}

/**
 * 功 能:
 *      自定义优先级
 * 参 数:
 *      ch:要判断的字符
 * 返回值:
 *      优先级高:1
 *      优先级低:0
 *      异   常:-1
 **/
int StackApp_Priority(char ch)
{
    if (ch == '+' || ch == '-')
        return 1;
    else if (ch == '*' || ch == '/')
        return 2;
    else
        return -1;
}

/**
 * 功 能:
 *      中缀表达式转换成后缀表达式
 * 参 数:
 *      str:运算表达式
 *      out:转换后的后缀表达式输出
 * 返回值:
 *      成功:0
 *      失败:-1
 **/
int StackApp_Transform(const char *str, char *out)
{
    char *temp = (char *)str;
    if (str == NULL)
        return -1;

    char *buf = (char *)malloc(strlen(str) + 1);
    memset(buf, 0, strlen(str) + 1);
    char *pbuf = buf;

    Linklist *stack = fun_LinkStack.create();
    if (stack == NULL)
        return -1;

    while (*temp != '\0')
    {
        if (StackApp_isNumber(*temp) == true)
        {
            printf("%c ", *temp);
            strncpy(pbuf, temp, 1);
            pbuf++;
        }
        else if (StackApp_isOperator(*temp) == true)
        {
            while (StackApp_Priority(*temp) <= StackApp_Priority((char)(int)fun_LinkStack.top(stack)))
            {
                char ch = (char)(int)fun_LinkStack.pop(stack);
                printf("%c ", ch);
                strncpy(pbuf, (const char *)&ch, 1);
                pbuf++;
            }
            fun_LinkStack.push(stack, (LinkListNode *)(int)(*temp));
        }
        else if (StackApp_isLeft(*temp) == true)
        {
            fun_LinkStack.push(stack, (LinkListNode *)(int)(*temp));
        }
        else if (StackApp_isRight(*temp) == true)
        {
            while (StackApp_isLeft((char)(int)fun_LinkStack.top(stack)) != true)
            {
                char ch = (char)(int)fun_LinkStack.pop(stack);
                printf("%c ", ch);
                strncpy(pbuf, (const char *)&ch, 1);
                pbuf++;
            }
            fun_LinkStack.pop(stack);
        }
        else
        {
            if (*temp != ' ')
            {
                printf("Invalid expression! please retry\n");
                break;
            }
        }

        temp++;
    }

    while ((fun_LinkStack.length(stack) > 0) && (*temp == '\0'))
    {
        char ch = (char)(int)fun_LinkStack.pop(stack);
        printf("%c ", ch);
        strncpy(pbuf, (const char *)&ch, 1);
        pbuf++;
    }

    fun_LinkStack.destroy(stack);

    //printf("%s\n", buf);

    if (out != NULL)
    {
        strcpy(out, buf);
    }

    free(buf);

    return 0;
}

/**
 * 功 能:
 *      将字符转换成对应的数字
 * 参 数:
 *      ch:要转换的字符
 * 返回值:
 *      成功:输入字符的对应的数字
 *      失败:-1
 **/
int StackApp_Str2Int(char ch)
{
    return (ch <= '9' && ch >= '0') ? ch - '0' : -1;
}
/**
 * 功 能:
 *      将字符转换成对应的数字
 * 参 数:
 *      n  : 运算的左边值
 *      m  : 运算的右边值
 *      opt: 运算方式
 *      out: 输出结果    
 * 返回值:
 *      在 out 为 NULL 的时候
 *          如果计算正常,则返回值为计算结果
 *          如果计算不正常,则返回值为 -1
 *      在 out 不为 NULL 的时候
 *          如果计算正常,则返回值为 0
 *          如果计算不正常,则返回值为 -1
 *      特殊情况:
 *          如果 out 为NULL,并且返回值为-1,那么此时的-1有两种情况
 *              1、正常的计算结果
 *              2、异常返回
 **/
int StackApp_Operation(int l, int r, char opt, int *out)
{
    int ret = -1;
    switch (opt)
    {
    case '+':
        ret = l + r;
        break;
    case '-':
        ret = l - r;
        break;
    case '*':
        ret = l * r;
        break;
    case '/':
        ret = l / r;
        break;
    default:
        break;
    }

    if (out != NULL)
        *out = ret;

    return ret;
}

/**
 * 功 能:
 *      进行数学计算
 * 参 数:
 *      str:要计算的数学表达式,为后缀表达式形式
 *      out:计算结果输出
 * 返回值:
 *      在 out 为 NULL 的时候
 *          如果计算正常,则返回值为计算结果
 *          如果计算不正常,则返回值为 -1
 *      在 out 不为 NULL 的时候
 *          如果计算正常,则返回值为 0
 *          如果计算不正常,则返回值为 -1
 *      特殊情况:
 *          如果 out 为NULL,并且返回值为-1,那么此时的-1有两种情况
 *              1、正常的计算结果
 *              2、异常返回
 **/
int StackApp_Work(const char *str, int *out)
{
    if (str == NULL)
        return -1;

    char *temp = (char *)str;
    int ret = -1;

    LinkStack *stack = fun_LinkStack.create();
    if (stack == NULL)
        return -1;

    while (*temp != '\0')
    {
        if (StackApp_isNumber(*temp) == true)
        {
            fun_LinkStack.push(stack, (LinkStackNode *)(int)StackApp_Str2Int((*temp)));
        }
        else if (StackApp_isOperator(*temp) == true)
        {
            int right = ((int)fun_LinkStack.pop(stack));
            int left = ((int)fun_LinkStack.pop(stack));
            int value = 0;
            ret = StackApp_Operation((left), (right), *temp, &value);
            if (ret == -1)
                goto ERROR;

            fun_LinkStack.push(stack, (LinkListNode *)value);
        }
        else
        {
            if (*temp != ' ')
            {
                goto ERROR;
            }
        }
        temp++;
    }
    if ((fun_LinkStack.length(stack) == 1) && (*temp == '\0'))
    {
        ret = (int)fun_LinkStack.pop(stack);
    }
    else
    {
    ERROR:
        printf("Invalid expression! please retry\n");
    }

    fun_LinkStack.destroy(stack);
    if (out != NULL)
        *out = ret;

    return ret;
}

func_StackApp fun_StackApp = {
    StackApp_Work,
    StackApp_Transform
};

3.4、测试案例

3.4.1、测试案例源码

  main.c文件为测试案例源码,主要内容如下。

#include "../src/stackapp/stackapp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern func_StackApp fun_StackApp;

int main(int argc, const char *argv[])
{
    const char *exp = "8+(3-1)*5";
    char abcd1[20] = {0};
    fun_StackApp.transform(exp, abcd1);
    printf("\tabcd1 = %s\n\n", abcd1);

    const char *exp1 = "8+4-6*2";
    char abcd2[20] = {0};
    fun_StackApp.transform(exp1, abcd2);
    printf("\tabcd2 = %s\n\n", abcd2);

    const char *exp2 = "1+2*3";
    char abcd3[20] = {0};
    fun_StackApp.transform(exp2, abcd3);
    printf("\tabcd3 = %s\n\n", abcd3);

    int ret = 0, out = 0;
    ret = fun_StackApp.computer(abcd1, &out);
    printf("ret = %d\n", out);

    ret = fun_StackApp.computer(abcd2, &out);
    printf("ret = %d\n", out);

    ret = fun_StackApp.computer(abcd3, &out);
    printf("ret = %d\n\n", out);

    return 0;
}

3.4.2、测试案例构建编译测试结果

  啪啪啪一顿乱按,就生成了可执行的程序,然后一运行,哈哈哈哈,so easy。。。。
在这里插入图片描述
  1、是的,本测试代码中只是测试数字为下小于10的数字,他在处理运算表达式的时候,默认字符 ‘0’ - ‘9’、’+’ 、’-’、 ‘*’、 ‘/’、 ‘ ’(空格)、‘(’、‘)’等 几个字符是有效的字符。
  2、如果想要计算大于10的数字,那就是不可能的,因为比如数字 10会被当做是 10 来进行处理。
  3、如果想要进行小于0的数字计算,应该也是出现问题的,因为在本例中并没有区别 -(负号) -(减号)的区别。

  好吧,就到这个地方吧。

四、栈的应用 - 进制转换

  哈哈哈,现在来个复杂的哈哈(复杂个屁,来一耳刮子先),来个简单的放松一下吧。

4.1、说明

  接触过计算的都应该知道,二进制,八进制,十进制,十六进制应该能频繁的看见,其实说到底就是进位计数制,是人为定义的带进位的计数方法
  进位制/位置计数法是一种记数方式,故亦称进位记数法/位值计数法,可以用有限的数字符号代表所有的数值。可使用数字符号的数目称为基数(en:radix)或底数,基数为n,即可称n进位制,简称n进制。现在最常用的是十进制,通常使用10个阿拉伯数字0-9进行记数。
  对于任何一个数,我们可以用不同的进位制来表示。比如:十进数57(10),可以用二进制表示为111001(2),也可以用五进制表示为212(5),也可以用八进制表示为71(8)、用十六进制表示为39(16),它们所代表的数值都是一样的。
(以上部分内容来源于《百度百科》)

4.2、算法解析

  比如要转换的数字名称为 NUM,基数为 M 进行说明。
  1、读入要转换的数字 NUM 是不是(废话么)
  2、循环判断 NUM 是否大于 0
    1)将 NUM % M 入栈
    2)将 NUM / M 重新赋值给 NUM
  3、然后出栈,出栈的数据即为转换后的数据

4.3、算法实现

  工程的结构,文件名称都与上面的形式甚至内容都一模一样,就并不废话了。
  stackapp.c文件为算法实现的源码,主要内容如下。

#include "stackapp.h"

extern func_LinkStack fun_LinkStack;

/**
 * 功 能:
 *      进制转换
 * 参 数:
 *      num :要转换的数字,为十进制
 *      base:基数,要转换的进制
 *      out :转换结果输出
 * 返回值:
 *      成功:0
 *      失败:-1
 **/
int StackApp_Conversion(int num, char base, char *out)
{
    if (base <= 0 || base > 16 || out == NULL)
        return -1;

    LinkStack *stack = fun_LinkStack.create();
    if (stack == NULL) return -1;

    char temp = 0;

    while (num)
    {
        temp = num % base;
        /* 将 temp 转换为字符 */
        if (temp > 10) temp += 0x37;
        else temp += 0x30;

        fun_LinkStack.push(stack, (LinkListNode *)(int)(temp));
        num = num / base;
    }

    char *buf = (char *)malloc(fun_LinkStack.length(stack) + 1);
    char *pbuf = buf;
    memset(buf, 0, fun_LinkStack.length(stack) + 1);

    while (fun_LinkStack.length(stack) > 0 && num == 0)
    {
        char tnum = (char)(int)fun_LinkStack.pop(stack);
        // printf("%c ", tnum);
        strncpy(pbuf, (const char *)&tnum, 1);
        pbuf++;
    }

    // printf("buf = %s \n", buf);
    if (out != NULL) strcpy(out, buf);

    free(buf);
    buf = NULL;
    pbuf = NULL;

    return 0;
}

func_StackApp fun_StackApp = {
    StackApp_Conversion
};

4.4、测试案例

4.4.1、测试案例源码

  main.c 文件为测试案例源码,主要内容如下。

#include "../src/stackapp/stackapp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern func_StackApp fun_StackApp;

int main(int argc, const char *argv[])
{
    int num = 0, base = 0;

    printf("请输入要转换的十进制的数字 : ");
    scanf("%d", &num);

    printf("请输入要转换的进制 : ");
    scanf("%d", &base);

    char buf[32] = {0};

    int ret = fun_StackApp.convert(num, base, buf);

    if (ret == 0)
        printf("转换后的数字为 :%s\n", buf);
    else
        printf("转换异常,请确认参数!\n");

    return 0;
}

4.4.2、测试案例构建编译测试结果

  啪啪啪(敲键盘的声音哈 ~ ),嘻嘻~。完事儿,看效果就行了。特意测试一下,16进制,8进制,2进制,10进制等等。。。
在这里插入图片描述
  看一下皮软大大的计算器的转换结果。

在这里插入图片描述

五、我想来个总结

  上面提到的只是栈的应用中比较简单的,也还算是比较典型的几个应用,当然,还有更多更好玩的也更复杂的应用,只能看看日后有没有机会试试、玩玩了,嘻嘻嘻。。。。。。。
  好了吧,有点总结感觉好像顺畅多了,好吧该死的强迫症。

在这里插入图片描述

上一篇:数据结构(六) – C语言版 – 栈和队列 - 栈的设计与实现
下一篇:数据结构(八) – C语言版 – 栈和队列 - 队列的设计与实现

猜你喜欢

转载自blog.csdn.net/zhemingbuhao/article/details/104486942