鱼C论坛

 找回密码
 立即注册
查看: 2234|回复: 0

[学习笔记] 006-栈

[复制链接]
发表于 2018-9-25 22:56:04 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 moc 于 2018-9-26 09:10 编辑

1、栈的基本概念
栈是一种   特殊的线性表
它的特殊之处在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行,这也就使得:栈底是固定的,最先进栈的只能在栈底。
栈是仅能在一端操作的线性表:
        栈顶(Top):允许操作的一端;
        栈底(Bottom):不允许操作的一端。
栈的插入操作,叫做进栈,压栈,入栈
栈的删除操作,叫做出栈,弹栈
59.jpg
Stack的常用操作:
        创建栈;
        销毁栈;
        清空栈;
        进栈;
        出栈;
        获取栈顶元素;
        获取栈的大小。
2、栈的顺序存储及实现
栈是一种特殊的线性表,所以实现顺序存储的栈  可以在顺序存储的线性表的基础上来实现。
当用线性表的顺序存储来模拟栈时,如果把线性表的尾部作为栈顶,不会涉及到栈元素的大小大量移动,效率更高。
  1. // 创建栈  相当于  创建线性表
  2. SeqStack* SeqStack_Create(int capacity)
  3. {
  4.         return SeqList_Create(capacity);
  5. }

  6. // 销毁栈  相当于  销毁线性表
  7. void SeqStack_Destroy(SeqStack* stack)
  8. {
  9.         SeqList_Destroy(stack);
  10. }

  11. // 清空栈  相当于  清空线性表
  12. void SeqStack_Clear(SeqStack* stack)
  13. {
  14.         SeqList_Clear(stack);
  15. }

  16. // 向栈中压入元素 相当于 向链表的尾部插入元素
  17. int SeqStack_Push(SeqStack* stack, void* item)
  18. {
  19.         return SeqList_Insert(stack, item, SeqList_Length(stack));
  20. }

  21. // 从栈中弹出元素 相当于 从链表的尾部取出元素
  22. void* SeqStack_Pop(SeqStack* stack)
  23. {
  24.         return SeqList_Delete(stack, SeqList_Length(stack) - 1);
  25. }

  26. // 获取栈顶元素 相当于 获取链表的尾部元素
  27. void* SeqStack_Top(SeqStack* stack)
  28. {
  29.         return SeqList_Get(stack, SeqList_Length(stack) - 1);
  30. }

  31. // 获取栈的大小 相当于 获取链表的实际长度
  32. int SeqStack_Size(SeqStack* stack)
  33. {
  34.         return SeqList_Length(stack);
  35. }

  36. // 获取栈的容量 相当于 获取链表的实际容量
  37. int SeqStack_Capacity(SeqStack* stack)
  38. {
  39.         return SeqList_Capacity(stack);
  40. }
复制代码

3、栈的链式存储及实现
同样可以用线性表的链式存储来   模拟 栈的链式存储结构。
需要注意
        ① 在入栈时,需要将栈的业务结点(void*)  转化为  线性表的业务结点(TLinkStackNode*);
        ② 栈结点是malloc而来的   于是要进行栈结点的生命周期管理
  1. typedef struct  _tag_LinkStackNode // 链表的业务结点
  2. {
  3.         LinkListNode node;   
  4.         void* item;   // 栈的业务结点
  5. }TLinkStackNode;

  6. // 创建栈  相当于  创建线性表
  7. LinkStack* LinkStack_Create()
  8. {
  9.         return LinkList_Create();
  10. }

  11. // 销毁栈  相当于  销毁线性表
  12. void LinkStack_Destroy(LinkStack* stack)
  13. {
  14.         LinkStack_Clear(stack);
  15.         LinkList_Destroy(stack);
  16. }

  17. // 清空栈  相当于  清空线性表
  18. // 清空栈的时候 涉及到 栈元素生命周期的管理
  19. // 所有入栈的元素都是 malloc过的,清空时需要弹出栈中所有元素
  20. void LinkStack_Clear(LinkStack* stack)
  21. {
  22.         if (stack == NULL)
  23.         {
  24.                 return;
  25.         }
  26.         while (LinkStack_Size(stack) > 0)
  27.         {
  28.                 LinkStack_Pop(stack);  // 在此函数中释放内存
  29.         }
  30. }

  31. // 向栈中压入元素 相当于 向链表的头部插入元素
  32. // void* item 栈的业务结点   --->  链表的业务结点
  33. int LinkStack_Push(LinkStack* stack, void* item)
  34. {
  35.         int ret = 0;
  36.         TLinkStackNode *tmp = NULL;
  37.         tmp = (TLinkStackNode*)malloc(sizeof(TLinkStackNode));
  38.         if (tmp == NULL)
  39.         {
  40.                 return -1;
  41.         }
  42.         memset(tmp, 0, sizeof(TLinkStackNode));
  43.         tmp->item = item;

  44.         ret = LinkList_Insert(stack, (LinkListNode*)tmp, 0);
  45.         if (ret != 0)
  46.         {
  47.                 printf("Fun LinkList_Insert Error: %d\n", ret);
  48.                 if (tmp != NULL)
  49.                 {
  50.                         free(tmp);
  51.                 }
  52.         }
  53.         return ret;
  54. }

  55. // 从栈中弹出元素 相当于 从链表的头部取出元素
  56. void* LinkStack_Pop(LinkStack* stack)
  57. {
  58.         void* item = NULL;  // 栈的业务结点
  59.         TLinkStackNode *tmp = NULL;
  60.         tmp = (TLinkStackNode*)LinkList_Delete(stack, 0);
  61.         if (tmp == NULL)
  62.         {
  63.                 return NULL;
  64.         }
  65.         item = tmp->item;
  66.         // 在入栈时分配了内存,所以在出栈时需要释放内存
  67.         free(tmp);
  68.         return item;
  69. }


  70. // 获取栈顶元素 相当于 获取链表的头部元素
  71. void* LinkStack_Top(LinkStack* stack)
  72. {
  73.         TLinkStackNode *tmp = NULL;
  74.         tmp = (TLinkStackNode*)LinkList_Get(stack, 0);
  75.         if (tmp == NULL)
  76.         {
  77.                 return NULL;
  78.         }
  79.         return tmp->item;
  80. }

  81. // 获取栈的大小 相当于 获取链表的实际长度
  82. int LinkStack_Size(LinkStack* stack)
  83. {
  84.         return LinkList_Length(stack);
  85. }
复制代码

4、栈的典型应用
1. 就近匹配 (检测代码中括号是否成对出现)
当需要检测成对出现但又不相互邻近的事物时,可以使用“栈”的后进先出特性。
算法思路:
        从第一个字符开始扫描;
        当遇见普通字符时忽略;
        当遇见左符号时,压入栈中;
        当遇见右符号时,从栈中弹出栈顶元素,并进行匹配:
                匹配成功: 继续下一个字符;
                匹配失败:立即停止,并报错;
        结束:
                成功:所有字符扫描完毕,且栈为空;
                失败:匹配失败或所有字符串扫描完毕但栈非空。

2. 中缀 转 后缀
我们平时的数学表达式,如“1+2*3”,是把运算符放在中间,即中缀表达式,它符合人类的思考习惯,但并不适合计算机运算。
20世纪50年代波兰科学家提出了一种将运算符放在后面的后缀表达式。
后缀表达式:指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行。
实例:
5 + 4   ==>  5 4 +
1 + 2 * 3  ==>  1 2 3 * +
8 + (3 - 1) * 5  ==> 8 3 1 - 5 * +
利用栈实现:中缀表达式  转换为  后缀表达式:
算法思路:
        逐个扫描中缀表达式中的数字和符号
        对于数字:  直接输出
        对于符号:
                左括号: 进栈
                运算符号:与栈顶符号进行优先级比较
                        若栈顶符号优先级低: 此符号进栈(默认左括号优先级最低)
                        若栈顶符号优先级不低:将栈顶符号弹出并输出,之后入栈
                右括号:将栈顶元素弹出并输出,直到匹配到左括号。
        遍历结束:将栈中所有符号弹出并输出。

3. 后缀表达式的计算
算法思路:
        遍历后缀表达式中的数字和符号
        对于数字:进栈
        对于符号:
                从栈中弹出左操作数
                从栈中弹出右操作数
                根据符号进行运算
                将运算结果压入栈中
        遍历结束:栈中唯一数字即为运算结果。

完整代码: 栈的顺序存储.zip (2.59 KB, 下载次数: 10)
                栈的链式存储.zip (2.93 KB, 下载次数: 7)
                栈的典型应用.zip (4.5 KB, 下载次数: 8)

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-6-8 21:59

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表