机器学习,深度学习基础算法原理详解(数据结构[持续更新])
机器学习,深度学习基础算法原理详解(图的搜索、交叉验证、PAC框架、VC-维(持续更新))
1. 链表、数组、栈、队列
链表:数据结构之一,其中的数据呈线性排列,数据添加与删除比较方便,访问较为耗时。链表中的数据是分散存储在内存中的,无需存储在连续空间。而访问数据则需要从第一个数据根据指针进行访问,而数据的增添、删除也对应其指针的改变以及增删。此外,循环列表、多向列表也是在链表尾部使用指针指向头部,或者多指向。
数组:数据结构之一,数据访问简单,而添加以及删除数据比较耗时。访问时候根据其索引,精确快速找到其位置。而增添以及删除数据需要进行索引位置的移位,删除等操作。
栈:存储的数据呈线性分布,访问、增添、删除数据整体先进后出,只能在一端操作,想要访问中间数据时,就必须通过出栈操作将目标数据移到栈顶才行。
队列:与栈较为类似,但是它是两端操作,下端出,上端入;取数据操作只能从下端取出操作。
2.哈希表、堆、二叉查找树
哈希表:类似于字典结构,数据由键和值组成,键为数据的标识符,值为数据的内容。查询数据方式则是利用哈希函数查询以及链表查询。当添加数据时,发现目标内存已被其他数据占据的情况便为哈希冲突,解决方式有:1.链地址法,即将要增添的数据存与目标内存已占数据按链表形式存放。2.开放地址法,若目标内存地址被占,那么就存放在其他位置,或者其其他位置。
堆:是一种图的树形结构,用以实现优先队列。优先队列可以自由添加数据,但取出数据要从最小值开始按顺序取出。结点的排列顺序为从下到上,从左到右。在增添、取出数据时候,堆依然按之前的顺序重新排序,如按父结点值小于子节点值。时间复杂度为(logn)。
二叉查找树:采用图的树形结构,和堆不同的是二叉查找树有两个性质:1.每个节点的值均大于其左子树上任意一个节点的值。2.每个节点的值均小于其右子树上任意一个节点的值。时间复杂度为(logn)。
3. 常用基础排序算法(附实现代码)
冒泡排序:比较相邻的元素,如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序
代码实现:
#冒泡排序(时间复杂度(平均n^2,最好n),空间复杂度(1),稳定)
def bubble_sort(lis):
#计算传入的列表长度
len_lis = len(lis)
#做双层循环
for i in range(len_lis - 1):
#内层循环减去已经循环的值
for j in range((len_lis - 1) - i):
#此处大小决定是降序还是升序
if lis[j] < lis[j + 1]:
lis[j],lis[j + 1] = lis[j + 1],lis[j]
return lis
lis = [2,4,8,1,44,71,18]
print(bubble_sort(lis))
快速排序:快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
① 从数列中挑出一个元素,称为 “基准”(pivot);
② 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
③ 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
【算法】排序算法之快速排序
代码实现:
#快速排序(递归思想)(时间复杂度(平均nlogn,最好nlogn),空间复杂度(logn),不稳定)
def quicksort(lis):
#计算列表长度
len_lis = len(lis)
#如果列表长度小于2,则返回列表
if len_lis < 2:
return lis
#设置基准值为lis[0]
basevalue = lis[0]
#将小于基准值的元素放左侧
left_lis = []
# 将大于基准值的元素放右侧
right_lis = []
#对列表进行循环
for i in range(1,len_lis):
#判断值是否小于基准
if lis[i] < basevalue:
left_lis.append(lis[i])
else:
right_lis.append(lis[i])
#递归调用
return quicksort(left_lis) + [basevalue] + quicksort(right_lis)
lis = [1,6,3,9,12,5,10,4,7]
print(quicksort(lis))
归并排序:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤 3 直到某一指针达到序列尾;将另一序列剩下的所有元素直接复制到合并序列尾。
代码实现:
#归并排序
def mergesort(lis): #定义归并排序
len_lis = len(lis) #计算列表长度
if len_lis <= 1:
return lis
num = int(len_lis / 2) #二分列表排序
left_lis = mergesort(lis[:num]) #进行最小化二分列表排序
right_lis = mergesort(lis[num:])
return merge(left_lis, right_lis)
def merge(left_lis, right_lis): #对最大左右列表元素排序
l,r = 0, 0 #定义两个指针
result = [] #定义一个新的空间
while l < len(left_lis) and r < len(right_lis): #两个指针对应元素相比较
if left_lis[l] < right_lis[r]: #小的放左,大的放右,对应指针自加1
result.append(left_lis[l])
l += 1
else:
result.append(right_lis[r])
r += 1
result += left_lis[l:]
result += right_lis[r:]
return result
if __name__ == '__main__':
list = [1,4,2,6,5,11,8,9,3]
lis = mergesort(list)
print(lis)
堆排序:利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。
① 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
② 依次将根节点与待排序序列的最后一个元素交换
③ 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列
代码实现:
#堆排序
from collections import deque #利用deque列表结构追加辅助位
#直接交换堆顶元素和最后元素
def swap_param(L,i,j):
L[i], L[j] = L[j], L[i]
return L
#堆调整函数
def heap_adjust(L,start,end):
temp = L[start]
#
i = start
j = 2 * i
while j <= end:
if (j < end) and (L[j] < L[j + 1]): #保证j取到较大子树的坐标,因为左子树大于右子树,
j += 1 # 所以if不执行
if temp < L[j]: #将根节点和较大的子树的值进行交换
L[i] = L[j]
i = j
j = 2 * i
else:
break
L[i] = temp
def heap_sort(L):
#引入辅助空间,所以计算位数 -1
L_length = len(L) - 1
first_sort_count = int(L_length / 2)
#第一个循环,将序列调整为大根堆(head_adjust函数)
for i in range(first_sort_count):
heap_adjust(L, first_sort_count - i, L_length)
#第二个循环是将堆顶元素和堆末尾元素交换(swap_param函数),然后将剩下元素调整为一个大根堆函数
for i in range(L_length - 1):
L = swap_param(L, 1, L_length - i)
heap_adjust(L, 1, L_length - i - 1)
return [L[i] for i in range(1, len(L))]
def main():
L = deque([50, 16, 30, 10, 60, 90, 2, 80, 70])
L.appendleft(0)
print(heap_sort(L))
if __name__ == '__main__':
main()
堆排序递归实现:
#堆排序(递归思想)
def heap_sort(lis):
len_lis = len(lis)
#第一个循环,将列表构建出大顶堆
for i in range(len_lis):
#判断根节点与子节点大小关系,使根节点大于两个子节点
while i > 0 and lis[(i - 1) // 2] < lis[i]:
lis[(i - 1) // 2], lis[i] = lis[i], lis[(i - 1) // 2]
i = (i - 1) // 2
#第二个循环,将根节点与最下右子节点呼唤(当前二叉树最大值与最小值互换),
#除了这两个值,整个二叉树依然是大顶堆,然后再次进行第一个循环排序,以此找出最大值
for i in range(len_lis):
#交换首尾位置
lis[0], lis[len_lis - 1 - i] = lis[len_lis - 1 - i], lis[0]
#引入flag做判断
flag = 0
#判断一个非叶子节点与其节点大小关系并随需要互换位置
while 1:
x , y = flag * 2 + 1, flag * 2 + 2
#构建一个字典,轮询三个值的大小关系,使用递归算法轮询整个二叉树
nums = {lis[flag] : flag}
if x < len_lis - 1 - i:
nums[lis[x]] = x
if y < len_lis - 1 - i:
nums[lis[y]] = y
temp = max(nums.keys())
#将当前节点与较大值交换位置
if lis[flag] < temp:
lis[flag], lis[nums[temp]] = lis[nums[temp]], lis[flag]
flag = nums[temp]
else:
break
return lis
lis = [8,11,4,6,2,1,10,9,8]
print(heap_sort(lis))
4. 常用基础查找算法(附实现代码)
线性查找:线性查找也叫顺序查找,这是最基本的一种查找方法,从给定的值中进行搜索,从一端开始逐一检查每个元素,直到找到所需元素的过程。
代码实现:
#查找方法——线性查找(顺序查找)
def linear_search(lis, val):
for i in range(len(lis)):
if lis[i] == val:
isFound = True
print('已找到数值', val, '在第', i, '个位置')
return i
print('没有找到数值', val)
return -1
lis = [2, 5, 6, 12, 0, 3, 11]
val1 = linear_search(lis, 12)
val2 = linear_search(lis, 22)
print(val1)
print(val2
print(lis.index(12))
二分查找:
1.待查找的数组有序或者部分有序
2.要求时间复杂度低于O(n),或者直接要求时间复杂度为O(log n)
代码实现:
#二分法查找
#非递归实现
def bin_search(lis, val):
#二分排序法实现需要有序列表
start = 0; end = len(lis) - 1
while start <= end:
# 判断指针位置
mid = int((start + end) / 2)
if lis[mid] == val:
return mid
elif lis[mid] > val:
end = mid - 1
else:
start = mid + 1
return -1
lis = [2, 5, 11, 13, 15, 22, 34, 45]
index1 = bin_search(lis,34)
print(index1)
#递归实现二分法
def recurbin_search(lis, start, end, val):
mid = int((start + end) / 2)
if (len(lis) == 0 | val < lis[start] | val > lis[end]):
return -1
elif lis[mid] == val:
return mid
elif lis[mid] < val:
return recurbin_search(lis, mid+1, end, val)
else :
return recurbin_search(lis, start, mid-1, val)
lis = [1,3,5,7,8,11,13,23,34,56,78]
end = len(lis) - 1
index2 = recurbin_search(lis, 0, end, 13)
print(index2)