一文彻底搞懂Java数据结构

1. 简介

在Java中,数据结构一般可以分为两大类:线性数据结构和非线性数据结构

  1. 线性数据结构: 线性数据结构是指数据元素之间存在一对一的关系,即每个元素都有一个前驱和一个后继元素,形成线性序列。常见的线性数据结构包括:
  • 数组(Array):一组连续存储的元素,通过索引进行访问。
  • 链表(Linked List):由一系列节点组成,每个节点包含数据和指向下一个节点的引用。
  • 栈(Stack):后进先出(LIFO)的数据结构,只允许在栈顶进行插入和删除操作。
  • 队列(Queue):先进先出(FIFO)的数据结构,允许在队尾插入元素,在队首删除元素。
  1. 非线性数据结构: 非线性数据结构是指数据元素之间存在一对多或多对多的关系,形成非线性结构。常见的非线性数据结构包括:
  • 树(Tree):由节点组成的层级结构,每个节点可以有零个或多个子节点。
  • 散列表(Hash Table):使用哈希函数将键映射到存储位置的数据结构,通常用于实现集合和映射。
  • 图(Graph):由节点(顶点)和边组成的集合,描述对象之间的关系,可以是有向图或无向图。
  • 堆(Heap):特殊的树形数据结构,通常用于实现优先队列。

2. 数组

在Java中,一些数据结构是直接暴露其底层实现的,而另一些数据结构则对其底层实现进行了封装。

  • 直接暴露底层实现的数据结构: 例如String[]和int[]这种数组,它们的底层实现是直接暴露的,开发者可以直接访问和操作数组元素。这样的数据结构通常具有简单的接口和操作,易于理解和使用。

  • 对底层实现进行封装的数据结构: 例如ArrayList,它对数组进行了封装,并提供了一系列方法来操作数组。在使用ArrayList时,开发者无需关心其底层实现细节,只需调用相应的方法即可。这样的数据结构通常提供了更丰富的功能和更高的灵活性,但也可能会带来一些性能上的损耗。

数组这种数据结构最大的好处,就是可以根据下标(或者叫索引)进行操作,插入的时候可以根据下标直接插入到具体的位置,但与此同时,后面的元素就需要全部向后移动,需要移动的数据越多,就越累。
在这里插入图片描述
ArrayList 的常见操作的时间复杂度为 O(1) 或 O(n),具体取决于操作类型和操作位置。

  1. 添加元素(Add): 如果添加元素不涉及扩容操作,即ArrayList内部数组的容量足够,时间复杂度为 O(1)。但如果需要进行扩容,即当前元素数量达到容量上限时,需要将所有元素复制到新数组中,时间复杂度为 O(n)。

  2. 删除元素(Remove): 删除指定索引位置的元素,需要将该索引后面的所有元素向前移动一位,时间复杂度为 O(n)。如果删除的是末尾元素,时间复杂度为 O(1)。

  3. 随机访问(Get): 通过索引获取元素,时间复杂度为 O(1),因为ArrayList是基于数组实现的,可以通过索引直接访问数组元素。

  4. 搜索元素(Contains): 判断ArrayList中是否包含某个元素,时间复杂度为 O(n),因为需要遍历整个数组来搜索元素。

  5. 插入元素(Insert): 在指定索引位置插入元素,需要将该索引后面的所有元素向后移动一位,时间复杂度为 O(n)。如果插入的是末尾元素,时间复杂度为 O(1)。

3. 链表

链表是一种非连续存储的数据结构,每个节点包含数据和指向下一个节点(单向链表)或者前一个节点和后一个节点(双向链表)的引用。这种相互链接的结构使得链表具有灵活性和高效性,在插入和删除操作时不需要像数组那样进行元素的移动。

LinkedList 是链表的典型实现之一,它是由一系列节点组成的,每个节点包含数据和指向下一个节点的引用。LinkedList的优点是插入和删除操作效率高,但随机访问效率较低,因为要从头节点开始遍历到目标位置。此外,LinkedList还支持双向链表的实现,每个节点同时具有指向前一个节点和后一个节点的引用,进一步增强了灵活性。
在这里插入图片描述

  • 第一个节点由于没有前一个节点,所以 prev 为 null
  • 最后一个节点由于没有后一个节点,所以 next 为 null
  • 这是一个双向链表,每一个节点都由三部分组成,前后节点和值

相比ArrayList,LinkedList具有以下优势:

  • 插入和删除操作效率高: 在链表中,插入和删除元素的效率不受集合大小的影响,因为只需要调整节点的指针,而不需要移动大量元素。而ArrayList在插入和删除操作时,需要移动数组中的元素,其效率受到集合大小的影响。

  • 内存空间利用率高: LinkedList的节点是动态分配的,不像ArrayList需要预先分配一定大小的数组空间,因此在内存空间利用率上更加灵活,不会出现过度分配或浪费的情况。

  • 支持高效的头部和尾部操作: 在LinkedList中,头部和尾部操作(如添加、删除)的时间复杂度都是 O(1),而在ArrayList中,如果在头部进行添加或删除操作,需要将所有元素向后移动,时间复杂度为 O(n)。

  • 支持更多的操作: LinkedList支持更多的操作,如在任意位置插入或删除元素的操作,以及在迭代过程中删除元素等,这些操作在ArrayList中可能效率较低或者需要额外的操作。

LinkedList在插入和删除操作频繁、需要高效的头部和尾部操作、以及对内存空间利用率要求较高的场景下,相比ArrayList具有更大的优势。

4. 栈

栈常被比喻为一摞盘子或者一堆书,遵循后进先出(LIFO)的原则。这意味着最后放入栈中的元素将被最先移除,而最先放入的元素将被最后移除。

对于栈这样一个数据结构来说,它有两个常见的动作:

  • 入栈(Push): 将新的元素放入栈顶。新元素被添加到栈顶后,它将成为下一个被移除的元素,即栈顶元素。
  • 出栈(Pop): 移除栈顶的元素并返回该元素的值。被移除的元素是最后一个被添加到栈中的元素,也是最后一个进栈的元素。
    在这里插入图片描述

5. 队列

队列是一种常见的数据结构,在Java中也有多种实现以满足不同的场景需求。队列的特点是只允许在队尾添加数据,而在队首移除数据,遵循先进先出(FIFO)的原则。

Java中常见的队列实现包括:

  • LinkedList: Java标准库中提供的LinkedList类实现了Queue接口,可以作为普通队列使用。
  • ArrayDeque: ArrayDeque类实现了双端队列(Deque)接口,可以作为普通队列使用,并且提供了高效的数组实现。
  • PriorityQueue: 优先级队列实现了Queue接口,具有优先级的概念,可以按照元素的优先级顺序进行插入和删除操作。
  • DelayQueue: 延时队列实现了BlockingQueue接口,用于存储实现了Delayed接口的元素,这些元素按照指定的延时时间从队列中移除。
  • LinkedBlockingQueue和ArrayBlockingQueue: 这两个类实现了BlockingQueue接口,是线程安全的阻塞队列,可用于多线程环境下的生产者-消费者模式。
    在这里插入图片描述

6. 树

树是一种典型的非线性结构,它是由 n(n>0)个有限节点组成的一个具有层次关系的集合。之所以叫“树”,是因为这种数据结构看起来就像是一个倒挂的树,只不过根在上,叶在下。树形数据结构有以下这些特点:

  • 层次关系: 树是由n(n>0)个有限节点组成的集合,节点之间存在着层次关系,即从根节点到每个节点都存在一条唯一的路径。
  • 根节点: 树有且仅有一个根节点,它位于树的顶部,是树中所有其他节点的起始点。
  • 分支节点和叶节点: 除了根节点外,树中的每个节点都有且仅有一个父节点,但可以有零个或多个子节点。没有子节点的节点称为叶节点,也可以称为叶子节点。
  • 子树: 每个节点都可以作为一个子树的根节点,包含该节点及其所有后代节点。
  • 路径: 从树的根节点到任意节点都存在唯一的路径,路径的长度是经过的边的数量。
  • 深度: 从根节点到某个节点的唯一路径的长度称为该节点的深度,根节点的深度为0。
  • 高度: 树中任意节点的最大深度称为树的高度。
  • 子树之间互不相交: 树中任意两个子树之间的节点都互不相交,即每个节点只能出现在一个子树中。
  • 无环: 树中不存在环路,即不存在任何节点可以通过任意数量的边回到自身。
    在这里插入图片描述

根节点是第 0 层,它的子节点是第 1 层,子节点的子节点为第 2 层,以此类推。

  • 深度:对于任意节点 n,n 的深度为从根到 n 的唯一路径长,根的深度为 0。
  • 高度:对于任意节点 n,n 的高度为从 n 到一片树叶的最长路径长,所有树叶的高度为 0。

树又可以细分为下面几种:

  1. 普通树:对子节点没有任何约束。

  2. 二叉树:每个节点最多含有两个子节点的树。 二叉树按照不同的表现形式又可以分为多种
    2.1 普通二叉树:每个子节点的父节点不一定有两个子节点的二叉树。
    2.2 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列。
    2.3 满二叉树:一颗每一层的节点数都达到了最大值的二叉树。有两种表现形式,第一种,像下图这样(每一层都是满的),满足每一层的节点数都达到了最大值 2。
    在这里插入图片描述

  3. 二叉查找树:英文名叫 Binary Search Tree,即 BST,需要满足:1、任意节点的左子树不空,左子树上所有节点的值均小于它的根节点的值。2、任意节点的右子树不空,右子树上所有节点的值均大于它的根节点的值。3、任意节点的左、右子树也分别为二叉音找树
    3.1 平衡二叉树:当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。
    平衡二叉树本质上也是一颗二叉查找树,不过为了限制左右子树的高度差,避免出现倾斜树等偏向于线性结构演化的情况,所以对二叉搜索树中每个节点的左右子树作了限制,左右子树的高度差称之为平衡因子,树中每个节点的平衡因子绝对值不大于 1。
    平衡二叉树的难点在于,当删除或者增加节点的情况下,如何通过左旋或者右旋的方式来保持左右平衡。
    红黑树是一种常见的平衡二叉树,节点是红色或者黑色,通过颜色的约束来维持着二叉树的平衡:
    在这里插入图片描述

  • 每个节点都只能是红色或者黑色
  • 根节点是黑色
  • 每个叶节点(NIL 节点,空节点)是黑色的
  • 如果一个节点是红色的,则它两个子节点都是黑色的。也就是说在一条路径上不能出现相邻的两个红色节点
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
  1. B 树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个的子树
    在这里插入图片描述
  2. B+ 树:B 树的变体
    在这里插入图片描述

HashMap 里面的 TreeNode 就用到了红黑树,而 B 树、B+ 树在数据库的索引原理里面有典型的应用。

7. 哈希表

哈希表(Hash Table),也称作散列表,是一种基于哈希算法实现的数据结构,可以通过关键码值(key)直接访问数据,其最大的特点是能够实现快速的查找、插入和删除操作。哈希表的主要原理是将关键码值通过哈希函数映射到一个固定长度的数组索引上,从而实现对数据的快速定位。

哈希算法是哈希表的核心,它将任意长度的输入映射为固定长度的输出,即哈希值。常见的哈希算法包括MD5(Message Digest Algorithm 5)、SHA1(Secure Hash Algorithm 1)等。每一个 Java 对象都会有一个哈希值,默认情况就是通过调用本地方法执行哈希算法,计算出对象的内存地址 + 对象的值的关键码值。

数组的最大特点就是查找容易,插入和删除困难;而链表正好相反,查找困难,而插入和删除容易。哈希表很完美地结合了两者的优点, Java 的 HashMap 在此基础上还加入了树的优点。

哈希表具有较快(常量级)的查询速度,以及相对较快的增删速度,所以很适合在海量数据的环境中使用。

尽管任意两个不同的数据块其哈希值相同的可能性极小,但在实际应用中仍可能发生哈希冲突。当发生哈希冲突时,Java 的 HashMap 会采取拉链法(数组+链表)来处理冲突:即在数组的同一个位置上存储一个链表,将冲突的元素依次连接在该链表上。若链表长度超过一定阈值(通常为8),HashMap 会将该链表转化成红黑树,以提高查询效率。这种处理冲突的机制使得 HashMap 在面对哈希冲突时仍能保持较好的性能表现。

8. 图

图是一种复杂的非线性结构,由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G 表示一个图,V 是图 G 中顶点的集合,E 是图 G 中边的集合。
在这里插入图片描述

上图共有 V0,V1,V2,V3 这 4 个顶点,4 个顶点之间共有 5 条边。

在线性结构中,数据元素之间满足唯一的线性关系,每个数据元素(除第一个和最后一个外)均有唯一的“前驱”和“后继”

在树形结构中,数据元素之间有着明显的层次关系,并且每个数据元素只与上一层中的一个元素(父节点)及下一层的多个元素(子节点)相关

而在图形结构中,节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。

9. 堆

在 Java 中,堆(Heap)通常指的是二叉堆(Binary Heap),它是一种特殊的树形数据结构,常被用于实现优先队列。

二叉堆具有以下特点:

  • 它是一个完全二叉树,即除了最底层,其他每一层都是满的,而且最底层的节点都集中在左边。
  • 它分为最大堆和最小堆。在最大堆中,父节点的值大于等于其子节点的值;在最小堆中,父节点的值小于等于其子节点的值。
  • 二叉堆的每个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。

Java 中的 PriorityQueue 类就是基于堆实现的优先队列。堆的特性使得优先队列能够快速地进行插入和删除操作,并且能够方便地获取优先级最高(或最低)的元素。

除了二叉堆外,堆还有其他形式,如斐波那契堆等,用于解决不同类型的问题,但在 Java 中常见的是二叉堆。

  • 41
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python面向对象编程(Object-Oriented Programming,简称OOP)是一种程序设计方法,它将数据和操作数据的方法组合成对象,通过定义类(class)来创建对象。下面是一些概念和原则,可以帮助你更好地理解Python面向对象编程。 1. 类和对象: - 类是一种抽象的数据类型,它定义了对象的属性和方法。 - 对象是类的实例,它具有类定义的属性和方法。 2. 属性和方法: - 属性是对象的数据,可以是整数、字符串、列表等。 - 方法是对象的行为,可以是函数或过程。 3. 封装: - 封装是将数据和对数据的操作封装在一起,以创建一个独立的实体。 - 使用类来封装数据和方法,可以隐藏实现细节,提高代码的可读性和可维护性。 4. 继承: - 继承是一种机制,允许一个类继承另一个类的属性和方法。 - 子类可以重用父类的代码,并且可以添加新的属性和方法。 5. 多态: - 多态是指同一个方法可以在不同的类中具有不同的实现方式。 - 多态可以提高代码的灵活性和可扩展性。 下面是一个简单的例子,展示了如何定义一个类、创建对象并调用对象的方法: ```python class Person: def __init__(self, name, age): self.name = name self.age = age def say_hello(self): print(f"Hello, my name is {self.name} and I'm {self.age} years old.") # 创建对象 person = Person("Alice", 25) # 调用对象的方法 person.say_hello() ``` 这个例子定义了一个名为`Person`的类,它有两个属性(`name`和`age`)和一个方法(`say_hello`)。我们通过`Person`类创建了一个名为`person`的对象,并调用了它的`say_hello`方法。 希望这个简单的例子能帮助你更好地理解Python面向对象编程。如果你有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值