栈、堆、队列是什么?
栈:
栈是一种后进先出(LIFO, Last In First Out)的数据结构。 对这种后进先出规则的一个叫法,先有这种方法,后面有的这种叫法,和我们的堆放方法类似。栈: 留宿客商或储存货物的房屋(新华字典网络词典)
堆:
堆是一种特殊的树形数据结构,分为最大堆(Max Heap)和最小堆(Min Heap) 在数据结构中,为具有优先级排序的队列,堆通常被实现为一棵完全二叉树。这种树形结构看起来就像一个倒置的沙堆或土堆,因此得名“堆”。堆:常为排列的整齐有序的叠堆 (新华字典网络词典)
队列:
队列(Queue)是一种基本的数据结构,它遵循“先进先出”(FIFO, First In First Out)的原则。 排队的这种情况,按照顺序执行,操作系统中将任务从就绪态到运行态。特别是时间片轮转的时候中断入队排列。
栈、堆、队列实现
栈:
用Java实现一个栈,并分片讲解具体内容。这包括基于数组和基于链表的两种实现方式。
基于数组的栈实现
1. 构造函数与基本属性
首先,我们需要定义栈的基本属性和构造函数。数组栈需要一个数组来存储元素,以及一个指针 top
来指示栈顶的位置。
public class ArrayStack {
private int maxSize; // 栈的最大容量
private int[] stackArray; // 存储栈元素的数组
private int top; // 栈顶指针
// 构造函数
public ArrayStack(int size) {
maxSize = size;
stackArray = new int[maxSize];
top = -1; // 初始化栈为空
}
}
2. 压栈操作
压栈操作用于在栈顶添加一个新元素。在数组栈中,只需将元素添加到数组的最后一个位置,并更新 top
指针。
public void push(int value) {
if (top == maxSize - 1) {
throw new StackOverflowError("Stack is full");
}
stackArray[++top] = value;
}
3. 弹栈操作
弹栈操作用于移除并返回栈顶元素。需要检查栈是否为空,如果不为空,则返回栈顶元素并更新 top
指针。
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return stackArray[top--];
}
4. 取顶操作
取顶操作用于返回栈顶元素但不移除它。同样需要先检查栈是否为空。
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return stackArray[top];
}
5. 检查栈是否为空
检查栈是否为空,只需判断 top
指针是否为 -1。
public boolean isEmpty() {
return top == -1;
}
6. 获取栈的大小
栈的大小即为 top
指针加1。
public int size() {
return top + 1;
}
完整实现
public class ArrayStack {
private int maxSize;
private int[] stackArray;
private int top;
public ArrayStack(int size) {
maxSize = size;
stackArray = new int[maxSize];
top = -1;
}
public void push(int value) {
if (top == maxSize - 1) {
throw new StackOverflowError("Stack is full");
}
stackArray[++top] = value;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return stackArray[top--];
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return stackArray[top];
}
public boolean isEmpty() {
return top == -1;
}
public int size() {
return top + 1;
}
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(5);
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.peek()); // 输出 3
System.out.println(stack.pop()); // 输出 3
System.out.println(stack.size()); // 输出 2
}
}
基于链表的栈实现
1. 定义节点类
首先我们需要定义一个节点类,表示链表中的每个节点。每个节点包含值和指向下一个节点的指针。
private class Node {
int value;
Node next;
Node(int value) {
this.value = value;
}
}
2. 构造函数与基本属性
接下来,我们定义链表栈的基本属性和构造函数。
public class LinkedListStack {
private Node top; // 栈顶指针
private int size; // 栈的大小
public LinkedListStack() {
top = null;
size = 0;
}
}
3. 压栈操作
压栈操作用于在栈顶添加一个新元素。新元素成为新的栈顶,并指向之前的栈顶元素。
public void push(int value) {
Node newNode = new Node(value);
newNode.next = top;
top = newNode;
size++;
}
4. 弹栈操作
弹栈操作用于移除并返回栈顶元素。需要检查栈是否为空。
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
int value = top.value;
top = top.next;
size--;
return value;
}
5. 取顶操作
取顶操作用于返回栈顶元素但不移除它。需要检查栈是否为空。
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return top.value;
}
6. 检查栈是否为空
检查栈是否为空,只需判断 top
指针是否为空。
public boolean isEmpty() {
return top == null;
}
7. 获取栈的大小
栈的大小直接由 size
属性表示。
public int size() {
return size;
}
完整实现
public class LinkedListStack {
private Node top;
private int size;
private class Node {
int value;
Node next;
Node(int value) {
this.value = value;
}
}
public LinkedListStack() {
top = null;
size = 0;
}
public void push(int value) {
Node newNode = new Node(value);
newNode.next = top;
top = newNode;
size++;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
int value = top.value;
top = top.next;
size--;
return value;
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return top.value;
}
public boolean isEmpty() {
return top == null;
}
public int size() {
return size;
}
public static void main(String[] args) {
LinkedListStack stack = new LinkedListStack();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.peek()); // 输出 3
System.out.println(stack.pop()); // 输出 3
System.out.println(stack.size()); // 输出 2
}
}
- 基于数组的实现:操作简单,随机访问效率高,但需要预先确定栈的最大容量,可能会浪费内存或导致栈溢出。
- 基于链表的实现:不需要预先确定容量,动态分配内存,但每个节点多了一个指针,会稍微浪费一些内存。
堆:
堆是一种特殊的完全二叉树,主要有两种类型:
- 最大堆(Max Heap):每个节点的值都大于或等于其子节点的值,根节点是堆的最大值。
- 最小堆(Min Heap):每个节点的值都小于或等于其子节点的值,根节点是堆的最小值。
基于数组的最大堆实现
1. 定义堆的基本属性和构造函数
我们首先定义堆的基本属性和构造函数。堆使用数组来存储元素,并使用 size
来表示当前堆的大小。
public class MaxHeap {
private int[] heapArray; // 存储堆元素的数组
private int maxSize; // 堆的最大容量
private int size; // 当前堆的大小
// 构造函数
public MaxHeap(int maxSize) {
this.maxSize = maxSize;
this.size = 0;
heapArray = new int[maxSize + 1];
heapArray[0] = Integer.MAX_VALUE; // 哨兵值,用于简化父子节点比较
}
}
2. 获取父节点和子节点的索引
在数组中,节点的父节点和子节点的位置可以通过数学计算得到:
private int parent(int pos) {
return pos / 2;
}
private int leftChild(int pos) {
return (2 * pos);
}
private int rightChild(int pos) {
return (2 * pos) + 1;
}
3. 判断是否为叶子节点
叶子节点是指没有子节点的节点,可以通过位置计算得出。
private boolean isLeaf(int pos) {
return pos > (size / 2) && pos <= size;
}
4. 堆化操作
堆化操作将当前节点与其子节点进行比较,并进行交换以维护堆的性质。
private void heapify(int pos) {
if (!isLeaf(pos)) {
if (heapArray[pos] < heapArray[leftChild(pos)] || heapArray[pos] < heapArray[rightChild(pos)]) {
if (heapArray[leftChild(pos)] > heapArray[rightChild(pos)]) {
swap(pos, leftChild(pos));
heapify(leftChild(pos));
} else {
swap(pos, rightChild(pos));
heapify(rightChild(pos));
}
}
}
}
5. 交换操作
交换操作用于交换数组中的两个元素。
private void swap(int fpos, int spos) {
int tmp;
tmp = heapArray[fpos];
heapArray[fpos] = heapArray[spos];
heapArray[spos] = tmp;
}
6. 插入操作
插入操作将新元素添加到堆的末尾,然后上浮以维护堆的性质。
public void insert(int element) {
if (size >= maxSize) {
return;
}
heapArray[++size] = element;
int current = size;
while (heapArray[current] > heapArray[parent(current)]) {
swap(current, parent(current));
current = parent(current);
}
}
7. 删除最大值
删除最大值操作将堆顶元素移除,并将最后一个元素移到堆顶,然后进行下沉操作。
public int extractMax() {
int popped = heapArray[1];
heapArray[1] = heapArray[size--];
heapify(1);
return popped;
}
完整实现
public class MaxHeap {
private int[] heapArray;
private int maxSize;
private int size;
public MaxHeap(int maxSize) {
this.maxSize = maxSize;
this.size = 0;
heapArray = new int[maxSize + 1];
heapArray[0] = Integer.MAX_VALUE;
}
private int parent(int pos) {
return pos / 2;
}
private int leftChild(int pos) {
return (2 * pos);
}
private int rightChild(int pos) {
return (2 * pos) + 1;
}
private boolean isLeaf(int pos) {
return pos > (size / 2) && pos <= size;
}
private void swap(int fpos, int spos) {
int tmp;
tmp = heapArray[fpos];
heapArray[fpos] = heapArray[spos];
heapArray[spos] = tmp;
}
private void heapify(int pos) {
if (!isLeaf(pos)) {
if (heapArray[pos] < heapArray[leftChild(pos)] || heapArray[pos] < heapArray[rightChild(pos)]) {
if (heapArray[leftChild(pos)] > heapArray[rightChild(pos)]) {
swap(pos, leftChild(pos));
heapify(leftChild(pos));
} else {
swap(pos, rightChild(pos));
heapify(rightChild(pos));
}
}
}
}
public void insert(int element) {
if (size >= maxSize) {
return;
}
heapArray[++size] = element;
int current = size;
while (heapArray[current] > heapArray[parent(current)]) {
swap(current, parent(current));
current = parent(current);
}
}
public int extractMax() {
int popped = heapArray[1];
heapArray[1] = heapArray[size--];
heapify(1);
return popped;
}
public static void main(String[] args) {
System.out.println("The Max Heap is ");
MaxHeap maxHeap = new MaxHeap(15);
maxHeap.insert(5);
maxHeap.insert(3);
maxHeap.insert(17);
maxHeap.insert(10);
maxHeap.insert(84);
maxHeap.insert(19);
maxHeap.insert(6);
maxHeap.insert(22);
maxHeap.insert(9);
System.out.println("The max value is " + maxHeap.extractMax());
}
}
基于链表的优先队列实现
虽然链表不常用于实现堆,但我们可以通过链表实现一个优先队列,以便更灵活地处理优先级问题。
1. 定义节点类
节点类用于表示链表中的每个节点。每个节点包含一个值和指向下一个节点的指针。
public class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
2. 定义优先队列类
我们需要定义优先队列的基本属性和构造函数。
public class PriorityQueue {
private Node head;
public PriorityQueue() {
head = null;
}
}
3. 插入操作
插入操作将新元素按优先级插入到正确的位置。
public void insert(int value) {
Node newNode = new Node(value);
if (head == null || head.value < value) {
newNode.next = head;
head = newNode;
} else {
Node current = head;
while (current.next != null && current.next.value >= value) {
current = current.next;
}
newNode.next = current.next;
current.next = newNode;
}
}
4. 删除最高优先级元素
删除最高优先级元素即移除链表的头节点。
public int extractMax() {
if (head == null) {
throw new IllegalStateException("Priority queue is empty");
}
int value = head.value;
head = head.next;
return value;
}
完整实现
public class PriorityQueue {
private Node head;
private class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
public PriorityQueue() {
head = null;
}
public void insert(int value) {
Node newNode = new Node(value);
if (head == null || head.value < value) {
newNode.next = head;
head = newNode;
} else {
Node current = head;
while (current.next != null && current.next.value >= value) {
current = current.next;
}
newNode.next = current.next;
current.next = newNode;
}
}
public int extractMax() {
if (head == null) {
throw new IllegalStateException("Priority queue is empty");
}
int value = head.value;
head = head.next;
return value;
}
public static void main(String[] args) {
PriorityQueue pq = new PriorityQueue();
pq.insert(5);
pq.insert(3);
pq.insert(17);
pq.insert(10);
pq.insert(84);
pq.insert(19);
pq.insert(6);
pq.insert(22);
pq.insert(9);
System.out.println("The max value is " + pq.extractMax()); // 输出 84
}
}
- 基于数组的最大堆:常用且高效,适用于实现完全二叉树结构。
- 基于链表的优先队列:灵活但实现复杂性较高,不常用于堆实现,但在某些场景下非常有用。
队列:
了解,接下来我们将详细介绍队列的概念及其在Java中的实现。队列是一种重要的数据结构,遵循“先进先出”(FIFO,First In First Out)的原则。我们将分片讲解队列的基本概念、基于数组的实现、基于链表的实现以及循环队列的实现。
队列的基本概念
队列是一种线性数据结构,具有以下操作:
- 入队(Enqueue):将元素添加到队列的末尾。
- 出队(Dequeue):从队列的前端移除元素。
- 查看队列头部元素(Peek/Front):获取队列的第一个元素但不移除它。
- 检查队列是否为空(IsEmpty):判断队列是否为空。
基于数组的队列实现
1. 定义队列的基本属性和构造函数
我们首先定义队列的基本属性和构造函数。队列使用数组来存储元素,并使用 front
和 rear
指针表示队列的前端和末尾。
public class ArrayQueue {
private int[] queueArray; // 存储队列元素的数组
private int maxSize; // 队列的最大容量
private int front; // 队列头部指针
private int rear; // 队列尾部指针
private int size; // 当前队列的大小
// 构造函数
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.queueArray = new int[maxSize];
this.front = 0;
this.rear = -1;
this.size = 0;
}
}
2. 入队操作
入队操作将新元素添加到队列的末尾,需要检查队列是否已满。
public void enqueue(int value) {
if (size == maxSize) {
throw new IllegalStateException("Queue is full");
}
rear = (rear + 1) % maxSize;
queueArray[rear] = value;
size++;
}
3. 出队操作
出队操作从队列的前端移除元素,需要检查队列是否为空。
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = queueArray[front];
front = (front + 1) % maxSize;
size--;
return value;
}
4. 查看队列头部元素
查看队列头部元素需要检查队列是否为空。
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return queueArray[front];
}
5. 检查队列是否为空
public boolean isEmpty() {
return size == 0;
}
完整实现
public class ArrayQueue {
private int[] queueArray;
private int maxSize;
private int front;
private int rear;
private int size;
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.queueArray = new int[maxSize];
this.front = 0;
this.rear = -1;
this.size = 0;
}
public void enqueue(int value) {
if (size == maxSize) {
throw new IllegalStateException("Queue is full");
}
rear = (rear + 1) % maxSize;
queueArray[rear] = value;
size++;
}
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = queueArray[front];
front = (front + 1) % maxSize;
size--;
return value;
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return queueArray[front];
}
public boolean isEmpty() {
return size == 0;
}
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(5);
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
System.out.println(queue.peek()); // 输出 1
System.out.println(queue.dequeue()); // 输出 1
System.out.println(queue.dequeue()); // 输出 2
System.out.println(queue.isEmpty()); // 输出 false
}
}
基于链表的队列实现
链表实现队列的灵活性更高,不需要预先确定队列的大小。
1. 定义节点类
节点类用于表示链表中的每个节点。每个节点包含一个值和指向下一个节点的指针。
public class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
2. 定义队列的基本属性和构造函数
我们需要定义队列的基本属性和构造函数。
public class LinkedListQueue {
private Node front;
private Node rear;
private int size;
public LinkedListQueue() {
this.front = null;
this.rear = null;
this.size = 0;
}
}
3. 入队操作
入队操作将新元素添加到队列的末尾。
public void enqueue(int value) {
Node newNode = new Node(value);
if (rear != null) {
rear.next = newNode;
}
rear = newNode;
if (front == null) {
front = newNode;
}
size++;
}
4. 出队操作
出队操作从队列的前端移除元素,需要检查队列是否为空。
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = front.value;
front = front.next;
if (front == null) {
rear = null;
}
size--;
return value;
}
5. 查看队列头部元素
查看队列头部元素需要检查队列是否为空。
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return front.value;
}
6. 检查队列是否为空
public boolean isEmpty() {
return size == 0;
}
完整实现
public class LinkedListQueue {
private Node front;
private Node rear;
private int size;
private class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
public LinkedListQueue() {
this.front = null;
this.rear = null;
this.size = 0;
}
public void enqueue(int value) {
Node newNode = new Node(value);
if (rear != null) {
rear.next = newNode;
}
rear = newNode;
if (front == null) {
front = newNode;
}
size++;
}
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = front.value;
front = front.next;
if (front == null) {
rear = null;
}
size--;
return value;
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return front.value;
}
public boolean isEmpty() {
return size == 0;
}
public static void main(String[] args) {
LinkedListQueue queue = new LinkedListQueue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
System.out.println(queue.peek()); // 输出 1
System.out.println(queue.dequeue()); // 输出 1
System.out.println(queue.dequeue()); // 输出 2
System.out.println(queue.isEmpty()); // 输出 false
}
}
循环队列实现
循环队列是一种特殊的队列,解决了数组队列的空间浪费问题。
1. 定义队列的基本属性和构造函数
public class CircularQueue {
private int[] queueArray;
private int maxSize;
private int front;
private int rear;
private int size;
// 构造函数
public CircularQueue(int maxSize) {
this.maxSize = maxSize;
this.queueArray = new int[maxSize];
this.front = 0;
this.rear = -1;
this.size = 0;
}
}
2. 入队操作
public void enqueue(int value) {
if (size == maxSize) {
throw new IllegalStateException("Queue is full");
}
rear = (rear + 1) % maxSize;
queueArray[rear] = value;
size++;
}
3. 出队操作
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = queueArray[front];
front = (front + 1) % maxSize;
size--;
return value;
}
4. 查看队列头部元素
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return queueArray[front];
}
5. 检查队列是否为空
public boolean isEmpty() {
return size == 0;
}
完整实现
public class CircularQueue {
private int[] queueArray;
private int maxSize;
private int front;
private int rear;
private int size;
public CircularQueue(int maxSize) {
this.maxSize = maxSize;
this.queueArray = new int[maxSize];
this.front = 0;
this.rear = -1;
this.size = 0;
}
public void enqueue(int value) {
if (size == maxSize) {
throw new IllegalStateException("Queue is full");
}
rear = (rear + 1) % maxSize;
queueArray[rear] = value;
size++;
}
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = queueArray[front];
front = (front + 1) % maxSize;
size--;
return value;
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return queueArray[front];
}
public boolean isEmpty() {
return size == 0;
}
public static void main(String[] args) {
CircularQueue queue = new CircularQueue(5);
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
System.out.println(queue.peek()); // 输出 1
System.out.println(queue.dequeue()); // 输出 1
System.out.println(queue.dequeue()); // 输出 2
System.out.println(queue.isEmpty()); // 输出 false
}
}
- 基于数组的队列:简单、高效,但需要预先确定队列的最大容量。
- 基于链表的队列:灵活,无需预先确定容量,但实现复杂性较高。
- 循环队列:解决了数组队列的空间浪费问题,适用于固定大小的队列。
明白了,我们来具体探讨栈、堆和队列在现实生活中的一些具体应用实例。
现实使用操作
栈:
现实使用实例:
-
浏览器的“后退”功能:当你在浏览器中浏览网页时,浏览器会将你访问的页面URL推入栈中。当你点击“后退”按钮时,浏览器会从栈中弹出最新的URL,并显示前一个页面。
- 举例:你访问了三个网页A -> B -> C,当你点击“后退”按钮时,显示顺序会是C -> B -> A,符合栈的后进先出(LIFO)特性。
-
文本编辑器的“撤销”功能:文本编辑器(如Microsoft Word或Google Docs)中的“撤销”功能使用栈来存储用户的操作记录。每次用户进行一个编辑操作,都会将该操作推入栈中,当用户点击“撤销”按钮时,栈顶的操作会被弹出并逆转。
- 举例:你在文档中输入了几个字母A -> B -> C,当你点击“撤销”按钮时,删除顺序会是C -> B -> A。
堆:
现实使用实例:
-
任务调度:操作系统中的任务调度器使用优先级队列(可以用堆实现)来确定应该执行哪个任务。优先级高的任务会被先执行。
- 举例:假设有三个任务,其中任务A的优先级最高,任务B的优先级中等,任务C的优先级最低。任务调度器会首先执行任务A,然后是任务B,最后是任务C。
-
实时系统中的事件管理:在实时系统中,事件可能有不同的优先级,系统需要根据事件的优先级来处理。优先级队列(堆)用于管理这些事件,使得优先级高的事件能够得到及时处理。
- 举例:在金融交易系统中,高优先级的交易请求需要优先处理,以确保交易的及时性和准确性。
队列:
现实使用实例:
-
排队系统:在银行、医院、餐馆等场所,排队系统管理等待队列,确保先到的人先得到服务。
- 举例:你在银行排队办理业务,你前面有三个人,你们的办理顺序就是你前面的人 -> 第二个人 -> 第三个人 -> 你,这与队列的先进先出(FIFO)特性一致。
-
打印队列:在办公室或家庭中,打印机通常有一个打印队列,按顺序处理打印任务。
- 举例:你和你的同事分别提交了三个打印任务A -> B -> C,打印机会按照任务提交顺序依次打印这三个任务。
-
消息队列:在分布式系统中,消息队列用于异步消息通信。生产者将消息放入队列,消费者按顺序处理消息。
- 举例:在一个电子商务系统中,订单生成服务将订单信息放入消息队列,订单处理服务按顺序从队列中取出订单进行处理。
持续更新中~~