抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

线性表

数组和矩阵

  • 优点
    • 读取速度快
  • 缺点
    • 事先必须直到数组的长度
    • 插入删除元素很慢,效率很低
    • 空间通常是有限制的
    • 需要大块连续的内存块

ArrayList源码解析

相关题目

链表

  • 优点

    • 空间没有限制
    • 插入删除元素很快
  • 缺点

    • 存取速度很慢
  • 分类

    • 单向链表(每个节点,存在一个指向下一个节点的指针)
    • 双向链表(每个节点,存在一个指针指向下一个节点,一个指针指向上一个节点)
    • 循环链表(上述两种链表的基础上,拥有头尾互指指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Node {
//数据域
public int data;
//指针域,指向下一个节点
public Node next;

public Node() {
}
public Node(int data) {
this.data = data;
}
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
}

LinkedList源码解析

相关题目

哈希表

栈和队列

  • 栈 - LIFO

    • 数组实现的叫静态栈
    • 链表实现的叫动态栈
  • 队列 - FIFO

    • 数组实现的叫静态队列
    • 链表实现的叫动态队列

知识脉络

结构说明

​ 树是一种数据结构,它是n(n>=0)个节点的有限集。n=0时称为空树。n>0时,有限集的元素构成一个具有层次感的数据结构。

​ 区别在于线性表是一对一的关系,树中的节点是一对多的关系。树具有以下特点:

  • n>0时,根节点是唯一的,不可能存在多个根节点。
  • 每个节点有零个或者多个子节点;除了根节点外,每个节点有且仅有一个父节点。根节点没有父节点。

树的相关概念

  • 子树:除了根节点外,每个子节点都可以由多个不相交的子树。
  • 孩子与双亲:若一个节点有子树,那么该节点称为子树根的"双亲",子树的根是该节点的"孩子"。在图一中,B、H是A的孩子,A是B、H的双亲。
  • 兄弟:具有相同双亲的节点互为兄弟。例如B和H互为兄弟。
  • 节点的度:一个节点拥有子树的数目。例如A的度就是2,B的度就是1,C的度就是3。
  • 叶子:没有子树,也即度为0的节点。
  • 分支节点:除了叶子节点之外的节点,也就是度不为0的节点。
  • 内部节点:除了根节点之外的分支节点。
  • 层次:根节点为第一层,其余节点的层次等于其双亲节点的层次加1。
  • 树的高度:也称为树的深度,树中节点的最大层次。
  • 有序树:树中节点各个子树之间的次序是重要的,不可以随意交换位置。
  • 无序树:树中节点各个子树之间的次序是不重要的,可以随意交换位置。
  • 森林:0或者多颗互不相交的树的集合。

二叉树

  • 二叉树

    • 最多有两颗子树的树
    • 二叉树的五种状态

  • 斜树

    • 所有节点都只有左子树的二叉树叫做左斜树;所有节点都只有右子树的二叉树叫做右斜树。其本质是链表

  • 满二叉树

    • 二叉树中所有非叶子节点的的度都是2,且叶子节点都处于同一个层次上。

  • 完全二叉树

    • 如果一个二叉树与满二叉树前m个节点的结构相同,这样的二叉树被称为完全二叉树。

二叉查找树 - BST

二叉查找树(Binary Search Tree)是指一棵空树或者具有下列性质的二叉树:

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于于它的根节点的值;
  • 任意节点的左右子树也分别为二叉查找树;
  • 没有键值相等的节点。

二叉查找树相比较其他数据结构的优势在于查找、插入的时间复杂度较低,为$O(log_{}n)$。

二叉查找树是基础性的数据结构,用于构建更为抽象的数据结构,如集合,多重集合,关联数组等。

二叉查找树的实现以及测试

平衡二叉树 - AVL

含有相同节点的二叉树可以有不同的形态,而二叉查找树的平均长度与树的深度有关,所以需要找出一个查到平均长度最小的一棵,那就是平衡二叉树,具有一下性质:

  • 要么是棵空树,要么其根节点左右子树的深度之差绝对值不超过1;
  • 其左右子树也都是平衡二叉树;
  • 二叉树节点的平衡因子定义:(该节点的左子树的深度 - 右子树的深度)。则平衡二叉树的所有节点的平衡因子只可能是-1,0,1。

平衡二叉树的实现以及测试

红黑树

红黑树也是一种自平衡的二叉查找树,它的每个节点增加了一个存储位来记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子检点路径上颜色的约束,红黑树保证最长路径不超过最短路径的两倍,因而近似平衡。

  • 每个节点要么是红的要和是黑的。(红或黑)
  • 根节点是黑的。(根黑)
  • 每个叶节点(指树尾端NIL指针或NULL节点)都是黑的。(叶黑)
  • 如果一个节点是红的,那么他的两个儿子都是黑的。(红子黑)
  • 对于任意节点而言,其到叶节点树尾端NIL指针的每条路径都包含相同数目的黑节点。(路径下黑相同)。

应用

  • Java ConcurrentHashMap & TreeMap
  • C++ STL: map & set
  • linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块
  • epoll在内核中的实现,用红黑树管理事件块
  • nginx中,用红黑树管理timer等

AVL树和红黑树的比较

  • AVL树的时间复杂度虽然优于红黑树,但是对于现在的计算机,CPU效率很高,所以性能差距也就变小了
  • 红黑树的插入删除比AVL树更便于控制操作
  • 红黑树整体性能略优于AVL树(红黑树旋转的情况少于AVL树)

红黑树的实现以及测试

哈夫曼树

哈夫曼树又称最优二叉树。是一种带权路径长度最短的二叉树,一般可以按下面的步骤构建:

  • 将所有左右子树都为空的节点作为根节点。
  • 在森林中选出两颗根节点的权值最小的树作为一棵新树的左右子树,且新树的附加节点的权值为其左右子树根节点的权值之和。注意,左子树的权值要小于右子树的权值。
  • 从森林中删除这两棵树,同时把新的树加入到森林中。
  • 重复2,3步骤,直到森林中只有一棵树为止,这棵树就是哈夫曼树。

B树

B树(B-Tree)是一种自平衡的树,能够保持数据的有序,这种数据结构能够让查找数据、顺序访问、插入数据以及删除的动作,都在对数时间内完成。B树,概括来说是一种自平衡的m阶树,与自平衡二叉树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。

  • 根节点至少有两个子女;
  • 每个中间节点都包含k-1个元素和k个孩子,其中$\frac{m}{2}\leq k \leq m$;
  • 每个叶子节点都包含k-1个元素,其中$\frac{m}{2}\leq k \leq m$;
  • 所有的叶子节点都位于同一层;
  • 每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。

B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:

B+树

  • 通常用于关系型数据库(如MySQL)和操作系统的文件系统中。
  • 特点是能够保持数据的稳定有序,其中插入与修改拥有稳定的对数时间复杂度。
  • 元素自底向上插入,这与二叉树相反。
  • 在B树的基础上,为叶子节点增加链表指针(B树+叶子有序链表),所有关键字都在叶子节点中出现,非叶子节点作为叶子节点的索引;B+树总是到叶子节点才命中。
  • B+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,B+树要比B树有更多的分支,使得这棵树更加矮胖,查询时做的I/O操作次数就会更少。

将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:

B+树的

R树

R树是用来做空间数据存储的树状数据结构。例如给地理位置,矩形和多边形这类多维数据建立索引。

R树的核心思想是聚合距离相近的节点并在树结构的上一层将其表示为这些节点的最小外接矩形(MBR),这个最小外接矩形就成为上一层的一个节点。因为所有节点都在它们的最小外接矩形中,所以跟某个矩形不相交的查询就一定跟这个矩形中的所有节点都不相交。叶子节点上的每个矩形都代表一个对象,节点都是对象的聚合,并且越往上层聚合的对象就越多。也可以把每一层看做是对数据集的近似,叶子节点层是最细粒度的近似,与数据集相似度100%,越往上层越粗糙。

总结

我们知道,实际应用当中,我们经常使用的是查找排序操作,这在我们的各种管理系统、数据库系统、操作系统等当中,十分常用。

  • 数组的下标寻址十分迅速,但计算机的内存是有限的,故数组的长度也是有限的,实际应用当中的数据往往十分庞大;而且无序数组的查找最坏情况需要遍历整个数组;后来人们提出了二分查找,二分查找要求数组的构造一定有序,二分法查找解决了普通数组查找复杂度过高的问题。任何一种数组无法解决的问题就是插入、删除操作比较复杂,因此,在一个增删查改比较频繁的数据结构中,数组不会被优先考虑

  • 普通链表由于它的结构特点被证明根本不适合进行查找

  • 哈希表是数组和链表的折中,同时它的设计依赖散列函数的设计,数组不能无限长、链表也不适合查找,所以也不适合大规模的查找

  • 二叉查找树因为可能退化成链表,同样不适合进行查找

  • AVL树是为了解决可能退化成链表问题,但是AVL树的旋转过程非常麻烦,因此插入和删除很慢,也就是构建AVL树比较麻烦

  • 红黑树是平衡二叉树和AVL树的折中,因此是比较合适的。集合类中的Map、关联数组具有较高的查询效率,它们的底层实现就是红黑树。

  • 多路查找树 是大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下。

  • B树与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。它的应用是文件系统及部分非关系型数据库索引。

  • B+树在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。通常用于关系型数据库(如MySQL)和操作系统的文件系统中。

  • B*树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针, 在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3。

  • R树是用来做空间数据存储的树状数据结构。例如给地理位置,矩形和多边形这类多维数据建立索引。

  • Trie树是自然语言处理中最常用的数据结构,很多字符串处理任务都会用到。Trie树本身是一种有限状态自动机,还有很多变体。什么模式匹配、正则表达式,都与这有关。

针对大量数据,如果在内存中作业优先考虑红黑树(map,set之类多为RB-tree实现),如果在硬盘中作业优先考虑B系列树(B+, B, B*)

基础知识点

图(Graph)是由顶点的有穷非空集合和顶点之间的边的集合组成,通常表示为:G(V,E),其中G表示图,V表示图中的顶点集合,E表示图中边的集合。

与线性表、树的差异:
1. 线性表中的数据叫元素;树的数据叫节点;图中的数据叫顶点(Vertex)
2. 线性表可以没有元素,叫空表;树可以没有节点,叫空树;图不能没有顶点 [有穷非空性]
3. 线性表中的各元素是线性关系;树中的各元素是层级关系;图中的各顶点的关系是用边来表示的

术语

  • 顶点的度

    顶点Vi的度(Degree)是指在图中与Vi相关联的边的条数。对于有向图来说,有入度(In-degree)和出度(Out-degree)之分,有向图顶点的度等于该顶点的入度和出度之和。

  • 邻接

    若无向图中的两个顶点V1V2存在一条边(V1,V2),则称顶点V1V2邻接(Adjacent);
    若有向图中存在一条表<V3,V2>,则称顶点V3与顶点V2邻接,且是V3邻接到V2或者V2邻接到V3

  • 路径

    在无向图中,若从顶点Vi出发有一组边可到达顶点Vj,则称之为顶点Vi到顶点Vj序列为从顶点Vi到顶点Vj的路径(Path)

  • 连通

    若顶点Vi到顶点Vj有路径可通,则称顶点Vi到顶点Vj是连通的(Connected)

  • 有些图的边或弧具有与他相关的数字,这种与图的边或弧相关的数叫权(Weight)

类型

  • 无向图

    如果图中任意两个顶点之间的边都是无向边(简而言之就是没有方向的边),则称该图为无向图(Undirected graphs)。

    无向图中的边使用小括号“()”表示; 比如 (V1,V2);

  • 有向图

    如果图中任意两个顶点之间的边都是有向边(简而言之就是有方向的边),则称该图为有向图(Directed graphs)。

    有向图中的边使用尖括号“<>”表示; 比如/<V1,V2>

  • 完全图

    • 无向完全图: 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。(含有n个顶点的无向完全图有(n×(n-1))/2条边)
    • 有向完全图: 在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。(含有n个顶点的有向完全图有n×(n-1)条边)

存储结构

邻接矩阵表示法

图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

  • 无向图

    我们可以设置两个数组,顶点数组为vertex[4]={v0,v1,v2,v3},边数组arc[4][4]为上图右边这样的一个矩阵。对于矩阵的主对角线的值全为0是因为不存在顶点的边。

  • 有向图

    顶点数组为vertex[4]={v0,v1,v2,v3},弧数组arc[4][4]为下图右边这样的一个矩阵。主对角线上数值依然为0。但因为是有向图,所以此矩阵并不对称,比如由v1v0有弧,得到arc[1][0]=1,而v0v1没有弧,因此arc[0][1]=0。

邻接矩阵表示法的缺陷:由于存在n个顶点的图需要n*n个数组元素进行存储,当图为稀疏图时,使用邻接矩阵存储方法将会出现大量0元素,这会造成极大的空间浪费。这时,可以考虑使用邻接表表示法来存储图中的数据

邻接表表示法

  • 无向图

    顶点表的各个结点由datafirstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。例如: v1顶点与v0v2互为邻接点,则在v1的边表中,adjvex分别为v0的0和v2的2。

  • 有向图

    若是有向图,邻接表结构是类似的,但要注意的是有向图由于有方向的。因此,有向图的邻接表分为出边表和入边表(又称逆邻接表),出边表的表节点存放的是从表头节点出发的有向边所指的尾节点;入边表的表节点存放的则是指向表头节点的某个顶点。

  • 带权图

    对于带权值的图,可以在边表结点定义中在添加一份weight的数据域,存储权值信息即可。

遍历

深度优先搜索

  • 无向图

    深度优先搜索顺序:

  • 有向图

    深度优先搜索顺序:

广度优先搜索

广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜素",简称BFS。

广度优先搜索遍历图的过程是以v为起点,由近至远,依次访问和v有路径相通且路径长度为1,2…的顶点

  • 无向图

  • 有向图

最小生成树

概念

  • 连通图:在无向图中,若任意两个顶点之间都有路径相通,则称该无向图为连通图。
  • 强连通图:在有向图中,若任意两个顶点之间都有路径相通,则称该有向图为强连通图。
  • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接两个顶点的代价,称这种连通图为连通网。
  • 生成树:一个连通网的生成树是指一个连通子图,它包含图中全部n个顶点,但只有足以构成一棵树的n-1条边。一棵有n个顶点的生成树有且仅有n-1条边,如果生成树中在添加一条边,则必定成环。
  • 最小生成树:在连通网的所有生成树中,所有的代价和最小的生成树,称为最小生成树。

最小生成树算法

Kruskal算法

也可以称作"加边法",初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合中。

  1. 把图中的所有边按代价大小排序
  2. 把图中的n个顶点看作独立的n棵树组成的森林
  3. 按权值从小到大选择边,所选的边连接的两个顶点之间应属于两棵不同的树。则称为最小生成树的一条边,并将这两棵树合并为一棵树。
  4. 重复3,直到所有顶点都在一棵树内或者有n-1条边为止。
Prim算法

也称作"加点法",每次迭代选择代价做小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐覆盖整个连通网的所有顶点。

  1. 图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;
  2. 在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
  3. 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。

由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息:


Java集合源码解析

ArrayList

概述

​ ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序是相同的,允许放入null元素,底层通过Object数组实现,未实现同步

​ 每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容量内存储元素的个数不能多于当前容量。在向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小(扩容机制)。

size(),isEmpty(),get(),set()方法均能在常数时间内完成,add()方法的时间开销跟插入的位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都时线性时间。

​ 追求效率,ArrayList也没有实现同步(synchronized),如果需要多个线程访问的,用户需要手动同步(分析查看)。

源码解析

底层数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;

/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access

/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;

/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}

注意

  • 在无参构造方法中,可以看到ArrayList初始化了一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这是一个容量为0的空Object数组。
  • 在有参构造方法中,当传入的参数为零,或者集合大小为0的时候,初始化一个EMPTY_ELEMENTDATA,这是同样是一个容量为0的空Object数组。
  • 综上两点,假若没有规定初始容量,ArrayList会初始化出一个容量为0的空Object数组。随后在往其中第一次添加元素的时候,进行扩容,默认容量(DEFAULT_CAPACITY)为10。

扩容机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}

/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;

if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

描述

​ 每当添加元素时,都会去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的要求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。所以,在添加大量元素前,可以通过这个方法来实现手动扩容,减少在添加元素过程中,自动扩容的次数,因为扩容的过程需要去复制源数组到新数组中,这个过程费时费空间。

​ 数组进行扩容时,会将老数组中的元素拷贝一份到新的数组中,在grow方法中有这么一条代码:int newCapacity = oldCapacity + (oldCapacity >> 1);可以看出,数组每次扩容的时候,会扩容到当前容量的1.5倍。要是newCapacity超过了Integer.MAX_VALUE - 8($2^{31}-1-8$)就会把容量扩大到(Integer.MAX_VALUE),这样就不会再扩容了。

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
rangeCheckForAdd(index);

ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator. The behavior of this operation is
* undefined if the specified collection is modified while the operation
* is in progress. (This implies that the behavior of this call is
* undefined if the specified collection is this list, and this
* list is nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element from the
* specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount

int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);

System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

add(int index,E e)需要对元素进行移动,然后完成插入操作,也就意味着该方法的时间复杂度是线性的。

addAll()方法能够一次添加多个元素,根据位置的不同,也有两个版本。一种是尾插法:addAll(Collection<? extends E> c),一种是指定位置插入:addAll(int index, Collection<? extends E> c)。跟add()方法类似,在插入之前需要进行空间检查,判断是否需要扩容;如果从指定的位置插入,也会移动元素。综上:addAll()方法的时间复杂度和插入的元素个数以及插入位置有关。

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);

E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

get

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);

return elementData(index);
}

remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

两个版本: 一个是remove(int index)删除指定位置的元素,另一个是remove(Object o)删除第一个满足o.equals(elementData[index])的元素 。

注意

elementData[--size] = null; // clear to let GC do its work,这是为了让GC起作用。那为什么这里需要这种显示赋null值呢?

​ 这是因为GC在判断对象能否回收是根据它是否在引用链上,而底层的数组维系着这个引用链。当删除一个元素之后,元素数量发生变化,所有后续元素往前移动,这样在引用链上就会多出来一个元素,按理这个元素应该是没有值的,要被GC掉。但是在后续元素往前移动的时候,是将后续元素拷贝一份,从删除位置开始从写原始位置上的值,这样的结果就导致了最后一个位置上是存在值的,GC确定不了这个对象是否该回收,故而在通过显示的赋null值,告诉GC可以回收该对象。

indexOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

/**
* Returns the index of the last occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the highest index <tt>i</tt> such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

Fail-Fast机制

ArrayList采用了快速失败机制,通过AbstractList抽象类中的modCount参数来实现。源码注解第二段提到:如果该字段的值发生意外变化,迭代器会快速失败,抛出ConcurrentModificationException异常,以响应next,remove,previous,set或add操作,以避免在迭代期间面对并发修改时的不确定性行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;

LinkedList

概述

LinkedList同时实现了List接口和Deque接口,它可以当作顺序容器,队列,栈。

LinkedList的实现方式决定了所有跟下表相关的操作都是线性时间,而在首段或者末尾删除元素只需要常数时间。为了追求效率,LinkedList没有实现同步,如果需要保证线程安全,可从这里了解

源码解析

底层数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
transient int size = 0;

/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;

/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;

private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

/**
* Constructs an empty list.
*/
public LinkedList() {
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}

LinkedList底层是双向链表,每个节点用内部类Node表示。通过firstlast引用分别指向链表的第一个和最后一个元素。当链表为空时,firstlast都指向null

getFirst,getLast

获取第一个元素和获取最后一个元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Returns the first element in this list.
*
* @return the first element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}

/**
* Returns the last element in this list.
*
* @return the last element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}

remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}

/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

/**
* Removes and returns the last element from this list.
*
* @return the last element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}

/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}

/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If this list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}

/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;

if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}

if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}

x.item = null;
size--;
modCount++;
return element;
}

remove有两个版本,一个用来删除指定下表的元素remove(int index),一个用来删除与指定元素相等的第一个元素remove(Object o)

删除元素:指删除第一次出现的元素,如果没有这个元素,则返回false;判断的依据是equals方法,如果equals,则直接unlink这个node;由于LinkedList可以存放null元素,故而也可以删除第一次出现的null元素。

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}

/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}


/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);

if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}

/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}

private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

add(E e)尾插法

add(int index,E e)在指定下标位置插入元素。当index == size时,等同于add(E e);如果不是,则分成两步:1. 根据index找到要插入的位置,即node(index)方法;2. 修改引用,完成插入操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);

if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

node(int index)方法中,通过index < (size >> 1)来提高节点查询的效率,时间复杂度是O($\frac{n}{2}$)。

addAll

addAll(index, c) 实现方式并不是直接调用add(index,e)来实现,主要是因为效率的问题,另一个是fail-fastmodCount只会增加1次;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's iterator. The behavior of this operation is undefined if
* the specified collection is modified while the operation is in
* progress. (Note that this will occur if the specified collection is
* this list, and it's nonempty.)
*
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element
* from the specified collection
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);

Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;

Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}

for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}

if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}

size += numNew;
modCount++;
return true;
}

clear

为了让GC更快可以回收放置的元素,需要将node之间的引用关系赋空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Removes all of the elements from this list.
* The list will be empty after this call returns.
*/
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}

get,set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}

查找

找到返回下标,在、没找到返回-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index {@code i} such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

/**
* Returns the index of the last occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the highest index {@code i} such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the last occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}

Coding

code:数组和矩阵

283. Move Zeroes (Easy)

目标:给定一个数组,将数组中的0,移动到数组的右边。例如[0,1,2,0,3],改成[1,2,3,0,0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void moveZeroes(int[] nums) {
// 记录当前非零数的位置
int idx = 0;
for (int num : nums) {
if (num != 0) {
// 元素不为0,将该元素放到非零位置上
nums[idx++] = num;
}
}
// 将剩余的元素全部赋值为0
while (idx < nums.length) {
nums[idx++] = 0;
}
}

566. Reshape the Matrix (Easy)

目的:改变矩阵的维度,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int[][] matrixReshape(int[][] nums, int r, int c) {
int m = nums.length, n = nums[0].length;
if (m * n != r * c) {
return nums;
}
int[][] reshapedNums = new int[r][c];
int index = 0;
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
reshapedNums[i][j] = nums[index / n][index % n];
index++;
}
}
return reshapedNums;
}

485. Max Consecutive Ones (Easy)

目的:找到数组中最长连续的1的长度

1
2
3
4
5
6
7
8
public int findMaxConsecutiveOnes(int[] nums) {
int max = 0, cur = 0;
for (int x : nums) {
cur = x == 0 ? 0 : cur + 1;
max = Math.max(max, cur);
}
return max;
}

code:链表


参考

♥数据结构基础知识体系详解♥ | Java 全栈知识体系 (pdai.tech)

java实现AVL树_java实现2-3树-CSDN博客

红黑树(五)之 Java的实现 - 如果天空不死 - 博客园 (cnblogs.com)

评论