栈
栈是一种重要的线性结构,它是受限的线性表,是仅能在表的一端进行插入和删除运算的线性表。栈被广泛的运用到各种系统的程序设计中。
1)通常称插入、删除的一段为栈顶,另一端称为栈底。
2)当表中没有元素时称为空栈。
3)栈为后进先出的线性表,简称LIFO表。
栈的修改是按照后进先出的原则进行。每次删除的总是当前栈中最新的元素,即最后插入的元素,而最先插入的则是被放在栈的底部,要到最后才能删除。
利用数组来完成的栈是顺序表
但是我近期用得都是链表 所以还是链式栈比较好理解
代码使用的是C++语言
首先代码我们分步来完成
先是头文件
声明
//Stack.h
#pragma once
#include <iostream>
#include <conio.h>
using namespace std;
typedef int stack_element;
struct node
{
stack_element str;
node* next;
};
typedef struct {
node* top;
int num;
}stack;
void init(stack* p);
int StackEmpty(stack* sp);
void push(stack* sp, stack_element c);
stack_element pop(stack* sp);
void Show(stack* sp);
这段代码完成了对今后代码中所有函数结构体以及变量的声明。
typedef struct {
node* top;
int num;
}stack;
这段代码是当时从书中找到的。但是个人认为这样的写法不太好理解 所以我就换了一种写法
struct stack{
node* top;
int num;
};
这样的代码与上面的代码道理是一样的
结构体指针的声明还是 stack * p;
然后,置于其中的num用于计数 来确定栈的个数
初始化函数
其次,我们要对指针或者是变量进行初始化
我们放在同一个函数中
void init(stack* p)
{
p->top = NULL;
p->num = 0;
}
防止top是野指针,先赋值为空
接着 要保证计数变量从0开始 我们赋值为0
因此在接下来的主函数中都要记得使用这个函数来初始化变量
判断是否为空栈函数
int StackEmpty(stack* sp)
{
return sp->top == NULL ? 1 : 0;
}
这个函数实现了对栈是否为空进行了判断
如果是空就返回真
此处利用了三目运算符
进栈函数
void push(stack* sp, stack_element c)
{
node* np;
np = new node;
np->str = c;
np->next = sp->top;
sp->top = np;
sp->num++;
}
形参是结构体指针和stack_element类型的值
此处是int类型
精髓:
np->next = sp->top;
sp->top = np;
np是第一个结构体的指针 结点里的指针域指向top
让top指到结点
那么next就会指向结点
第二次 申请一个新的结点 赋值后
next处指向前面的那个结点
top指针指向新的结点
那么不断循环就形成了一个栈 这个栈的顶部指针的top处next是指向前面的一个结点的 这个地方就个链表有很大的不同
而且这样的指向对后面出栈有很大的好处
因为可以利用next来把top持续指向栈顶
当然 sp->num++;告诉栈变长了
出栈函数
stack_element pop(stack* sp)
{
if (StackEmpty(sp))
return 0;
else
{
node* tp;
stack_element value;
tp = sp->top;
value = tp->str;
sp->top = tp->next;
free(tp);
sp->num--;//后来加上去的
tp = NULL;
return value;
}
}
之前就判断栈是否为空
不是空栈就操作
声明一个临时指针tp
就像是我的世界中的那个tp指令一样
tp=sp->top;
把tp传送到top指针处,就是栈顶
让变量value赋值为栈顶的str;
然后top跳到下一个结点
释放栈顶的内存
因此top就又是指向栈顶的指针。
上面说的好处就是这里了 next是指向前面的,就可以保证top永远指向栈顶
遍历函数
在遍历函数这个地方发生了一个严重的错误
这个错误是在写博客 写到这个地方的时候发现的
就是遍历了一遍后这个栈变成了空栈
防止错误
就是要把栈内的所有元素都暂时放到另一个栈 遍历结束后把另一个栈放回原来的栈中
代码如下
void Show(stack* sp)
{
stack_element sta;
stack* po;
po = new stack;
for (int i = 0; i < sp->num; i++)
{
sta = pop(sp);
cout << "此时的栈顶:" << sta << endl;
push(po, sta);
}
for (int i = 0; i < sp->num; i++)
{
sta = pop(po);
push(sp, sta);
}
po = NULL;
}
建立一个临时指针po
初始化po的内存之后
进入for循环 然后让变量sta接收出栈的元素 但是一旦出栈 这个栈元素就没有了
但是栈元素暂时放到了sta中 利用cout输出 再利用进栈函数把原来是栈顶的元素放到po指针指向的栈底
下一个for循环把po指针指向的栈元素取出
放到原来的栈中 让po变成空指针
防止出现野指针
原来栈顶变成栈底 两次后顺序是不变的 所以不用担心顺序的问题
以上是对所有函数的实现
接下来回归正题
数组元素的取反就是让数组的所有元素倒序
int main()
{
int sta;
stack* p;
p = new stack;
stack_element input;
int arry[N];
init(p);
for (int i = 0; i < N; i++)
{
cin >> arry[i];
push(p, arry[i]);
}
for (int i = 0; i < N; i++)
cout << arry[i] << " ";
cout << endl;
for (int i = 0; i < N; i++)
arry[i] = pop(p);
for (int i = 0; i < N; i++)
cout << arry[i] << " ";
if (StackEmpty(p))
cout << "kong";
return 0;
}
第一个for循环先是输入数组的值
然后输出数组确保输入了
第三个就是出栈 让数组变成出栈的值
注意的是这个地方栈已经是空栈了
利用判断函数确定这个栈是空
因为上次遍历的错误的结果
这个错误让我写出了清空栈的函数
其实很简单,就是让所有元素都出来
void Delete(stack* p)
{
for (int i = 0; i < p->num; i++)
pop(p);
}
这些函数都能体现出计数变量的重要性
确保这个函数可以使用我现场写一段代码
int main()
{
stack* p;
p=new stack;
init(p);
push(p,123);
if(StackEmpty())
cout<<"空"<<endl;
else
cout<<"非空"<<endl;
Delete(p);
if(StackEmpty())
cout<<"空"<<endl;
else
cout<<"非空"<<endl;
return 0;
}
此处如果没有init(),那么就不知道num的值
所以在遍历栈的时候就不知道num的值
而且上边的代码 在出栈的时候 应该加上num–;不然只有加 没有减 那么栈的长度是不真实的。
现在我加上去了,但是刚写的时候是没有的
这些东西包括链表 都涉及内存 让人不清楚这个数值是不是真的存在
就像是栈 出栈后,值没有了。但是我刚开始没有发现。
好了好了,结束了
这就是栈
栈不像我以前写链表 这篇博客写完栈后就不会有下篇了
最后是镇店之图