数据结构原理:Hash表的时间复杂度为什么是O(1)

注: 本文是极客时间后端技术基础详解的读书笔记.

01. 数组

数组是最常用的数据结构,创建数组必须要内存中一块连续的空间,并且数组中必须存放相同的数据类型。比如我们创建一个长度为 10,数据类型为整型的数组,在内存中的地址是从 1000 开始,那么它在内存中的存储格式如下。

0.png

这个位置的数据 241 进行快速读写访问,时间复杂度为 O(1)。随机快速读写是数组的一个重要特性,但是要随机访问数据,必须知道数据在数组中的下标。如果我们只是知道数据的值,想要在数组中找到这个值,那么就只能遍历整个数组,时间复杂度为 O(N)。

思考: 数组的特点就是在内存中的空间必须是连续的,所以大数组其实是比较占用内存的.在Java中ArrayList就是基于数组实现的,通过李老师的叙述,我们知道了数组由于连续的特点,根据下标随机访问复杂度为O(1).ArrayList的增删可能会改变原来数组的大小,对于大数组来说比较消耗资源.所以数组适合需要通过下标访问,以及增删比较少的情形.

02. 链表

同于数组必须要连续的内存空间,链表可以使用零散的内存空间存储数据。不过,因为链表在内存中的数据不是连续的,所以链表中的每个数据元素都必须包含一个指向下一个数据元素的内存地址指针。如下图,链表的每个元素包含两部分,一部分是数据,一部分是指向下一个元素的地址指针。最后一个元素指向 null,表示链表到此为止。

1.png
2.png

相比在链表中轻易插入、删除一个元素这种简单的操作,如果我们要想在数组中插入、删除一个数据,就会改变数组连续内存空间的大小,需要重新分配内存空间,这样要复杂得多。

思考: 链表的数据结构简直和区块链是一样对,最新出来的区块是要指向之前的区块.并且链表的特点是一个链分为两部分内容,一部分是本身自己的数据,另外一部分指向了下一个数据的地址.这个链表的使用情景主要是用于增删比较多的情况,并且不需要根据下标去访问数据,遍历的情况比较多.

03. Hash表

Hash表的物理存储其实是一个数组,如果我们能够根据 Key 计算出数组下标,那么就可以快速在数组中查找到需要的 Key 和 Value。许多编程语言支持获得任意对象的 HashCode,比如 Java 语言中 HashCode 方法包含在根对象 Object 中,其返回值是一个 Int。我们可以利用这个 Int 类型的 HashCode 计算数组下标。最简单的方法就是余数法,使用 Hash 表的数组长度对 HashCode 求余, 余数即为 Hash 表数组的下标,使用这个下标就可以直接访问得到 Hash 表中存储的 Key、Value。

3.png

通过对key的hashCode取余数,从而获取到将这个key,value存到数组当中的位置.事实上,数组中存放的是key,value元素的地址指针而已,如果使用余数法得到的余数相同的时候,这个时候可以使用链表的方式来解决,key,value元素后面接一个新的key,value元素,到时候,通过key获取value首先取余数,获取到链表的地址,然后遍历这个链表就可以了.

4.png

因为有 Hash 冲突的存在,所以“Hash 表的时间复杂度为什么是 O(1)?”这句话并不严谨,极端情况下,如果所有 Key 的数组下标都冲突,那么 Hash 表就退化为一条链表,查询的时间复杂度是 O(N)。但是作为一个面试题,“Hash 表的时间复杂度为什么是 O(1)”是没有问题的。

拓展: 在jdk1.7中,HashMap的实现原理是哈希表 + 链表的方式实现.在jdk1.8当中.链表数量达到8,就会转化为红黑树.

04. 栈

数组和链表的操作可以是随机的,可以对其上任何元素进行操作,如果对操作方式加以限制,就形成了新的数据结构。栈就是在线性表的基础上加了这样的操作限制条件:后面添加的数据,在删除的时候必须先删除,即通常所说的“后进先出”。我们可以把栈可以想象成一个大桶,往桶里面放食物,一层一层放进去,如果要吃的时候,必须从最上面一层吃,吃了几层后,再往里放食物,还是从当前的最上面一层放起。

5.png

程序运行过程中,方法的调用需要用栈来管理每个方法的工作区,这样,不管方法如何嵌套调用,栈顶元素始终是当前正在执行的方法的工作区。这样,事情就简单了。而简单,正是我们做软件开发应该努力追求的一个目标。

思考: 方法在内存当中运行的时候,是用栈的数据结构来运行的,栈里面的元素是栈帧,所以每个栈帧之间是相互隔离的,所以方法内部的局部变量定义相同的变量名没有任何影响.其次,当前工作的只有栈顶的一个方法,这样程序的运行就非常有规律,我们就能够非常好的去排查程序当中的异常.

05. 树

软件开发中,也有很多地方用到树,比如我们要开发一个 OA 系统,部门的组织结构就是一棵树;我们编写的程序在编译的时候,第一步就是将程序代码生成抽象语法树。传统上树的遍历使用递归的方式,而我个人更喜欢用设计模式中的组合模式进行树的遍历,具体我将会在设计模式部分详细讨论。

6.png

思考: 树状接口用递归的方式来遍历.

你可能感兴趣的:(数据结构原理:Hash表的时间复杂度为什么是O(1))