目录
栈(Stack)
栈的概念
栈是一种特殊的线性表,只允许在固定的一端插入或删除数据,进行数据的插入和删除的一端称为栈顶,另一端称为栈底,栈中的数据遵循先进后出的原则
入栈/压栈: 数据的插入,插入的数据在栈顶
出栈: 数据的删除,删除的数据在栈顶
从上图可以看到,Stack继承了Vector,实现了List接口,Vector与ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的.
栈的使用
构造方法
方法 | 解释 |
---|---|
Stack() | 构造一个空栈 |
常用方法
//压栈
public E push(E item) {}
//出栈
public synchronized E pop() {}
//查看栈顶元素
public synchronized E peek() {}
//判断栈是否为空
public boolean empty() {}
//栈的有效元素的长度
public int size() {}
Stack<Integer> stack = new Stack<>();
stack.push(1); // 入栈
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.empty()); // 判断栈中是否为空 --> false
System.out.println(stack.size()); //输入栈的有效的元素的个数 --> 4
System.out.println(stack.pop()); //出栈,删除栈顶元素 --> 4 此时栈中还剩 1 2 3
System.out.println(stack.peek()); //查看栈顶元素 --> 3
System.out.println(stack.pop()); //出栈,删除栈顶元素 --> 3 此时栈中还剩 1 2
System.out.println(stack.pop()); //出栈,删除栈顶元素 --> 2 此时栈中还剩 1
System.out.println(stack.pop()); //出栈,删除栈顶元素 --> 1 此时栈中没有元素了
System.out.println(stack.empty()); //判断栈中是否为空 --> true
栈的模拟实现
public class MyStack {
public int[] elem;
public int size;
public MyStack() {
this.elem = new int[5];
}
//压栈
public int push(int x) {
//判断栈是否满
if (isFull()) {
//二倍扩容
this.elem = Arrays.copyOf(elem,2*elem.length);
}
this.elem[size++] = x; //先压栈,在size++
return 0;
}
private boolean isFull() {
if (size == elem.length) {
return true;
}
return false;
}
//出栈
public int pop() {
int a = elem[size-1];
size--;
//因为添加数据的时候,是根据size的值添加的,这里的size--,
//后面添加新的数据的时候,可以直接覆盖旧的数据,这样就达到了删除~~
return a;
}
//获取栈顶元素
public int peek() {
if (empty()) {//如果栈为空的话,抛出一个异常
throw new RuntimeException("栈为空,无法获取栈顶元素!");
}
//获取栈顶元素并返回
int a = elem[size-1];
return a;
}
//栈中有效元素的长度
public int size() {
//在每次添加或删除时,让size++或size--,随时记录栈中的长度,在获取栈的长度的时候,
//直接返回size,此时的时间复杂度为O(1) ,, 如果要遍历栈的活,这样时间复杂度为O(N).
return size;
}
//判断栈是否为空
public boolean empty() {
//如果栈中没有元素,也就是长度为0,则返回true
if(size == 0) {
return true;
}
return false;
}
}
队列(Queue)
队列的概念
只允许在一端进行插入操作,在另一端进行删除操作的一种的特殊的线性结构,队列具有先进后出的机制,进行插入的一端称为队尾,删除的一端称为队头
在java中,Queue是一个接口,底层使用LinkedList实现的,在实例化Queue的时候,必须实例化LinkedList对象.
队列的使用
常用方法
//入队列
boolean offer(E e);
//出队列,删除对头元素
E poll();
//判断队列是否为空
boolean isEmpty();
//获取队列头元素
E peek();
//获取队列的长度
int size();
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); // 入队列
queue.offer(2);
queue.offer(3);
System.out.println(queue.isEmpty()); // 判断队列是否为空 --> false
System.out.println(queue.poll()); //出队列 --> 1
System.out.println(queue.peek()); //获取队列头元素 --> 2
System.out.println(queue.size()); //获取队列的长度 --> 2
队列的模拟实现
public class MyQueue {
static class Node {
int val;
Node prev;
Node next;
Node(int val) {
this.val = val;
}
}
private Node head; //队头
private Node last; //队尾
private int usedSize;
//尾插
public void offer(int e) {
MyDeque.Node node = new MyDeque.Node(e);
if (head == null) { // 第一次插入
head = node;
}else {
//不是第一次插入
last.next = node;
last.next.prev = last;
}
last = node;
usedSize++;
}
//头删
public int poll() {
if(isEmpty()) {
throw new NullPointerException("链表无数据!");
}
int val = head.val;
if (head == last) { //只有一个节点
head = null;
last = null;
} else {
head = head.next;
head.prev.next = null;
head.prev = null;
}
usedSize--;
return val;
}
//获取队头元素,获取链表中第一个节点的值
public int peek(){
if (isEmpty()) {
throw new NullPointerException("链表无元素!");
}
return head.val;
}
//获取队列的长度
public int size() {
return usedSize;
}
//判断队列是否为空
public boolean isEmpty() {
if (head == null) {
return true;
}
return false;
}
}
循环队列
实际中我们有时还会使用一种队列叫做循环队列,循环队列通常使用数组实现
把普通的数组想象成一个环,然后让它循环起来就好了~~
-
如何让数组循环起来?
这里提供一个方法:
公式: (index+1)%array.length
在向下一步走 让下标+1在模上数组的长度就可以得到下一个元素的下标了,来达到数组循环的目的~~ -
如何区分空与满?
1.可以使用一个计数器usedSize记录当前数组的元素 usedSize = array.length 代表数组满了
2.可以"牺牲"一个数组空间 代表满(实现目的):
当 head = last 为空 当 (last+1)%array.length = head 为满
代码实现
class MyCircularQueue {
private int[] elme;
private int head;//代表首元素
private int last;//代表尾巴元素
public MyCircularQueue(int k) {
this.elme = new int[k+1]; // 因为是要浪费一个空间的所以这里多给了一个空间
}
//插入元素
public boolean enQueue(int value) {
//如果队列满了就没法插入了
if (isFull()) {
return false;
}
this.elme[last] = value;
last = (last+1)%this.elme.length;
return true;
}
//删除元素
public boolean deQueue() {
//如果队列为空没法删除
if (isEmpty()) {
return false;
}
head = (head+1)%this.elme.length;
return true;
}
//获取头元素
public int Front() {
//如果队列为空则返回-1
if (isEmpty()) {
return -1;
}
//返回首元素
return this.elme[head];
}
//获取队尾元素
public int Rear() {
//如果队列为空则返回-1
if (isEmpty()) {
return -1;
}
//当last = 0 时,返回数组最后一个下标的元素 , 否则返回last上一个元素
if (last == 0) {
return this.elme[this.elme.length-1];
} else {
return this.elme[last-1];
}
}
//判断队列是否为空
public boolean isEmpty() {
//当head等于last说明队列是空的
return head == last;
}
//判断队列是否为满
public boolean isFull() {
//当last下一个元素为head则说明队列满了
return ((last+1)%this.elme.length == head);
}
}
双端队列(Deque)
双端队列的概念
双端队列是指,允许两端都可以进行入队和出队操作,deque 是 “double ended queue” 的简称,既可以从队头出队入队,也可以从队尾出队入队.
在java中Deque是一个接口,要想使用Deque在进行实例化的时候,必须实例化它的实现类
Deque<Integer> deque = new ArrayDeque<>(); //双端队列的线性实现 Deque<Integer> deque = new LinkedList<>(); //双端队列的链性实现
双端队列的使用
boolean offerFirst(E e); // 头插
boolean offerLast(E e); // 尾插
E pollFirst(); // 头删
E pollLast(); // 尾删
E peekFirst(); // 获取头元素
E peekLast(); // 获取尾巴元素
boolean isEmpty(); // 判断队列是否为空
int size(); // 获取队列的长度
这里用LinkedList举例
public static void main(String[] args) {
Deque<Integer> deque = new LinkedList<>();
System.out.println("===头插法===");
deque.offerFirst(1);
deque.offerFirst(2);
deque.offerFirst(3);
System.out.println("===尾插法===");
deque.offerLast(4);
deque.offerLast(5);
deque.offerLast(6);
System.out.println("===头删法===");
System.out.println(deque.pollFirst()); // --> 3
System.out.println("===尾删法===");
System.out.println(deque.pollLast());// --> 6
System.out.println("===查看头结点===");
System.out.println(deque.peekFirst());// --> 2
System.out.println("===查看尾巴结点===");// --> 5
System.out.println(deque.peekLast());
}
双端队列的模拟实现
public class MyDeque {
static class Node {
int val;
Node prev;
Node next;
Node(int val) {
this.val = val;
}
}
private Node head; //队头
private Node last; //队尾
private int usedSize;
//入队,向双向链表尾部位置插入新节点
public void lastOffer(int e) {
Node node = new Node(e);
if (head == null) { // 第一次插入
head = node;
}else {
//不是第一次插入
last.next = node;
last.next.prev = last;
}
last = node;
usedSize++;
}
//入队,向双向链表头部位置插入新节点
public void headOffer(int e) {
Node node = new Node(e);
if (head == null) { // 第一次插入
head = node;
last = node;
} else {
//不是第一次插入
node.next = head;
head.prev = node;
head = node;
}
usedSize++;
}
//出队列,将双向链表尾部第一个节点删除掉
public int lastPoll() {
if(isEmpty()) {
throw new NullPointerException("链表无数据!");
}
int val = last.val;
if (head == last) {
head = null;
last = null;
}else {
last = last.prev;
last.next = null;
}
usedSize--;
return val;
}
//出队列,将双向链表头部第一个节点删除掉
public int headPoll() {
if(isEmpty()) {
throw new NullPointerException("链表无数据!");
}
int val = head.val;
if (head == last) { //只有一个节点
head = null;
last = null;
} else {
head = head.next;
head.prev.next = null;
head.prev = null;
}
usedSize--;
return val;
}
//获取队头元素,获取链表中第一个节点的值
public int headPeek(){
if (isEmpty()) {
throw new NullPointerException("链表无元素!");
}
return head.val;
}
//获取队尾巴元素,获取链表中第一个节点的值
public int lastPeek(){
if (isEmpty()) {
throw new NullPointerException("链表无元素!");
}
return last.val;
}
//队列的长度
public int size() {
return usedSize;
}
//队列是否为空
public boolean isEmpty() {
if (head == null) {
return true;
}
return false;
}
//打印队列 ,, 注意,java 中Deque并没有这个方法,只是为了测试能看到队列中的数据方便写的
public void display() {
Node cur = head;
while(cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
}