Stack by pointer
Posted 仪式黑刃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Stack by pointer相关的知识,希望对你有一定的参考价值。
前言:因为栈的很多操作是基于表的,所以这篇文章里的例程就不再大面积地写注释了,有不理解的地方可以翻看之前的链表笔记,或者直接写在评论区。
咳咳,说到这个栈,很多人乍听之下感觉很陌生、卧槽这是什么玩意。其实生活中随处可见,在一些小餐馆,客人不多的时候,椅子都是放成一摞的,一个叠一个。有客人来了就搬下来一把——肯定是搬最上面那一把,没人会从下面搬凳子吧2333 用完之后从上面再叠放上去,这是一个例子。 刷知乎或者看网页的时候需要返回,我们按一下,就跳转到上一个页面了,那这是怎么做的呢?我们用直觉考虑一下,应该是浏览器把每一次操作的结果都保存下来,要返回的时候,就把当前层移除——移除的是最新的那一层。如果有新的跳转或者其他操作,就依次叠放到之前最新的上面。类似的,主流的文本编辑器也都支持撤销操作,我们的编辑操作被记录在一个栈中,一旦出现误操作,只需要按下撤销(一般是control+z)按钮,就可以取消最近的一次操作,并回到之前的状态。
而我们在写程序的时候会涉及到不同函数之间的相互调用,被调函数(callee)执行完后,把权限返还给主调函数(caller)这也用到了“栈”这种结构。许多程序语言本身就是建立于栈结构上的,比如Postscript和Java运行环境都是基于栈结构的虚拟机。
我们再联系上一节提到的那个“free list”,可以很明显的感到一个性质:这些行为的次序,都是增加的时候从最新的那一端增加,要移除的时候,往往是把“最后移动的元素”首先给拿出去。这就叫后进先出(Last In First Out)。而且相对于一般的序列结构,它的数据操作范围都仅限于整个表的末端。
对栈的基本操作有Push(进栈or压栈)和Pop(出栈),前者相当于插入,后者则是删除最后的元素。
这是一个进行若干操作后的抽象栈,一般的模型是存在某个元素位于栈顶,而这是唯一的可见元素。不过这样说可能有点不好理解,那比如说一摞椅子。
这就可以视作一个栈,为了维持这一放置形式,对这个栈的操作只能在顶部实施:新的椅子只能叠放到最顶端;反过来只有最顶端的椅子才能被取走。因此和这个实例相比照,栈中可操作的一端被叫做栈顶,而另外一个无法操作的盲端被称为栈底。
就像这样。
因为栈是一个表,所以任何实现表的方法都能实现栈,这次就说一下好理解的的指针实现吧,比数组貌似好理解一些。用单链表实现的话,我们要通过在表顶端插入来实现Push,通过删除顶端元素实现Pop。而Top操作仅仅是返回顶端元素的值。不过在很多时候都是把Pop和Top合二为一的。本来可以用前一节的代码段,不过为了清楚起见,还是从头开始写吧
和之前一样,先给出一些前提性声明,实现栈同样要用到表头。
1 struct Node; 2 typedef struct Node *PtrToNode; 3 typedef PtrToNode Stack; 4 struct Node{ 5 int Element; 6 PtrToNode Next; 7 };
测试空栈与测试空表的方式一样。
1 int IsEmpty(Stack i){ 2 return i->Next==NULL; 3 }
创建一个栈的话也很简单,只需要建立一个头结点就好。
1 Stack Creat(){ 2 Stack S; 3 S=(Stack)malloc(sizeof(struct Node)); 4 if(S==NULL) 5 printf("out of space!!!"); 6 else 7 S->Next=NULL; 8 MakeEmpty(S); 9 return S; 10 }
现在是中场问答时间,我们创建一个栈之后,里面会有什么?就是仅仅申请一块内存,然后什么也不做。里面会有——
垃圾数据,对吧。这是上学期的知识,声明一个变量后,系统会随机填充一段数据,我们不知道里面是什么,但是,我们能确定一点——这东西十有八九不是我们所期望的,因此我们需要把它扔掉。这就是MakeEmpty的意义。
1 void MakeEmpty(Stack S){ 2 if(S==NULL) 3 printf("Must creat a stack first"); 4 else 5 while(!IsEmpty(S)) 6 Pop(S); 7 }
关于这个Pop函数是什么,emmm接着往后看吧,你看这涉及到了函数间的相互调用,就是运用了栈的特性。还有一个好玩的事实,就是——我们在写一个栈的时候已经用到了栈的环境,用栈来写栈,这就陷入递归了233 从这个角度再次理解一下递归吧,毕竟理解递归是筛选合格程序员的一道门槛。
创建之后就该讨论对栈的各项操作了,主要就三个:出栈,入栈和取栈顶元素。先说入,有入才有出嘛,Push是作为向链表前端进行插入而实现的,其中表的前端作为栈顶。所以实现起来也很顺畅
1 void Push(int X,Stack S){ 2 Stack TemCell; 3 TemCell=(Stack)malloc(sizeof(S)); 4 if(S==NULL) printf("Out of space!!!"); 5 else{ 6 TemCell->Element=X; 7 TemCell->Next=S->Next; 8 S->Next=TemCell; 9 } 10 }
这里提一句,S是表头,里面什么都不存,而第一个有效元素是S->Next,原因是S仅作为一个地址说明,告诉我们第一个有效元素“在哪”,我们不可能指望S存数据,不然的话,谁来告诉我们这个栈的顶在哪呢?这很重要,理解这个观点是看懂下面所有函数的基础,是重中之重。
接着说取栈顶元素,Top的实施是通过考察整个表在第一个位置上的元素而完成的,也就是把Head的元素返回
1 int Top(Stack S){ 2 if(!IsEmpty(S)) 3 return S->Next->Element; 4 printf("Empty stack"); 5 return 0; 6 }
最后,Pop是通过删除表的前端元素而实现的。
1 void Pop(Stack S) { 2 PtrToNode FirstNode; 3 if(IsEmpty(S)) 4 printf("Empty stack"); 5 else{ 6 FirstNode=S->Next; 7 S->Next=S->Next->Next; 8 free(FirstNode); 9 } 10 }
到这里,已经很清楚了,所有的操作均花费常数时间,因为这些函数没有任何地方涉及到栈的size,更不用说依赖于size的循环了。但是这种实现方法的缺点在于对malloc和free的调用开销是昂贵的。避免这个缺点的方法就是用数组实现,具体的实现方法以后会说到,在后面几篇文章里会详细讨论栈的应用和数组实现。
下面写了一个测试程序
1 #include <stdio.h> 2 #include <stdlib.h> 3 struct Node; 4 typedef struct Node *PtrToNode; 5 typedef PtrToNode Stack; 6 struct Node{ 7 int Element; 8 PtrToNode Next; 9 }; 10 //函数签名 11 int IsEmpty(Stack i); 12 void Push(int X,Stack S); 13 int Top(Stack S); 14 void Pop(Stack S); 15 void MakeEmpty(Stack S); 16 Stack Creat(); 17 void Traverse(Stack S); 18 19 //入口 20 int main(){ 21 Stack S; 22 S=Creat(); 23 int n; 24 printf("Please input all elements to complete a stack,finished by 0\\n"); 25 while (scanf("%d",&n)&&n) 26 Push(n, S); 27 Traverse(S); 28 printf("Input imperative(1:top\\t2:remove\\t3:add),0 to quit\\n"); 29 while (scanf("%d",&n)&&n) { 30 if (n==1) 31 printf("Top element:%d\\n",Top(S)); 32 else if(n==2){ 33 Pop(S); 34 Traverse(S); 35 } 36 else if(n==3){ 37 printf("number:"); 38 scanf("%d",&n); 39 Push(n, S); 40 Traverse(S); 41 } 42 else 43 printf("Input again,it is invalid"); 44 } 45 } 46 47 //接口内部一览 48 int IsEmpty(Stack i){ 49 return i->Next==NULL; 50 } 51 void Push(int X,Stack S){ 52 Stack TemCell; 53 TemCell=(Stack)malloc(sizeof(S)); 54 if(S==NULL) printf("Out of space!!!"); 55 else{ 56 TemCell->Element=X; 57 TemCell->Next=S->Next; 58 S->Next=TemCell; 59 } 60 } 61 62 int Top(Stack S){ 63 if(!IsEmpty(S)) 64 return S->Next->Element; 65 printf("Empty stack"); 66 return 0; 67 } 68 69 void Pop(Stack S) { 70 PtrToNode FirstNode; 71 if(IsEmpty(S)) 72 printf("Empty stack"); 73 else{ 74 FirstNode=S->Next; 75 S->Next=S->Next->Next; 76 free(FirstNode); 77 } 78 } 79 void MakeEmpty(Stack S){ 80 if(S==NULL) 81 printf("Must creat a stack first"); 82 else 83 while(!IsEmpty(S)) 84 Pop(S); 85 } 86 Stack Creat(){ 87 Stack S; 88 S=(Stack)malloc(sizeof(struct Node)); 89 if(S==NULL) 90 printf("out of space!!!"); 91 else 92 S->Next=NULL; 93 MakeEmpty(S); 94 return S; 95 } 96 void Traverse(Stack S){ 97 for (; S->Next; S=S->Next) { 98 printf("%d->",S->Next->Element); 99 } 100 printf("NULL\\n"); 101 }
然后自己调试一下吧,这会加深你对栈的理解的,祝食用愉快~
以上是关于Stack by pointer的主要内容,如果未能解决你的问题,请参考以下文章
在这个 spark 代码片段中 ordering.by 是啥意思?
link them together by means of pointers
[LeetCode] 844. Backspace String Compare_Easy tag: Stack **Two pointers
[RxJS] Implement RxJS `switchMap` by Canceling Inner Subscriptions as Values are Passed Through(代码片段