一、数据结构
1.1 常用的数据结构
栈
-
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
简单的说:采用该结构的集合,对元素的存取有如下的特点
-
先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
-
栈的入口、出口的都是栈的顶端位置。
这里两个名词需要注意:
-
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
-
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列
-
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
-
队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
数组
-
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
查找元素快:通过索引,可以快速访问指定位置的元素
增删元素慢
-
指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置
指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中
链表
-
链表:linked list,由一系列节点node(链表中每一个元素称为结点)组成,节点可以在运行时i动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
-
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
-
增删元素快:
-
增加元素:只需要修改连接下个元素的地址即可。
-
删除元素:只需要修改连接下个元素的地址即可。
红黑树
-
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
如图:
我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
-
节点可以是红色的或者黑色的
-
根节点是黑色的
-
叶子节点(特指空节点)是黑色的
-
每个红色节点的子节点都是黑色的
-
任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
二、List集合
List接口特点:
-
它是一个元素存取有序的集合。
-
它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
-
集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
常用方法:
-
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。 -
public E get(int index)
:返回集合中指定位置的元素。 -
public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。 -
public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
public class ListDemo {
public static void main(String[] args) {
// 创建List集合对象
List<String> list = new ArrayList<String>();
// 往 尾部添加 指定元素
list.add("图图");
list.add("小美");
list.add("不高兴");
System.out.println(list);
// add(int index,String s) 往指定位置添加
list.add(1,"没头脑");
System.out.println(list);
// String remove(int index) 删除指定位置元素 返回被删除元素
// 删除索引位置为2的元素
System.out.println("删除索引位置为2的元素");
System.out.println(list.remove(2));
System.out.println(list);
// String set(int index,String s)
// 在指定位置 进行 元素替代(改)
// 修改指定位置元素
list.set(0, "三毛");
System.out.println(list);
// String get(int index) 获取指定位置元素
// 跟size() 方法一起用 来 遍历的
for(int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}
//还可以使用增强for
for (String string : list) {
System.out.println(string);
}
}
}
三、List的子类
3.1ArrayList集合
数据结构特点
- 数据结构: ArrayList 是基于对象数组实现。它可以存储对象的可变大小列表。
- 特点:
- 动态大小: ArrayList 可以根据需要自动调整大小,不需要事先定义容量。
- 随机访问比较快: 由于底层是数组,ArrayList 支持快速的随机访问,时间复杂度为 O(1)。
- 插入和删除的效率比较低: 在数组中间插入或删除元素时,可能会导致后续元素的移动,时间复杂度为 O(n)。
- 非同步: ArrayList 是非线程安全的,如果在多线程环境下使用,需要手动同步。
底层实现原理
- ArrayList 底层使用一个 Object 数组来存储元素。创建一个 ArrayList 时,会初始化一个默认的空数组。
- 当元素被添加到 ArrayList 中,首先会检查当前数组的容量是否足够。如果容量不足,就会触发扩容。
扩容原理
- 默认情况下,ArrayList 会初始化一个大小为 10 的数组。
- 当添加新元素超过当前数组的大小时,会进行扩容。扩容的过程是:
- 创建一个新的数组,通常是原数组大小的 1.5 倍(或者是 2 倍,具体实现可能有所不同)。
- 将原数组中的元素复制到新创建的数组中。
- 重新管理元素的索引。
3.2 LinkedList集合
LinkedList
集合数据存储的结构是链表结构。方便元素添加、删除的集合
特点:
- 动态大小: 与 ArrayList 不同,LinkedList 的大小是动态的,不需要事先设置容量,节点的增加和删除操作不会涉及其他节点的移动。
- 插入和删除: 在链表中,插入和删除操作非常高效(时间复杂度为 O(1)),只需调整相关节点的指针即可,而不需要移动元素。
- 迭代性能: 由于链表的节点分布在内存中,随机访问(使用索引访问元素)效率较低,时间复杂度为 O(n),适合于顺序访问和动态变化的场景。
LinkedList是非线程安全的
②、底层原理
- 双向链表: LinkedList 是通过双向链表实现的,允许从链表的任意一端快速插入和删除元素。
- 头指针和尾指针: LinkedList 通常维护一个指向第一个节点的头指针和指向最后一个节点的尾指针,使得在链表的两端进行添加和删除操作更加高效。
③、没有扩容的
④、常用方法
-
ublic void addFirst(E e)
:将指定元素插入此列表的开头。 -
public void addLast(E e)
:将指定元素添加到此列表的结尾。 -
public E getFirst()
:返回此列表的第一个元素。 -
public E getLast()
:返回此列表的最后一个元素。 -
public E removeFirst()
:移除并返回此列表的第一个元素。 -
public E removeLast()
:移除并返回此列表的最后一个元素。 -
public E pop()
:从此列表所表示的堆栈处弹出一个元素。 -
public void push(E e)
:将元素推入此列表所表示的堆栈。 -
public boolean isEmpty()
:如果列表不包含元素,则返回true。
LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解)
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
//添加元素
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
System.out.println(link);
// 获取元素
System.out.println(link.getFirst());
System.out.println(link.getLast());
// 删除元素
System.out.println(link.removeFirst());
System.out.println(link.removeLast());
while (!link.isEmpty()) { //判断集合是否为空
System.out.println(link.pop()); //弹出集合中的栈顶元素
}
System.out.println(link);
“重地 通话”
}
}
四、Set接口
Set
接口和jList
接口一样,同样继承自Collection
接口,它与Collection
接口中的方法基本一致,并没有对Collection
接口进行功能上的扩充,只是比Collection
接口更加严格了。与List
接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
3.1 HashSet集合
HashSet
是Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。HashSet
底层的实现其实是一个HashMap
支持。HashSet
是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode
与equals
方法。
public class HashSetDemo {
public static void main(String[] args) {
//创建 Set集合
HashSet<String> set = new HashSet<String>();
//添加元素
set.add(new String("cba"));
set.add("abc");
set.add("bac");
set.add("cba");
//遍历
for (String name : set) {
System.out.println(name);
}
}
}
输出结果如下,说明集合中不能存储重复元素
cba
abc
bac
4.2 HashSet集合存储数据的结构(哈希表)
HashSet(哈希表实现)
哈希表(HashMap),也被称为散列表,是一种高效的数据结构,它支持快速查找、插入和删除操作。哈希表的核心思想是通过一个称为哈希函数的映射机制,将存储的数据(通常是键值对形式)转换成一个数组索引,从而可以直接访问存储位置。
主要特点:
快速存取:理想情况下,哈希表的平均时间复杂度为O(1),意味着无论是查找、插入还是删除操作,理论上都能在常数时间内完成。
哈希函数:这是决定哈希表性能的关键因素之一。一个好的哈希函数应该能够均匀地分布数据,减少哈希冲突的发生。
哈希冲突:由于哈希函数产生的索引范围有限,当两个不同的键通过哈希函数得到相同的索引时,就会发生哈希冲突。解决哈希冲突的方法包括开放寻址法、拉链法等。
动态调整:随着哈希表中数据量的变化,为了保持良好的性能,可能需要对哈希表的大小进行动态调整。
JDK1.8 HashMap:
底层数据结构: 数组 + 链表 + 红黑树