赞
踩
需要将数据存储到内存时,你请求计算机提供存储空间,计算机给你一个存储地址。需要存储多项数据时,有两种基本方式——数组和链表。
链表中的元素可存储在内存的任何地方。
链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。
链表的优势在于插入元素方面,需要同时读取所有元素时,链表的效率很高。
链表的缺点:在读取链表的最后一个元素时,你不能直接读取,因为你不知道他所处的地址,必须先访问元素1,然后元素2…直到访问最后一个元素。跳跃读取的话链表的效率就很低。
当需要在中间插入元素时,链表是更好地选择。
删除元素时链表也是更好地选择,因为只需修改前一个元素的指向地址就可以了,二使用数组时,删除元素后,必须将后面的元素都向前移。
通常我们都记录了链表的第一个元素和最后一个元素,因此删除这些元素时的运行时间为O(1)
数组知道其中每个元素的地址。需要随机读取元素时,数组的效率很高,因为可以迅速找到数组中的任何元素。在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,而必须先访问第一个元素以获得第二个元素的地址,在访问第二个元素以获得第三个元素的地址,以此类推,直到访问第五个元素。
通常情况下数组用的多,因为他支持随机访问。顺序访问:从第一个元素开始逐个的读取元素。链表只能顺序访问!随机访问意味着可以直接跳到要访问的元素上。所以支持随机访问的数组读取速度更快!
数组的元素带编号,编号从0开始而不是从1开始,主要是从0开始觊觎数组的代码编写起来更容易,因此程序员始终坚持这样做。几乎所有的编程语言都从0开始对数组进行编号。
元素的位置称为索引,用索引表示位置。
删除元素总能成功,因为是释放内存,如果没有足够的内存,插入操作可能失败,但任何情况下都能将元素删除。
# 选择排序 # 获取数组中的最小 import time def findSmallest(arr): smallest = arr[0] # 可以是数组中的任意一个元素 smallest_index = 0 # 元素的索引 for i in range(1, len(arr)): # 遍历数组中的元素 if arr[i] < smallest: smallest = arr[i] # 最小元素换绑定名 smallest_index = i # 最小元素 索引换绑定名 return smallest_index # 返回索引名 def selectionSort(arr): start = time.time() newArr = [] for i in range(len(arr)): smallest = findSmallest(arr) newArr.append(arr.pop(smallest)) end = time.time() print(f"find运行时间: {end - start}") return newArr
def selectSort(arr):
start = time.time()
newArr = []
newArr.append(min(arr))
end = time.time()
print(f"min运行时间: {end - start}")
return newArr
arr = [i for i in range(999999)]
res2 = selectionSort(arr)[:9]
res1 = selectSort(arr)[:9]
print(res1, res2)
在速度上方法一远远没有方法二快,但是可以做为初学的一种方式,我们知道min
其实是一种迭代器,将数组中两个元素作对比。
如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要。
由于递归函数调用自己,因此编写这样的函数容易出错,进而导致无限循环。所以编写递归时,必须告诉他何时停止递归。正因如此,每个地柜函数都有两部分:基线条件和递归条件。递归条件指的是函数自己调用自己,而基线条件则指的是函数不在调用自己,从而避免无限循环。
For example
def add(i, result=0):
result += i
# print(result)
if i <= 0: # 基线条件
return
else: # 递归条件
return (add(i - 1, result=result))
add(88)
栈是拥有插入和弹出操作的数据结构
计算机在内部使用被称为调用栈
的栈。我们来看看计算机是如何使用调用栈的。下面是一个简单地函数。
def greet2(name):
print(f"how are you {name}?")
def bye():
print("ok bye!")
def greet(name):
print(f"hello {name}!")
greet2(name)
print("nice to meet you!")
bye()
我们知道每次定义一个函数,都会在内存中生成一个名称空间,每调用一次函数都会执行里面的代码,函数里调用涉及的所有变量的值存储到内存中。我们运行greet(superme)
,打印hello superme
,再调用greet2(superme)
。同样,计算机也为这个函数调用分配了一块内存。
计算机使用一个栈来表示这些内存块,其中第二个内存块位于第一个内存块上面(greet2(superme)
在greet(superme)
上面),当greet2(superme)
运行完毕(调用返回
),此时栈顶的内存被弹出。
现在栈顶的内存是greet(superme)
,此时greet(superme)
函数还未运行完毕,还在栈底(第一个栈),然后运行nice to meet you
,执行bye
函数,栈顶加载bye
的内存块。最终运行完bye
,返回到greet
函数,完成整个函数的返回。这个栈用于存储多个函数的变量,称为调用栈
递归函数也使用调用栈,下面是计算阶乘的递归函数
def fact(x):
if x == 1:
return 1
else:
return x * fact(x - 1)
result = fact(3)
print(result)
下面详细分析调用fact(3)
时调用栈是如何变化的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ygZtXSGU-1598689218201)(C:%5CUsers%5CS%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200815230406416.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IYvNybSp-1598689218204)(C:%5CUsers%5CS%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200815230511472.png)]
注意每个fact
调用都有自己的x
变量。在一个函数调用不能访问另一个的x
变量。
栈在递归中扮演着重要的角色。
1)找出基线条件,这种条件必须尽可能简单。
2)不断将问题分解(或者说缩小规模),知道符合基线条件。
分而治之并非可用于解决问题的算法,而是一种解决问题的思路。
对于排序算法来说不用排序的数组就是最简单的数组,因此基线条件为数组为空或者只包含一个元素的(len(arr)<2
),只需要原样返回,不需要排序
那么针对多个元素的数组呢?我们先来了解下快速排序的工作原理:
先从数组中选择一个元素,这个元素被称为基准值
找出比基准值小的元素以及比基准值大的元素(分区)。
两个子数组是无序的,如果是有序的,排序将会非常容易
如果是无序的呢?那么再对这两个数组进行快速排序
# 快速排序
def quicksort(array):
if len(array) < 2: # 基线条件
return array # 返回值
else:
pivot = array[0] # 基准值 任意位置都可以
less = [i for i in array[1:] if i <= pivot] # 所有小于基准值构成一个数组
greater = [i for i in array[1:] if i > pivot] # 所有大于基准值构成一个数组
return quicksort(less) + [pivot] + quicksort(greater) # 数组的拼接
# 感觉并不快 还是要遍历整个数组
array = [1, 23, 45453, 4, 34, 523, 23, 4, 234, 23, 42, 34, 23, 423, 4, 235, 2, 35, 23, 523, 52, ]
result = quicksort(array)
print(result)
每次都能分到两个相同长度的数组 时间复杂度 O(logn)
每次都能分割到两个数组 时间复杂度 nO(logn)
列表原本有序,只能得到一个数组相当于遍历 时间复杂度O(n)
最有用的数据结构之一
散列函数
散列函数是这样的函数,即无论你给它什么数据,他都还你一个数字。(看起来像字典)
如果用专业术语来表达的话,我们会说,散列函数“将输入映射到数字”。你可能认为散列函数输出的数字没什么规律,但其实散列函数必须满足一些要求。
散列函数准确的指出了价格的存储位置,你根本不用查找!原因如下
在我们学习的复杂数据结构中,散列表可能是最有用的,也被称为散列映射、映射、字典和关联数组。散列表的速度很快!
python提供的散列表实现为字典,你可使用函数dict来创建散列表。
你几乎根本不用自己去实现散列表,因为你使用的编程语言提供了散列表的实现。你可使用python给你提供的散列表,并假定能够获得平均情况下的性能:常量时间。
散列表是一种功能强大的数据结构,期操作速度快,还能让你以不同的方式建立数据模型。你可能很快会发现自己经常在使用它。
本章将介绍图。首先,我将说说什么是图(他们不涉及X和Y轴),再介绍第一种图算法——广度优先搜索。
广度优先搜索让你能够找出两样东西之间的最短距离,不过最短距离的含义有很多!使用广度优先搜索可以:
编写国际跳棋AI,计算最少走多少部就可以获胜
编写拼写检查器,计算最少编辑多少个地方就可以将拼错的单词改成正确的单词,如将READED改为READER只需要编辑一个地方:
根据你的人际关系网络找到关系最近的医生。
在我所知道的算法中,图算法应该是最有用的。
图模拟一组连接。图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。
树是一种特殊的图,其中没有往后指的边。
列队只支持两种操作:入队和出队。
如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可以使用队列来表示查找名单!这样,先加入的人将先出队并先被检查。
队列是一种先进先出的数据结构,而栈是一种后进先出的数据结构
迪克斯拉特算法用于每条边都有关联数字的图,这些数字称为权重(weight)。
带权重的图称为加权图,不带权重的图称为非加权图
要计算非加权图中的最短路径可以使用广度优先算法,计算加权图中的最短路径,意义使用迪克斯特拉算法(图还可能有环)
无向图就是 环, 无向图汇总每条边都是一个环。迪克斯特拉算法只适用于有向无环图
迪克斯特拉算法关键理念,找到最优秀的解决方案,并确保没有比这个更好地解决方案。
如果有负权边,就不能使用迪克斯特拉算法。
NP完全问题的简单定义是,以难解著称的问题,如旅行商问题和集合覆盖问题很多非常聪明的人都认为,根本不可能编写出快速解决这些问题的算法。
什么是NP完全问题:
毕达哥拉斯定理
余弦相似度
朴素贝叶斯分类器
在庞大数组中查找,最快的方法是二分查找,常用于注册
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIYodZMa-1598689218205)(C:%5CUsers%5CS%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200823162059293.png)]
一个散列表。将单词映射到包含它的页面。常用于创建搜索引擎。
绝妙优雅且应用广泛的算法少之又少,傅里叶变换算是一个。傅里叶变换就相当于,你给它一杯冰沙,它能告诉你其中包含哪些成分。
傅里叶变换科创就爱你类似于shazam这样的音乐识别软件。傅里叶变换的用途及其广泛,遇到他的可能性极高!
映射函数和归并函数
映射函数:
归并函数:
布隆过滤器是一种概率型数据结构,他提供的答案有可能不对,但很有可能是正确的
布隆过滤器非常适合适用于不要求答案绝对准确的情况
HyperLogLog是一种类似于布隆过滤器的算法。
局部敏感的散列算法
Diffie-Hellman算法及其代替者RAS依然被广泛使用
擎。
绝妙优雅且应用广泛的算法少之又少,傅里叶变换算是一个。傅里叶变换就相当于,你给它一杯冰沙,它能告诉你其中包含哪些成分。
傅里叶变换科创就爱你类似于shazam这样的音乐识别软件。傅里叶变换的用途及其广泛,遇到他的可能性极高!
映射函数和归并函数
映射函数:
归并函数:
布隆过滤器是一种概率型数据结构,他提供的答案有可能不对,但很有可能是正确的
布隆过滤器非常适合适用于不要求答案绝对准确的情况
HyperLogLog是一种类似于布隆过滤器的算法。
局部敏感的散列算法
Diffie-Hellman算法及其代替者RAS依然被广泛使用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。