数据结构专题( 二)——栈与队列

什么是栈?

其实栈就是一种后进先出的结构,就像一沓书本,每次取书本只能从最上面取,那么压在最底下的书本肯定是最先摆放在桌面上的,自然而然肯定是最后被取走的。

什么是队列?

队列是一种先进先出的结构,队列在生活中的例子就更多了,火车站排队,食堂买盒饭的队伍,其实都可以抽象成一个队列,每当一个人买完票或者拿到盒饭就会离开队列,先来到售票或者食堂窗口的人肯定是先买到票或者吃饭的。

数据结构其实充满了生活的影子,它们都是实际生活中抽象出来供人们更好理解计算机的工具而已。那栈与队列有什么用处呢? 

先说栈吧,栈可以用来实现图的搜索算法以及括号匹配,表达式计算等功能,正因为有栈,才有现在众多能够实现括号匹配功能的开发软件,网络页面搜索等。队列正因为有先进先出的功能,可以用来实现操作系统中的消息队列功能等。

关于栈与队列的面试题

  (1)栈的创建

  (2)队列的创建

  (3)两个栈实现一个队列

  (4)两个队列实现一个栈

  (5)设计含最小函数min()的栈,要求min、push、pop、的时间复杂度都是O(1)

  (6)判断栈的push和pop序列是否一致

       (7)计算表达式的值

1. 栈的创建

在Java的API本来就已经有栈与队列的对象,所以我们只需要使用已有的工具进行构造。但是若想要自己构建这两种数据结构,具体实现可以使用数组或者链表,通过设置元素的添加与删除顺序就可以形成栈或者队列的结构。接下来我将使用Java自己创建栈与队列。

class Stack {
    final int MAX_SIZE = 10;
    int[] stack;
    int top;
 
    public Stack() {
        stack = new int[MAX_SIZE];
        top = -1;
    }
 
    public void push(int data) {
        stack[++top] = data;
    }
 
    public int pop() {
        return stack[top--];
    }
 
    //查看栈顶元素
    public int peek() {
        return stack[top];
    }
 
    public boolean isEmpty() {
        return top == -1;
    }
}

2. 队列的创建

关于队列的创建可以使用上一章内容的链表结构,通过设置头节点与尾节点来控制链表的操作。至于Node<T>对象可以参照

数据结构(一)——数组与链表(问题解决与总结)中构建节点对象的过程。

class Queue{
	
	Node<Integer> front;
	Node<Integer> rear;
	int size = 0;
	
	public Queue(){
	    front = null;
	    rear = null;
	}
	
	//队列尾部增加元素
	public void insert(Node<Integer> newNode) {
		
		if(isEmpty()) {
			front = newNode;
		}else {
			rear.next = newNode;
		}
		rear = newNode;
		size++;
	}
	
	//队头删除元素
	public int remove() {
		
		int temp = 0;
		
		if(isEmpty()) {
			System.out.print("the queue is empty");
		}else {
			
			temp = front.data;
			front = front.next;
			size--;	
		}
		return temp;
	}
	
	//取出队头元素进行查看
	public int poll() {
		return front.data;
	}
	
	public boolean isEmpty() {
		return front == null;
	}
}

3. 两个栈实现一个队列

两个栈实现一个队列:通过绘图我们可以很清楚看到解决办法,栈1用来存储数据,栈2用来取出数据。

public void stackToQueue(int[] arr) {
		
		Stack stack1 = new Stack();
		Stack stack2 = new Stack();
		
		for(int i = 0; i < arr.length; i++) {
			stack1.push(arr[i]);
		}
		
		if(stack2.isEmpty()) {
			while(!stack1.isEmpty()) {
				stack2.push(stack1.pop());
			}
			while(!stack2.isEmpty()) {
				System.out.println(stack2.pop());
			}
			
		}
	}

4. 两个队列实现一个栈

思路:两个队列交替进行进队出队操作,具体的可以通过步骤示意图来进行了解。

具体的代码实现如下:

public class queueToStack {
	
	Queue q1 = new Queue();
	Queue q2 = new Queue();
	
	//假设队列1中有n个元素,在取数据时,需要将队列1中的n-1个元素放到队列2中存储,队列一最后一个元素则进行出队操作,该元素出队后的队列1为空
	//此时再将队列2中的n-2个数据存储到队列1中,队列2执行出队操作,于是第二个元素就被取出,初步完成栈的操作。
	//存储+取出的过程由两个队列交替完成。

	public int pop() {

		int result = 0;
		
		//pop操作 先判断两个队列的状态,如果q1,q2都为空,那么就没有值可以返回,此时可以设置返回-1表示异常
		if(q1.isEmpty() && q2.isEmpty()) {
			return -1;
		}
		//如果q1为空,q2不为空,那么就先操作q2
		if(q1.isEmpty() && !q2.isEmpty()) {
			//若q2里有多个元素,那么需要全部移动到q1中去,使得q2中只留下最后一个元素,那么这是第一个应该输出的元素
			while(!q2.isEmpty()) {
				result = q2.remove();
				if(!q2.isEmpty()) {
					q1.insert(new Node<Integer>(result));
				}	
			}
		//若q1里本来有多个元素,那么需要借助q2去存储,使得q1只剩下最后一个元素的时候,那就是第一个输出元素,基本原理和上步骤一致
		}else if(!q1.isEmpty() && q2.isEmpty()) {
			while(!q1.isEmpty()) {
				result = q1.remove();
				if(!q1.isEmpty()) {
					q2.insert(new Node<Integer>(result));
				}
			}
		}
		return result;
	}	
	
	public void push(Node<Integer> newNode) {
		
		if(q1.isEmpty() && q2.isEmpty()) {
			q1.insert(newNode);
		}
		
		if(!q1.isEmpty() && q2.isEmpty()) {
			q1.insert(newNode);
		}
		
		if(q1.isEmpty() && !q2.isEmpty()) {
			q2.insert(newNode);
		}
	}

}

5. 设计含最小函数min()的栈,要求min、push、pop、的时间复杂度都是O(1)

具体思路可以使用空间换取时间的策略,通过制造一个新的栈去比较存储最小值,每次进行进栈操作的时候都与存储在另一个栈中的最小值进行比较,对结果进行不断的更新,最后调用getMin()函数时,弹出最小栈中的值。

代码实现如下:

public class MinStack {
	
	Stack mainStack = new Stack();
	Stack minStack = new Stack();
	
	public int pop() {
		if(mainStack.peek() == minStack.peek()) {
			return minStack.pop();
		}
		return mainStack.pop();
	}
	
	public void push(int data) {
		mainStack.push(data);
		int min = 0;
		if(minStack.isEmpty() || minStack.peek() >= data) {
			min = mainStack.peek();
			minStack.push(min);
		}
	}
	
	public int getMin() {
		return minStack.peek();
	}
}

(6)判断栈的push和pop序列是否一致

题目要求是判断栈的push与pop是否一致。要求函数接受两个整数数组的输入(两组整数对应的分别是push顺序与pop顺序),然后判断在该push顺序下是否存在输入的pop顺序。

最容易想到的解决方式就是构造一个栈模拟输入,看是否能够得到输入的pop顺序。

	public void judgeStack(int[] input, int[] sample) {
		
		Stack s = new Stack();
		
		int i = 0, j = 0;
		List<Integer> ans = new ArrayList<Integer>();
	
		//算法思想:设立两个指针,分别指向这两个数组,注意指针角标移动的位置,避免出现空指针异常。
		while(i <= input.length - 1) {
			if(input[i] != sample[j]) {
				s.push(input[i]);
				i++;
			}else{
				ans.add(sample[j]);
				i++;
				j++;
			}
		}
		
		while(!s.isEmpty()) {
			int temp = s.pop();
			ans.add(temp);
		}
		
		for(int k = 0; k < ans.size(); k++) {
			System.out.println(ans.get(k));
		}
	}

以上的算法占用的资源是比较大的,也许会有更好的解法,希望读者能给出更好的意见哈~

7. 计算表达式的值

这道题目使我发自内心喜欢Python的简介,真的是“人生苦短,我用python”的真实反映了。

如果选择用python实现这个功能,那么代码将只有如下两行:

s=input()
print(s)

这就是python非常强大的地方,如果使用Java实现,那么代码量可能就是大几十行,可能超过一百行。计算表达式的数据结构也是基于栈。首先我们需要了解的知识点有前缀,中缀与后缀表达式。

人们最常用的表达式是中缀表达式,运算符号在数字之间,需要使用括号来确定运算次序,例如:(1+2)*3

中缀表达式符合人类的习惯,但是对于计算机来说去没有这么容易理解,所以很多时候人们会将中缀表达式变形为前缀与后缀表达式,从而让计算机更好的进行计算工作。

前缀表达式又称为“波兰式”(发明者是一位波兰科学家)其特征就是没有括号,运算符都在数字前面,例如:* 3 + 1 2

后缀表达式的特征也是没有括号,但运算符都在数字后面,例如:3 * 2 1 +

使用栈实现计算机的一种方法就是狄杰斯特拉的双栈模型,即创建两个栈,一个用来存在数字,另外一个用于存储各种运算符号。具体的解决过程网络上有很多啦,请读者自己查阅。

栈与队列的内容先到这儿,后期若有新的思路会进一步进行补充,以上的解法有很多可以改进的地方,希望各位读者给出宝贵的意见,共同进步,一起发展!

最后还要祝各位读者猪年大吉,幸福安康:>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值