关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达!
引言
在上篇文章中,我们深入探讨了数组(Array)、链表(Linked List)、HashMap、哈希表(Hash Table)以及图(Graph)这些基础数据结构的具体使用场景,了解了它们在不同编程任务中的独特优势和适用性。这些数据结构为我们解决复杂问题提供了坚实的基础,帮助我们在数据存储、检索和操作方面更加高效。
在本篇章中,我们将继续探讨堆(Heap)、栈(Stack)、队列(Queue)、树(Tree)以及集合(Set)等重要数据结构,深入剖析这些数据结构的内部机制、优缺点及实际应用。通过学习,你将全面掌握数据结构的核心,灵活应对各种编程挑战。让我们继续探索数据结构的奥秘,提升技术实力,迎接更复杂的编程任务。
堆(Heap)
堆(Heap)是一种特殊的完全二叉树,分为最大堆和最小堆。在最大堆中,每个节点的值都大于或等于其子节点的值;在最小堆中,每个节点的值都小于或等于其子节点的值。堆通常用于实现优先队列。
特点
- 完全二叉树结构。
- 每个节点的值大于或等于其子节点的值(最大堆),或者小于或等于其子节点的值(最小堆)。
优点:
- 插入和删除操作的时间复杂度为 O(log n)。
- 适合实现优先队列。
缺点:
- 查找操作的时间复杂度为 O(n)。
解决的问题:
- 实现优先级队列。
- 动态集合中的最小/最大元素检索。
相关算法:
- 堆排序
- 优先队列操作
代码示例 (简单的最大堆)
import java.util.PriorityQueue;
import java.util.Comparator;
public class MaxHeapExample {
public static void main(String[] args) {
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
maxHeap.add(10);
maxHeap.add(20);
maxHeap.add(15);
System.out.println("Maximum element: " + maxHeap.peek());
System.out.println("Elements in max heap:");
while (!maxHeap.isEmpty()) {
System.out.print(maxHeap.poll() + " ");
}
}
}
执行结果
栈(Stack)
栈是一种后进先出(LIFO)的数据结构。
特点:
- 后进先出(LIFO)特性。
- 只能访问栈顶元素。
优点:
- 操作简单,只需要两个基本操作:压栈(push)和弹栈(pop)。
- 适用于表达式求值、函数调用管理等场景。
缺点:
- 只能访问栈顶元素。
解决的问题:
- 表达式求值。
- 函数调用管理。
- 判断括号匹配。
相关算法:
- 判断括号匹配。
- 塔汉诺伊问题。 代码示例
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
// Popping an element
System.out.println("Popped element: " + stack.pop());
// Peek at the top element without removing it
System.out.println("Top element: " + stack.peek());
// Iterating through the stack
System.out.println("Stack elements:");
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
// Bracket matching example
String expression = "{[(])}";
System.out.println("Bracket matching result: " + isBalanced(expression));
}
public static boolean isBalanced(String expression) {
Stack<Character> stack = new Stack<>();
for (char ch : expression.toCharArray()) {
if (ch == '{' || ch == '[' || ch == '(') {
stack.push(ch);
} else if (ch == '}' || ch == ']' || ch == ')') {
if (stack.isEmpty()) {
return false;
}
char top = stack.pop();
if ((ch == '}' && top != '{') ||
(ch == ']' && top != '[') ||
(ch == ')' && top != '(')) {
return false;
}
}
}
return stack.isEmpty();
}
}
执行结果
队列 (Queue)
队列是一种先进先出(FIFO)的数据结构。
特点:
- 先进先出(FIFO)特性。
优点:
- 符合许多实际需求,如资源分配、任务调度等。
- 支持多线程环境下的并发操作。
缺点:
- 不支持随机访问。
解决的问题:
- 资源分配。
- 任务调度。
- 广度优先搜索(BFS)。
相关算法:
- 广度优先搜索(BFS)。
代码示例
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("Alice");
queue.add("Bob");
queue.add("Charlie");
// Polling an element
System.out.println("Polled element: " + queue.poll());
// Peek at the front element without removing it
System.out.println("Front element: " + queue.peek());
// Iterating through the queue
System.out.println("Queue elements:");
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
// BFS Example on a simple graph
int[][] graph = {
{1, 2},
{0, 3, 4},
{0, 4},
{1, 4},
{1, 2, 3}
};
bfs(graph, 0);
}
public static void bfs(int[][] graph, int start) {
boolean[] visited = new boolean[graph.length];
Queue<Integer> queue = new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int vertex = queue.poll();
System.out.print(vertex + " ");
for (int neighbor : graph[vertex]) {
if (!visited[neighbor]) {
queue.add(neighbor);
visited[neighbor] = true;
}
}
}
}
}
执行结果
树(Tree)
树(Tree)是一种分层数据结构,由节点组成,每个节点包含一个值和一个或多个子节点。
二叉搜索树 (BST):
特点:
- 每个节点最多有两个子节点
- 左子树的所有节点值小于根节点值,右子树的所有节点值大于根节点值
优点:
- 结构清晰,便于理解和编程
缺点:
- 平衡性差可能导致搜索时间变长
平衡二叉树 (如 AVL Tree, Red-Black Tree):
特点:
- 自动调整以保持树的平衡
优点:
- 自动调整以保持树的平衡,确保操作的时间复杂度为 O(log n)
缺点:
- 实现较为复杂
解决的问题:
- 数据排序
- 范围查询
相关算法:
- 中序遍历
- 前序遍历
- 后序遍历 代码示例 (简单的二叉搜索树)
代码示例
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
class BinarySearchTree {
private TreeNode root;
public void insert(int val) {
root = insertRec(root, val);
}
private TreeNode insertRec(TreeNode root, int val) {
if (root == null) {
root = new TreeNode(val);
return root;
}
if (val < root.val) {
root.left = insertRec(root.left, val);
} else if (val > root.val) {
root.right = insertRec(root.right, val);
}
return root;
}
public boolean search(int val) {
return searchRec(root, val);
}
private boolean searchRec(TreeNode root, int val) {
if (root == null) {
return false;
}
if (root.val == val) {
return true;
}
return val < root.val ? searchRec(root.left, val) : searchRec(root.right, val);
}
public void inorderTraversal() {
inorderRec(root);
}
private void inorderRec(TreeNode root) {
if (root != null) {
inorderRec(root.left);
System.out.print(root.val + " ");
inorderRec(root.right);
}
}
}
public class BSTExample {
public static void main(String[] args) {
BinarySearchTree bst = new BinarySearchTree();
bst.insert(50);
bst.insert(30);
bst.insert(20);
bst.insert(40);
bst.insert(70);
bst.insert(60);
bst.insert(80);
System.out.println("Inorder Traversal:");
bst.inorderTraversal();
System.out.println("\nSearch 40: " + bst.search(40));
System.out.println("Search 90: " + bst.search(90));
}
}
执行结果
集合(Set)
集合是一种不允许存储重复元素的容器。
HashSet:
特点:
- 基于哈希表实现。
- 无序集合。
优点:
- 平均情况下,插入、删除和查找的时间复杂度为 O(1)。
缺点:
- 内存占用较大,尤其是当哈希冲突较多时。
TreeSet:
特点:
- 基于红黑树实现。
- 有序集合。
优点:
- 插入、删除和查找的时间复杂度为 O(log n)。
缺点:
- 性能稍低于 HashSet。
LinkedHashSet
特点:
- 基于哈希表和双向链表实现。
- 维护插入顺序。
优点:
- 插入、删除和查找的时间复杂度为 O(1)。
缺点:
- 内存占用较大。
解决的问题:
- 去重。
- 唯一性检查。
相关算法:
- 查找重复元素。
- 统计频率。
代码示例
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetExample {
public static void main(String[] args) {
// HashSet example
Set<Integer> hashSet = new HashSet<>();
hashSet.add(1);
hashSet.add(2);
hashSet.add(3);
hashSet.add(2); // Duplicate will not be added
System.out.println("HashSet elements:");
for (int num : hashSet) {
System.out.print(num + " ");
}
// TreeSet example
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(3);
treeSet.add(4);
treeSet.add(2);
treeSet.add(1);
System.out.println("\n\nTreeSet elements:");
for (int num : treeSet) {
System.out.print(num + " ");
}
// LinkedHashSet example
Set<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(1);
linkedHashSet.add(2);
linkedHashSet.add(3);
linkedHashSet.add(2); // Duplicate will not be added
System.out.println("\n\nLinkedHashSet elements:");
for (int num : linkedHashSet) {
System.out.print(num + " ");
}
// Finding duplicates in an array using HashSet
int[] nums = {1, 2, 3, 4, 5, 2, 3};
findDuplicates(nums);
}
public static void findDuplicates(int[] nums) {
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = new HashSet<>();
for (int num : nums) {
if (!seen.add(num)) {
duplicates.add(num);
}
}
System.out.println("\n\nDuplicate elements:");
for (int num : duplicates) {
System.out.print(num + " ");
}
}
}
执行结果
结语
在过去的文章中,我们深入探索了编程世界中不可或缺的数据结构:数组、链表、HashMap、哈希表、图、堆、栈、队列、树和集合。每一种结构都以其独特的方式,为我们的代码提供了组织和操作数据的强大力量。从数组的简洁高效,到图的复杂关系映射,再到堆的优先级管理,这些数据结构构成了我们解决编程问题的坚实基石。感谢你的陪伴,让我们在技术的海洋中继续前行,探索未知,创造更多可能。