数组
数组由相同类型的元素组成,使用一块连续的内存来存储。
数组的特点是:
1.利用索引进行访问
2.容量固定
3.使用一块连续的内存来存储
各种操作的时间复杂度:
查找/修改:O(1)//访问特定位置的元素
插入:O(n )//最坏的情况发生在插入发生在数组的首部并需要移动所有元素时
删除:O(n)//最坏的情况发生在删除数组的开头发生并需要移动第一元素后面所有的元素时
链表简介
链表使用的不是连续的内存空间来存储
链表的插入和删除操作的复杂度为 O(1)
查询或者访问特定位置的节点的时候复杂度为 O(n) 。
链表相比于数组会占用更多的空间,因为链表中每个节点存放的还有指向其他节点的指针
链表分类
常见链表分类:
单链表。单向链表只有一个方向,结点只有一个指针 next 指向后方的节点
循环链表。循环链表和单链表不同的是循环链表的尾结点不是指向 null,而是指向链表的头结点
双向链表。双向链表 包含两个指针,prev 指向前一个节点, next 指向后一个节点
双向循环链表。双向循环链表 尾节点的 next 指向头节点,而头节点的 prev 指向最后一个节点
数组 vs 链表
- 数组支持随机访问,而链表不支持。
- 数组使用连续的内存空间来存储,链表则相反。
- 数组的大小固定,而链表则支持动态扩容。如果初始声明的数组过小,后续需要申请一个更大的数组,然后将原数组拷贝进去,浪费时间
栈简介
栈 只允许栈顶( top)进行加入数据(push)和移除数据(pop)。按照 后进先出(LIFO, Last In First Out) 的原理运作。
栈常用一维数组或链表来实现,用数组实现的栈叫作 顺序栈 ,用链表实现的栈叫作 链式栈 。
应用场景
- 实现浏览器的回退和前进功能
只需要使用两个栈(Stack1 和 Stack2)和就能实现这个功能。比如你按顺序查看了 1,2,3,4 这四个页面,我们依次把 1,2,3,4 这四个页面压入 Stack1 中。当你想回头看 2 这个页面的时候,你点击回退按钮,我们依次把 4,3 这两个页面从 Stack1 弹出,然后压入 Stack2 中。假如你又想回到页面 3,你点击前进按钮,我们将 3 页面从 Stack2 弹出,然后压入到 Stack1 中。 - 深度优先遍历(DFS)
在深度优先搜索过程中,栈被用来保存搜索路径,以便回溯到上一层
队列简介
队列(Queue) 是 先进先出 (FIFO) 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 顺序队列 ,用链表实现的队列叫作 链式队列 。队列只允许在后端(rear)进行插入操作也就是入队(enqueue),在前端(front)进行删除操作也就是出队(dequeue)
队列的常见应用场景
当我们需要按照一定顺序来处理数据的时候可以考虑使用队列这个数据结构。
- 阻塞队列:当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。
- 广度优先搜索(BFS),在图的广度优先搜索过程中,队列被用于存储待访问的节点,保证按照层次顺序遍历节点
队列的分类
有无固定大小划分:
有界队列
有固定大小的队列
常见的有界队列:
ArrayBlockingQueue
常见的无界队列为:
指的是没有设置固定大小的队列。
1)ConcurrentLinkedQueue 无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常
2)PriorityBlockingQueue 具有优先级的阻塞队列
3)DelayedQueue 延时队列,使用场景
根据阻塞/非阻塞划分
阻塞队列
当生产者向队列中生产数据时,若队列已满,那么生产线程会暂停,直到队列中有空闲;而当消费者向队列中获取数据时,若队列为空,则消费者线程会暂停,直到容器中有元素出现
非阻塞队列
与阻塞队列相反,非阻塞队列的执行并不会被阻塞,无论是消费者的出队,还是生产者的入队。
在底层,非阻塞队列使用的是CAS(compare and set)来实现线程执行的非阻塞
根据存储结构划分
链式队列
链式队列内部使用单向链表来实现。它的好处的是灵活,队列容量理论上是不受限制的。
循环队列
循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。
顺序队列
队列是只允许在一端进行插入操作,在另外一段进行删除操作的线性表,先进先出(First In First Out)
双端队列
双端队列两端都可以删除元素和插入元素。双端队列用双向链表实现。
顺序队列和链式队列都属于单向队列