跟着JDK学数据结构-线性与非线性数据结构的应用与实现

发布于:2025-03-31 ⋅ 阅读:(19) ⋅ 点赞:(0)

线性数据结构

学习线性数据结构是理解 JDK 数据结构的第一步。线性数据结构指的是元素之间存在线性关系的数据结构,其中每个元素有且只有一个前驱和后继元素。常见的线性数据结构包括数组、链表、栈和队列。

1. 数组(Array)

数组是最简单的线性数据结构,具有固定的大小,元素在内存中是连续存储的。

JDK中的实现:

  • java.util.ArrayList:基于数组实现的动态数组。相比于普通数组,它的大小可以自动扩展。
  • java.util.Arrays:提供数组操作的工具类,包含了对数组的排序、查找、填充等常用操作。

关键操作:

  • 访问:通过索引可以快速访问元素。
  • 插入/删除:在数组中间插入/删除元素可能需要移动大量元素,时间复杂度是O(n)。
  • 动态调整:ArrayList 在大小不足时会自动扩展。
import java.util.ArrayList;
import java.util.Arrays;

public class ArrayExample {
    public static void main(String[] args) {
        // ArrayList示例
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(20);
        System.out.println(list);  // 输出: [10, 20]

        // Arrays工具类示例
        int[] arr = {1, 2, 3, 4};
        System.out.println(Arrays.toString(arr));  // 输出: [1, 2, 3, 4]
    }
}

2. 链表(Linked List)

链表是一种线性数据结构,其中的元素(节点)通过指针连接。链表中的每个节点包含一个数据部分和一个指向下一个节点的指针。

JDK中的实现:

  • java.util.LinkedList:实现了双向链表,支持快速的插入和删除操作。

关键操作:

  • 访问:访问链表中的元素需要从头开始遍历,时间复杂度是O(n)。
  • 插入/删除:插入和删除操作相对简单,只需要调整指针即可,时间复杂度为O(1)(如果知道插入位置)。
import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(10);
        list.add(20);
        list.addFirst(5);  // 在开头插入
        System.out.println(list);  // 输出: [5, 10, 20]
    }
}

3. 栈(Stack)

栈是一种先进后出(LIFO,Last In First Out)的数据结构。你可以在栈的顶部推入元素,也可以从栈的顶部弹出元素。

JDK中的实现:

  • java.util.Stack:栈的传统实现类(虽然它在 JDK 中已经不再推荐使用)。
  • java.utilDeque:接口,ArrayDeque 实现了栈的功能,且效率更高。

关键操作:

  • push():压栈,将元素推入栈顶。
  • pop():弹栈,从栈顶移除并返回元素。
  • peek():查看栈顶元素,但不移除。
import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(10);
        stack.push(20);
        System.out.println(stack.peek());  // 输出: 20
        stack.pop();
        System.out.println(stack.peek());  // 输出: 10
    }
}

4. 队列(Queue)

队列是一种先进先出(FIFO,First In First Out)的数据结构。元素在队列的尾部进入,在队列的头部移除。

JDK中的实现:

  • java.util.LinkedList:也可以作为队列使用,使用 offer()poll() 来操作队列。
  • java.util.Queue:接口,LinkedListPriorityQueue 都实现了它。

关键操作:

  • offer():将元素添加到队列尾部。
  • poll():移除并返回队列头部的元素。
  • peek():查看队列头部元素,但不移除。
import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(10);
        queue.offer(20);
        System.out.println(queue.peek());  // 输出: 10
        queue.poll();
        System.out.println(queue.peek());  // 输出: 20
    }
}

总结:

这些线性数据结构在 JDK 中有很好的实现和优化。在学习这些数据结构时,理解它们的基本特性和在 JDK 中的应用非常重要。你可以通过 Java 代码练习这些数据结构的操作,掌握它们的优缺点和适用场景,进一步提升对 JDK 中数据结构的理解。

非线性数据结构

非线性数据结构是与线性数据结构相对的,元素之间的关系不是线性的,而是呈现更复杂的结构。常见的非线性数据结构包括树(Tree)和图(Graph)。在 JDK 中,这些数据结构也有相关的实现。

1. 树(Tree)

树是一种非线性的数据结构,由节点和连接节点的边组成,树的特点是有一个根节点,从根节点出发,每个节点都有零个或多个子节点。

常见的树结构:

  • 二叉树:每个节点最多有两个子节点。
  • 平衡二叉树(AVL树、红黑树):保证树的高度平衡,避免出现极端不平衡的情况,保证操作的时间复杂度。
  • 堆:特殊的树形结构,通常用于优先队列。

JDK中的实现:

  • java.util.TreeMap:基于红黑树实现的 Map,它保证了键值对的有序性。
  • java.util.TreeSet:基于红黑树实现的集合,提供了有序的元素集合。

关键操作:

  • 插入、删除、查找:这些操作的时间复杂度通常为O(log n),但在最坏情况下可能是O(n),如不平衡的树。
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        set.add(10);
        set.add(5);
        set.add(20);
        System.out.println(set);  // 输出: [5, 10, 20]
    }
}

2. 图(Graph)

图是一种由节点(顶点)和边组成的非线性数据结构,边用于表示节点之间的关系。图可以是有向的或无向的,也可以是加权的或非加权的。

JDK中的实现:
JDK 本身没有直接的图结构实现,但是可以通过使用集合(SetMap)来自定义图的表示方法。

  • 邻接表表示法:通过 Map 存储每个节点及其相邻节点。
  • 邻接矩阵表示法:通过二维数组存储节点之间的边。

图的类型:

  • 有向图(Directed Graph):边有方向,表示从一个节点到另一个节点的有向关系。
  • 无向图(Undirected Graph):边没有方向,表示节点之间的双向关系。
  • 加权图(Weighted Graph):每条边都有权重。

关键操作:

  • 查找:可以通过深度优先搜索(DFS)或广度优先搜索(BFS)进行遍历。
  • 插入、删除:可以添加或删除节点及其相邻的边。
import java.util.*;

public class GraphExample {
    static class Graph {
        private Map<Integer, List<Integer>> adjList = new HashMap<>();

        public void addEdge(int v1, int v2) {
            adjList.computeIfAbsent(v1, k -> new ArrayList<>()).add(v2);
            adjList.computeIfAbsent(v2, k -> new ArrayList<>()).add(v1);  // 无向图
        }

        public void printGraph() {
            for (Map.Entry<Integer, List<Integer>> entry : adjList.entrySet()) {
                System.out.println(entry.getKey() + " -> " + entry.getValue());
            }
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.addEdge(1, 2);
        graph.addEdge(1, 3);
        graph.addEdge(2, 4);

        graph.printGraph();
    }
}

输出:

1 -> [2, 3]
2 -> [1, 4]
3 -> [1]
4 -> [2]

3. 堆(Heap)

堆是一种特殊的完全二叉树,满足堆的性质:

  • 最大堆(Max-Heap):每个父节点的值都大于或等于其子节点的值,根节点的值最大。
  • 最小堆(Min-Heap):每个父节点的值都小于或等于其子节点的值,根节点的值最小。

JDK中的实现:

  • java.util.PriorityQueue:基于堆实现的优先队列,默认实现最小堆(最小值优先)。

关键操作:

  • 插入:在堆中插入一个元素,时间复杂度是O(log n)。
  • 删除:删除根节点并调整堆,时间复杂度是O(log n)。
import java.util.PriorityQueue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // 默认是最小堆
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        pq.add(10);
        pq.add(20);
        pq.add(5);

        System.out.println(pq.poll());  // 输出: 5 (最小值被移除)
        System.out.println(pq);  // 输出: [10, 20]
    }
}

总结:

非线性数据结构相比于线性数据结构,具有更复杂的结构和更强大的功能。它们适用于需要处理多对多关系、层次结构或优先级等问题的场景。

  • :适用于表示层次结构,如文件系统、组织结构等。
  • :适用于表示复杂关系,如社交网络、网络拓扑等。
  • :适用于需要快速访问最大或最小元素的场景,如优先队列、图的最短路径算法等。

通过 JDK 提供的类和接口,我们可以高效地使用这些非线性数据结构。