数据结构与算法–Python版本
算法
递归
汉诺塔问题
def hanoi(n, a, b, c):
if n>0:
hanoi(n-1, a, c, b)
print("moving from %s to %s" % (a, c))
hanoi(n-1, b, a, c)
列表查找
顺序查找
顺序查找:也叫线性查找,从列表第⼀个元素开始,顺序进⾏搜索,直到找到元素或搜索到列表最后⼀个元素为⽌。
时间复杂度:O(n)
def linear_search(li, val):
for ind, v in enumerate(li):
if v == val:
return ind
else:
return None
二分查找
⼜叫折半查找,从有序列表的初始候选区li[0:n]开始,通过对待查找的值与候选区中间值的⽐较,可以使候选
区减少⼀半
时间复杂度:O(logn)
def binary_search(li, val):
left = 0
right = len(li) - 1
while left <= right: # 候选区有值
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] > val: # 带查找的值在mid左侧
right = mid - 1
else: # li[mid] < val 带查找的值在mid右侧
left = mid + 1
else:
return None
列表排序
冒泡排序算法
列表每两个相邻的数,如果前⾯⽐后⾯⼤,则交换这两个数。
⼀趟排序完成后,则⽆序区减少⼀个数,有序区增加⼀个数。
如果冒泡排序中的⼀趟排序没有发⽣交换,则说明列表已经有序,可以直接结束算法。
时间复杂度:O(n²)
def bubble_sort(li):
for i in range(len(li)-1): #第i趟
exchange = False
for j in range(len(li)-i-1):
if li[j] > li[j+1]:
li[j], li[j+1] = li[j+1], li[j]
exchange = True
if not exchange:
return
选择排序
⼀趟排序记录最⼩的数,放到第⼀个位置
再⼀趟排序记录记录列表⽆序区最⼩的数,放到第⼆个位置
时间复杂度:O(n²)
def select_sort(li):
for i in range(len(li)-1): # i是第几趟
min_loc = i
for j in range(i+1, len(li)):
if li[j] < li[min_loc]:
min_loc = j
li[i], li[min_loc] = li[min_loc], li[i]
print(li)
插入排序
时间复杂度:O(n²)
def insert_sort(li):
for i in range(1, len(li)): #i 表示摸到的牌的下标
tmp = li[i]
j = i - 1 #j指的是手里的牌的下标
while j >= 0 and li[j] > tmp:
li[j+1] = li[j]
j -= 1
li[j+1] = tmp
print(li)
快速排序
时间复杂度:O(nlogn)
def partition(li, left, right):
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp: #从右面找比tmp小的数
right -= 1 # 往左走一步
li[left] = li[right] #把右边的值写到左边空位上
# print(li, 'right')
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left] #把左边的值写到右边空位上
# print(li, 'left')
li[left] = tmp # 把tmp归位
return left
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 quick_sort(li):
_quick_sort(li, 0, len(li)-1)
堆排序
1.建⽴堆。
2.得到堆顶元素,为最⼤元素
3.去掉堆顶,将堆最后⼀个元素放到堆顶,此时可通过⼀次调整重新使堆有序。
4.堆顶元素为第⼆⼤元素。
5.重复步骤3,直到堆变空。
时间复杂度:O(nlogn)
def sift(li, low, high):
"""
:param li: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
:return:
"""
i = low # i最开始指向根节点
j = 2 * i + 1 # j开始是左孩子
tmp = li[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子有并且比较大
j = j + 1 # j指向右孩子
if li[j] > tmp:
li[i] = li[j]
i = j # 往下看一层
j = 2 * i + 1
else: # tmp更大,把tmp放到i的位置上
li[i] = tmp # 把tmp放到某一级领导位置上
break
else:
li[i] = tmp # 把tmp放到叶子节点上
def heap_sort(li):
n = len(li)
for i in range((n-2)//2, -1, -1):
# i表示建堆的时候调整的部分的根的下标
sift(li, i, n-1)
# 建堆完成了
for i in range(n-1, -1, -1):
# i 指向当前堆的最后一个元素
li[0], li[i] = li[i], li[0]
sift(li, 0, i - 1) #i-1是新的high
topk问题
def sift(li, low, high):
i = low
j = 2 * i + 1
tmp = li[low]
while j <= high:
if j + 1 <= high and li[j+1] < li[j]:
j = j + 1
if li[j] < tmp:
li[i] = li[j]
i = j
j = 2 * i + 1
else:
break
li[i] = tmp
def topk(li, k):
heap = li[0:k]
for i in range((k-2)//2, -1, -1):
sift(heap, i, k-1)
# 1.建堆
for i in range(k, len(li)-1):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
# 2.遍历
for i in range(k-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i - 1)
# 3.出数
return heap
归并排序
分解:将列表越分越⼩,直⾄分成⼀个元素。
终⽌条件:⼀个元素是有序的。
合并:将两个有序列表归并,列表越来越⼤
时间复杂度:O(nlogn)
空间复杂度:O(n)
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执行完,肯定有一部分没数了
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
li[low:high+1] = ltmp
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)
希尔排序
⾸先取⼀个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进⾏直接插⼊排序;
取第⼆个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同⼀组内进⾏直接插⼊排序。
希尔排序每趟并不使某些元素有序,⽽是使整体数据越来越接近有序;最后⼀趟排序使得所有数据有序
时间复杂度:
def shell_sort(li):
gap = len(li) // 2
while gap > 0:
for i in range(gap, len(li)):
tmp = li[i]
j = i - gap
while j >= 0 and tmp < li[j]:
li[j + gap] = li[j]
j -= gap
li[j+gap] = tmp
gap /= 2
计数排序
对列表进⾏排序,已知列表中的数范围都在0到100之间。设计时间复杂度为O(n)的算法
时间复杂度:O(n)
def count_sort(li, max_count=100):
count = [0 for _ in range(max_count+1)]
for val in li:
count[val] += 1
li.clear()
for ind, val in enumerate(count):
for i in range(val):
li.append(ind)
桶排序
⾸先将元素分在不同的桶中,在对每个桶中的元素排序
平均情况时间复杂度:O(n+k)
最坏情况时间复杂度:O(n2k)
空间复杂度:O(nk)
基数排序
时间复杂度:O(kn)
空间复杂度:O(k+n)
贪心算法
贪⼼算法(⼜称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪⼼算法并不保证会得到最优解,但是在某些问题上贪⼼算法的解就是最优解。要会判断⼀个问题能否⽤贪⼼算法来计算。
动态规划
欧几里得算法
深度优先算法
广度优先算法
数据结构
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。
比如:列表、集合与字典等都是一种数据结构。
数据结构按照其逻辑结构可分为线性结构、树结构、图结构
线性结构:数据结构中的元素存在一对一的相互关系
树结构:数据结构中的元素存在一对多的相互关系
图结构:数据结构中的元素存在多对多的相互关系
栈
栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表。
栈的特点:后进先出 LIFO(last-in, first-out)
栈的概念:栈顶、栈底
栈的基本操作:
进栈(压栈):push
出栈:pop
取栈顶:gettop
队列
队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除。
进行插入的一端称为队尾(rear),插入动作称为进队或入队
进行删除的一端称为队头(front),删除动作称为出队
队列的性质:先进先出(First-in, First-out)
双向队列
双向队列的两端都支持进队和出队操作
链表
链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串联成一个链表。
双链表
双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点。
哈希表
哈希表是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。
二叉树
二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接。
二叉树的遍历方式
- 前序遍历
- 中序遍历
- 后序遍历
- 层次遍历