PS:本文将介绍什么是栈和队列以及,顺序栈、队列(顺序队列、循环队列)、链式栈和链式队列的代码实现,以及栈和队列的面试题:两个栈实现一个队列、两个队列实现一个栈
栈
什么是栈?
①栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
②栈就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来(先进后出)
③栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有FIFO的特性,在编译的时候可以指定需要的Stack的大小。
栈遵循先入后出原则,栈的基本操作有 入栈、出栈、判断栈空、栈满以及获取栈顶元素,下面为栈的代码实现:
代码实现:
import java.util.Arrays;
import java.util.Random;
class SeqStack {
//存储栈的元素的数组
private int [] stack;
//top表示栈顶的位置
private int top;
/**
* 默认的构造函数,默认开辟10个位置空间
*/
public SeqStack() {
this (10);
}
/**
* 用户可以指定栈的大小size
* @param size
*/
public SeqStack(int size) {
this.stack = new int[size];
this.top = 0;
}
/**
* 入栈操作
* @param val
*/
public void push(int val) {
//如果栈满,需要进行内存的二倍扩容
if (full ()) {
this.stack = Arrays.copyOf (this.stack, this.stack.length * 2);
}
this.stack[this.top] = val;
this.top++;
}
/**
* 出栈操作
*/
public void pop() {
if (empty ()) {
return;
}
this.top--;
//如果栈内元素,少于栈内存大小的一半,进行缩容处理
if (this.top < this.stack.length / 2) {
this.stack = Arrays.copyOf (this.stack, this.stack.length / 2);
}
}
/**
* 获取栈顶元素
*/
public int top() {
return this.stack[this.top - 1];
}
/**
* 判断栈栈满
*/
public boolean full() {
return this.top == this.stack.length;
}
/**
* 判断栈空
*/
public boolean empty() {
return this.top == 0;
}
}
public class App {
public static void main(String[] args) {
SeqStack s1 = new SeqStack();
Random random = new Random();
//打印入栈顺序
for (int i = 0; i < 20; i++) {
int data = random.nextInt(100);
System.out.print(data + "\t");
s1.push(data);
}
//打印出栈顺序
System.out.println();
while (!s1.empty()) {
int top = s1.top();
System.out.print(top + "\t");
s1.pop();
}
System.out.println();
}
}
打印结果:
56 38 51 30 7 95 30 24 8 77 83 66 99 3 88 34 14 83 90 36
36 90 83 14 34 88 3 99 66 83 77 8 24 30 95 7 30 51 38 56
队列
什么是队列?
①队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
②队列中没有元素时,称为空队列。
③建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置。
④队列采用的FIFO(first in first out),新元素(等待进入队列的元素)总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由结构体间接而成,遍历也方便。(先进先出)
队列遵循先入先出,后入后出的原则,队列的基本操作有入队、出队、判读队列是否为空,下面为代码实现
顺序队列实现:
import java.util.Arrays;
import java.util.Random;
/**
* 顺序队列(循环队列) 特点:先入先出、后入后出
*/
class Queue<T> extends Object {
// 存储队列元素的数组
private T[] que;
// 表示队头的位置
private int front;
// 表示队尾的位置
private int rear;
/**
* 默认构造队列,初始大小是10
*/
public Queue() {
this (10);
}
/**
* 用户可以指定队列的大小size
*
* @param size
*/
public Queue(int size) {
this.que = (T[]) new Object[size];
this.front = 0;
this.rear = 0;
}
/**
* 入队操作
*
* @param val
*/
public void offer(T val) {
//队满进行扩容,需要重新开辟新的数组
if (full ()) {
T[] newQue = Arrays.copyOf (this.que, this.que.length * 2);
int index = 0;
for (int i = front; i != rear; i = (i + 1) % this.que.length) {
newQue[index++] = this.que[i];
}
this.front = 0;
this.rear = index;
this.que = newQue;
}
que[rear] = val;
rear = (rear + 1) % que.length;
}
/**
* 出队操作,并把出队的元素的值返回
*/
public T pop() {
if (empty ()) {
throw new IndexOutOfBoundsException ("空队列异常");
}
T oldVal = que[front];
que[front] = null;
front = (front+1) % que.length;
return oldVal;
}
/**
* 查看队头元素
* @return
*/
public T poll() {
return que[front];
}
/**
* 判断队满
* @return
*/
public boolean full() {
return (rear + 1) == que.length;
}
/**
* 判断队空
* @return
*/
public boolean empty() {
return rear == front;
}
}
/**
* 测试代码
*/
public class App {
public static void main(String[] args) {
Queue<Integer> queue = new Queue ();
Random random = new Random ();
for (int i = 0; i < 20; i++) {
int data = random.nextInt (100);
System.out.print (data + " ");
queue.offer (data);
}
System.out.println ();
while (! queue.empty ()) {
Object front = queue.poll ();
System.out.print (front + " ");
queue.pop ();
}
}
}
顺序队列的弊端:
设顺序存储队列用一维数组q[m]表示,其中m为队列中元素个数,队列中元素在向量中的下标从0到m-1。
设队头指针为front,队尾指针是rear,约定front指向队头元素的前一-位置,rear指向队尾元素。当front等于-1时队空,rear等于m-1时为队满。由于队列的性质(“删除”在队头而“插入”在队尾),所以当队尾,指针rear等于m-1时,若front不等于-1, :则队列中仍有空闲单元,所以队列并不是真满,这时若再有入队操作,会造成假溢出。如下图:
上图中,front指针指向队头元素,rear指针指向队尾元素的下一个位置。图(d)中b、c、d出队后,front指针指向元素e,rear指针在数组外面。假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经被占用,再向后加就会产生数组越界的错误,可实际上队列在下标为0、1、2、3、4的地方还是空闲的,我们把这种现象叫做“假溢出”。
循环队列实现:
所以解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种逻辑上首尾相连的顺序存储结构称为循环队列:
import java.util.Arrays;
import java.util.Random;
/**
* 顺序队列(循环队列) 特点:先入先出、后入后出
*/
class Queue<T> extends Object {
// 存储队列元素的数组
private T[] que;
// 表示队头的位置
private int front;
// 表示队尾的位置
private int rear;
/**
* 默认构造队列,初始大小是10
*/
public Queue() {
this (10);
}
/**
* 用户可以指定队列的大小size
*
* @param size
*/
public Queue(int size) {
this.que = (T[]) new Object[size];
this.front = 0;
this.rear = 0;
}
/**
* 入队操作
*
* @param val
*/
public void offer(T val) {
//队满进行扩容,需要重新开辟新的数组
if (full ()) {
T[] newQue = Arrays.copyOf (this.que, this.que.length * 2);
int index = 0;
for (int i = this.front; i != this.rear; i = (i + 1) % this.que.length) {
newQue[index++] = this.que[i];
}
this.front = 0;
this.rear = index;
this.que = newQue;
}
que[rear] = val;
rear = (rear + 1) % que.length;
}
/**
* 出队操作,并把出队的元素的值返回
*/
public T pop() {
if (empty ()) {
throw new IndexOutOfBoundsException ("空队列异常");
}
T oldVal = que[front];
que[front] = null;
front = (front+1) % que.length;
return oldVal;
}
/**
* 查看队头元素
* @return
*/
public T poll() {
return que[this.front];
}
/**
* 判断队满
* @return
*/
public boolean full() {
return (rear + 1) == que.length;
}
/**
* 判断队空
* @return
*/
public boolean empty() {
return rear == front;
}
}
/**
* 测试代码
*/
public class App {
public static void main(String[] args) {
Queue<Integer> queue = new Queue ();
Random random = new Random ();
for (int i = 0; i < 20; i++) {
int data = random.nextInt (100);
System.out.print (data + " ");
queue.offer (data);
}
System.out.println ();
while (! queue.empty ()) {
Object front = queue.poll ();
System.out.print (front + " ");
queue.pop ();
}
}
}
栈和队列的区别
1、队列先进先出,栈先进后出。
2、对插入和删除操作的"限定"不同。
栈是限定只能在表的一端进行插入和删除操作的线性表。
队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
3、遍历数据速度不同。
栈只能从头部取数据,也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性。
队列则不同,它基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
链式栈代码实现:
面试题
ps:下面使用的栈和队列都是基于上面所写的代码为基础
两个栈实现一个队列:
实现思路:
利用栈先入后出的特性,使用两个栈可以实现队列的pop和push操作:
push: 往stack1中push元素。
pop: 先判断stack2是否为空,若为空,将stack1中的所有元素pop至stack2中,取出stack2的栈顶元素pop出去; 若不为空,直接取出stack2的栈顶元素pop出去
class Queue<T>{
//定义两个栈
private SeqStack<T> stack1;
private SeqStack<T> stack2;
public Queue(){
this.stack1 = new SeqStack<>();
this.stack2 = new SeqStack<>();
}
//在stack1入栈操作
public void offer(T val){
this.stack1.push(val);
}
public T poll() {
T data = null;
//若stack2为空,将stack1中的所有元素出栈,然后入栈到stack2中
if (stack2.empty ()) {
while (!stack1.empty ()) {
data = stack1.top ();
stack1.pop ();
if (stack1.empty ()) {
break;
}
stack2.push (data);
}
}
//若不为空,取出stack2的栈顶元素进行出栈操作
while (!stack2.empty ()) {
stack1.push (stack2.top ());
stack2.pop ();
}
return data;
}
public boolean empty(){
return stack1.empty();
}
}
/**
* 测试代码
*/
public class 两个栈实现一个队列 {
public static void main(String[] args) {
Queue q = new Queue<>();
System.out.print ("入队: " );
for(int i=0; i<10; ++i){
System.out.print ( i + " ");
q.offer(i);
}
System.out.println ();
System.out.print ("出队: " );
while(!q.empty()){
System.out.print(q.poll() + " ");
}
System.out.println();
}
}
运行结果:
入队: 0 1 2 3 4 5 6 7 8 9
出队: 0 1 2 3 4 5 6 7 8 9
两个队列实现一个栈:
实现思路:
入栈操作:将元素插入到queen1;
出栈操作:把queue1里所有元素出队,放入queue2里面,然后把queue1最后一个出队的元素直接返回,不用放入que2,(此时queue1队列为空)获取到该出队元素之后,再把queue2的元素再放入到queue1中(此时queue2队列为空),继续重复操作直到queue1中的元素全部出队,两个队列实现出栈操作
class Stack<T> {
private Queue<T> queue1; // 存放栈的元素
private Queue<T> queue2;// 做一个辅助操作
public Stack() {
this.queue1 = new Queue<> ();
this.queue2 = new Queue<> ();
}
public void push(T val) {
queue1.offer (val);
}
public T pop(){
T data = null;
//把queue1里面的所有元素出队,放入queue2里面,
// 然后把que1最后一个出队的元素直接返回,不用放入que2
while (!queue1.empty ()){
data = queue1.pop ();
if(queue1.empty ()){
break;
}
queue2.offer (data);
}
// 获取该出队的元素以后,再把queue2的元素再放入queue1里面
while (!queue2.empty ()){
queue1.offer (queue2.pop ());
}
return data;
}
public boolean empty(){
return this.queue1.empty();
}
}
public class 两个队列实现一个栈 {
public static void main(String[] args) {
Stack<Integer> s = new Stack<>();
for(int i=0; i<11; ++i){
System.out.print (i + " ");
s.push(i);
}
System.out.println ();
while(!s.empty())
System.out.print(s.pop () + " ");
System.out.println();
}
}
打印结果:
0 1 2 3 4 5 6 7 8 9 10
10 9 8 7 6 5 4 3 2 1 0