前言
栈作为基础的数据结构,拥有着先进后去的特点,但他的本质依旧是一个线性表,在函数管理调用和算法等方面有广泛的应用,笔者也写过一些用栈的算法题,本篇博客是介绍如何自己手搓一个栈出来,虽然前任已经替我们总结好了,我们可以直接用,但是知其然和知其所以然还是有很大区别的话不多说,开始吧
栈的概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
栈的部分方法
本次我们手搓的栈,有如下功能
void push(int x);
// 进栈
int size();
// 栈内有多少元素
int peek();
// 显示最近入栈的元素
int pop();
// 弹出栈顶的元素
boolean empty();
// 判断是否为空
内容不多,大致的流程也和此前的手搓顺序表类似
JavaDS-学习数据结构之如果从零开始手搓顺序表,顺带学习自定义异常怎么用!-CSDN博客
接口
我们首先定义一个接口
在接口中,我们规定了我们的栈有那些方法,接下来我们就要实现这些方法
package stack;
public interface MyStack
{
void push(int x);
// 进栈
int size();
// 栈内有多少元素
int peek();
// 显示最近入栈的元素
int pop();
// 弹出栈顶的元素
boolean empty();
// 判断是否为空
}
Stack类
属性
在Stack类中,首先要定义好各类属性,我们这次是以数组的方式来实现,当然了,双向链表也可以,但是单向链表不行,至于为什么后面会说
private int [] elem;
private int usedsize;
//存放数据的下标
private static final int DEFAULT_NUM =10;
public Stack()
{
this.elem = new int[DEFAULT_NUM];
this.usedsize = usedsize;
}
我们默认的构造是一个大小为10的数组
push方法
push方法也就是入栈,那么,我们应该怎么入栈的,通过栈的特点我们可以知道,每次都是从最后的位置插进去,换而言之,就是从数组的最后面一个个插进去,那么我们前面已经定义了usedsize,很显然,
我们就是通过usedsize,令我们需要插的元素的下标为usedsize即可
具体写法如下
private void checkcap()
{
if(usedsize==elem.length)
{
elem= Arrays.copyOf(elem,elem.length*2);
}
}
@Override
public void push(int x)
{
checkcap();
elem[usedsize]=x;
usedsize++;
//保证永远是放在数组的最后一个位置
}
可以看到,我们首先检查我们的数组大小是否够,不够就扩容
然后插入元素,最后usedsize++;
size方法
这个EZ ,直接返回usedsize
@Override
public int size()
{
return usedsize;
}
empty方法
同样不难就是了
判断usedsize是否为0
@Override
public boolean empty()
{
if(usedsize==0)
{
return true;
}
return false;
}
}
pop方法
pop方法就是弹出栈顶的元素,换而言之,就是删除最新插入的元素,那我们应该怎么做????
那就返回最新插入的元素呗,这个元素的下标是多少呢? usedsize-1呗
为什么?
因为每次插入以后,我们的usedsize会+1,当作下次插入的元素的下标,那么它的前一个,即usedsize-1,也就是栈顶元素的下标,弹出后,再令usedsize-1,代表确实弹出去了
package stack;
public class EmptyException extends RuntimeException
{
public EmptyException(String message)
{
super(message);
}
}
@Override
public int pop()
{
if(empty())
{
throw new EmptyException("栈为空");
}
int oldnum=elem[usedsize-1];
usedsize--;
// elem[usedsize]=null;
return oldnum;
}
同时注意,我们的例子是基础数据,如果是引用类型,还是要置为空的. 如果栈本来就是空的,就要抛异常了,所以还要自定义异常,这都没什么说的
peek方法
也就是C++中的top(),展示栈顶元素,那好办的,usedsize不要-1就好了
@Override
public int peek()
{
if(empty())
{
throw new EmptyException("栈为空");
}
int oldnum=elem[usedsize-1];
// 不删
return oldnum;
}
至此,一切都已经结束了!这就是一个简单的数据结构——栈!!!!!!!
链表实现栈的问题
以链表实现的栈叫做链式栈,那么,双向链表和单向链表,那个可以实现呢?
在回答这个问题之前,我们要明白,插入一个节点和删除一个节点,一定要它相邻的节点的地址,这个没有问题吧!!!!!!!!!
如果是一个双向链表,当然没问题了
你如果以head为入口插入栈,不管是出栈还是入栈,时间复杂度都是O(1),毋庸置疑
哪怕我们以last为入口,也没问题啊,因为我们知道last的地址,last 节点也保留了前一个元素的位置
只要
last.prev.next=null;
last.prev=last;
// 删除的
last.next=temp;
temp.prev=last;
last=temp;
// 增加的
那么单链表可以吗?如果是head当作入口,那么没问题
如果是Last,那么,铁定不行,因为要插入或者删除,你一定要遍历一下链表,找到前一个的坐标,那么时间复杂度就是0(N)了,所以是不行滴
结尾
暑假开摆了,没有怎么更新了,多点点赞,这样我才好有动力去更新
而且最近流量收益也砍了,看来CSDN是要数量不要质量了