1、散列表
散列表可以通过给定的关键字直接访问到具体值的一个数据结构,这里的关键字就是 key ,计算具体指位置的方法叫做散列函数(hash函数),其中有个特殊情况,就是通过不同的 Key,可能访问到同一个地址,这种现象叫作哈希冲突。
- 解决冲突
-
- 开放寻址法:对Key哈希之后,如果发现这个地址已经有值了就对计算出来的地址进行一个探测再哈希,比如往后移动一个地址,如果没人占用,就用这个地址。如果超过最大长度,则可以对总长度取余。
- 再哈希法:使用多个散列函数,我们先用第一个散列函数。如果计算得到的位置已经被占用,就再进行一次。
- 链表法:在散列表中每个下标位置对应一个链表,所有经过散列函数得到的散列值相同的元素,我们都放到对应下标位置的链表中。
不管用哪种方法,当空闲位置变少的时候,散列冲突的概率会变得很高。为了尽可能保证散列表的操作效率,一般情况下,我们会尽可能保证散列表中有一定比例的空闲槽位。我们用装载因子来表示空位的多少。
- 扩容
我们可以设定一个阈值,当装载因子大于阈值的时候,就需要对散列表动态扩容。当我们插入数据的时候,如果装载因子大于阈值,就需要先扩容,再执行插入操作。
2、链表
链表中的数据是以结点来表示的,每个结点由元素和指针构成。Data 就是我们要保存的数据,Next 为指针,用来指向下一个节点,链表尾部的 Next 都是指向 NULL。
public class ListNode {
public int val;(Data)
public ListNode next;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
public void display () {
System.out.println("val = " + val);
}
}
链表只能通过遍历的方式进行数据的查找,查询数据的效率较慢,是O(N) 这个级别,但是它在头节点(第一个节点)进行插入和删除比较快,只需要O(1) 就可以了,只需要改变一下第一个元素的引用。适合动态插入和删除的应用场景,但不能快速的定位和随机访问数据。
3、数组
数组是一种可以快速访问的数据结构,它在内存中是一块连续的内存地址,且创建后长度无法进行更改。
在数组中,每一个数组都有自己地址,可以通过数组的下标实现快速访问和赋值,所以它通过下标查找的效率是O(1) 级别。如果是在数组尾部插入、删除元素则可以直接进行,但如果在其他位置插入或者删除则需要调整其他下标下数据的位置,比如删除第一个元素的话,就需要将后面的元素都往前移动一个位置。
- 和链表的对比
-
- 数组为静态结构,静态分配内存。链表支持动态分配内存
- 数组在数据储存时是一段连续的内存空间,链表是非连续的通过指针来串联
- 数组可以根据下标定位快速查找,链表则需要遍历查找
- 数组在插入和删除时会有大量的数据移动补位,链表只需要改变指针指向