Android技能树 — 数组,链表,散列表基础小结

前言:

如今安卓面试,对于数据结构的问题也愈来愈多了,要求也愈来愈多,因此我对于数据结构只能慢慢补起来了。(灬ꈍ ꈍ灬)面试

Android技能书系列:算法

Android基础知识数组

Android技能树 — 动画小结数据结构

Android技能树 — View小结函数

Android技能树 — Activity小结post

Android技能树 — View事件体系小结动画

Android技能树 — Android存储路径及IO操做小结3d

Android技能树 — 多进程相关小结指针

Android技能树 — Drawable小结cdn

数据结构基础知识

Android技能树 — 数组,链表,散列表基础小结

Android技能树 — 树基础知识小结(一)

算法基础知识

Android技能树 — 排序算法基础小结

本文主要讲 数组,链表,散列表(哈希表)。

当咱们去看电影的时候,咱们知道电影院门口会有一个储物柜,

上面还会有连续的数字,一个抽屉连着一个抽屉。 而后你就会把你的东西放在相应号码的小抽屉中,而后进去看电影了。

咱们在将数据存储到内存时候,你请求计算机提供存储空间,计算机会给你一个存储地址,而后你把内容存进去。就相似上面的储物柜。


线性表

线性表:零个或多个数据元素的有限序列。

线性表顺序存储(数组):

若是你有三袋东西,你一个抽屉只能存一袋东西,这时候你就可使用了连续三个柜子。好比你使用了01,02,03号抽屉。

线性表的顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。

而后别人来使用了04号抽屉,这时候你朋友又给你一袋东西,说帮忙也去存一下,可是这时候由于04号抽屉已经被别人使用了,而大家又由于要求你们的东西都按照顺序放在一块儿,因此这时候大家只能从新找连续在一块儿的抽屉,好比08,09,10,11。万一12号被人使用了,而后大家又要再多存一袋物品呢??

这里咱们看出数组的特色:

  1. 若是咱们有四袋物品,咱们已经知道了第一袋物品在N号码的抽屉,那么其余三个确定在N+1,N+2,N+3号,因此在查询的时候十分方便,由于咱们只须要知道一个的位置,其余的位置都知道了。(因此查询起来很方便,由于全部的位置都知道具体在哪一个)
  2. 若是咱们把A放在01,B放在02,C放在03,这时候咱们说在A和B之间插入一个D,这时候咱们须要把B和C都日后移动。同理删除一个也是同样,好比咱们删除了A,B和C都要往前移动。(因此插入删除比较麻烦,须要移动全部后面位置的数据)
  3. 若是你忽然多了一个须要存储的物品,并且已经不够放了,那么须要所有从新移动到新的连续的存储地方。

相似咱们在排队买车票,忽然半路有我的插队,大家全部人都须要日后退后了一位;最前面的人买好票走了一个,大家全部人均可以往前前进一位。

数组 时间复杂度
读取 O(1)
插入/删除 O(n)

线性表链式存储(链表):

单链表:

不知道你们有没有看过相似古墓丽影相似的探宝电影。

它们的步骤就是先知道到了一个地点,而后到了第一个目的地A,到了A以后根据线索才知道下一个目的地B在哪里,而后再去B,而后这样下去A-- B-- C --.....这样,一直到最终的藏宝地方。没错,咱们的链表就是相似这种,好比咱们知道一共有四袋物品,可是你不能直接知道最后一个物品在哪里,你只能从第一个开始,一个个找下去。

好比咱们第一个存在了01号抽屉,存储内容为A,同时告诉你们,下一个物品在05号抽屉,里面内容为B,同时再下一个在08号。

由上面的图咱们能够知道,结点由存放数据元素的数据域和存放后继节点地址的指针域组成。

由上面咱们举例的古墓丽影的剧情可知,咱们不能直接知道最后一个线索在哪里,只能一个个从头至尾查过去,因此链表的读取会很慢;可是咱们若是想要插入和删除就很方便。

好比咱们要插入一个新的结点:

好比咱们要删除其中一个结点:

链表 时间复杂度
读取 O(n)
插入/删除 O(1)

循环链表:

将单链表中终端结点的指针端改成指向头结点,就使整个单链表造成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。

双向链表:

双向链表是在单链表的每一个结点中,再设置一个指向其前驱结点的指针域。

静态链表:

静态链表是为了让没有指针的高级语言也可以用数组实现链表功能。

这个我就直接用网上的截图来讲明了:

静态链表是用相似于数组方法实现的,是顺序的存储结构,在物理地址上是连续的,并且须要预先分配地址空间大小。因此静态链表的初始长度通常是固定的。而后在这个里面存的时候不只存储数据域,同时存入了下一个数组index的位置。至关于咱们上面的指针域换成了数组的index值。

散列表(哈希表):

由上面咱们已经能够知道数组和链表各自的优点和缺点了。

操做 数组 链表
读取 擅长(能够随机/顺序访问) 不擅长(只能顺序访问)
插入/删除 不擅长 擅长

有了上面的知识,咱们就能够引入散列表了,咱们用具体的故事需求来引入散列表:

若是你有一天开了一家水果店,你会拿一个本子来记各类水果的价格,由于你们知道数组对于读取来讲很方便,因此咱们用一个数组来记录各类水果的价格,而且是按照开头字母来进行顺序写入的。

这时候,若是有人问Apple,你就查询一下价格,可是若是水果不少,甚至不少都是A开头的水果,好比有20个A开头的水果,这时候你只能知道A开头的水果是前面20个,可是具体是哪一个,你又要一个个的查过来,若是咱们立刻就知道Apple对应的数组index值就行了,这样就立刻知道在数组的哪一个位置,而后立刻就能够读取出来了。

好比下图:

这样咱们就在index为2的地方存储了苹果的价格,而后在index为8的地方存储了香蕉的价格,依次类推,全部水果都记录进去,这样顾客问你苹果价格时候,你就立刻知道在index为2的地方去读取。

而把Apple变为2是经过散列函数来实现的。

散列函数:

咱们要实现上面的需求,这个散列函数须要一些基本要求:

  1. 若是输入的内容相同时,每次获得的值都相同,好比你每次输入都是Apple,好比每次获得的结果都是2,不能一会儿2,一会儿5。

  2. 若是无论输入什么值获得的结果都相同,那么这个函数也没用,你输入Apple和输入Banana获得的值都相同,那么没有任何分辨做用。

  3. 散列函数须要返回有效的索引,好比上面咱们的数组的长度只有40,你输入Pair时候输出100,这样是无效索引。

根据上面的状况咱们知道了,咱们输入不一样的值的时候,经过散列函数换算后,最好的结果是每一个值都是不一样,这样的话他们的index 也不一样。

可是若是咱们的数组只有长度为6,可是咱们有7种水果,那么必定会有二个水果获得的index是相同的。这时候咱们称这种状况为冲突

处理冲突的方式有不少,最简单的办法就是:若是二个键映射到了同一个位置,就在这个位置存储一个链表。

这样,咱们在查询其余水果时候仍是很快,只是在查询index为0的水果时候稍微慢一点,由于要在index为0的链表中找到相应的水果。

散列表操做 平均状况 最糟状况
查找 O(1) O(n)
插入 O(1) O(n)
删除 O(1) O(n)

咱们能够看到:

  1. 散列表的查找(获取给定索引处的值)速度与数组同样快,而插入和删除速度与链表同样快。所以它具有了两者的有点
  2. 可是最糟状况下,散列表的各类操做速度都很慢(好比都集中在index为0的链表下面,则查询就跟链表查询同样了。)

因此针对最糟的状况,咱们须要:

  1. 较低的填装因子: 散列表使用数组来存储数据,所以须要计算数组中被占用的位置数。 (好比,数组的长度为10,咱们填入的数占用了其中三个,则填装因子为0.3;若是填入的数正好把长度占满,则填装因子为1;若是填入了20个,则填装因子为2。) 当填装因子太大了,说明数组长度不够了,咱们就要再散列表中添加位置了。称为调整长度。(一旦填装因子大于0.7就调整散列表的长度,为此你首先建立一个更长的新数组,一般将数组增加一倍)
  2. 良好的散列函数: 良好的散列好书让数组中的值呈均匀分布,糟糕的散列函数让值扎堆,致使大量的冲突。

这样咱们之后想要知道某个水果价格,只须要输入水果名字,而后经过散列函数返回一个index值就能够去数组中找相应的价格了。

结语:

哪里错误请帮忙指正,thanks。

参考:

《大话数据结构》

《算法图解》

相关文章
相关标签/搜索