java之数据结构【入门篇】

定义

数据结构是指相互之间存在着一种或多种关系的数据元素的集合

常见的数据结构

数据存储的常用结构有数组,栈,队列,链表,树,图,堆,散列表等,如下图所示。
在这里插入图片描述

数组(Array)

数组是连续存储多个元素的序列,在内存中的分配也是连续的,数组中的元素通过下标进行访问。

        String[] data = new String[]{
    
    "Tom","Jack","Jessica","Katherine","Layman"};
        System.out.println(data[3]);

在这里插入图片描述

优点:
1、查找元素快:通过索引,可以快速访问指定位置的元素
2、遍历数组快:通过索引,能够快速遍历数组

缺点:
1、数组的大小一旦确定就无法再扩容
2、数组只能存储单一类型的数据
3、添加,删除的操作慢,因为要移动其他的元素。

  • 增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。

  • 删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素舍弃,不复制。

适用场景:

频繁查询,对存储空间要求不大,很少增加和删除的情况。

栈(Stack)

栈,又称堆栈,是一种特殊的(运算受限)线性表,其限制是仅允许在栈顶允许进行操作(类似弹匣)。
从栈顶放入元素的操作叫入栈,也叫压栈,取出元素叫出栈,也叫弹栈。
在这里插入图片描述

扫描二维码关注公众号,回复: 12614462 查看本文章

特点

  • 先进后出,后进先出(类比弹匣上子弹)

适用场景:

递归,例如斐波那契数列。

队列(Queue)

队列和堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。

  • 可以把队列想象成两端开口的栈

在这里插入图片描述
特点

  • 先进先出。

试用场景

因为队列具有先进先出的特点,因此非常适用于多线程阻塞队列管理。

链表(Linked List)

链表是由一系列结点node(链表中每一个元素称为结点)组成,每个结点至少有两个域,一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

链表的第一个节点和最后一个节点,分别称为头节点和尾节点。

尾节点的特征是其 next 引用为空(null)。

单向链表

单向链表中,每个节点的数据域都是通过一个 Object 类的对象引用来指向数据元素。

与数组类似,单向链表中的节点也具有线性次序,即如果节点 a1 的 next 引用指向节点 a2,则 a1 是 a2 的直接前驱节点,a2 是 a1 的直接后续节点。

只能通过前驱节点找到后续节点,而无法从后续节点找到前驱节点。
在这里插入图片描述

特点

  • 查询慢: 存储地址不连续,每次查询元素都需要从头开始
  • 增删快:不必移动节点,只要改变节点中的指针,但是需要先定位到元素上。
  • 不保证元素的顺序:存储元素和取出元素的顺序有可能不一致。
  • 只能通过前驱节点找到后续节点,而无法从后续节点找到前驱节点。

双向链表

单向链表中找到某个节点的前驱节点,必须从链表的头节点出发依次向后寻找,需要Ο(n)时间。

因此我们可以扩展单向链表的节点结构,使得通过一个节点的引用,不但能够访问其后续节点,也可以访问其前驱节点。这就是双向链表。

双向链表,就是在单链表节点结构中新增一个域,该域存储指向该节点的前驱节点。

单向链表只能从一个方向遍历,双向链表可以从两个方向遍历。

双向链表头节点的前驱节点(pre)为空,而尾节点的后续节点(Next)为空
在这里插入图片描述
在双向链表中插入和删除节点,无论插入和删除的节点位置在何处。因为首尾节点的存在,插入、删除操作都可以被归结为某个中间节点的插入和删除。

因为首尾节点的存在,双向链表永不为空。

简单用双向列表模拟LinkedList,代码如下:

import lombok.Data;

/**
 * 简单用双向列表模拟LinkedList
 * @author layman
 * @date 2021/2/24
 */
public class MyLinkedList {
    
    
    //首节点
    private Node first;
    //尾节点
    private Node last;

    public void add(Object obj){
    
    
        if(first==null){
    
    
            //说明该元素存于首节点上
            Node firstNode = new Node();
            firstNode.setPrevious(null);
            firstNode.setObj(obj);
            firstNode.setNext(null);

            //此时该LinkedList只存了一个元素,所以首尾节点都设置为首节点
            first = firstNode;
            last = firstNode;
        }else{
    
    
            //存入的不是首节点,则该元素存于首节点之后,指向前一个节点的last
            Node node=new Node();
            node.setPrevious(last);
            node.setObj(obj);
            node.setNext(null);
            
            //将链表的last指针指向node
            last.setNext(node);
            //将链表的last改为node
            last = node;
        } 
    }
}
// 节点类
@Data
class Node {
    
    
    //前驱节点
    private Object previous;
    //存储的数据
    private Object obj;
    //后续节点
    private Object next;
}

循环链表

头节点和尾节点被连接在一起的链表称为循环链表,这种方式在单向和双向链表中皆可实现。

循环链表中第一个节点之前就是最后一个节点,反之亦然。

在这里插入图片描述

总结

链表的优点:

  • 链表不需要初始化容量,可以任意加减元素;
  • 查询慢: 存储地址不连续,每次查询元素都需要从头开始,非常耗时
  • 增删快:不必移动节点,只要改变节点中的指针,但是需要先定位到元素上。
  • 因为含有大量的指针域,所以占用空间较大

适用场景:

数据量较小,需要频繁增加,删除操作的场景

树(Tree)

树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。

之所以把它叫做 “树”, 是因为它看起来像一棵倒挂的树(根朝上,叶朝下)。

特点

  • 每个节点有零个或多个子节点。
  • 没有父节点的节点称为根节点。
  • 每一个非根节点有且仅有一个父节点。
  • 除了根节点外,每个子节点可以分为多个不相交的子树。

树分为二叉树,平衡树,红黑树等。

在Java语言中,讨论和使用最多的是二叉树。

二叉树(binary tree)是每个结点不超过2的有序树(tree),具有如下特点:

  • 每个结点最多有两颗子树,结点的度最大为2。
  • 左子树和右子树是有顺序的,次序不能颠倒。
  • 即使某结点只有一个子树,也要区分左右子树。

适用场景:

二叉树添加,删除元素都很快,并且在查找方面也有很多的算法优化,所以,二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面具有得天独厚的优势。

扩展:

二叉树有很多扩展的数据结构,比如平衡二叉树、红黑树、B+树等。

这些数据结构在二叉树的基础上衍生了很多的功能,在实际应用中被广泛使用。

比如MySQL数据库索引结构用的是B+树,HashMap的底层源码中用到了红黑树。这些二叉树的功能强大,但算法比较复杂,想学习的话需要花时间深入,本文限于篇幅不再一一展开。
在这里插入图片描述

图(Graph)

(了解即可)

图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。

按照顶点指向的方向可分为无向图和有向图。
在这里插入图片描述
在这里插入图片描述
图是一种比较复杂的数据结构,有着非常复杂和高效的算法,这里不做展开,读者有兴趣可以自己学习深入

堆(Heap)

堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

常见的堆有二叉堆、斐波那契堆等。

堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2),满足前者的表达式的成为小顶堆,满足后者表达式的为大顶堆,这两者的结构图可以用完全二叉树排列出来,示例图如下:
在这里插入图片描述
因为堆有序的特点,一般用来做数组中的排序,称为堆排序。

散列表(Hash)

散列表,也叫哈希表,是根据关键码和值 (Key和Value) 直接进行访问的数据结构。

哈希值(hashCode):是一个十进制整数,由系统随机给出(就是对象的地址值,是一个逻辑地址, 不是数据的实际存储地址。)

public class Demo02 {
    
    
    public static void main(String[] args) {
    
    
        Layman layman = new Layman("layman",18);
        /**
         * public native int hashCode() : Object类中的方法,可以返回对象的哈希值(一个十进制整数)。
         */
        System.out.println(layman.hashCode()); //-1109720331
    }
    //Object类中toString()的源码
    public String toString() {
    
    
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
}
@Data
@AllArgsConstructor
class Layman{
    
    
    private String name;
    private Integer age;
}

记录的存储位置=F(key)

散列表就是把Key通过算法函数F(哈希函数)转换成一个十进制整数,然后将该整数对数组长度进行取余,取余的结果当作数组下标,将value存储在以该数字为下标的数组空间中。

这种存储空间充分利用数组的查找优势来查找元素,所以查找速度快

哈希表应用广泛,比如Java中有些集合类(HashMap,HashTable)就是借鉴了哈希原理构造的,查找元素时非常方便。

然而,因为哈希表是基于数组衍生的数据结构,所以增删元素慢。出于解决增删元素慢的问题,哈希表的底层采用数组+链表(数组查询快,链表增删快)。

  • JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的数据都存储在一个链表里。但是当hash值相等的元素较多时,通过key值依次查找的效率较低。
  • JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

猜你喜欢

转载自blog.csdn.net/single_0910/article/details/114004426