欢迎来到第三章
在此处稍微回顾一下前两章的内容
在第一章我们从“零”开始实现了一个ArrayList线性表,我把它称之为是一种底层数据结构
在第二章我们用ArrayList实现了一个栈结构ArrayStack,通过底层数据结构的封装,继而产生出了更加高级的数据结构
所以,请大家注意,在数据结构的广袤世界中,高级的数据结构就是这样子由底层数据结构一层一层封装起来的
- 先比如后面要学的Trie前缀树,就是 多叉树 + 映射 实现的高级数据结构
- 仍比如后面要学的红黑树,就是 二叉树 + 2-3树 + 自平衡技术 实现的高高级数据结构
- 再比如后面要学的哈希表,就是 数组 + 红黑树 实现的高高高级数据结构
所以,想学好数据结构,最好的途径就是从底层做起
还有,我想提醒一下大家,数据结构的设计不是一成不变的,大家可以完全按照自己的需求去定义一种新的数据结构,这都是完全可以的!数据结构不是死知识,而是活学活用的,比如这一章,给大家介绍一种比较奇葩但是非常常用的数据结构——双端栈
大家加油!
什么是双端栈
直白的说,双端栈是指一个数组中,存在两个栈,其中一个栈的栈底在数组表头,另一个在数组表尾
主要利用了栈“栈底位置不变,而栈顶位置动态变化” 的特性
如图所示
可以看出我们此时需要两个栈顶的标记,那么之前学过的ArrayList还能直接拿来用吗?
答案是不能的,为什么,因为在实现ArrayStack时,其实我们巧妙地使用了ArrayList中的 size-1 当成了栈顶指针
size不仅可以当做记录元素个数使用,也可以当做在表尾插入新元素时新元素的位置,然后size++
看图说明,其实会发现实际是 size-1 当做了ArrayStack的栈顶,明白了吧?
那么我们的双端栈是需要两个栈顶指针的,所以直接用ArrayList是不行的
但是双端栈本身还是栈结构,所以还是需要实现上一章的Stack接口
来,开始写代码吧
1.双端栈类的定义ArrayStackDoubleEnd
public class ArrayStackDoubleEnd<E> implements Stack<E>{
private E[] data; //元素容器
private int left; //左端栈顶 从-1开始
//(left+1)表示左端栈元素的个数
private int right; //右端栈顶 date.length开始
//(data.length-right)表示右端栈元素的个数
public static final int L=0; //左端栈标记
public static final int R=1; //右端栈标记
private int size; //栈中元素的总个数
public ArrayStackDoubleEnd() { //创建默认容量为10的双端栈
this(10);
}
public ArrayStackDoubleEnd(int capacity){ //创建指定容量为capacity的双端栈
data=(E[]) new Object[capacity];
left=-1;
right=data.length;
size=0;
}
}
2.getSize()函数
获取栈中元素的个数
但是我们这里面有两个栈,获取哪个呢?可以再增加个方法getSize(int which),获取指定端栈的元素个数
因为在类定义中,已经定义了L和R两个标记了
那么重写的getSize()可以获取的是全部元素个数
之后的操作也是这个思路
public int getSize() {
return size;
}
public int getSize(int which){
if(which==L){
return left+1;
}else{
return data.length-right;
}
}
3.isEmpty()函数
判断栈是否为空;判断指定端的栈是否为空
public boolean isEmpty() {
return left==-1&&right==data.length&&size==0;
}
public boolean isEmpty(int which){
if(which==L){
return left==-1;
}else{
return right==data.length;
}
}
4.clear()函数
清空栈;清空某一端的栈
public void clear() {
left=-1;
right=data.length;
size=0;
}
public void clear(int which){
if(which==L){
left=-1;
}else{
right=data.length;
}
}
5.getCapacity()函数
获取双端栈的容量
public int getCapacity(){
return data.length;
}
6.push()函数
入栈一个元素;向指定端入栈一个元素
注意,在没有指定入栈到某一端时,逻辑是这样处理的
左端元素个数少于右端元素个数,入栈左端
右端元素个数少于左端元素个数,入栈右端
当 left+1==right 时,表示栈满了,此处应该进行扩容操作,但是这里我就不实现了,留个大家来实现,思路和ArrayList一样
public void push(E e) {
if(size==data.length){
throw new IllegalArgumentException("栈已满!");
}
if(getSize(L)<=getSize(R)){
push(L,e);
}else{
push(R,e);
}
}
public void push(int which,E e){
if(size==data.length){
throw new IllegalArgumentException("栈已满!");
}
if(which==L){
data[++left]=e;
}else{
data[--right]=e;
}
size++;
}
7.pop()函数
出栈一个元素;指定某一端出栈一个元素
同样,在没有指定哪个端出栈时
左端元素个数大于右端,出栈左端
右端元素个数大于左端,出栈右端
public E pop() {
if(getSize(L)>=getSize(R)){
return pop(L);
}else{
return pop(R);
}
}
public E pop(int which){
if(isEmpty(which)){
throw new IllegalArgumentException("栈已空!");
}
size--;
if(which==L){
return data[left--];
}else{
return data[right++];
}
}
8.peek()函数
查看栈顶元素;查看指定端的栈顶元素
public E peek() {
if(getSize(L)>=getSize(R)){
return peek(L);
}else{
return peek(R);
}
}
public E peek(int which){
if(isEmpty(which)){
throw new IllegalArgumentException("栈为空!");
}
if(which==L){
return data[left];
}else{
return data[right];
}
}
好啦,至此双端栈讲完了~大家加油