什么是数据结构?
数据结构(Data Structure)是计算机存储、组织数据的方式。一个好的数据结构可以让程序运行得更快、占用更少的内存。选择合适的数据结构,是编写高效程序的关键。
数据结构的分类
数据结构按照不同的维度可以分为多种类型:
按存储结构分类
1. 线性结构
数据元素之间存在一对一的线性关系。
- 数组 (Array)
- 链表 (LinkedList)
- 栈 (Stack)
- 队列 (Queue)
2. 非线性结构
数据元素之间存在一对多或多对多的关系。
- 树 (Tree)
- 图 (Graph)
- 堆 (Heap)
按逻辑结构分类
- 集合结构: 数据元素之间除了”同属一个集合”外,没有其他关系
- 线性结构: 数据元素之间是一对一的关系
- 树形结构: 数据元素之间是一对多的层次关系
- 图形结构: 数据元素之间是多对多的关系
线性数据结构
数组 (Array)
定义: 数组是最基础的数据结构,用连续的内存空间存储相同类型的元素。
特点:
- ✅ 支持快速随机访问 (时间复杂度 O(1))
- ✅ 内存连续,缓存友好
- ❌ 插入/删除效率低 (需要移动元素)
- ❌ 大小固定,不易扩展
Java 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13
| int[] array = new int[10]; array[0] = 1; array[1] = 2;
List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3);
System.out.println("元素访问: " + list.get(0)); list.remove(0);
|
时间复杂度:
- 访问: O(1)
- 查找: O(n)
- 插入: O(n) (平均)
- 删除: O(n) (平均)
应用场景:
- 需要快速随机访问元素
- 数据量固定或变化不大
- 频繁读取,很少修改
ArrayList 扩容机制:
1 2 3 4 5 6 7 8 9
|
int newCapacity = oldCapacity + (oldCapacity >> 1);
|
链表 (LinkedList)
定义: 链表是由一系列节点组成的数据结构,每个节点包含数据和指向下一个节点的指针。
分类:
- 单向链表: 每个节点只有一个指向下一个节点的指针
- 双向链表: 每个节点有两个指针,分别指向前后节点
- 循环链表: 尾节点指向头节点,形成环
特点:
- ✅ 插入/删除效率高 (O(1))
- ✅ 大小动态可变
- ❌ 不支持随机访问
- ❌ 需要额外的内存存储指针
单向链表实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| class Node { int data; Node next;
public Node(int data) { this.data = data; this.next = null; } }
class LinkedList { private Node head;
public void insertAtHead(int data) { Node newNode = new Node(data); newNode.next = head; head = newNode; }
public void insertAtTail(int data) { Node newNode = new Node(data);
if (head == null) { head = newNode; return; }
Node current = head; while (current.next != null) { current = current.next; } current.next = newNode; }
public void delete(int data) { if (head == null) return;
if (head.data == data) { head = head.next; return; }
Node current = head; while (current.next != null) { if (current.next.data == data) { current.next = current.next.next; return; } current = current.next; } }
public boolean search(int data) { Node current = head; while (current != null) { if (current.data == data) { return true; } current = current.next; } return false; }
public void print() { Node current = head; while (current != null) { System.out.print(current.data + " -> "); current = current.next; } System.out.println("null"); } }
LinkedList list = new LinkedList(); list.insertAtHead(3); list.insertAtHead(2); list.insertAtHead(1); list.insertAtTail(4); list.print();
list.delete(2); list.print();
|
双向链表实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| class DoublyNode { int data; DoublyNode prev; DoublyNode next;
public DoublyNode(int data) { this.data = data; this.prev = null; this.next = null; } }
class DoublyLinkedList { private DoublyNode head; private DoublyNode tail;
public void insertAtHead(int data) { DoublyNode newNode = new DoublyNode(data);
if (head == null) { head = tail = newNode; } else { newNode.next = head; head.prev = newNode; head = newNode; } }
public void insertAtTail(int data) { DoublyNode newNode = new DoublyNode(data);
if (tail == null) { head = tail = newNode; } else { tail.next = newNode; newNode.prev = tail; tail = newNode; } }
public void printForward() { DoublyNode current = head; while (current != null) { System.out.print(current.data + " <-> "); current = current.next; } System.out.println("null"); }
public void printBackward() { DoublyNode current = tail; while (current != null) { System.out.print(current.data + " <-> "); current = current.prev; } System.out.println("null"); } }
|
时间复杂度:
- 访问: O(n)
- 查找: O(n)
- 插入: O(1) (已知位置) / O(n) (需要查找)
- 删除: O(1) (已知位置) / O(n) (需要查找)
数组 vs 链表:
| 操作 |
数组 |
链表 |
| 随机访问 |
O(1) |
O(n) |
| 插入/删除 |
O(n) |
O(1) |
| 内存占用 |
连续空间 |
分散空间 + 指针 |
| 缓存友好性 |
好 |
差 |
栈 (Stack)
定义: 栈是一种后进先出(LIFO - Last In First Out)的数据结构。
核心操作:
push(): 入栈 - 将元素压入栈顶
pop(): 出栈 - 弹出栈顶元素
peek(): 查看栈顶元素(不删除)
isEmpty(): 判断栈是否为空
基于数组实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class ArrayStack { private int[] array; private int top; private int capacity;
public ArrayStack(int capacity) { this.capacity = capacity; this.array = new int[capacity]; this.top = -1; }
public void push(int data) { if (top == capacity - 1) { throw new StackOverflowError("栈已满"); } array[++top] = data; }
public int pop() { if (isEmpty()) { throw new IllegalStateException("栈为空"); } return array[top--]; }
public int peek() { if (isEmpty()) { throw new IllegalStateException("栈为空"); } return array[top]; }
public boolean isEmpty() { return top == -1; }
public int size() { return top + 1; } }
ArrayStack stack = new ArrayStack(5); stack.push(1); stack.push(2); stack.push(3);
System.out.println("栈顶元素: " + stack.peek()); System.out.println("出栈: " + stack.pop()); System.out.println("出栈: " + stack.pop());
|
基于链表实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class LinkedStack { private Node top; private int size;
private class Node { int data; Node next;
Node(int data) { this.data = data; } }
public void push(int data) { Node newNode = new Node(data); newNode.next = top; top = newNode; size++; }
public int pop() { if (isEmpty()) { throw new IllegalStateException("栈为空"); } int data = top.data; top = top.next; size--; return data; }
public int peek() { if (isEmpty()) { throw new IllegalStateException("栈为空"); } return top.data; }
public boolean isEmpty() { return top == null; }
public int size() { return size; } }
|
Java 标准库中的栈:
1 2 3 4 5 6 7 8 9 10 11
| Stack<Integer> stack = new Stack<>(); stack.push(1); stack.push(2); stack.pop();
Deque<Integer> stack = new ArrayDeque<>(); stack.push(1); stack.push(2); stack.pop();
|
应用场景:
- 函数调用栈: 程序运行时的函数调用记录
- 表达式求值: 后缀表达式计算
- 括号匹配: 检查括号是否匹配
- 浏览器历史: 后退功能
- 撤销操作: Ctrl+Z 功能
经典题目: 括号匹配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public boolean isValidParentheses(String s) { Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) { if (c == '(' || c == '[' || c == '{') { stack.push(c); } else { if (stack.isEmpty()) return false;
char top = stack.pop(); if (c == ')' && top != '(') return false; if (c == ']' && top != '[') return false; if (c == '}' && top != '{') return false; } }
return stack.isEmpty(); }
System.out.println(isValidParentheses("()[]{}")); System.out.println(isValidParentheses("([)]")); System.out.println(isValidParentheses("{[]}"));
|
队列 (Queue)
定义: 队列是一种先进先出(FIFO - First In First Out)的数据结构。
核心操作:
enqueue() / offer(): 入队 - 在队尾添加元素
dequeue() / poll(): 出队 - 从队头移除元素
peek(): 查看队头元素(不删除)
isEmpty(): 判断队列是否为空
基于数组实现(循环队列):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| class CircularQueue { private int[] array; private int front; private int rear; private int size; private int capacity;
public CircularQueue(int capacity) { this.capacity = capacity; this.array = new int[capacity]; this.front = 0; this.rear = -1; this.size = 0; }
public void enqueue(int data) { if (isFull()) { throw new IllegalStateException("队列已满"); } rear = (rear + 1) % capacity; array[rear] = data; size++; }
public int dequeue() { if (isEmpty()) { throw new IllegalStateException("队列为空"); } int data = array[front]; front = (front + 1) % capacity; size--; return data; }
public int peek() { if (isEmpty()) { throw new IllegalStateException("队列为空"); } return array[front]; }
public boolean isEmpty() { return size == 0; }
public boolean isFull() { return size == capacity; }
public int size() { return size; } }
CircularQueue queue = new CircularQueue(5); queue.enqueue(1); queue.enqueue(2); queue.enqueue(3);
System.out.println("队头元素: " + queue.peek()); System.out.println("出队: " + queue.dequeue()); System.out.println("出队: " + queue.dequeue());
|
基于链表实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class LinkedQueue { private Node front; private Node rear; private int size;
private class Node { int data; Node next;
Node(int data) { this.data = data; } }
public void enqueue(int data) { Node newNode = new Node(data);
if (isEmpty()) { front = rear = newNode; } else { rear.next = newNode; rear = newNode; } size++; }
public int dequeue() { if (isEmpty()) { throw new IllegalStateException("队列为空"); }
int data = front.data; front = front.next;
if (front == null) { rear = null; }
size--; return data; }
public int peek() { if (isEmpty()) { throw new IllegalStateException("队列为空"); } return front.data; }
public boolean isEmpty() { return front == null; }
public int size() { return size; } }
|
Java 标准库中的队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Queue<Integer> queue = new LinkedList<>(); queue.offer(1); queue.offer(2); queue.poll();
Queue<Integer> queue2 = new ArrayDeque<>(); queue2.offer(1); queue2.offer(2); queue2.poll();
Queue<Integer> pq = new PriorityQueue<>(); pq.offer(3); pq.offer(1); pq.offer(2); System.out.println(pq.poll());
|
特殊队列:
双端队列 (Deque - Double Ended Queue)
可以在两端进行插入和删除操作。
1 2 3 4 5 6 7 8 9 10 11
| Deque<Integer> deque = new ArrayDeque<>();
deque.addFirst(1); deque.removeFirst(); deque.peekFirst();
deque.addLast(2); deque.removeLast(); deque.peekLast();
|
优先队列 (Priority Queue)
元素按照优先级出队,而不是按照插入顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13
| PriorityQueue<Integer> minHeap = new PriorityQueue<>(); minHeap.offer(3); minHeap.offer(1); minHeap.offer(2); System.out.println(minHeap.poll());
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a); maxHeap.offer(3); maxHeap.offer(1); maxHeap.offer(2); System.out.println(maxHeap.poll());
|
应用场景:
- 任务调度: 按顺序处理任务
- 消息队列: Kafka、RabbitMQ
- 广度优先搜索: BFS 算法
- 打印队列: 打印机任务队列
- 线程池: 任务等待队列
非线性数据结构
树 (Tree)
定义: 树是一种层次结构的数据结构,由节点和边组成。每个节点包含数据和指向子节点的指针。
基本概念:
- 根节点: 树的顶端节点
- 父节点: 有子节点的节点
- 子节点: 某个节点的下一层节点
- 叶子节点: 没有子节点的节点
- 深度: 从根节点到某节点的边数
- 高度: 从某节点到叶子节点的最长路径
二叉树 (Binary Tree)
定义: 每个节点最多有两个子节点(左子节点和右子节点)。
二叉树节点定义:
1 2 3 4 5 6 7 8 9 10 11
| class TreeNode { int val; TreeNode left; TreeNode right;
public TreeNode(int val) { this.val = val; this.left = null; this.right = null; } }
|
二叉树的分类:
- 满二叉树: 所有非叶子节点都有两个子节点,且所有叶子节点在同一层
- 完全二叉树: 除最后一层外,其他层都是满的,最后一层从左到右连续
- 平衡二叉树: 任意节点的左右子树高度差不超过 1
二叉树的遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| class BinaryTree { TreeNode root;
public void preOrder(TreeNode node) { if (node == null) return;
System.out.print(node.val + " "); preOrder(node.left); preOrder(node.right); }
public void inOrder(TreeNode node) { if (node == null) return;
inOrder(node.left); System.out.print(node.val + " "); inOrder(node.right); }
public void postOrder(TreeNode node) { if (node == null) return;
postOrder(node.left); postOrder(node.right); System.out.print(node.val + " "); }
public void levelOrder(TreeNode root) { if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root);
while (!queue.isEmpty()) { TreeNode node = queue.poll(); System.out.print(node.val + " ");
if (node.left != null) queue.offer(node.left); if (node.right != null) queue.offer(node.right); } } }
TreeNode root = new TreeNode(1); root.left = new TreeNode(2); root.right = new TreeNode(3); root.left.left = new TreeNode(4); root.left.right = new TreeNode(5);
BinaryTree tree = new BinaryTree(); tree.root = root;
System.out.print("前序遍历: "); tree.preOrder(root);
System.out.print("\n中序遍历: "); tree.inOrder(root);
System.out.print("\n后序遍历: "); tree.postOrder(root);
System.out.print("\n层序遍历: "); tree.levelOrder(root);
|
二叉搜索树 (BST - Binary Search Tree)
定义: 一种特殊的二叉树,满足以下性质:
- 左子树的所有节点值 < 根节点值
- 右子树的所有节点值 > 根节点值
- 左右子树也都是二叉搜索树
BST 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| class BST { TreeNode root;
public TreeNode insert(TreeNode node, int val) { if (node == null) { return new TreeNode(val); }
if (val < node.val) { node.left = insert(node.left, val); } else if (val > node.val) { node.right = insert(node.right, val); }
return node; }
public boolean search(TreeNode node, int val) { if (node == null) { return false; }
if (val == node.val) { return true; } else if (val < node.val) { return search(node.left, val); } else { return search(node.right, val); } }
public TreeNode delete(TreeNode node, int val) { if (node == null) { return null; }
if (val < node.val) { node.left = delete(node.left, val); } else if (val > node.val) { node.right = delete(node.right, val); } else {
if (node.left == null && node.right == null) { return null; }
if (node.left == null) { return node.right; } if (node.right == null) { return node.left; }
TreeNode successor = findMin(node.right); node.val = successor.val; node.right = delete(node.right, successor.val); }
return node; }
private TreeNode findMin(TreeNode node) { while (node.left != null) { node = node.left; } return node; } }
BST bst = new BST(); bst.root = bst.insert(bst.root, 50); bst.insert(bst.root, 30); bst.insert(bst.root, 70); bst.insert(bst.root, 20); bst.insert(bst.root, 40); bst.insert(bst.root, 60); bst.insert(bst.root, 80);
System.out.println(bst.search(bst.root, 40)); System.out.println(bst.search(bst.root, 90));
bst.root = bst.delete(bst.root, 30);
|
BST 的优缺点:
- ✅ 查找、插入、删除的平均时间复杂度为 O(log n)
- ✅ 中序遍历可以得到有序序列
- ❌ 在最坏情况下(退化为链表),时间复杂度为 O(n)
堆 (Heap)
定义: 堆是一种特殊的完全二叉树,分为最大堆和最小堆。
堆的性质:
- 最大堆: 父节点的值 ≥ 子节点的值,根节点是最大值
- 最小堆: 父节点的值 ≤ 子节点的值,根节点是最小值
堆的存储: 通常用数组实现,对于索引 i 的节点:
- 父节点索引:
(i - 1) / 2
- 左子节点索引:
2 * i + 1
- 右子节点索引:
2 * i + 2
最小堆实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| class MinHeap { private int[] heap; private int size; private int capacity;
public MinHeap(int capacity) { this.capacity = capacity; this.heap = new int[capacity]; this.size = 0; }
private int parent(int i) { return (i - 1) / 2; }
private int leftChild(int i) { return 2 * i + 1; }
private int rightChild(int i) { return 2 * i + 2; }
private void swap(int i, int j) { int temp = heap[i]; heap[i] = heap[j]; heap[j] = temp; }
public void insert(int val) { if (size == capacity) { throw new IllegalStateException("堆已满"); }
heap[size] = val; int current = size; size++;
while (current > 0 && heap[current] < heap[parent(current)]) { swap(current, parent(current)); current = parent(current); } }
public int extractMin() { if (size == 0) { throw new IllegalStateException("堆为空"); }
int min = heap[0];
heap[0] = heap[size - 1]; size--;
heapify(0);
return min; }
private void heapify(int i) { int minIndex = i; int left = leftChild(i); int right = rightChild(i);
if (left < size && heap[left] < heap[minIndex]) { minIndex = left; } if (right < size && heap[right] < heap[minIndex]) { minIndex = right; }
if (minIndex != i) { swap(i, minIndex); heapify(minIndex); } }
public int getMin() { if (size == 0) { throw new IllegalStateException("堆为空"); } return heap[0]; }
public boolean isEmpty() { return size == 0; }
public int size() { return size; } }
MinHeap heap = new MinHeap(10); heap.insert(5); heap.insert(3); heap.insert(7); heap.insert(1);
System.out.println("最小元素: " + heap.getMin()); System.out.println("删除: " + heap.extractMin()); System.out.println("最小元素: " + heap.getMin());
|
堆的应用:
- 优先队列: Java 的
PriorityQueue 就是基于堆实现的
- 堆排序: 时间复杂度 O(n log n)
- Top K 问题: 找出最大/最小的 K 个元素
- 任务调度: 按优先级调度任务
经典问题: 查找第 K 大的元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) { minHeap.offer(num); if (minHeap.size() > k) { minHeap.poll(); } }
return minHeap.peek(); }
int[] nums = {3, 2, 1, 5, 6, 4}; System.out.println(findKthLargest(nums, 2));
|
哈希表 (Hash Table)
定义: 哈希表是根据键(Key)直接访问值(Value)的数据结构。通过哈希函数将键映射到数组的索引位置。
核心概念:
- 哈希函数: 将键转换为数组索引的函数
- 哈希冲突: 不同的键映射到同一个索引
- 负载因子: 元素个数 / 数组容量
解决哈希冲突的方法:
1. 链地址法 (Separate Chaining)
每个数组位置存储一个链表,冲突的元素都存在同一个链表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| class HashTable { private class Entry { String key; int value; Entry next;
Entry(String key, int value) { this.key = key; this.value = value; } }
private Entry[] table; private int capacity; private int size;
public HashTable(int capacity) { this.capacity = capacity; this.table = new Entry[capacity]; this.size = 0; }
private int hash(String key) { return Math.abs(key.hashCode()) % capacity; }
public void put(String key, int value) { int index = hash(key); Entry entry = table[index];
while (entry != null) { if (entry.key.equals(key)) { entry.value = value; return; } entry = entry.next; }
Entry newEntry = new Entry(key, value); newEntry.next = table[index]; table[index] = newEntry; size++; }
public Integer get(String key) { int index = hash(key); Entry entry = table[index];
while (entry != null) { if (entry.key.equals(key)) { return entry.value; } entry = entry.next; }
return null; }
public void remove(String key) { int index = hash(key); Entry entry = table[index]; Entry prev = null;
while (entry != null) { if (entry.key.equals(key)) { if (prev == null) { table[index] = entry.next; } else { prev.next = entry.next; } size--; return; } prev = entry; entry = entry.next; } }
public int size() { return size; } }
HashTable ht = new HashTable(10); ht.put("apple", 1); ht.put("banana", 2); ht.put("orange", 3);
System.out.println(ht.get("apple")); System.out.println(ht.get("banana"));
ht.remove("banana"); System.out.println(ht.get("banana"));
|
2. 开放地址法 (Open Addressing)
当发生冲突时,寻找下一个空闲位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class OpenAddressingHashTable { private String[] keys; private Integer[] values; private int capacity; private int size;
public OpenAddressingHashTable(int capacity) { this.capacity = capacity; this.keys = new String[capacity]; this.values = new Integer[capacity]; this.size = 0; }
private int hash(String key) { return Math.abs(key.hashCode()) % capacity; }
public void put(String key, int value) { if (size >= capacity * 0.75) { throw new IllegalStateException("哈希表负载过高"); }
int index = hash(key);
while (keys[index] != null) { if (keys[index].equals(key)) { values[index] = value; return; } index = (index + 1) % capacity; }
keys[index] = key; values[index] = value; size++; }
public Integer get(String key) { int index = hash(key);
while (keys[index] != null) { if (keys[index].equals(key)) { return values[index]; } index = (index + 1) % capacity; }
return null; } }
|
Java 中的哈希表:
1 2 3 4 5 6 7 8 9 10 11
| Map<String, Integer> map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); System.out.println(map.get("apple"));
Set<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); System.out.println(set.contains("apple"));
|
HashMap 的工作原理:
- 计算 key 的 hashCode
- 通过哈希函数计算数组索引:
(n - 1) & hash
- 如果没有冲突,直接存入
- 如果有冲突:
- JDK 1.7: 使用链表
- JDK 1.8+: 链表长度 ≤ 8 时使用链表,> 8 时转为红黑树
时间复杂度:
- 平均情况: O(1)
- 最坏情况: O(n) (所有元素都冲突)
应用场景:
- 缓存系统: LRU Cache
- 数据库索引: 快速查找
- 去重: HashSet
- 统计频率: 字符/单词频率统计
经典问题: 两数之和:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); }
return null; }
int[] nums = {2, 7, 11, 15}; int[] result = twoSum(nums, 9); System.out.println(Arrays.toString(result));
|
时间复杂度和空间复杂度
时间复杂度
时间复杂度用大O表示法描述算法执行时间与输入规模的关系。
常见时间复杂度(从快到慢):
- O(1) - 常数时间: 数组随机访问、哈希表查找
- O(log n) - 对数时间: 二分查找、平衡二叉树操作
- O(n) - 线性时间: 遍历数组、链表查找
- O(n log n) - 线性对数时间: 快速排序、归并排序、堆排序
- O(n²) - 平方时间: 冒泡排序、选择排序、插入排序
- O(n³) - 立方时间: 三层嵌套循环
- O(2^n) - 指数时间: 递归计算斐波那契数列
- O(n!) - 阶乘时间: 旅行商问题(暴力解法)
时间复杂度对比:
1 2 3 4 5 6 7
| n = 100 时: O(1) = 1 O(log n) ≈ 7 O(n) = 100 O(n log n)≈ 700 O(n²) = 10,000 O(2^n) ≈ 1.27 × 10³⁰
|
空间复杂度
空间复杂度描述算法占用内存空间与输入规模的关系。
常见空间复杂度:
- O(1) - 常数空间: 几个变量
- O(n) - 线性空间: 一个数组
- O(n²) - 平方空间: 二维数组
数据结构对比总结
| 数据结构 |
访问 |
查找 |
插入 |
删除 |
空间复杂度 |
| 数组 |
O(1) |
O(n) |
O(n) |
O(n) |
O(n) |
| 链表 |
O(n) |
O(n) |
O(1) |
O(1) |
O(n) |
| 栈 |
O(n) |
O(n) |
O(1) |
O(1) |
O(n) |
| 队列 |
O(n) |
O(n) |
O(1) |
O(1) |
O(n) |
| 哈希表 |
- |
O(1) |
O(1) |
O(1) |
O(n) |
| 二叉搜索树 |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
O(n) |
| 堆 |
O(1) |
O(n) |
O(log n) |
O(log n) |
O(n) |
注: 表中的时间复杂度为平均情况。
如何选择合适的数据结构?
- 需要快速随机访问 → 数组、ArrayList
- 频繁插入/删除 → 链表、LinkedList
- 后进先出 → 栈
- 先进先出 → 队列
- 快速查找/去重 → 哈希表(HashMap/HashSet)
- 有序数据 → 二叉搜索树、TreeMap/TreeSet
- 优先级处理 → 堆、PriorityQueue
- 图相关问题 → 邻接表、邻接矩阵
总结
数据结构是程序设计的基石,理解和掌握常用数据结构对于编写高效程序至关重要。
学习建议:
- 理解原理: 不要只记API,要理解底层实现
- 动手实践: 自己实现一遍各种数据结构
- 刷题巩固: 通过 LeetCode 等平台练习算法题
- 分析复杂度: 养成分析时间和空间复杂度的习惯
- 阅读源码: 研究 JDK 集合框架的源码实现
推荐资源:
掌握数据结构,从理解概念开始,通过实践加深理解,最终做到灵活运用!