算法入门

# 递归
# 两个特点
# 调用自身
# 有结束条件

def fun1(x):
if x > 0:
print(x)
fun1(x - 1)


# 递归实现汉若塔问题
def hanoi(n, a, b, c): # 有n个盘子 三个柱子
if n > 0:
hanoi(n - 1, a, c, b) # 把上n-1个盘子从a柱通过c柱移动到b柱
print("moving from %s to %s" % (a, c))
hanoi(n - 1, b, a, c)

# hanoi(3, 'A', 'B', 'C')

# 列表查找
# 顺序查找:线性查找,从列表的第一个元素开始 顺序进行搜索
# 直到找到元素或搜索到列表的最后一个元素为止

def linear_search(data_set, value):
for ind, v in enumerate(data_set):
if v == value:
print(ind)
return ind # 返回的是位置

else:
return None


# linear_search([1, 2, 3, 4, 5, 6, 7, 8], 5)


# 二分查找:折半查找从有序列表的初始候选区li[0:n]开始,
# 通过对待查找的值与候选区中间值的⽐较,可以使候选区减少⼀半。

def bin_search(data_set, val):
left = 0
right = len(data_set) - 1
while left <= right: # 候选区有值
mid = (left + right) // 2
if data_set[mid] == val:
return mid
elif data_set[mid] > val: # 带查找的值在mid左侧
right = mid - 1
else: # li[mid] < val 带查找的值在mid右侧
left = mid + 1
else:
return None


# 列表排序
# 冒泡排序
# 列表每两个相邻的数,如果前⾯⽐后⾯⼤,则交换这两个数。
# ⼀趟排序完成后,则⽆序区减少⼀个数,有序区增加⼀个数。
# 代码关键点:趟、⽆序区范围
def bubble_sort(li):
for i in range(len(li) - 1): # 循环第i趟
flag = False
for j in range(len(li) - i - 1): # 无序范围 j箭头指的位置
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
flag = True
print(li)
if not flag: # 一趟结束后flag = False 没有发生交换 就不再往继续
return


# li = [9, 3, 7, 6, 3, 8, 3, 8, 2]
# bubble_sort(li)
# 选择排序
# ⼀趟排序记录最⼩的数,放到第⼀个位置
# 再⼀趟排序记录记录列表⽆序区最⼩的数,放到第⼆个位置
# 算法关键点:有序区和⽆序区、⽆序区最⼩数的位置
def select_sort(li):
for i in range(len(li) - 1): # 第i趟
min_loc = i # 认为i是最小的数位置
for j in range(i + 1, len(li)):
if li[j] < li[min_loc]: # ⼀趟排序记录最⼩的数
min_loc = j
if min_loc != i: # ⼀趟排序最⼩的数,放到第⼀个位置
li[i], li[min_loc] = li[min_loc], li[i]
print(li)


# select_sort([1,6,3,4,2,6])
# 插入排序 抓牌的思想

def insert_sort(li):
for i in range(1, len(li)): # i 表示摸到的牌的下标(能摸n-1次牌 手里的牌的位置是0)
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)


# li = [3, 2, 4, 1, 5, 7, 9, 6, 8]
# print(li)
# insert_sort(li)

# 牛逼三人组
# 快速排序
# 取⼀个元素p(第⼀个元素),使元素p归位
# (坐标少一个位置 先从最右边找比P小的元素 放到空位置这样右边又少了一位置,再从左边找比p大的元素放到右边空位置 往复);
# 列表被p分成两部分,左边都⽐p⼩,右边都⽐p⼤;
# 递归完成排序。

def partition(li, left, right):
tmp = li[left] # 取⼀个元素
while left < right:
while left < right and li[right] >= tmp: # 从右面找比tmp小的数 有符合要求的直接把值写到左边空位上
right -= 1 # 如果右边的值比tmp大 就往左走一步 走到left>=right跳出内循环
li[left] = li[right] # 直到跳出内循环 把右边的值写到左边空位上

while left < right and li[left] <= tmp:
left += 1
li[right] = li[left] # 把左边的值写到右边空位上
li[left] = tmp # 当left和right碰面了跳出外循环 把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) # 再将右边排序


# li = [9, 8, 7, 6, 5, 4, 3, 2, 1]
# quick_sort(li, 0, len(li) - 1)
# print(li)
'''
# 堆排序(一个对列表排序规则)
# 树是⼀种数据结构 ⽐如:⽬录结构 ,树是⼀种可以递归定义的数据结构
# 树是由n个节点组成的集合:
# 如果n=0,那这是⼀棵空树;
# 如果n>0,那存在1个节点作为树的根节点,
# 其他节点可以分为m个集合,每个集合本身⼜是⼀棵树
# ⼀些概念:
# 根节点、叶⼦节点(没有子节点)
# 树的深度(⾼度)
# 树的度(宽度)
# 孩⼦节点/⽗节点
# ⼦树
# ⼆叉树:度不超过2的树
# 每个节点最多有两个孩⼦节点
# 两个孩⼦节点被区分为左孩⼦节点和右孩⼦节点
# 满⼆叉树:⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。
# 完全⼆叉树:叶节点只能出现在最下层和次下层,并且最下⾯⼀层的结点都集中在该层最左边的若⼲位置的⼆叉树。、
# ⼆叉树的存储⽅式(表示⽅式)
# 链式存储⽅式
# 顺序存储⽅式
# ⽗节点和左孩⼦节点的编号下标有什么关系?
# 0-1 1-3 2-5 3-7 4-9
# i(父节点) → 2i+1(左孩子子节点)
# ⽗节点和右孩⼦节点的编号下标有什么关系?
# 0-2 1-4 2-6 3-8 4-10
# i(⽗节点) → 2i+2(右孩⼦节点)
# 孩子找父亲 都是(n-1)//2
# 堆:⼀种特殊的完全⼆叉树结构
# ⼤根堆:⼀棵完全⼆叉树,满⾜任⼀节点都⽐其孩⼦节点⼤
# ⼩根堆:⼀棵完全⼆叉树,满⾜任⼀节点都⽐其孩⼦节点⼩
# 堆排序̶̶堆的向下调整性质
# 假设根节点的左右⼦树都是堆,但根节点不满⾜堆的性质
# 可以通过⼀次向下的调整来将其变成⼀个堆
# 堆排序过程
# 1.建⽴堆。
# 2.得到堆顶元素,为最⼤元素
# 3.去掉堆顶,将堆最后⼀个元素放到堆顶,此时可通过⼀次调整重新使堆有序。(挨个出数)
# 4.堆顶元素为第⼆⼤元素。
# 5.重复步骤3,直到堆变空
堆排序̶̶内置模块
Python内置模块——heapq(小根树)
常⽤函数
heapify(x) # 建堆
heappop(heap) # 弹出最小的元素
heappush(heap,item)

应用:
现在有n个数,设计算法得到前k⼤的数。(k<n)
解决思路:
排序后切⽚ O(nlogn) (数据量大不好使)
排序LowB三⼈组 O(kn)
堆排序 O(nlogk)
取列表前k个元素建⽴⼀个⼩根堆。堆顶就是⽬前第k⼤的数。
依次向后遍历原列表,对于列表中的元素,如果⼩于堆顶,则忽略该元
素;如果⼤于堆顶,则将堆顶更换为该元素,并且对堆进⾏⼀次调整;
遍历列表所有元素后,倒序弹出堆顶





'''


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指向右孩子 j就变成了右孩子
if li[j] > tmp: # 如果左或右孩子比根节点大
li[i] = li[j] # 和根节点互换
i = j # 往下看一层 i指向下移一层
j = 2 * i + 1 # j还是i的左孩子节点
else: # 否则就是根节点大tmp更大,把tmp放到i的位置上
li[i] = tmp # 把tmp放到某一级领导位置上
break
else:
li[i] = tmp # 把tmp放到叶子节点上(已经到最底部了 没有j的位置了 循环跳出了 再把拿走的数据放回来)


# 堆排序
def heap_sort(li):
n = len(li)
# 1建堆(以农村包围城市 是个循环的过程 从小堆开始)
# 找到最后一个非叶子节点 孩子找父亲的规律是(i-1)//2 i的位置是n-1
# 从(n - 2) // 2这个位置开始第一个父亲循环每个孩子的父亲 倒序-1 到0位置停止-1(最顶部)
for i in range((n - 2) // 2, -1, -1):
# i表示建堆的时候调整的部分的根(父亲)的下标
# high的作用是判断有没有越界(n-1 整个元素的最后一个下标)
sift(li, i, n - 1)
# for循环结束建堆完成

# 2挨个出数(最大的拿出去 最后面的放上去)
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


# li = [i for i in range(100)]
# import random
#
# random.shuffle(li)
# print(li)
#
# heap_sort(li)
# print(li)

# 堆排序实践
# 现在有n个数,设计算法得到前k⼤的数。(k<n)
# 比较排序
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]
# 1.建堆(小根堆 最小的在顶)
for i in range((k - 2) // 2, -1, -1):
sift(heap, i, k - 1)

# 2.遍历(遍历你列表里剩下的所有元素 如果比堆顶大就加进去 并进行调整)
for i in range(k, len(li) - 1):
# 和堆顶比较
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k - 1)

# 3.出数(heap是堆)
for i in range(k - 1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i - 1)

return heap # 排好序的前k大的数


# import random
# li = list(range(1000))
# random.shuffle(li)
# print(topk(li, 10))

# 归并排序
# 假设现在的列表分两段有序,如何将其合成为⼀个有序列表
def merge(li, low, mid, high):
"""
前提有两段有序列表
:param li: 列表
:param low: 最开始位置
:param mid: 两段的分界点位置
:param high: 最后的位置
:return:
"""
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 # 将新的排好序的列表赋值给原先的列表


# li = [2,4,5,7,1,3,6,8]
# merge(li, 0, 3, 7)
# print(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 = list(range(1000))
import random

# random.shuffle(li)
# print(li)
# merge_sort(li, 0, len(li) - 1)
# print(li)

'''
总结:
三种排序算法的时间复杂度都是O(nlogn)
⼀般情况下,就运⾏时间⽽⾔:
快速排序 < 归并排序 < 堆排序
三种排序算法的缺点:
快速排序:极端情况下排序效率低
归并排序:需要额外的内存开销
堆排序:在快的排序算法中相对较慢
'''


# 希尔排序
# 希尔排序(Shell Sort)是⼀种分组插⼊排序算法。
# ⾸先取⼀个整数d1=n/2 n是列表长度,将要排序的元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进⾏直接插⼊排序;
# 取第⼆个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同⼀组内进⾏直接插⼊排序。
# 希尔排序每趟并不使某些元素有序,⽽是使整体数据越来越接近有序;最后⼀趟排序使得所有数据有序。

def insert_sort_gap(li, gap):
# gap就是分的组
# 从gap开始到最后
for i in range(gap, len(li)): # i 表示摸到的牌的下标
tmp = li[i]
j = i - gap # j指的是手里的牌的下标
while j >= 0 and li[j] > tmp:
li[j + gap] = li[j]
j -= gap
li[j + gap] = tmp


def shell_sort(li):
d = len(li) // 2
while d >= 1:
insert_sort_gap(li, d)
d //= 2


import random, copy


# li = list(range(100))
# random.shuffle(li)
# shell_sort(li)
# print(li)


# 计数排序
# 对列表进⾏排序,已知列表中的数范围都在0到100之间。
# 设计时间复杂度为O(n)的算法

def count_sort(li, max_count=100):
count = [0 for _ in range(max_count + 1)] # 建100个0列表用于计数
print(count)
for val in li: # 遍历列表
count[val] += 1 # li列表的数字和count列表中的0的位置对应,每出现一次就在相应位置+1计数
li.clear() # 循环完将列表清除 备用 不再创建新内存
for ind, val in enumerate(count): # 遍历count列表元素及下标 列表中有val个nid
for i in range(val):
li.append(ind) # 把ind append val次到li列表中
# 相当于将li列表中原有的元素清除 又重新加入了count里的元素


# import random
# li = [random.randint(0, 100) for _ in range(100)]
# count_sort(li)
# print(li)


# 桶排序
# 在计数排序中,如果元素的范围⽐较⼤(⽐如在1到1亿之间)如何改造算法?
# 桶排序(Bucket Sort):⾸先将元素分在不同的桶中,在对每个桶中的元素排序。
# 桶排序的表现取决于数据的分布。也就是需要对不同数据排序时采取不同的分桶策略。
# 平均情况时间复杂度:O(n+k)
# 最坏情况时间复杂度:O(n2k)
# 空间复杂度:O(nk)

import random


def bucket_sort(li, n=100, max_num=10000): # n是分了几个桶 max_num=10000最大数
buckets = [[] for _ in range(n)] # 创建桶 一个二维列表
for var in li: # 遍历列表的所有数
i = min(var // (max_num // n), n - 1) # i 表示把var放到i号桶里 桶号从0到n-1 ,min的意思如果最大值超过10000就放到最后一个桶里不再加桶了
buckets[i].append(var) # 把var加到桶里边
# 保持桶内的顺序 插入一个元素就让他有序 用冒泡和前面元素比较
for j in range(len(buckets[i]) - 1, 0, -1): # 从0位置开始 倒序
if buckets[i][j] < buckets[i][j - 1]: # buckets 是二维列表 i表示桶 j表示桶里元素
buckets[i][j], buckets[i][j - 1] = buckets[i][j - 1], buckets[i][j]
else:
break
print(buckets)
sorted_li = [] # 建空列表
for buc in buckets:
print(buc)
sorted_li.extend(buc) # extend()向列表尾部追加一个列表将列表中的每个元素都追加进来,在原有列表上增加
return sorted_li


# li = [random.randint(0, 1000) for i in range(1000)]
# # print(li)
# li = bucket_sort(li)
# print(li)

# 基数排序
# 多关键字排序:加⼊现在有⼀个员⼯表,要求按照薪资排序,年龄相同的员⼯按照年龄排序。
# 先按照年龄进⾏排序,再按照薪资进⾏稳定的排序。
# 对32,13,94,52,17,54,93排序,是否可以看做多关键字排序?先看十位在看个位
# 先按个位分桶再排序 再按十位分桶排序

def radix_sort(li):
# 分桶排序的表情基数排序
max_num = max(li) # 最大值 9->1次, 99->2次, 888->3次, 10000->5次 位数是几就做几次循环
it = 0
while 10 ** it <= max_num: # 10的it次方和最大值比较
buckets = [[] for _ in range(10)] # 建桶 分10个桶
for var in li:
# 举举例子987 it=1 987//10->98 98%10->8 取到第二位; it=2 987//100->9 9%10=9 取到第三位
digit = (var // 10 ** it) % 10 # 取到第几位的数
buckets[digit].append(var) # 分桶完成

li.clear()
for buc in buckets:
li.extend(buc)
# 把数重新写回li
it += 1


# import random
#
# li = [random.randint(0, 1000) for _ in range(1000)]
# random.shuffle(li)
# radix_sort(li)
# print(li)
posted @ 2022-05-25 09:40  贰号猿  阅读(48)  评论(0)    收藏  举报