算法学习
-
算法
-
所谓的算法就是对问题进行处理且求解的一种实现思路或者思想。
-
案例: a+b+c = 1000 a2 + b2 = c**2 (a,b,c均为自然数),求出a,b,c可能的组合?
-
for a in range(0,1001): for b in range(0,1001): for c in range(0,1001): if a**2+b**2 == c**2 and a+b+c==1000: print(a,b,c)
-
for a in range(0,1001): for b in range(0,1001): c = 1000 - a - b if a**2+b**2 == c**2 and a+b+c==1000: print(a,b,c)
-
如何取衡量一个算法性能的好坏优劣?
- 计算算法执行的耗时
- 量化算法执行消耗计算机的资源大小
- 时间复杂度(推荐)
-
时间复杂度
-
量化执法执行步骤的数量。
-
案例:
-
1 a=5 2 b=6 3 c=10 4 for i in range(n): 5 for j in range(n): 6 x = i * i 7 y = j * j 8 z = i * j 9 for k in range(n): 10 w = a*k + 45 11 v = b*b 12 d = 33
-
执行步骤:4+2n+3n**2
-
时间复杂度的表示方法:大O记法
-
将执行步骤数量表示的表达式中最有意义的一项取出,放置在大O后面的括号即可。
-
4+2n+3n**2最有意义的一项是 n**2,大O记法:O(n**2)
-
常见的时间复杂度: O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
-
-
-
-
-
数据结构
-
对组织方式就被称作为数据结构 。或者,认为所有不同形式的数据结构都可以表示一种容器,容器是用来装载数据。
-
案例: 需要存储一些学生的学生信息(name,score),那么这些数据应该如何组织呢?查询某一个具体学生的时间复杂度是什么呢?
-
#方式1 [{'name':'zhangsan','score':100}, {'name':'lisi','score':99} ] #方式2 [('zhangsan',100), ('lisi',99) ] #方式3 {'zhangsan':{'score':100}, 'lisi':{'score':99} }
-
需求:对指定学生的成绩进行查询?上述的数据结构那种形式是最好的?
- 方式3最好,因为时间复杂度为O(1),剩下两个为O(n)
-
算法和数据结构之间的关系?
- 因此认为算法是为了解决实际问题而设计的,数据结构是算法需要处理问题的载体 。
-
-
栈:先进后出
- 栈顶,栈底
- 元素的添加和提取都是从栈顶向栈底的方向进行。
-
案例:浏览器的回退按钮。
-
实现:
-
class Stack(): def __init__(self): self.items = [] #构建一个空栈 def push(self,item): #添加元素 self.items.append(item) def pop(self):#取出元素 return self.items.pop() def isEmpty(self): return self.items == [] def length(self): return len(self.items) s = Stack() s.push(1) s.push(2) s.push(3) for i in range(s.length()): print(s.pop())
-
队列:先进先出
- 队头,队尾
- 元素必须是从队尾向队头的方向进行添加,且必须从队头以此取出元素。
-
案例:一个教室有10台电脑和一台打印机联网。电脑给打印机发送的打印任务必须放置在队列中。
-
队列的实现:
-
class Queue(): def __init__(self): self.items = [] def enqueue(self,item):#添加元素 self.items.insert(0,item) def dequeue(self):#取出元素 return self.items.pop() def isEmpty(self): return self.items == []
-
面试题:烫手的山芋
-
6个孩子围城一个圈,排列顺序孩子们自己指定。第一个孩子手里有一个烫手的山芋,需要在计时器计时1秒后将山芋传递给下一个孩子,依次类推。规则是,在计时器每计时7秒时,手里有山芋的孩子退出游戏。该游戏直到剩下一个孩子时结束,最后剩下的孩子获胜。请使用队列实现该游戏策略,排在第几个位置最终会获胜。
-
找已知的条件:
- 把圈屡直了就是一个队列的结构
- 只可以从队头取元素,从队尾添加元素
- 山芋是1s中向后传递一次
- 一轮游戏是7s的时长,一轮游戏山芋需要被传递6次
- 当队列中只剩下一个孩子的时候游戏结束
- 核心:必须要让手中有山芋的孩子作为队头元素!
- 如何实现:山芋不动,人动
- 把圈屡直了就是一个队列的结构
-
class Queue(): def __init__(self): self.items = [] def enqueue(self,item):#添加元素 self.items.insert(0,item) def dequeue(self):#取出元素 return self.items.pop() def isEmpty(self): return self.items == [] def length(self): return len(self.items) kids = ['A','B','C','D','E','F'] queue = Queue() for kid in kids:#将孩子入队列 queue.enqueue(kid) while queue.length() > 1: #多轮游戏进行时 #实现一轮游戏策略 for i in range(6): #一轮游戏山芋被传递的次数 item = queue.dequeue() queue.enqueue(item) queue.dequeue() #一轮游戏后将队头元素淘汰 print('最终的获胜者是:',queue.dequeue())
-
如何使用两个队列实现一个栈?(使用两个先进先出的数据结构实现一种先进后出的效果)
-
q1 = Queue() q2 = Queue() #数据存储到q1中 alist = [1,2,3,4,5] for item in alist: q1.enqueue(item) while q1.length() >= 1: for i in range(q1.length()-1): item = q1.dequeue() q2.enqueue(item) print(q1.dequeue()) q1,q2 = q2,q1
-
-
链表
-
计算机本质作用是什么?
- 存储和运算二进制的数据!
-
变量表示的是什么?
-
面向对象的语言中所有的变量都是引用。
-
a = 10表示什么?
- 在计算机的内存中开启一块内存空间,然后将10这个数据存储到该块内存空间中。a就是用来引用这个块内存空间的。通俗理解,所谓的变量或者引用表示的就是计算机内存中某一块内存空间。
- 在计算机中开辟的每一块内存空间都拥有两个默认的属性:
- 大小:决定可以存储最大的数据范围是什么
- bit位:只能存放以为二进制的数。最大可以存储的数据是1,最小是0
- byte字节:8bit。
- 地址:用来让cpu寻址
- 大小:决定可以存储最大的数据范围是什么
- 指向:如果一个变量引用了某一块内存空间,则该变量指向该块内存空间。
-
计算机在为数据开辟内存的时候,内存空间大小是根据数据实际值的大小开辟呢还是统一大小开辟呢?
- 统一开辟固定大小的内存
- 整型数据:4字节
- 字符串:一个字符一个字节
- 小数:4、8字节
- 统一开辟固定大小的内存
-
为什么要有链表数据结构呢?
- 顺序表:内存开辟是连续
- 集合中存储的元素是有顺序的。顺序表的结构可以分为两种形式:单数据类型和多数据类型。
- 弊端: 顺序表的结构需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁。
- 顺序表:内存开辟是连续
-
实现:
-
class Node(): #构建一个节点 def __init__(self,item): self.item = item self.next = None class Link(): def __init__(self): #构建空链表 self.head = None def add(self,item):#向链表头部插入节点:类比于列表的insert(0,item) node = Node(item) node.next = self.head self.head = node def travle(self): # print(self.head.item) # print(self.head.next.item) # print(self.head.next.next.item) cur = self.head #head要永远指向头结点 while cur: print(cur.item) cur = cur.next def isEmpty(self): return self.head == None def size(self): cur = self.head count = 0 while cur: count += 1 cur = cur.next return count def append(self,item): #向链表尾部添加新的节点 node = Node(item) if self.isEmpty():#链表为空 self.head = node return #链表为非空 pre = None #pre永远指向cur前一个节点 cur = self.head while cur: pre = cur cur = cur.next pre.next = node def insert(self,pos,item):#向pos表示的指定位置添加节点 if pos == 0: self.add(item) return node = Node(item) #pos的位置的值刚好可以作为pre和cur向后偏移次数的值 pre = None cur = self.head for i in range(pos): pre = cur cur = cur.next pre.next = node node.next = cur def remove(self,item): #删除item表示的节点 pre = None cur = self.head #如果删除的是第一个节点 if item == self.head.item: self.head = self.head.next return while cur: pre = cur cur = cur.next if cur: if cur.item == item: pre.next = cur.next return
-
-
-
-
链表节点倒置:
-
def reverse(self): pre = None cur = self.head next_node = cur.next while cur: cur.next = pre pre = cur cur = next_node if cur: next_node = next_node.next self.head = pre
二叉树
- 全局根节点:树状结构中最上层的那一个节点
- 左右叶子节点:左右分支对应的节点
- 子树:
- 完整的子树
- 根节点和左右叶子节点组成的
- 非完整的子树
- 根节点+左叶子节点
- 根节点+右叶子节点
- 根节点
- 如何区分不同的子树?
- 根据根节点可以区分不同的子树
- 一个子树的子节点可以作为另一颗子树的根节点
- 根据根节点可以区分不同的子树
- 完整的子树
class Node():
def __init__(self,item):
self.item = item
self.left = None
self.right = None
class Tree():
def __init__(self):
self.root = None
def insertNode(self,item):
node = Node(item)
#树为空的情况
if self.root == None:
self.root = node
return
#树为非空的情况
cur = self.root
queue = [cur]
while queue:
pop_node = queue.pop(0)
if pop_node.left != None:
queue.append(pop_node.left)
else:
pop_node.left = node
break
if pop_node.right != None:
queue.append(pop_node.right)
else:
pop_node.right = node
break
def travel(self):
cur = self.root
queue = [cur]
while queue:
pop_node = queue.pop(0)
print(pop_node.item)
if pop_node.left != None:
queue.append(pop_node.left)
if pop_node.right != None:
queue.append(pop_node.right)
def findNode(self,item):
find = False
cur = self.root
queue = [cur]
while queue:
pop_node = queue.pop(0)
if pop_node.item == item:
find = True
break
else:
if pop_node.left != None:
queue.append(pop_node.left)
if pop_node.right != None:
queue.append(pop_node.right)
return find
def changeValueOfNode(self,item,new_value):
cur = self.root
queue = [cur]
while queue:
pop_node = queue.pop(0)
if pop_node.item == item:
pop_node.item = new_value
break
else:
if pop_node.left != None:
queue.append(pop_node.left)
if pop_node.right != None:
queue.append(pop_node.right)
-
二叉树的遍历方式:
-
广度遍历:逐层遍历
-
深度遍历:竖直方向的遍历。让前中后序作用在每一颗子树中返回的遍历结果。
- 前序:根左右
- 中序:左根右
- 后序:左右根
-
def forward(self,root): #root就是不同子树的根节点 if root == None: return print(root.item) self.forward(root.left) self.forward(root.right) def middle(self,root): if root == None: return self.middle(root.left) print(root.item) self.middle(root.right) def back(self,root): if root == None: return self.back(root.left) self.back(root.right) print(root.item)
-
排序二叉树
-
插入节点以此根节点进行大小比较:
- 原则:比根节点小插入到根节点左侧,否则插入到根节点右侧
-
class Node(): def __init__(self,item): self.item = item self.left = None self.right = None class SortTree(): def __init__(self): self.root = None def addNode(self,item): node = Node(item) if self.root == None: self.root = node return cur = self.root while True: if cur.item < item: #插入节点大于根节点,往根节点右侧插入 #判断根节点的右侧子节点位置是否为空 if cur.right == None: cur.right = node break else: cur = cur.right else:#插入节点的值小于等于根节点,往根节点左侧插入 if cur.left == None: cur.left = node break else: cur = cur.left def middle(self,root): if root == None: return self.middle(root.left) print(root.item) self.middle(root.right) tree = SortTree() alist = [3,8,5,7,6,2,1] for item in alist: tree.addNode(item) tree.middle(tree.root)
二分查找
-
前提:进行查找的序列必须是有序。
-
def b_find(alist,item):#在alist列表中查找item是否存在 find = False left = 0 #序列第一个元素下标 right = len(alist)-1 #序列最后一个元素下标 while left <= right: mid = (left+right)//2 #序列中间元素下标 if alist[mid] > item: #中间元素大于要查找的元素,则要查找的元素可能在中间元素左侧 right = mid - 1 elif alist[mid] < item:#查找的元素大于中间元素,则查找的元素在中间元素右侧 left = mid + 1 else:#查找的元素等于中间元素 find = True break return find alist = [1,2,3,4,5,6,7] print(b_find(alist,7))
-
排序算法
-
冒泡排序
-
将乱序序列两两比较,逐步将最大值移动到序列最后位置
-
def sort(alist): for i in range(len(alist)-1):#控制两两元素比较的次数 if alist[i] > alist[i+1]: alist[i],alist[i+1] = alist[i+1],alist[i] return alist
-
-
将上述操作重复作用在其他乱序的部分,直到整个序列变为有序序列
-
def sort(alist): for j in range(len(alist)-1): for i in range(len(alist)-1-j):#控制两两元素比较的次数 if alist[i] > alist[i+1]: alist[i],alist[i+1] = alist[i+1],alist[i] return alist
-
-
-
选择排序
-
直接将乱序序列的最大值找出,和序列中最后一个元素交换位置
-
def sort(alist): max_index = 0 #保存序列中最大元素的下标 for i in range(len(alist)-1): if alist[i+1] > alist[max_index]: max_index = i+1 alist[max_index],alist[len(alist)-1] = alist[len(alist)-1],alist[max_index] return alist alist = [3,8,5,7,6,2,1] print(sort(alist))
-
-
将上述操作重复作用在剩下乱序序列中
-
def sort(alist): for j in range(len(alist)-1): max_index = 0 #保存序列中最大元素的下标 for i in range(len(alist)-1-j): if alist[i+1] > alist[max_index]: max_index = i+1 alist[max_index],alist[len(alist)-1-j] = alist[len(alist)-1-j],alist[max_index] return alist
-
-
-
插入排序
-
将乱序序列分为两部分:
- 有序部分:默认情况序列中的第一个元素
- 无序部分:默认情况下序列中最后n-1个元素
- 核心:将无序部分的每一个元素以此插入到有序部分合适的位置
-
[3, 8,5,7,6] [3,8, 5,7,6] [3,5,8, 7,6] [3,5,7,8, 6] [3,5,6,7,8 ]
-
设计一个变量i,该变量表示有序部分元素的个数,i的初始值一定是1.
-
i = 1时,对应的插入情况的实现:
-
#i = 1的情况 #alist[i-1]:有序部分最后一个元素 #alist[i]:无序部分的第一个元素 if alist[i] < alist[i-1]: alist[i],alist[i-1] = alist[i-1],alist[i]
-
-
i = 2或者i>=2的情况
-
#i = 2的情况 #alist[i-1]:有序部分最后一个元素 #alist[i]:无序部分的第一个元素 while i >= 1: if alist[i] < alist[i-1]: alist[i],alist[i-1] = alist[i-1],alist[i] i -= 1 else: break
-
-
完整代码:
-
def sort(alist): for i in range(1,len(alist)): while i >= 1: if alist[i] < alist[i - 1]: alist[i], alist[i - 1] = alist[i - 1], alist[i] i -= 1 else: break return alist alist = [3,8,5,7,6,2,1] print(sort(alist))
-
-
希尔排序
- 增量(gap)
- 初始值:乱序序列元素个数整除2
- 作用:
- gap的值表示的是分组后的组数
- gap可以表示每组数据之间的间隔
- 理解:插入排序就是增量为1的希尔排序
- 增量(gap)
-
#在插入排序的基础之上加入增量的概念 def sort(alist): gap = len(alist) // 2 for i in range(gap,len(alist)): while i >= gap: if alist[i] < alist[i - gap]: alist[i], alist[i - gap] = alist[i - gap], alist[i] i -= gap else: break return alist
-
完整:加入增量缩减的操作
-
def sort(alist): gap = len(alist) // 2 while gap >= 1: for i in range(gap,len(alist)): while i >= gap: if alist[i] < alist[i - gap]: alist[i], alist[i - gap] = alist[i - gap], alist[i] i -= gap else: break gap //= 2 return alist alist = [3,8,5,7,6,2,1] print(sort(alist))
-
-
快速排序
-
定义概念:基数
-
作用:
-
初始值:序列中第一个元素
-
最终实现的效果:不断的移动元素,实现将序列中比基数小的元素移动到基数左侧,否则移动到基数右侧
-
def sort(alist): low = 0 high = len(alist)-1 mid = alist[low] while low < high: #让high向左移动 while low < high: if mid < alist[high]: high -= 1 else: alist[low] = alist[high] break #让low向右移动 while low < high: if alist[low] < mid: low += 1 else: alist[high] = alist[low] break if low == high: alist[low] = mid return alist
-
完整操作:
-
def sort(alist,start,end): low = start high = end #结束递归的条件 if low > high: return mid = alist[low] while low < high: #让high向左移动 while low < high: if mid < alist[high]: high -= 1 else: alist[low] = alist[high] break #让low向右移动 while low < high: if alist[low] < mid: low += 1 else: alist[high] = alist[low] break if low == high: alist[low] = mid #递归将上述核心操作作用到基数左侧的子序列中 sort(alist,start,low-1) #递归将上述核心操作作用到基数右侧子序列中 sort(alist,high+1,end) return alist
-
-