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

107 阅读8分钟

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

引言

在编程的世界里,数据结构是构建高效算法的基石。每一位优秀的程序员都应该掌握它们,就像剑客熟悉自己的剑法一样。今天,我们将揭开高效编程的秘密武器——十种数据结构。这些数据结构不仅是编程的基础,更是提升代码性能、优化资源利用的关键。你是否真的了解它们?是否能够运用自如?让我们一起探索,解锁编程高手之路。正文开始之前请尝试回忆下十种数据数据结构有那些。

数组(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);
    }
}

代码执行结果

结语

在这篇文章中,我们一同探索了十种对程序员来说至关重要的数据结构的前五种。从基础的数组和链表,到复杂的散列表和图,每一种数据结构都是高效编程的秘密武器。它们不仅帮助我们解决了实际问题,还提升了代码的效率和可读性。掌握这些数据结构不仅仅是了解它们的定义和用法,更重要的是理解它们背后的设计哲学和适用场景。

感谢阅读,如果你对数据结构有更深的见解或疑问,欢迎在评论区留言讨论。让我们一起在编程的道路上,越走越远!