栈的定义
栈是一个后进先出(Last In Fist Out, LIFO)的线性表,它要求只在表尾进行删除和插入操作。
所谓的栈,其实就是一个特殊的线性表(顺序表、链表),但是它在操作上有一些特殊的要求和限制:
- 栈的元素必须“后进先出”
- 栈的操作只能在这个线性表的表尾进行。
- 对于栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)。
栈的插入和删除操作
栈的插入操作(Push),叫做进栈,也称为压栈,入栈。
栈的删除操作(Pop),叫做出栈,也称为弹栈。
02.
栈的顺序存储结构
因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也分为栈的顺序存储结构和栈的连式存储结构。
最开始栈中不包含任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
入栈与出栈操作typedef struct
{
ElemType *base;
ElemType *top;
int stackSize;
}sqStack;
这里定义了一个顺序存储的栈,它包含了三个元素:base,top,stackSize。其中base是指向栈底的指针变量,top是指向栈顶的指针变量,stackSize指示栈的当前可使用的最大容量。
创建一个栈
#define STACK_INIT_SIZE 100
initStack(sqStack *s)
{
s->base = (ElemType *)malloc( STACK_INIT_SIZE * sizeof(ElemType) );
if( !s->base )
exit(0);
s->top = s->base; // 最开始,栈顶就是栈底
s->stackSize = STACK_INIT_SIZE;
}
入栈操作
入栈操作又叫压栈操作,就是向栈中存放数据。
- 入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,直到栈满为止
#define SATCKINCREMENT 10
Push(sqStack *s, ElemType e)
{
// 如果栈满,追加空间
if( s->top – s->base >= s->stackSize )
{
s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
if( !s->base )
exit(0);
s->top = s->base + s->stackSize; // 设置栈顶
s->stackSize = s->stackSize + STACKINCREMENT; // 设置栈的最大容量
}
*(s->top) = e;
s->top++;
}
出栈操作
出栈操作就是在栈顶取出数据,栈顶指针随之下移的操作。
- 每当从栈内弹出一个数据,栈的当前容量就-1
Pop(sqStack *s, ElemType *e)
{
if( s->top == s->base ) // 栈已空空是也
return;
*e = *--(s->top);
}
清空一个栈
所谓清空一个栈,就是将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。
因此我们只要将s->top的内容赋值为s->base即可,这样s->base等于s->top,也就表明这个栈是空的了。这个原理跟高级格式化只是但单纯地清空文件列表而没有覆盖硬盘的原理是一样的。其实就是说栈所在磁盘空间的数据并未清除,只是逻辑上清空了一个栈。
ClearStack(sqStack *s)
{
s-top = s->base;
}
销毁一个栈
销毁一个栈与清空一个栈不同,销毁一个栈是要释放掉该栈所占据的物理内存空间,因此不要把销毁一个栈与清空一个栈这两种操作混淆。
DestroyStack(sqStack *s){
int i, len;
len = s->stackSize; //当前栈中的元素个数
for( i=0; i < len; i++ ){
free( s->base ); //循环释放栈所占的物理内存空间
s->base++; //栈底指针上移
}
s->base = s->top = NULL;
s->stackSize = 0;
}
计算栈的当前容量
计算栈的当前容量也就是计算栈中元素的个数,因此只要返回s.top-s.base即可。
- 注意,栈的最大容量是指该栈占据内存空间的大小,其值是s->stackSize,它与栈的当前容量不是一个概念哦。
int StackLen(sqStack s){
return(s.top – s.base); // 初学者需要重点讲解
}
例题:
题目:利用栈的数据结构特点,将二进制转换为十进制数。
for( i=0; i < len; i++ )
{
Pop(&s, &c);
sum = sum + (c-48) * pow(2, i);
}
二进制转十进制操作
题目:利用栈的数据结构特点,将二进制转换为八进制数。
for( i=0; i < len; i+=3 )
{for( j=0; j < 3; j++ )
{
Pop( &s1, &c ); // 取出栈顶元素
sum = sum + (c-48) * pow(2, j);if( s1.base == s1.top )
{break;
}
}
Push( &s2, sum+48 );
sum = 0;
}
题目:利用栈的数据结构特点,将二进制转换为十六进制数。
for( i=0; i < len; i+=4 )
{for( j=0; j < 4; j++ )
{
Pop( &s1, &c ); // 取出栈顶元素
sum = sum + (c-48) * pow(2, j);if( s1.base == s1.top )
{break;
}
}switch( sum )
{case 10: sum = 'A'; break;case 11: sum = 'B'; break;case 12: sum = 'C'; break;case 13: sum = 'D'; break;case 14: sum = 'E'; break;case 15: sum = 'F'; break;default: sum += 48;
}
Push( &s2, sum );
sum = 0;
}
03.
栈的链式存储结构
teypedef struct StackNode
{
ElemType data; // 存放栈的数据
struct StackNode *next;
} StackNode, *LinkStackPtr;
teypedef struct LinkStack
{
LinkStackPrt top; // top指针
int count; // 栈元素计数器
}
进栈操作
对于栈链的Push操作,假设元素值为e的新结点是s,top为栈顶指针,我们得到如下代码:
Status Push(LinkStack *s, ElemType e)
{
LinkStackPtr p = (LinkStackPtr) malloc (sizeof(StackNode)); p->data = e; p->next = s->top; s->top = p; s->count++;
return OK;
}
出栈操作
至于链栈的出栈Pop操作,假设变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可。
Status Pop(LinkStack *s, ElemType *e)
{
LinkStackPtr p;
if( StackEmpty(*s) ) // 判断是否为空栈
return ERROR;
*e = s->top->data;
p = s->top;
s->top = s->top->next;
free(p);
s->count--;
return OK;
}
逆波兰表达式
逆波兰表达式又叫做后缀表达式。逆波兰表示法是波兰逻辑学家J・卢卡西维兹(J・ Lukasewicz)于1929年首先提出的一种表达式的表示方法 。后来,人们就把用这种表示法写出的表达式称作“逆波兰表达式”。逆波兰表达式把运算量写在前面,把算符写在后面。
我们人对于这种后缀表达式并不习惯,但只要以后你学习了编译原理这门课,你就会知道计算机是多么的爱后缀表达式。
逆波兰表达式计算过程 逆波兰表达式实现代码int main(){
sqStack s;
char c;
double d, e;
char str[MAXBUFFER];
int i = 0;
InitStack(&s);
printf("Please input:");
scanf("%c", &c);
while(c != '#')
{
while( isdigit(c))
{
str[i++] = c;
//'\0'一般放在字符串的结束处,表示字符串的结束,其是ascii值为0的字符的转义。
str[i] = '\0';
if(i >= 10)
{
printf("ERROR: The data entered is too large!\n");
}
scanf("%c", &c);
if( c == ' ')
{
d = atof(str);
Push(&s, d);
i = 0;
break;
}
}
switch(c)
{
case '+':
Pop(&s, &e);
Pop(&s, &d);
Push(&s, d+e);
break;
case '-':
Pop(&s, &e);
Pop(&s, &d);
Push(&s, d-e);
break;
case '*':
Pop(&s, &e);
Pop(&s, &d);
Push(&s,d*e);
break;
case '/':
Pop(&s, &e);
Pop(&s, &d);
if(e != 0){
Push(&s, d/e);
}
else{
printf("\nERROR: Divisor is zero !\n");
}
break;
}
scanf("%c", &c);
}
Pop(&s, &d);
printf("\n The result is :%f\n", d);
return 0;
}
04.
人生建议(不是我,是前辈)
1、技术不是万能的,但打好基础依然必要。
2、尽量去大公司,小公司的风险高。不要让别人的梦想绑架你的前程。
3、没有完美的团队,也没有完美的领导。放弃幻想,学会坚持,学会接受不完美。4、要不要跳槽,多久跳一次,没有固定答案,取决于你的实际情况。
5、经历过才懂是代价最大的获取知识的途径,应该尽量从别人的经历学到知识,尽量少走弯路。
6、多思考,找到事情背后的真相,多跟人交谈,了解行业趋势。
7、选择大于努力,去体量大、发展快、天花板高的行业。选择很重要,努力也重要 8、互联网对中年人确实不够友好,这并非贩卖焦虑,相反,人无远虑、必有近忧,要提早做准备。
9、人生没有捷径,勤劳致富,这些奉若圭臬的名言,有可能是假的。人生如果比作一场游戏,有些悟性高的,便能更快领悟到其中的奥妙和诀窍,便能通过最高效玩法升级,打排位赛,而悟性低的一直在刷低经验怪和副本。
10、欲戴皇冠,必承其重,一旦选择远方,那便一往无前,每个人生都有高潮和低谷,无论身处顺境逆境,都应该保持乐观的心态,哪怕世界以痛吻我,我也要报之以歌。
景禹的建议很简单,坚持下去,不论你选择了那一条道路,坚定地走下去,生活定会对你报之以歌! 景禹本科是个不知名的小211,大学生活太多坎坷,后来考研进了兰州大学,今年毕业,签了一份自己满意的工作,最重要的是,研究生期间,我认识了自己现在的女友,这可能是我感到最幸福的事情,相信你自己,你终究会得到你想要的,遇见你想遇见的人,珍惜这一切的美好!爱你们,祝你们都能有一个美好的未来生活~~
烦请各位看到文章的贵人能帮忙随手送朵花,下方留言,哪怕下方骂骂景禹 「贱贱」也好,哈哈,就到这里啦,明天见!!!
推荐阅读
史上最全的数据结构flash演示动画!(最全整理,必须收藏!) 链式存储结构之双向链表与跳表 链式存储结构之循环链表 被人遗忘了的静态链表END
作者:景禹,一个追求极致的共享主义者,想带你一起拥有更美好的生活,化作你的一把伞。