讲师:覃超
平台:极客时间
账号:小宝微信
一、数组、链表、跳表的原理和实现
1、数组:
list=[],数组里面的类型是泛型;
数组在内存中开辟了一段连续的地址,可以通过内存管理器访问,
访问任何一个元素的时间复杂度都是O(1)
插入、删除的时间复杂度是O(N)
2、链表:
参考文章:https://www.cnblogs.com/mooncode/p/11039266.html
链表这种数据结构,在一些修改、增加、删除操作比较频繁的情况下使用,包含value和index指针(指向下一个元素),最后一个元素指向空,如果不为空指向头部(head),则为循环链表。
链表每个节点有两个域,一个是Value,一个是Next,Next指向下一节点
单链表:它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
图 单链表
双链表:每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)
图 双链表
循环链表:首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。
图 循环链表
链表在添加或删除节点时,不会引起其他元素的群移操作或复制元素
但要访问链表中任何一个节点,操作就不简单了,比如访问Head或Tail,时间复杂度就是O(1),但要访问中间节点那就得一步一步往后挪,时间复杂度是O(N)
链表添加操作:
- 新节点插入第一个节点之前,即成为此链表的首节点,只需要把新节点的指针指向链表原来的第一个节点,再把链表头指针指向新节点即可
算法如下:
newnode.next=first
first=newnode
- 新节点插入最后一个节点之后,只需要把链表最后一个节点的指针指向新节点,新节点的指针再指向None即可
算法:
pre.next= newnode
newnode=None
- 新节点插入链表中间位置例如插入节点在X、Y之间只要将X节点的指针指向新节点,新节点指针指向Y即可
算法:
X.next= newnode
newnode.next=Y
# 原文
newnode.next=x.next
x.next=newnode
Python 链表添加
https://www.cnblogs.com/jzxs/p/10284177.html
鉴于数组在添加/删除时时间复杂度较高,我们使用链表来降低时间复杂度,而链表在查询时时间复杂度又为O(N),故有跳表的发明。
3、跳表:升维思想+空间换时间
跳表的特点:只能用于元素有序的情况
跳表对标的是平衡树(AVL Tree)和二分查找,是一种插入/删除/搜索,都是O(logn)的数据结构。
优势:原理简单,容易实现,方便扩展,效率更高。
思考方式:一维数据想要查找更快的话,要升维变成二维,二维可以有多一维的信息。
跳表查询的时间复杂度:
n/2, n/4, n/8,第k级索引节点的个数就是
假设索引有h级,最高级的索引有2个节点,=2,从而求得h=logn-1
算法:
import math
n=28
#向上取整 math.ceil()
h=math.ceil(math.log(n,2))
print(h) #5
跳表查询的空间复杂度:
现实中跳表的形态:
增加或删除会改变他的索引,有些数字它并不是工整的,有的地方索引会跨几步,有的地方只会跨两步,是因为里面的元素因为增加而删除了,维护成本高,增加(删除)会改变跳表的索引。
时间复杂度 | 数组 | 链表 | 跳表 |
Prepend | O(1) | O(1) |
|
append | O(1) | O(1) |
|
lookup | O(1) | O(N) | O(logN) |
insert | O(N) | O(1) |
|
delete | O(N) | O(1) |
|
工程中的应用:
LRU缓存算法
https://www.jianshu.com/p/b1ab4a170c3c
LRU缓存机制
https://leetcode-cn.com/problems/lru-cache/description/
作业:
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
"""
逼近法
每次左右两端都舍去短的那一端
若选择短的那一端【S=小于等于此端的高*小于等于当前的最大区间长度】
至多的面积也是选择最左最右的一个矩形,因此不必再考虑短的一端,直接舍去逼近
"""
# 记录当前最大容量的面积
max_area = 0
# 记录最左边的下标
left = 0
# 记录右边的下标
right = len(height) - 1
# 当右边下标大于左边下标的时候循环
while right > left:
# 当前循环中最大的容量面积,使用max方法比较上次的max_area和此次的容量面积,取最大值
# min(height[left], height[right]) * (right - left) 取左边和右边的高当中的最小值, 下标right-left为宽,两者相乘为最大面积
max_area = max(max_area, min(height[left], height[right]) * (right - left))
# 判断哪条高小,小的那边下标进行操作
if height[right] > height[left]:
left += 1
else:
right -= 1
return max_area
二、树、二叉树、二叉搜索树
三、递归