算法学习系列(4)——栈,队列,链表

1.用数组结构实现大小固定的队列和栈

1.0 经典数据结构(队列(queue)、栈(stack)、链表(linkedList)、数组)

1.0.1队列(双向链表)

先进者先出,就是"队列" 我们可以想象成,排队买票,先来的先买,后来的只能在末尾,不允许插队。

队列的两个基本操作入队 将一个数据放到队列尾部;出队 从队列的头部取出一个元素。队列也是一种操作受限的线性表数据结构 它具有先进先出的特性,支持队尾插入元素,在队头删除元素。

队列的概念很好理解,队列的应用也非常广泛如:循环队列、阻塞队列、并发队列、优先级队列等。
来个顺序队列的图感受一下:(借鉴队列:彻底理解队列
在这里插入图片描述

1.0.2 栈

后进先出(LIFO-last in first out):最后插入的元素最先出来。
来个图感受一下(栈和队列
在这里插入图片描述

1.0.3链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

1.0.4 链表与队列区别

  • 链表是一种数据的存储方式,保存的数据在内存中不连续的,用指针对数据进行访问;
  • 队列是一种数据结构,其特点是先进先出,后进后出;
  • 队列的存储方式可以使用线性表进行存储,也可以使用链表进行存储

1.1 使用数组结构实现大小固定的队列和栈【面试重点】

1)栈的实现

public class ArrayToStack{
		private Integer[] arr;
        private Integer index;

        //初始化构造方法
        public Array2Stack(int iniSize){
            if(iniSize < 0){
                throw new IllegalArgumentException("the init size is less than 0");
            }
            arr = new Integer[iniSize];
            index = 0;//给定初始索引值为0,其实索引记录的是压栈的时候元素所放置的位置,需要进行弹出操作的时候,必须先进行索引值减1操作
        }
        //定义一个可以抛出栈顶的操作,并且不能改变原来数组的结构
        public Integer peak(){
            if(index == 0){
                return null;
            }
            return arr[index - 1];
        }

        //定义一个压栈操作,然后索引加1操作
        public void push(int num){
            if(index == arr.length){
                throw new ArrayIndexOutOfBoundsException("the stack is full!");
            }
            arr[index++] = num;
        }

        //定义一个出栈操作,索引先进行减操作
        public Integer pop(){
            if(index == 0){
                throw new ArrayIndexOutOfBoundsException("the stack is empty!");
            }
            return arr[--index];
        }
}

2)队列的实现(比栈就多了两个参数)

public class ArrayToQueue{
		private Integer[] arr;
		private Integer size;//记录整个数组的长度
		private Integer start;//记录的是取一个数的时候,应该是从哪个位置上取,记录的是一个索引
		private Integer end;//记录的是新加的数应该填在哪个位置上,记录的也是一个索引值

		public ArrayQueue(int initSize) {
			if (initSize < 0) {
				throw new IllegalArgumentException("The init size is less than 0");
			}
			arr = new Integer[initSize];
			size = 0;
			start = 0;
			end = 0;
		}

		public Integer peek() {
			if (size == 0) {
				return null;
			}
			return arr[start];
		}

		public void push(int obj) {//入队操作
			if (size == arr.length) {
				throw new ArrayIndexOutOfBoundsException("The queue is full");
			}
			size++;
			arr[end] = obj;
			end = end == arr.length - 1 ? 0 : end + 1;
		}

		public Integer poll() {//出队操作
			if (size == 0) {
				throw new ArrayIndexOutOfBoundsException("The queue is empty");
			}
			size--;
			int tmp = start;
			start = start == arr.length - 1 ? 0 : start + 1;
			return arr[tmp];
		}
}

2.栈(队列)的经典应用【面试】

1)实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。

【要求】

  • 1.pop、push、getMin操作的时间复杂度都是O(1)。

  • 2.设计的栈类型可以使用现成的栈结构。
    思路:(先来个简单图解)
    在这里插入图片描述
    思路解释:
    采用两个栈:

    stackData作为数据栈保存数据;

    stackMin作为辅助栈保存stackData数据栈中每一步的最小元素。

压入数据push操作:

1.判断stackMin辅助栈是否为空,
若为空,将newNum同步压入stackMin辅助栈,执行下一步;

      若不为空,直接执行下一步;

2.判断newNUm是否小于此时的stackMin.peek(),

    若小于,将newNum压入stackMin中。否则,执行下一步3。

3.判断newNUm是否大于等于此时的stackMin.peek()(代码中用getmin()函数表示),

    若大于等于,将此时的stackMin赋值给新变量newMin,并将newMin压入进stackMin。

4.stackData同时一直压入newNum,结束操作。

弹出数据pop操作:

1.判断stackMin是否为空,

    若为空,提示报错。执行下一步。

2.直接弹出stackMin栈顶元素。

3.返回stackData栈顶元素。

代码实现:

public class Code2_StackGetMin {

    private Stack<Integer> dataStack;
    private Stack<Integer> minStack;

    public Code2_StackGetMin(){
        this.dataStack = new Stack<Integer>();
        this.minStack = new Stack<Integer>();
    }

    //入栈操作
    public void push(int newNum){
        if(minStack.isEmpty()){
            minStack.push(newNum);
        }else if(newNum < this.getMin()){
            minStack.push(newNum);
        } else {
            int newMin = this.minStack.peek();
            minStack.push(newMin);
        }
        dataStack.push(newNum);
    }

    //出栈操作
    public int pop(){
        if(this.dataStack.isEmpty()){
            throw new RuntimeException("栈已经为空!");
        }
        this.minStack.pop();
        return this.dataStack.pop();
    }

    //获取最小值的操作
    public int getMin(){
        if(minStack.isEmpty()){
            throw new RuntimeException("最小值为空!");
        }
        return this.minStack.peek();
    }

    public static void main(String[] args) {
        Code2_StackGetMin stack2 = new Code2_StackGetMin();
        stack2.push(3);
        System.out.println(stack2.getMin());
        stack2.push(4);
        System.out.println(stack2.getMin());
        stack2.push(1);
        System.out.println(stack2.getMin());
        System.out.println(stack2.pop());
        System.out.println(stack2.getMin());
    }

}

2)如何仅用队列结构实现栈结构?如何仅用栈结构实现队列结构?

图的深度优先算法需要使用栈结构实现,如果问:只给你队列结构怎么实现图的深度优先。

两个队列实现栈

这是一个面试可能经常碰到的简单题目核心思想就是可以利用两个队列实现一个栈。定义两个队列,一个data队列,一个help队列,数据压栈的时候只压入data队列,一旦需要出栈的时候,因为栈是先进后出的特点,也就是后进先出,则需要将data队列里面除最后一个数据外的全部压入help栈,留下的最后一个数据就是应该出栈的数据,最后将help和data进行引用交换即可。
简单图解:
在这里插入图片描述
代码实现:

public class TwoQueuesStack {
		private Queue<Integer> queue;
		private Queue<Integer> help;

		public TwoQueuesStack() {
			queue = new LinkedList<Integer>();
			help = new LinkedList<Integer>();
		}

		public void push(int pushInt) {//使用两个队列模拟原来栈的压入操作,其实压入操作是看不见的,就直接压入队列就对了
			queue.add(pushInt);
		}

		public int peek() {//使用两个队列模拟原来的返回一个栈顶元素的操作
			if (queue.isEmpty()) {
				throw new RuntimeException("Stack is empty!");
			}
			while (queue.size() != 1) {//将原队列中除了最后加进来的那个元素之外的元素都加到辅助队列中去
				help.add(queue.poll());
			}
			int res = queue.poll();
			help.add(res);
			swap();//这和上一步都是为了恢复现场,保证原来的queue的完整性、一致性
			return res;
		}

		public int pop() {//使用两个队列模拟原来的弹出栈顶元素的操作
			if (queue.isEmpty()) {
				throw new RuntimeException("Stack is empty!");
			}
			while (queue.size() > 1) {
				help.add(queue.poll());
			}
			int res = queue.poll();
			swap();//这时候的交换就不需要恢复现场了,所以比上边少了一步的操作
			return res;
		}

		private void swap() {//实现的是两个队列交换的操作
			Queue<Integer> tmp = help;
			help = queue;
			queue = tmp;
		}

}

两个栈实现队列

图解:在这里插入图片描述
代码实现:

public class TwoStacksQueue {
		private Stack<Integer> stackPush;
		private Stack<Integer> stackPop;

		public TwoStacksQueue() {
			stackPush = new Stack<Integer>();
			stackPop = new Stack<Integer>();
		}

		public void push(int pushInt) {//实现队列的压入功能
			stackPush.push(pushInt);
		}

		public int poll() {//实现队列的弹出功能
			if (stackPop.empty() && stackPush.empty()) {
				throw new RuntimeException("Queue is empty!");
			} else if (stackPop.empty()) {//只有当弹出栈的内容都清空了,把压入栈的内容放进去才有意义
				while (!stackPush.empty()) {//每次的压入到弹出栈的操作,都要全部地压入
					stackPop.push(stackPush.pop());
				}
			}
			return stackPop.pop();//当有新的内容再次压入的时候其实是和这个弹出没有什么联系的了,因为队列实现的先进先出的元素已经在这里边了。这话不大顺畅。。。
		}

		public int peek() {//实现队列的返回压入的最后一个元素的功能
			if (stackPop.empty() && stackPush.empty()) {
				throw new RuntimeException("Queue is empty!");
			} else if (stackPop.empty()) {
				while (!stackPush.empty()) {
					stackPop.push(stackPush.pop());
				}
			}
			return stackPop.peek();//和弹出功能的唯一不同
		}
	}
  • 一个细节:
    栈的压入操作适应的是push,队列的压入操作可以使用add也可以使用push
    栈的弹出使用pop,队列的弹出使用poll
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值