高效编程的秘密武器:十种数据结构,你真的了解吗?(下)

91 阅读6分钟

关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达!

引言

在上篇文章中,我们深入探讨了数组(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、哈希表、图、堆、栈、队列、树和集合。每一种结构都以其独特的方式,为我们的代码提供了组织和操作数据的强大力量。从数组的简洁高效,到图的复杂关系映射,再到堆的优先级管理,这些数据结构构成了我们解决编程问题的坚实基石。感谢你的陪伴,让我们在技术的海洋中继续前行,探索未知,创造更多可能。