数据结构:列表 字典 元组 数组 链表 树
算法:在数据结构基础上执行的操作(增删改查)
一.算法
什么是算法?
一个计算过程,解决问题的方法
输入->算法->输出
时间复杂度:用来评估算法运行效率的一个东西
一般来说,时间复杂度高的算法比复杂度低的算法慢
常见的时间复杂度(按效率排序)
O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^2logn)<O(n^3)
如何一眼判断时间复杂度?
循环减半的过程 -> O(logn)
几次循环就是n的几次方的复杂度
列表查找
列表查找:从列表中查找指定元素
输入:列表、待查找的元素
输出:元素下标或未查找到元素
1.顺序查找
从列表第一个元素开始,顺序进行搜索,直到找到为止
def linear_search(li,value):
for i in li:
if li[i] == value:
return i
return
时间复杂度:O(n)
2.二分查找
从有序列表的候选区开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半
def bin_search(li,value):
low = 0
high = len(li) - 1
while low <= high:
mid = (low + high) // 2
if li[mid] == value:
return mid
elif li[mid] > value:
high = mid - 1
else:
low = mid + 1
li = [1,2,3,4,5,6,7,8]
print(bin_search(li,3))
时间复杂度:O(logn)
列表排序
列表排序:将无序列表变为有序列表
应用场景:各种榜单,各种表格,给二分排序用,给其他算法用
输入:无序列表 输出:有序列表
升序与降序
1.冒泡排序
#冒泡排序
#思路:列表每两个相邻的数,如果前边的比后边的大,那么交换这两个数的位置
#代码关键点:趟 无序区
def bubble_sort(li):
for i in range(len(li)-1):
for j in range(len(li)-i-1):
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
li = [2,4,5,6,7,3,8,9,1]
bubble_sort(li)
print(li)
时间复杂度:O(n^2)
空间复杂度:1
2.选择排序
#选择排序
#思路:一趟遍历记录最小的数,放到第一个位置,再一趟遍历记录剩余列表中最小的数,继续放置
#代码关键点:无序区 最小数的位置
def select_sort(li):
for i in range(len(li)-1):
min_loc = i
for j in range(i+1,len(li)):
if li[j] < li[min_loc]:
li[j],li[min_loc] = li[min_loc],li[j]
li = [2,4,6,5,7,3,8,9,1]
select_sort(li)
print(li)
时间复杂度:O(n^2)
空间复杂度:1
3.插入排序
#插入排序
#思路:列表分为有序区和无序区两个部分.最初有序区只有一个元素.每次从无序区中选择一个元素,插到有序区的位置,直到无序区变空.
#代码关键点:摸到的牌 手里的牌
def insert_sort(li):
for i in range(1,len(li)):
tmp = li[i]
j = i - 1
while j >= 0 and tmp < li[j]:
li[j+1] = li[j]
j = j - 1
li[j+1] = tmp
li = [2,4,6,5,7,3,8,9,1]
insert_sort(li)
print(li)
时间复杂度:O(n^2)
空间复杂度:1
4.快速排序
好写的排序算法里最快的
快的排序算法里最好写的
思路:
1.取一个元素p(第一个元素),使元素p归位;
2.列表被p分为两部分,左边都比p小,右边都比p大;
3.递归完成排序
算法关键点:1.整理 2.递归
#快速排序
def partition(li,left,right):
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp:
right -= 1
li[left] = li[right]
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left]
li[left] = tmp
return left
def quit_sort(li,left,right):
if left < right:
mid = partition(li,left,right)
quit_sort(li,left,mid - 1)
quit_sort(li,mid + 1,right)
li = [2,4,6,5,7,3,8,9,1]
quit_sort(li,0,len(li)-1)
print(li)
时间复杂度:O(nlogn)
空间复杂度:1
5.归并排序
分解:将列表越分越小,直至分成一个元素
一个元素是有序的
合并:将两个有序列表归并,列表越来越大
#归并排序
def merge(li,low,mid,high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
li[low:high + 1] = ltmp
def mergesort(li,low,high):
if low < high:
mid = (low + high) // 2
mergesort(li,low,mid - 1)
mergesort(li,mid + 1,high)
merge(li,low,mid,high)
li = [2,4,6,5,7,3,8,9,1]
mergesort(li,0,len(li)-1)
print(li)
时间复杂度:O(nlogn)
空间复杂度:n
6.希尔排序
希尔排序是一种分组插入排序算法
希尔排序每趟并不是使某些元素有序,而是使整体数据越来越接近有序,最后一趟排序使得所有数据有序
时间复杂度:O(1,3T)
7.桶排序
-计数排序 :创建一个列表用来统计每个数出现的次数
def count_sort(li,max_num):
count = [0 for i in range(max_num + 1)]
for num in li:
count[num] += 1
li.clear()
for index,val in enumerate(count):
for i in range(val):
li.append(index)
思想
Brute Force(暴力破解 枚举)
分治(分而治之)
动态规划
贪心算法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方案 案例:分糖果
二.数据结构
1.线性结构
数组(列表) 和 链表
区别:数组需要一块连续的内存空间来储存,对内存的要求比较高。如果我们申请一个 100MB 大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。 而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题.
应用
栈:
一种实现'先进后出'的存储结构
只能操作栈顶的元素
栈的分类:
静态栈:静态栈的核心是数组.类似于一个连续内存的数组,我们只能操作其栈顶的元素
动态栈:动态栈的核心是链表
栈的应用:
①函数的调用:
def f():
print('FFF')
g()
def g():
print('GGG')
k()
def k():
print('KKK')
if __name__ == "__main__":
f()
当有多个函数调用时,按照“先调用后返回”的原则,函数之间的信息传递和控制转移必须借助栈来实现,即系统将整个程序运行时所需要的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放他的存储区,即进行出栈操作,当前运行的函数永远在栈顶的位置
②浏览器的前进或后退:有两个栈
③表达式的求值:例如:4+5-2*3 有两个栈.一个是数字栈.一个是运算符栈,循环,将数字放入数字栈,将运算符放入运算符栈,放入运算符时要判断运算符的优先级,当此时运算符的优先级小于或等于栈顶的运算符优先级,将其放到栈顶,当此时运算符的优先级大于栈顶的运算符优先级,我们不会将其加入,而是将此时数字栈顶的数字弹出,与此运算符后的数字进行运算,然后将得到的值放入数字栈的栈顶.
队列:
一种可以实现“先进先出”的数据结构
队列的分类:链式队列 静态队列
生产者消费者模型不是python独有的,而是操作系统中提出的一种思想和概念,可以通过队列实现
①连续存储(数组)
数组,在其python语言中称为列表,是一种基本的数据结构类型
优点:存取速度快
缺点:事先需要知道数组的长度
需要大块的连续内存
插入删除非常的慢,效率极低
时间复杂度:插入删除:O(n) 随机访问:O(1)
②离散存储(链表)
时间复杂度:插入删除:O(1) 随机访问:O(n)
定义:
n个节点离散分配
彼此通过指针相连
每个节点只有一个前驱节点,每个节点只有一个后续节点
首节点没有前驱节点,尾节点没有后续节点
优点:空间没有限制,插入删除数据很快
缺点:查询比较慢
专业术语:
首节点:第一个有效节点
尾节点:最后一个有效节点
头节点:第一个有效节点之前的那个节点,头节点并不存储任何数据,目的是为了方便对链表的操作
头指针:指向头节点的指针变量
尾指针:指向尾节点的指针变量
链表的分类:
单链表
双链表
循环链表
非循环链表
单链表
#单链表
class Hero():
def __init__(self,number = None, name = None, nickname = None, pNext = None):
self.number = number
self.name = name
self.nickname = nickname
self.pNext = pNext
#增加
def addHero(head,hero):
cur = head #指针
while cur.pNext != None:
if cur.pNext.number > hero.number:
break
cur = cur.pNext
hero.pNext = cur.pNext
cur.pNext = hero
#查找
def get_all(head):
cur = head
while cur.pNext != None:
cur = cur.pNext
print('编号是:%s,名字是:%s,外号是:%s'%(cur.number,cur.name,cur.nickname))
#删除
def delete(head,hero):
cur = head
while cur.pNext != None:
cur = cur.pNext
if cur.pNext == hero:
cur.pNext = cur.pNext.pNext
break
#改
def change(head,hero):
cur = head
while cur.pNext != None:
cur = cur.pNext
if cur.pNext.number == hero.number:
cur.pNext = hero
break
head = Hero()
h1 = Hero(1,'宋江','及时雨') #h1其实是一个地址
addHero(head,h1)
h2 = Hero(2,'卢俊义','玉麒麟')
addHero(head,h2)
h3 = Hero(3,'吴用','智多星')
addHero(head,h3)
h5 = Hero(5,'大刀','关胜')
addHero(head,h5)
h4 = Hero(4,'公孙胜','入云龙')
addHero(head,h4)
get_all(head)
2.非线性结构
递归的应用
树和森林就是以递归的方式定义的
树和图的算法就是以递归的方式实现的
很多数学公式就是以递归的方式实现的(斐波那楔序列)
n!
def f(n):
if 1 == n:
return 1
else:
return f(n-1)*n
1+2+3...+n
def sum(n):
if 1 == n:
return n
else:
return sum(n-1) + n
①树
树的专业术语:
节点、父节点、子节点:没有节点的节点、子孙、堂兄弟、兄弟、深度:从根节点到最底层节点的层数被称为深度,根节点是第一层、叶子节点:没有子节点的节点、度:子节点的个数
树的分类:
一般树:任意一个节点的子节点的个数不受限制
二叉数:定义:任意一个节点的子节点的个数最多是两个,且子节点的位置不可更改
满二叉树:定义:在不增加层数的前提下,无法再多添加一个节点的二叉树
完全二叉树:只是删除了满二叉树最底层最右边连续若干个节点
一般二叉树
森林:n个互不相交的数的集合
二叉树集体的操作
A
B C
D E F
1.二叉树的先序遍历(先访问根节点)
-先访问根节点,再先序遍历左子树,再先序遍历右子数
-A B D E C F
2.二叉树中序遍历(中间访问根节点)
-先中序遍历左子树,再访问根节点,再中序遍历右子数
-D B E A F C
3.二叉树后序遍历(最后访问根节点)
-先后续遍历左子树,再后续遍历右子数,再访问根节点
-D E B F C A
树的应用:
树是数据库中数据组织的一种重要形式
操作系统子父进程的关系本身就是一颗树
面向对象语言中类的继承关系
②图