目录
一、栈
1.1栈的概念
栈:是一种特殊的线性表,只允许在固定的一端进行插入和删除元素。
栈顶:进行数据的插入和删除的一端,另一端则为栈底。
压栈:栈的插入操作叫做进栈/入栈/压栈。压栈操作在栈顶进行。
出栈:栈的删除操作叫做出栈。出栈操作也是在栈顶进行。
1.2栈的使用
public static void main(String[] args) {
Stack<Integer> stack=new Stack<>();
stack.push(1);//入栈
stack.push(2);
stack.push(3);
System.out.println(stack.peek());//喵一眼栈顶
System.out.println(stack.empty());//判断栈是否为空
System.out.println(stack.size());//栈大小
while(!stack.empty()){
System.out.println(stack.pop());//出栈
}
}
1.3栈的模拟实现
从图中我们可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。
1.栈的创建
这里我们用动态顺序表实现。
class MyStack{
private int[] elem;//存储元素的数组
private int capacity;//容量
private int size;//使用的元素个数
public MyStack(){
this.elem=new int[10];
this.capacity=10;
this.size=0;
}
public void push(int val){
}
}
2.入栈
使用顺序表时,要先判断栈内满了没,满了要进行扩容,再进行入栈
public void push(int val){
if(isFull()){//满了,进行扩容
elem= Arrays.copyOf(elem,capacity*2);
this.capacity*=2;
}
//进行入栈操作
this.elem[this.size++]=val;
}
public boolean isFull(){
return this.size==this.capacity;
}
3.出栈
在进行出栈时,我们要判断栈是否为空,为空返回-1,否则进行出栈操作
public int pop(){
if(isEmpty()){
return -1;
}
//进行出栈操作,弹出栈顶元素
return this.elem[--this.size];
}
public boolean isEmpty(){
return this.size==0;
}
4.查看栈顶元素
public int peek(){
if(isEmpty()){
return -1;
}
return this.elem[this.size-1];
}
5.获取栈的大小
public int size(){
return this.size;
}
测试
public class StackAL {
public static void main(String[] args) {
MyStack myStack=new MyStack();
myStack.push(1);
myStack.push(2);
myStack.push(3);
System.out.println(myStack.size());
System.out.println(myStack.peek());
while(!myStack.isEmpty()){
System.out.print(myStack.pop()+" ");
}
}
1.4共享栈
共享栈主要是利用栈底位置不变,可以利用一个一维数组来存放两个顺序栈
这两个栈的栈顶向着一维数组内部,当top0=-1时0号栈为空,当too1=MaxSize时1号栈为空。当top0+1=top1时,判断栈为满。
1.共享栈的实现
1.1共享栈的创建
class ShareStacks{
private int[] elem;
private int top0;
private int top1;
public ShareStacks(int size){
elem=new int[size];
this.top0=-1;
this.top1=size;
}
}
1.2入栈
我们借助index判断是要进哪一个栈,在入栈时还需要判断top0+1==top1,如果相等,则栈满。
public void push(int val,int index){
if(top0+1==top1){
System.out.println("栈已满");
return;
}
if(index==0){//在栈0中压入元素
elem[++top0]=val;
}
if(index==1){//在栈1中压入元素
elem[--top1]=val;
}
if(index<0||index>1){//栈索引输入错误
System.out.println("index输入错误");
}
}
1.3出栈
我们在出栈的过程中,我们需要判断栈内是否为空,不为空才能进行出栈操作。
这里我们把判断栈内是否为空封装成一个方法isempty()。
public boolean isempty(int index){
if(index==0){
if(top0==-1){
return true;
}else{
return false;
}
}
if(index==1){
if(top1==elem.length){
return true;
}else{
return false;
}
}
return false;
}
/**
* 从指定栈中弹出元素。
* @param index 栈的索引,0表示栈0,1表示栈1。
* @return 弹出的元素值。如果栈为空,则不返回任何值。
*/
public int pop(int index){
int val=0;
// 尝试从栈0中弹出元素
if(index==0){
// 如果栈0为空,则打印信息并返回
if(isempty(index)){
System.out.println("栈0为空");
return 0;
}else{
// 从栈0弹出元素并返回
val=elem[top0--];
}
}
// 尝试从栈1中弹出元素
if(index==1){
// 如果栈1为空,则打印信息并返回
if(isempty(index)){
System.out.println("栈1为空");
return 0;
}else{
// 从栈1弹出元素并返回
val=elem[top1++];
}
}
// 如果索引既不是0也不是1,返回0作为错误指示
return val;
}
其余操作与栈基本相同。
1.4共享栈基本操作完整代码
class ShareStacks{
private int[] elem;
private int top0;
private int top1;
public ShareStacks(int size){
elem=new int[size];
this.top0=-1;
this.top1=size;
}
public void push(int val,int index){
if(top0+1==top1){
System.out.println("栈已满");
return;
}
if(index==0){//在栈0中压入元素
elem[++top0]=val;
}
if(index==1){//在栈1中压入元素
elem[--top1]=val;
}
if(index<0||index>1){//栈索引输入错误
System.out.println("index输入错误");
}
}
/**
* 从指定栈中弹出元素。
* @param index 栈的索引,0表示栈0,1表示栈1。
* @return 弹出的元素值。如果栈为空,则不返回任何值。
*/
public int pop(int index){
int val=0;
// 尝试从栈0中弹出元素
if(index==0){
// 如果栈0为空,则打印信息并返回
if(isempty(index)){
System.out.println("栈0为空");
return 0;
}else{
// 从栈0弹出元素并返回
val=elem[top0--];
}
}
// 尝试从栈1中弹出元素
if(index==1){
// 如果栈1为空,则打印信息并返回
if(isempty(index)){
System.out.println("栈1为空");
return 0;
}else{
// 从栈1弹出元素并返回
val=elem[top1++];
}
}
// 如果索引既不是0也不是1,返回0作为错误指示
return val;
}
public int peek(int index){
if(index<0||index>1){
throw new IllegalArgumentException("index输入错误");
}
if(index==0){
if(isempty(index)){
System.out.println("栈0为空");
return 0;
}else{
return elem[top0];
}
}
if(index==1){
if(isempty(index)){
System.out.println("栈1为空");
return 0;
}else{
return elem[top1];
}
}
return 0;
}
public boolean isempty(int index){
if(index==0){
if(top0==-1){
return true;
}else{
return false;
}
}
if(index==1){
if(top1==elem.length){
return true;
}else{
return false;
}
}
return false;
}
}
二、队列
2.1概念
队列:只允许在一端进行插入,在另一端进行删除数据操作的特殊线性表,队列是先进先出,进行插入操作的一端叫做队尾,进行删除操作的一端叫做队头。
2.2队列的使用
在java中,Queue是个接口,底层是通过链表来实现的。
队列中常用的方法
使用:由于Queue底层是双向链表LinkList,LinkList实现了Queue接口,所以在实例化时必须实例化LinkList的对象。下面是对java中Queue的使用
public static void main(String[] args) {
Queue<Integer> queue=new LinkedList<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue.size());
while(!queue.isEmpty()){
System.out.print(queue.poll()+" ");
}
}
2.3队列的模拟实现
队列的实现可以用顺序结构,也可以使用链式结构
队列的顺序存储是指分配一块空间给队列存储元素,并设置两个成员变量,一个front指向队头,一个rear指向队尾。
1.假溢出
对于顺序队列,我们在使用的时候,可能会遇到假溢出的情况
举个例子:我们一个长度为5的顺序队列,在这个队列中已经存储了3个元素(4,6,8)。
我们现在进行出队,出队两个元素,那么front就要往前走两步。
我们再进行入队操作,这次我们入队3个元素。
我们可以看到此时rear已经走到了数组外了,我们已经不能再进行入队操作了,但队列中的空间还没满,如果再进行入队操作就会溢出,这就叫做“假溢出”。
为了避免这种情况,我们可以使用循环队列。
2.循环队列
头尾相接的队列,能够避免假溢出的问题
2.1实现思路
对于循环队列,我们依旧采用的是一维数组来进行存储队列的元素。
1.使用两个指针:
头指针(front):作为队头,指向队列的第一个元素。
尾指针(rear):作为队尾,指针队列的最后一个元素之后的位置。
2.判断为空:即front==rear时,队列为空。
3.判断是否为满:这里我们不能再用简单的判断两个指针是否相等,头尾指针都有可能回到数组的初始位置。我们这里牺牲一个元素空间用来判断队列是否为满,即(rear+1)%size==front,其中size为数组的长度
2.2循环队列实现
1.队列的创建
这里我们使用是顺序存储。
class MyQueue{
private int[] elem;
private int front;//队头
private int rear;//队尾
public MyQueue(int size){
elem=new int[size];
}
}
2.判断是否为空&&判断是否为满
public boolean isEmpty(){
return rear==front;
}
public boolean isFull(){
return (rear+1)%elem.length==front;
}
3.入队操作
在入队的时候,我们需要判断一下队列是否已经满了(这里不进行扩容操作)。
public boolean offer(int val){
if(isFull()){
return false;
}
//进行入队操作
elem[rear]=val;
rear=(rear+1)%elem.length;
return true;
}
4.出队操作
对于front,不可以直接进行+1操作,当front在数组的最后一个位置时,我们想让front回到数组初始位置,我们需要让头指针(front+1)%elem.length。
public int poll(){
if(isEmpty()){
return -1;
}
//进行出队操作
int val=elem[front];
front=(front+1)%elem.length;
return val;
}
5.查看队头元素&&查看队尾元素
查看队头元素,我们只需要返回front所在位置的元素即可。
对于查看队尾元素,我们可能会返回elem[rear-1]位置的元素,但这样有缺陷,若rear所处位置为0,那么减1就有问题了。可以定义一个变量,用来判断rear所处位置。如果rear=0,那么就返回elem.length-1位置处的元素,否则直接返回rear-1处的元素。
public int peek(){//查看队头元素
if(isEmpty()){
return -1;
}
return elem[front];
}
public int peekRear(){//查看队尾元素
if(isEmpty()){
return -1;
}
int index=(rear==0)? elem.length-1:rear-1;
return elem[index];
}
实现了这些功能,我们可以测试一下
public static void main(String[] args) {
MyQueue myQueue=new MyQueue(3);
myQueue.offer(1);//入队
myQueue.offer(2);
System.out.println(myQueue.peek());//查看队头元素
System.out.println(myQueue.peekRear());//查看队尾元素
System.out.println(myQueue.poll());//出队
System.out.println(myQueue.poll());
}
6.循环队列完整代码
class MyQueue{
private int[] elem;
private int front;//队头
private int rear;//队尾
public MyQueue(int size){
elem=new int[size];
}
public boolean offer(int val){
if(isFull()){
return false;
}
//进行入队操作
elem[rear]=val;
rear=(rear+1)%elem.length;
return true;
}
public int poll(){
if(isEmpty()){
return -1;
}
//进行出队操作
int val=elem[front];
front=(front+1)%elem.length;
return val;
}
public int peek(){//查看队头元素
if(isEmpty()){
return -1;
}
return elem[front];
}
public int peekRear(){//查看队尾元素
if(isEmpty()){
return -1;
}
int index=(rear==0)? elem.length-1:rear-1;
return elem[index];
}
public boolean isEmpty(){
return rear==front;
}
public boolean isFull(){
return (rear+1)%elem.length==front;
}
}
2.4双端队列(Deque)
双端队列是允许在两端进行入队和出队操作。
Deque也是一个接口,在使用的时候需要创建LinkList的对象。
我们可以在Deque (Java SE 11 & JDK 11 ) (runoob.com)中查看Deque的常见操作。
栈和队列均可以使用该接口。
public static void main(String[] args) {
Deque<Integer> deque = new LinkedList<>();
deque.offer(1);
deque.offer(2);
deque.offer(3);// 1 2 3
System.out.println(deque.peekLast());//查看队尾元素
System.out.println(deque.peekFirst());//查看队首元素
System.out.println(deque.pollLast());//弹出队尾元素
System.out.println(deque.pollFirst());//弹出队首元素
}
相关习题
1. 用队列实现栈225. 用队列实现栈 - 力扣(LeetCode)
class MyQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MyQueue() {
stack1=new Stack<>();
stack2=new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if(empty()){
return -1;
}
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if(empty()){
return -1;
}
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.empty()&&stack2.empty();
}
}
2. 用栈实现队列232. 用栈实现队列 - 力扣(LeetCode)
/**
* 用队列实现栈
*/
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
//判断栈内为不为空
if(empty()){
//入队到第一个
queue1.offer(x);
}
//不为空,判断哪个队列不为空进哪个
if(!queue1.isEmpty()){
queue1.offer(x);
}else{
queue2.offer(x);
}
}
public int pop() {
//判断哪个队列不为空
if(!queue1.isEmpty()){
int size=queue1.size();
//让size-1个元素放到第二个队列中
for(int i=0;i<size-1;i++){
queue2.offer(queue1.poll());
}
return queue1.poll();
}else{
int size=queue2.size();
for(int i=0;i<size-1;i++){
queue1.offer(queue2.poll());
}
return queue2.poll();
}
}
public int top() {
if(!queue1.isEmpty()){
int size=queue1.size();
int res=-1;
for(int i=0;i<size;i++){
res =queue1.poll();
queue2.offer(res);
}
return res;
}else{
int size=queue2.size();
int res=-1;
for(int i=0;i<size;i++){
res=queue2.poll();
queue1.offer(res);
}
return res;
}
}
public boolean empty() {
return queue1.isEmpty()&&queue2.isEmpty();
}
}
若有不足,欢迎指正~