数据结构:数据(基本类型(int,float,char))的组织方式
算法复杂度
时间复杂度:用来估计算法运行时间的一个单位;O(n)、O(1) 常见于for循环, 或者log(n)—常见于while循环。循环减半时复杂度为log(n)
常见时间复杂度排序 O(1) < O(logn)< O(n) < O(n^log n) < O(n^2) < O(n^2 log n ) < O(n^3)
timeit模块,测试python代码的执行速度;
空间复杂度:估算算法内存占用大小的单位;"空间换时间"; O(1),O(n)
算法:完成算法基础模块后,算法优化是关键点(方法:查找算法模块中重复或者无效计算,用内层加判断替换)
递归算法:调用自身,结束条件 --- 汉诺塔,斐波那契数列
列表查找:顺序查找(for循环),二分查找(数据候选区二分data[:0],查找范围缩减一半)
排序l初级:冒泡、选择、插入;------- 思想:数据分区(有序区/无序区,已查找/未查找),数据比较,数据换位
排序高级:快速排序、堆排序、归并排序
python语言使用的内置排序方法是timsort,原理基于归并排序
#冒泡排序:列表中每相邻的两个数,如果前面的比后面的大,则互相替换位置
def bubble_sort(li):
for i in range(len(li)-1): # i 表示第N趟,一共n或者n-1趟
exchange = False
for j in range(len(li) - i - 1): #第i趟,无序区【0,n-i-1】 j 表示箭头 0- n-i-2
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
exchange = True
if not exchange:
break
#选择排序:选择最小的数排在第一的位置
def select_sort(li):
for i in range(len(li)-1): #n 或者n-1 趟
min_pos = i #第i趟无序区范围 i~最后 #min_pos 更新为无序区最小值位置
for j in range(i+1,len(li)):
if li[j] < li[min_pos]:
min_pos = j
li[i],li[min_pos] = li[min_pos],li[i]
#插入排序:挪位置,不断地将尚未排好序的数插入到已经排好序的部分。
def insert_sort(li):
for i in range(1,len(li)): #i表示摸到的牌的下标
tmp = li[i] #摸到的牌
j = i - 1 #排好序的最后一张牌的位置
while j >= 0 and li[j] > tmp: #只要往后挪就循环, 2个条件都得到满足
li[j+1] = li[j] #如果 j = -1 停止挪,如果li[j] 小了,停止挪
j -= 1 #j位置在循环结束时要么是 -1,要么是一个比tmp小的值
li[j+1] = tmp
位运算: & | - ^ ~
快速排序:取一个元素P,列表被P分成两部分,左边比P小,右边比P大,递归完成排序
import sys
sys.setrecursionlimit(5000) #设置递归深度
def quick_sort(li):
_quick_sort(li,0,len(li)-1)
def _quick_sort(li,left,right): if left < right: mid = partition(li,left,right) _quick_sort(li,left,mid-1) _quick_sort(li,mid+1,right) 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 merge(li,low,mid,high): i = low j = mid +1 li_tmp = [] while i <= mid and j <= high: if li[i] <= li[j]: li_tmp.append(li[i]) i += 1 else: li_tmp.append(li[j]) j += 1 while i <= mid: li_tmp.append(li[i]) i += 1 while j <= high: li_tmp.append(li[i]) j += 1 for i in range(low,high+1): li[i] = li_tmp[i-low] def merge2list(li1,li2): li = [] i = 0 j = 0 while i < len(li1) and j < len(li2): if li1[i] <= li2[j]: li.append(li1[i]) i += 1 else: li.append(li2[j]) j += 1 while i < len(li1): li.append(li1[i]) i += 1 while j < len(li2): li.append(li2[j]) j += 1 return li
#先递归,后归并
def merge_sort(li,low,high):
if low < high:
mid = (low + high)//2 #先分解
merge_sort(li,low,mid)
merge_sort(li,mid+1,high)
merge(li,low,mid,high) #归并合并
li = [10,4,6,3,8,2,5,7]
merge_sort(li,0,len(li)-1)
print(li)
堆排序前传———树与二叉树
树是一种数据结构,是一种可以递归定义的数据结构,是有n个节点组成的集合
树的概念:根节点、叶子节点、深度、孩子节点/父节点,子树
def sift(data,low,high): i = low # i 最开始指向根节点 j = 2*i + 1 # j 开始是左孩子 tmp = data[i] # 把堆顶存起来 while j <= high: #只要j位置有数 if j <high and data[j] <data [j+1]: #如果右孩子有且比较大 j += 1 # j指向右孩子 if tmp < data[j]: data[i] = data[j] i = j # 往下看一层 j = 2*i +1 else: #tmp更大,把tmp放在i的位置上 data[i] = tmp #把tmp放在某一级领导位置上 break data [i] = tmp # 把tmp放在叶子节点上 def heap_sort(data): n = len(data) for i in range(n//2 -1,-1,-1): # i 表示建堆时调整部分根的下标 sift(data,i,n-1) #建堆完成 for i in range(n -1,-1,-1): #i指向当前堆最后一个元素 data[0],data[i] = data [i],data[0] sift(data,0,i-1) # i-1是新的high # python内置堆模块 import heapq import random li = list(range(20)) random.shuffle(li) heapq.heapify(li) #建堆 heapq.heappop(li) #弹出最小值 n = len(li) for i in range(n): print(heapq.heappop(li),end = ',')
希尔排序:
def insert_sort(li,d): for i in range(d,len(li)): tmp = li[i] j = i - d while j >= 0 and li[j]> tmp: li[j+d] = li[j] j -= d li[j+d] = tmp def shell_sort(li): d = len(li)//2 while d > 0: insert_sort(li,d) d = d//2 li = list(range(1000)) random.randint(li) insert_sort(li)
计数排序
def count_sort(li,max_num = 100): count = [0 for _ in range(max_num +1)] for val in li: count[val] += 1 li.clear() for i,v in enumerate(count): for _ in range(v): li.append(i)
栈:数据集合,只能在一端进行插入或删除操作的列表,特点是后进先出(last-in,first_out)。栈的基本操作:进栈push,出栈pop,取栈顶gettop。
队列:数据集合,仅允许在列表一端进行插入,另一端进行删除。插入的一端是队尾,删除的称为队头。先进先出。
迷宫找路
dirs = [ lambda x,y:(x-1,y) #上 lambda x,y:(x,y+1) #右 lambda x,y:(x+1,y) #下 lambda x,y:(x,y-1) #左 ] def solve_maze(x1,y1,x2,y2): stack = [] stack.append((x1,y1)) maze[x1][y1] = 2 while len(stack) > 0: # 当栈不空时循环 cur_node = stack[-1] if cur_node == (x2,y2): #如果到终点了 print(stack) return True for d in dirs: next_node = d(*cur_node) if maze[next_node[0]][next_node[1]] ==0: stack.append(next_node) maze[next_node[0]][next_node[1]] = 2 # 2表示已经走过的点 break else: stack.pop() else: print('无路') return False
- 冒泡排序和插入排序是最基础的,面试官有时候喜欢拿它们来考察你的基础知识,并且看看你能不能快速地写出没有 bug 的代码。
- 归并排序、快速排序和拓扑排序的思想是解决绝大部分涉及排序问题的关键,我们将在这节课里重点介绍它们。
- 堆排序和桶排序,本节课不作深入研究,但有时间的话一定要看看,尤其是桶排序,在一定的场合中(例如知道所有元素出现的范围时),能在线性的时间复杂度里解决战斗
冒泡排序(Bubble Sort)
基本思想
给定一个数组,我们把数组里的元素通通倒入到水池中,这些元素将通过相互之间的比较,按照大小顺序一个一个地像气泡一样浮出水面。
实现
每一轮,从杂乱无章的数组头部开始,每两个元素比较大小并进行交换,直到这一轮当中最大或最小的元素被放置在数组的尾部,然后不断地重复这个过程,直到所有元素都排好位置。其中,核心操作就是元素相互比较。
例题分析
给定数组 [2, 1, 7, 9, 5, 8],要求按照从左到右、从小到大的顺序进行排序。
解题思路
从左到右依次冒泡,把较大的数往右边挪动即可。空间复杂度是 O(1)
插入排序(Insertion Sort)
基本思想
不断地将尚未排好序的数插入到已经排好序的部分。空间复杂度是 O(1),时间复杂度是 O(n2)
特点
在冒泡排序中,经过每一轮的排序处理后,数组后端的数是排好序的;而对于插入排序来说,经过每一轮的排序处理后,数组前端的数都是排好序的。
例题分析
对数组 [2, 1, 7, 9, 5, 8] 进行插入排序。
解题思路
首先将数组分成左右两个部分,左边是已经排好序的部分,右边是还没有排好序的部分,刚开始,左边已排好序的部分只有第一个元素 2。接下来,我们对右边的元素一个一个进行处理,将它们放到左边。
归并排序(Merge Sort)
基本思想
核心是分治,就是把一个复杂的问题分成两个或多个相同或相似的子问题,然后把子问题分成更小的子问题,直到子问题可以简单的直接求解,最原问题的解就是子问题解的合并。归并排序将分治的思想体现得淋漓尽致。空间复杂度就是 O(n)。归并排序也是稳定的排序算法
实现
一开始先把数组从中间划分成两个子数组,一直递归地把子数组划分成更小的子数组,直到子数组里面只有一个元素,才开始排序。
排序的方法就是按照大小顺序合并两个元素,接着依次按照递归的返回顺序,不断地合并排好序的子数组,直到最后把整个数组的顺序排好。
快速排序(Quick Sort)
基本思想
快速排序也采用了分治的思想。
实现
把原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组。
拓扑排序(Topological Sort)
基本思想
和前面介绍的几种排序不同,拓扑排序应用的场合不再是一个简单的数组,而是研究图论里面顶点和顶点连线之间的性质。拓扑排序就是要将这些顶点按照相连的性质进行排序。
要能实现拓扑排序,得有几个前提:
图必须是有向图、图里面没有环,拓扑排序一般用来理清具有依赖关系的任务。
- 将问题用一个有向无环图(DAG, Directed Acyclic Graph)进行抽象表达,定义出哪些是图的顶点,顶点之间如何互相关联。
- 可以利用广度优先搜索或深度优先搜索来进行拓扑排序。
相关资源: 数据结构与算法