关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达!
引言
在编程的世界里,数据结构是构建高效算法的基石。每一位优秀的程序员都应该掌握它们,就像剑客熟悉自己的剑法一样。今天,我们将揭开高效编程的秘密武器——十种数据结构。这些数据结构不仅是编程的基础,更是提升代码性能、优化资源利用的关键。你是否真的了解它们?是否能够运用自如?让我们一起探索,解锁编程高手之路。正文开始之前请尝试回忆下十种数据数据结构有那些。
数组(Array)
数组(Array) 是一系列有序数据的集合,它存储了一系列相同类型的数据元素。在数组中,每个数据元素都可以通过一个唯一的索引(或下标)来访问,索引通常从0开始。
特点:
- 固定长度:一旦创建,数组的长度就固定不变,不能动态增减。
- 连续内存分配:数组中的所有元素都存储在连续的内存位置中,这使得通过索引访问元素非常快速。
- 类型一致:数组只能存储相同类型的数据,例如整数、浮点数或对象引用。
优点:
- 访问速度快,时间复杂度为 O(1)。
- 内存占用连续,缓存友好。
缺点:
- 大小固定,不能动态扩展。
- 插入和删除操作效率低,时间复杂度为 O(n)。
适用场景:
适用于需要快速访问元素且数据大小事先已知的情况,如存储大量相同类型的数据,如温度记录、学生成绩等。
相关算法:
- 线性搜索
- 二分搜索(适用于有序数组)
public class ArrayExample {
public static void main(String[] args) {
int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;
System.out.println("Element at index 2: " + numbers[2]);
for (int number : numbers) {
System.out.print(number + " ");
}
}
}
执行结果
链表(Linked List)
链表(Linked List) 是由一系列节点(Node)组成的集合,每个节点包含数据域和指向列表中下一个节点的指针(在双向链表中还会有指向前一个节点的指针)。链表的一个显著特点是它对内存的要求不高,因为它不像数组那样需要在创建时分配连续的内存空间。
特点:
- 动态大小:链表的长度不是固定的,可以在运行时动态地增加或减少。
- 非连续存储:链表中的元素可以存储在内存中的任何位置,每个节点通过指针连接。
- 类型多样性:链表可以存储不同类型的数据,只要节点能够存储所需的数据类型。
优点:
- 插入和删除操作高效:在链表中插入或删除节点只需要改变指针的指向,时间复杂度为O(1)。
- 内存使用灵活:链表不需要预分配固定大小的内存空间,可以更加灵活地使用内存。
缺点:
- 随机访问慢:由于链表的元素不是连续存储的,因此无法像数组那样通过索引快速访问元素,访问链表中的元素需要从头节点开始遍历,时间复杂度为O(n)。
- 额外的内存开销:每个节点除了存储数据外,还需要额外的内存空间来存储指向下一个节点的指针。
适用场景:
链表适用于那些频繁进行插入和删除操作的场景,如任务调度、数据缓存等。
相关算法
- 遍历链表
- 反转链表
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
list.add(1, "Date");
list.remove("Banana");
System.out.println("Linked List:");
for (String fruit : list) {
System.out.println(fruit);
}
reverseList(list);
System.out.println("\nReversed Linked List:");
for (String fruit : list) {
System.out.println(fruit);
}
}
public static void reverseList(LinkedList<String> list) {
int size = list.size();
for (int i = 0; i < size / 2; i++) {
String temp = list.get(i);
list.set(i, list.get(size - i - 1));
list.set(size - i - 1, temp);
}
}
}
执行结果如下
HashMap
HashMap 是 Java 中的一个非常重要的集合类,它实现了基于散列的映射接口 HashMap 是 Java 编程语言中的一个具体实现,它实现了基于哈希表的 Map 接口。HashMap 允许存储键值对(Key-Value Pair),并提供了丰富的操作方法。HashMap 是 Java 编程语言中的一个具体实现,它实现了基于哈希表的 Map 接口。HashMap 允许存储键值对(Key-Value Pair),并提供了丰富的操作方法。
特点:
- 键值对存储:HashMap 存储键值对(Key-Value Pair),其中键是唯一的,而值可以重复。
- 散列机制:通过哈希函数来决定每个键值对在底层数组中的存储位置。
- 动态扩容:当 HashMap 中的元素数量达到一定的阈值时,它会自动扩容,即增加底层数组的大小,并重新计算所有键的哈希值以重新分布键值对。
优点:
- 平均情况下,插入、删除和查找的时间复杂度为 O(1)。
- 适用于需要快速查找键值对的场景。
缺点:
- 内存占用较大,尤其是当哈希冲突较多时。
- 无序性,无法保证元素的顺序。
适用场景
缓存机制、实现关联数组等场景。
相关算法:
- 查找重复元素
- 统计频率
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);
System.out.println("Age of Alice: " + map.get("Alice"));
System.out.println("Contains Bob? " + map.containsKey("Bob"));
System.out.println("Map entries:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
int[] nums = {1, 2, 3, 4, 5, 2, 3};
findDuplicates(nums);
}
public static void findDuplicates(int[] nums) {
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (int num : nums) {
frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);
}
System.out.println("Duplicate elements:");
for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
if (entry.getValue() > 1) {
System.out.println(entry.getKey());
}
}
}
}
执行结果
哈希表(Hash Table)
哈希表也称为散列表,是一种用于存储键值对的数据结构,它通过一个哈希函数来计算每个键的哈希值,哈希值决定了在表中的位置。
特点:
- 键值对存储:哈希表存储的是由键(Key)和值(Value)组成的元素。
- 哈希函数:通过哈希函数将键转换成数组索引,用于快速查找。
- 数组实现:通常基于数组实现,数组的每个槽位对应一个或多个键值对。
优点:
- 快速查找:理想情况下,哈希表的查找、插入和删除操作的时间复杂度为O(1)。
- 高效的空间利用:相比于其他数据结构,如树,哈希表在存储大量元素时通常更加节省空间。
缺点:
- 哈希冲突:不同的键可能映射到同一个位置,这称为哈希冲突。解决冲突需要额外的机制,如链表法或开放寻址法。
- 哈希函数设计困难:设计一个既高效又均匀分布的哈希函数是困难的,不好的哈希函数会导致性能下降。
适用场景
- 快速检索:在大量数据中快速查找特定元素。
- 数据去重:利用哈希表的唯一性快速检测重复元素。
相关算法:
- 哈希函数:用于将键映射到哈希表中的位置。
- 冲突解决:链表法(Chaining)、开放寻址法(Open Addressing)、双重哈希法(Double Hashing)等。
import java.util.Hashtable;
import java.util.Map;
public class HashtableExample {
public static void main(String[] args) {
Hashtable<String, Integer> table = new Hashtable<>();
table.put("Alice", 25);
table.put("Bob", 30);
table.put("Charlie", 35);
System.out.println("Age of Alice: " + table.get("Alice"));
System.out.println("Contains Bob? " + table.containsKey("Bob"));
System.out.println("Hashtable entries:");
for (String key : table.keySet()) {
System.out.println(key + ": " + table.get(key));
}
int[] nums = {1, 2, 3, 4, 5, 2, 3};
findDuplicates(nums);
}
public static void findDuplicates(int[] nums) {
Hashtable<Integer, Integer> frequencyTable = new Hashtable<>();
for (int num : nums) {
frequencyTable.put(num, frequencyTable.getOrDefault(num, 0) + 1);
}
System.out.println("Duplicate elements:");
for (Map.Entry<Integer, Integer> entry : frequencyTable.entrySet()) {
if (entry.getValue() > 1) {
System.out.println(entry.getKey());
}
}
}
}
执行结果
hashTable和hashMap有什么区别呢?感觉差不多的样子,评论区交流下,运气好的小伙伴有机会拿到作者送书的奖励。
图(Graph)
一种复杂的数据结构,用于表示实体(称为顶点或节点)以及它们之间的相互关系(称为边或弧)。图在现实世界中有着广泛的应用,如社交网络、网络路由、依赖关系管理等。
优点:
- 灵活性高,能够表示复杂的实体关系。
缺点:
- 复杂度较高,算法实现较难。
适用场景:
- 社交网络分析
- 路径寻找等场景。
相关算法:
- 深度优先搜索 (DFS)
- 广度优先搜索 (BFS) 代码示例
import java.util.ArrayList;
import java.util.List;
public class GraphExample {
private final int V;
private final List<List<Integer>> adj;
public GraphExample(int v) {
V = v;
adj = new ArrayList<>(v);
for (int i = 0; i < v; ++i) {
adj.add(new ArrayList<>());
}
}
public void addEdge(int v, int w) {
adj.get(v).add(w);
adj.get(w).add(v); // For undirected graph
}
public void dfs(int s) {
boolean[] visited = new boolean[V];
dfsUtil(s, visited);
}
private void dfsUtil(int v, boolean[] visited) {
visited[v] = true;
System.out.print(v + " ");
for (Integer n : adj.get(v)) {
if (!visited[n]) {
dfsUtil(n, visited);
}
}
}
public void bfs(int s) {
boolean[] visited = new boolean[V];
List<Integer> queue = new ArrayList<>();
visited[s] = true;
queue.add(s);
while (!queue.isEmpty()) {
s = queue.remove(0);
System.out.print(s + " ");
for (Integer n : adj.get(s)) {
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
public static void main(String[] args) {
GraphExample g = new GraphExample(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("Depth First Traversal starting from vertex 2:");
g.dfs(2);
System.out.println("\n\nBreadth First Traversal starting from vertex 2:");
g.bfs(2);
}
}
代码执行结果
结语
在这篇文章中,我们一同探索了十种对程序员来说至关重要的数据结构的前五种。从基础的数组和链表,到复杂的散列表和图,每一种数据结构都是高效编程的秘密武器。它们不仅帮助我们解决了实际问题,还提升了代码的效率和可读性。掌握这些数据结构不仅仅是了解它们的定义和用法,更重要的是理解它们背后的设计哲学和适用场景。
感谢阅读,如果你对数据结构有更深的见解或疑问,欢迎在评论区留言讨论。让我们一起在编程的道路上,越走越远!