数据结构

170 阅读7分钟

1 线性表

1.1 数组

  • 简单:数组是一种最简单的数据结构。
  • 占用连续内存:数组空间连续,按照申请的顺序存储,但是必须制定数组大小。
  • 数组空间效率低:数组中经常有空闲的区域没有得到充分的应用。
  • 操作麻烦:数组的增加和删除操作很麻烦 。

1.2 顺序表

内存连续

  1. ArrayList的大小是如何自动增加的? add时,数组需要的大小超过原有大小,扩容到原有大小的1.5倍。

    int newCapacity = oldCapacity + (oldCapacity >> 1);

  2. 什么情况下你会使用ArrayList?

    ArrayList优点:尾插效率高,支持随机访问。缺点:中间插入或者删除(修改)效率低。应用:所以用到修改频率低,查找效率高的情景。

  3. 在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么?

    arrayCopy是很耗内存,耗时间的操作。

  4. ArrayList如何顺序删除节点

    迭代器删除 for循环只能删除最后一个节点

  5. arrayList的遍历方法

    1. 迭代器 next hasNext
    2. for循环

1. 删除排序数组中的重复项

2. 搜索插入位置

3. 在排序数组中查找元素的第一个和最后一个位置

1.3 链表

内存不连续。

  • 增加节点

    P点后增加S节点:

    s.next = p.next;
    p.next = s;
  • 删除节点

    删除P后面的节点:

    p.next = p.next.next

  • 修改

    p.data = new data();

  • 查找

    循环查找。

手写一个单链表,并且实现单链表元素的逆置,(a0, a1,a2,a3,..an)-> (an,an-1,… a1, a0),算法的空间复杂度和时间复杂度经可能低。

手写双向链表, 实现增删改查,同时对比自己的LinkList 和源码Linkedlist的异同点

1. 合并两个有序链表

2. 两两交换链表中的节点

3. 复制带随机指针的链表

LinkedList、ArrayList比较:

  1. LinkedList 基于链表实现,链表内存是散列的,增删快(增删只需修改节点的引用关系,指向新的节点),查找慢(需要对链表进行遍历);
  2. ArrayList 基于数组实现,非线程安全,效率高,增删慢(增删需要复制原有数据),查找快(内存连续,直接使用下标访问元素)。

1.4 什么是内存缓存

预先将数据写到了容器(list,map,set)等数据存储单元中,就是软件内存缓存。

1.5 LRU算法(Least Recently Used)

算法思想:喜新厌旧

  1. 新数据插入到链表头部
  2. 当缓存命中(即缓存数据被访问),数据要移到表头
  3. 当链表满的时候,将链表尾部的数据丢弃

1.6 HashMap

两个key计算出的数组下标index = hash&(length-1),相等时,会产生哈希冲突。 链表存储结构可解决哈希冲突,多个Node对象,以链表的形式,存储在同一个bucket上。

HashMap默认大小为16,加载因子默认值0.75,当map中键值对的大小大于加载因子*length时,HashMap会扩容。

扩容是为了避免哈希冲突。

当加载因子为0.75时,在最大限度利用内存空间的前提下,减少哈希冲突。

HashMap在扩容的时候最耗内存,为避免扩容,new HashMap需要传入map的大小:map大小的预估值/0.75 +1,在HashMap构造方法中,会把该值转化成比该值大的,最接近该值的2次幂,作为map的大小。

HashMap的数组什么时候初始化的?

HashMap的数组初始值为空,put的时候,检测到数组为空,调用reSize,初始化数组。

put方法是HashMap真正使用的时候才调用的,所以在put方法中初始化数组是为了节约内存。

HashMap大小为什么是2的次幂:在计算数组下标的时候,2的次幂能充分利用hashCode的每一位,减少哈希碰撞。

加载因子*length=阈值,由于阈值的存在会造成hashMap有25%的内存空间浪费,这是HashMap为了提升效率,而采用的空间换时间大法。

Android设备内存一般不是特别大,为了减少内存的浪费出现了SparseArray。

  1. 什么是HashMap?为什么用它?

    HashMap的数据结构:

    • JDK1.7及之前:数组+链表
    • JDK1.8:数组+链表+红黑树 其中链表/红黑树中节点的数据结构为Node{hash、key、value、next}。

    hashMap结合数组(查找快)、链表(增删快)两者的优点,提高存储性能。

  2. HashMap工作原理?

    HashMap通过map.put(key,value),向map中存放数据,通过get(key)获取value。

    put过程:对key进行hash,计算数组下标index = hash&(length-1),创建Node{hash、key、value、next}节点,插入到链表中。

    get过程:对key进行hash,计算数组下标index = hash&(length-1),定位到目标bucket,遍历链表,比较hash和key均相等时,返回目标value。

  3. 当两个对象的hsahCode相同时会发生什么? 哈希冲突

  4. 当两个对象的hsahCode相同时怎么获取对象?

    map.get过程中,遍历链表时,满足hash相同和key相同两个条件,才会返回value。

  5. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

    负载因子默认值0.75,当HashMap大小超过了loadFactor*length时,HashMap会扩容成原来的2倍。

  6. 重新调整hashMap大小存在什么问题?

    当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了

  7. 为什么String、Interger这样的warpper类适合做为键?

    因为String是不可变的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到想要的对象。不可变性还有其他的优点如线程安全。

  8. 自定义对象可以作为key吗?

    重写hsahCode和equels方法就可以作为key。

1.7 SparseArray

SparseArray是基于HashMap + 二分查找的思想实现的双数组结构,一个int[]保存key,一个Object[],通过数组下标建立映射关系。

key升序排列,

put:通过二分查找法,找到插入的位置,通过ArrayCopy实现插入。

get:使用二分查找法,找到目标key,并定位出value。

remove:通过二分查找法,找到目标位置,并不真正删除value,而是把对应位置的value设置成delete。减少删除时的ArrayCopy带来的性能损耗,并且再次向该位置put数据的时候,直接修改value即可。达到了越用越快的目的。

SparseArray节约内存的同时,也不会出现冲突的问题。

SparseArray速度越用越快。

缺点:key为int。

根据SparseArray的这些特点。我们能分析出其使用场景:

  1. key为整型;
  2. 元素个数相对较少;

1.8 ArrayMap

ArrayMap是基于HashMap + SparseArray实现的,与SparseArray原理一致。

ArrayMap解决了SparseArraykey为int的问题;

SparseArray解决HashMap内存浪费的问题。

HashMap解决ArrayList增删慢、LinkedList查找慢的问题。

1.9 容器总结

Collection依赖Iterator,包括:List(有序的)和Set(元素不允许重复)

图片.png

Map:

图片.png

2 树

2.1 树

N个节点构成的有限集合。

术中常用术语:

  • 根节点:树中有一个称为”根(Root)”的特殊结点。
  • 子树:其余节点可分为若干个互不相交的树,成为原来节点的“子树”。
  • 结点的度:结点的子树个数。
  • 树的度:树中所有结点中最大的度。
  • 叶结点:度为0的结点。
  • 父结点:有子树的结点是其子树的根结点的父结点。
  • 子结点:若A是B的父结点,B就是A的子结点。

树的特点:

  • 子树是不相交的
  • 除了根结点之外,每个结点只有且只有一个父结点
  • 一个N个结点的树只有N-1条边

2.2 二叉树

度为2的树,并且子树有左右顺序之分。

2.3 哈夫曼树

3 堆栈

4 图论

5 排序和查找算法

hashMap:1.8之前用的synchronized实现,1.8使用volatile+CAS实现