1.栈
1.栈的定义
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
栈遵循的规则:先进后出的原则。
2.栈的使用
import java.util.Stack;
public class Test {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);//压栈
stack.push(2);
stack.push(3);
Integer a = stack.pop();//出栈
System.out.println(a);
Integer b = stack.peek();//查看栈顶的元素
System.out.println(b);
Integer b2 = stack.peek();
System.out.println(b2);
System.out.println(stack.size());//获取栈中有效的元素个数
System.out.println(stack.isEmpty());//判断栈是否为空
}
}
3.栈的模拟实现
Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表。
package Stack;
import java.util.Arrays;
public class EmptyException extends RuntimeException {
public EmptyException() {
}
public EmptyException(String message) {
super(message);
}
}
public class MyStack {
public int[] elem;
public int usedSize;
public MyStack() {
this.elem = new int[10];
}
//压栈
public void push(int val) {
if(isFull()) {
//扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
usedSize++;
//elem[usedSize++] = val;
}
public boolean isFull() {
return usedSize == elem.length;
}
//出栈
public int pop() {
if (isEmpty()) {
throw new EmptyException("此时栈为空!");
}
int val = elem[usedSize-1];
usedSize--;
return val;
//return elem[--useSize];
}
public boolean isEmpty() {
return usedSize == 0;
}
public int peek() {
if (isEmpty()) {
throw new EmptyException("此时栈为空!");
}
return elem[usedSize - 1];
}
}
4.栈、虚拟机栈、栈帧的区别
虚拟机栈:具有特殊作用的一块内存空间。
jvm为了对数据进行更好的管理,将内存按照不同的需求划分出来的一种结构
栈区:线程私有的,栈区中存放的是函数调用相关的一些信息,主要是栈帧
当栈区中内存空间不足时,会抛出StackOverflowException
当中的元素(栈帧)是按照数据结构中的栈的特性来实现的
栈帧:一种结构,这种结构与函数调用相关的,内部:局部变量表、操作数栈…
每个方法在运行时,jvm都会创建一个栈帧,然后将栈帧压入到虚拟机栈中
当方法调用结束时,该方法对应的栈帧会从虚拟机栈中出栈
2.队列
1.队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头Head/Front)
队列遵循的规则:先进先出
2.队列的使用
注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
class Queue {
public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 从队尾入队列
System.out.println(q.size());
System.out.println(q.peek()); // 获取队头元素
q.poll();
System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
if(q.isEmpty()){
System.out.println("队列空");
}else{
System.out.println(q.size());
}
}
}
3.队列的模拟实现
package Stack;
import java.util.Arrays;
public class MyStack {
public int[] elem;
public int usedSize;
public MyStack() {
this.elem = new int[10];
}
//压栈
public void push(int val) {
if(isFull()) {
//扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
usedSize++;
//elem[usedSize++] = val;
}
public boolean isFull() {
return usedSize == elem.length;
}
//出栈
public int pop() {
if (isEmpty()) {
throw new EmptyException("此时栈为空!");
}
int val = elem[usedSize-1];
usedSize--;
return val;
//return elem[--useSize];
}
public boolean isEmpty() {
return usedSize == 0;
}
public int peek() {
if (isEmpty()) {
throw new EmptyException("此时栈为空!");
}
return elem[usedSize - 1];
}
}
3.循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
如何让数组下标循环呢?
如何区分数组满没满??
- 1.设置一个标记位
设置初始标记: boolean flag=false
当入队列时,让rear往后移动,让flag=true
当出队列时,让front往后移动,让flag=false
当队列为空时: rear == front && flag==false
当队列为满时: rear == front && flag == true
-
- 保留一个位置
- 保留一个位置
-
3.计数count——队列中有效元素个数
队列为空时,count == 0
当有元素入队时,count++,当count和队列的maxsize相等时,代表队列已满
代码实现:
class MyCircularQueue {
private int[] elem;
private int front;//表示队列的头
private int rear;//表示队列的尾
public MyCircularQueue(int k) {
this.elem = new int[k+1];
}
/**
* 入队列
* @param value
* @return
*/
public boolean enQueue(int value) {
//1.检查队列是否是满的
if (isFull()) {
return false;
}
//2.
elem[rear] = value;
rear = (rear + 1) % elem.length;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
front = (front + 1) % elem.length;
return true;
}
public int Front() {
if (isEmpty()) {
return -1;
}
return elem[front];
}
public int Rear() {
if (isEmpty()) {
return -1;
}
int index = (rear == 0) ? elem.length-1 : rear-1;
return elem[index];
}
public boolean isEmpty() {
return front == rear;
}
/**
* 队列是否为满
* @return
*/
public boolean isFull() {
/*if ((rear + 1) % elem.length == front) {
return true;
}
return false;*/
return (rear + 1) % elem.length == front;
}
}
4.双端队列
双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
//循环双端队列类
class DQueue{
public int maxSize; //双端队列的最大容量
public int queue[]; //数据域
public int head = 0; //队头指针,指向第一个元素
public int tail = 0; //队尾指针,指向最后一个元素的后一个位置
public DQueue(int n) {
//队尾指针需要指向最后一个元素的后一个位置,所以需要空出一个位置,即数组的实际大小+1
this.maxSize = n + 1;
queue = new int[this.maxSize];
}
//判断队列是否已满
public boolean isFull() {
return (tail + 1) % maxSize == head;
}
//判断队列是否为空
public boolean isEmpty() {
return tail == head;
}
//从头部入队
public void headInsert(int data) {
//先判断队列是否已满
if(isFull())
return ;
//头插,head自减1找到待插入位置,使用%防止越界
head = (head - 1 + maxSize) % maxSize;
queue[head] = data;
}
//从尾部入队
public void tailInsert(int data) {
//先判断队列是否已满
if(isFull())
return ;
queue[tail] = data;
//尾插,tail自增1找到待插入的位置,使用%防止越界
tail = (tail + 1) % maxSize;
}
//从头部出队
public int headPop() {
//先判断队列是否为空
if(isEmpty())
return -1;
int value = queue[head];
//出队后向后移动head指针
head = (head + 1) % maxSize;
return value;
}
//从尾部出队
public int tailPop() {
//先判断队列是否为空
if(isEmpty())
return -1;
//先向前移动tail指针指向待出队数据位置
tail = (tail - 1 + maxSize) % maxSize;
int value = queue[tail];
return value;
}
//获取队头元素
public int getHead() {
return queue[head];
}
//获取队尾元素
public int getTail() {
return queue[(tail - 1 + maxSize) % maxSize];
}
//遍历队列
public void display() {
if(isEmpty()) {
System.out.println("队列空");
return ;
}
while(head != tail) {
System.out.println(queue[head]);
//head向后移动
head = (head + 1) % maxSize;
}
}
}
5.用队列实现栈
class MyStack {
private Queue<Integer> qu1;
private Queue<Integer> qu2;
public MyStack() {
qu1 = new LinkedList<>();
qu2 = new LinkedList<>();
}
public void push(int x) {
if(!qu1.isEmpty()) {
qu1.offer(x);
}else if(!qu2.isEmpty()) {
qu2.offer(x);
}else {
qu1.offer(x);
}
}
public int pop() {
if (empty()) {
return -1;//两个队列都为空
}
if (!qu1.isEmpty()) {
int size = qu1.size();
for (int i = 0; i < size - 1; i++) {
int val = qu1.poll();
qu2.offer(val);
}
return qu1.poll();
} else {
if (!qu2.isEmpty()) {
int size = qu2.size();
for (int i = 0; i < size - 1; i++) {
int val = qu2.poll();
qu1.offer(val);
}
}
return qu2.poll();
}
}
public int top() {
if (empty()) {
return -1;//两个队列都为空
}
if (!qu1.isEmpty()) {
int size = qu1.size();
int val = -1;
for (int i = 0; i < size; i++) {
val = qu1.poll();
qu2.offer(val);
}
return val;
} else {
int size = qu2.size();
int val = -1;
for (int i = 0; i < size; i++) {
val = qu2.poll();
qu1.offer(val);
}
return val;
}
}
public boolean empty() {
return qu1.isEmpty() && qu2.isEmpty();
}
}
6.用栈实现队列
class MyQueue {
private Stack<Integer> stack1;
private 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.isEmpty()) {
while(! stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if (empty()) {
return -1;
}
if (stack2.isEmpty()) {
while(! stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}