查找
查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程;
列表查找(线性查找):从列表中找指定元素
输入:列表、带查找元素
输出:元素下标(未找到元素时,未找到元素时一般返回None 或者-1)
内置列表查找函数 index()
顺序查找 LinearSearch
顺序查找:也叫做线性查找,从列表的第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。
在这里插入代码片
def linear_search(li,val):
for ind,v in enumerate(li):
if v == val:
return ind
else:
return None
时间复杂度:n列表的长度 O(n)
二分查找
二分法查找时从中间元素开始,而不是按照顺序查找列表。如果该元素是我们正在查找的元素,我们就完成了查找。如果他不是我们,我们可以使用列表的有序性质来消除剩余元素的一半。
如果我们正在查找的元素大于中间元素,就可以消除中间元素,以及比中间元素小的一般元素。如果该元素在列表中肯定在大的那半部分。然后我们可以用大的半部分与酸奶重复该过程,继续从中间元素开始,将其与我们正在寻找的内容进行比较。
冒泡排序
选择排序
插入排序
在这里插入代码片
def instertion_stor(li):
# 方法一,占用内存多需要重新定义一个列表存放排好序的列表;
lit=[]
for i in range(0,len(li)):
lit.append(li[i])
j = len(lit)-1
while lit[j] < lit[j-1] and j >= 1:
lit[j],lit[j-1] = lit[j-1],lit[j]
j -= 1
print(lit)
import random
li = list(range(18))
random.shuffle(li)
# print(li)
# instertion_stor(li)
# print(li)
#
def instertion_stor_y(li):
#方法二,在原来的列表前i个位置作为排序序列
for i in range(0,len(li)):
tot = i
while li[tot] > li[tot-1] and tot >=1 :
li[tot],li[tot-1] = li[tot-1],li[tot]
tot -= 1
print(li)
instertion_stor_y(li)
print(li)
快速排序
堆排序
堆排序前传树与二叉树
树是一种数据结构
树是一种可以递归定义的数据结构
树是由n个节点组成的集合:
-
如果 n= 0 ,那么这是一颗空树
-
如果n>0,那存在1个节点的作为树的根节点,其他节点可以分为m个节点,每个节点右时一棵树;
一些概念:
根节点、叶子节点;叶子节点 不能分叉的节点B/C/H/
树的深度(高度) 最深有几层本树右4层
树的度 #分叉的数量 F节点的度,树的度,就是节点分叉最多的节点数量这棵树度时6 A节点时6 ;
孩子节点/父节点 B是A的孩子节点,A是B的父节点
子树
什么是二叉树
**满二叉树:**一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树。
**完全二叉树:**叶子节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层左边的若干位置的二叉树。
堆–是一个特殊的完全二叉树;
二叉树的存储方式(表示方式)
- 链式存储方式
- 顺序存储方式
二叉树的顺序存储方式:
父节点和左孩子节点的编号下标有什么关系?
- 0-1 1-3 2-5 3-7 4-9
- i--> 2i+2
父节点和有右孩子的编号有什么关系?
- 0-2 1-4 2-6 3-8 4-10
- i--> 2i+2
孩子的节点是i则父亲的节点是(i-1)//2
堆排序用堆来排序,堆是一种特殊的完全二叉树;
大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大;
小根堆:一颗完全二叉树,满足任一节点都比其孩子节点小;
堆的向下调整:
假设:节点的左右子树都是堆,但自身不是堆
----当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换程一个堆。
堆的向下调整性质:
假设根节点的左右树都是堆,但跟节点不满足堆的性质。可以通过一次向下的调整来将其变成一个堆。
堆排序过程:
- 建立堆 ;
- 得到堆顶顶元素,为最大元素;
- 去掉堆顶,将堆最后一个元素放到堆顶,此时可以通过一次调整重新使堆有序;
- 堆顶元素为第二大元素;
- 重复步骤3 ,直到堆变空;
挨个出数,第三步骤的说明
1、找最大的数,拿到顶节点,(顶节点出走后)后,不能让下面的子节点放在顶的位置(原因最后有可能会构造出来一个非完全二叉树),
2、应该拿最后一个元素,通过一次向下调整
构造堆:
可以参考:https://www.jb51.net/article/222484.htm
-
将待排序列表中的数据胡按从上到下、从左到右构造成一颗完全为叉树;
-
从完全二叉树的最后一个元素开始,将他的值与其子节点中较大的值进行比较,如果值小于子节点则交换;24是最后一个节点的非叶子节点,他只有一个子节点21,24大于21,不需要交换。
-
继续将到数大二个非叶子节点的值与其子节点中的较大的值进行比较,如果小于子节点则进行交换。节点30有两个子节点5和36,较大的是36,30小于36,交换位置。
-
重复对下一节点的比较,7小于叶子节点,选择较大的交换,7和45交换机位置。
-
继续重复,50大于27,不需要交换位置。如果不需要进行交换,则不用再比较子节点与孙节点。
-
继续重复,17小于45,交换位置。
-
17和45交换位置之后,17交换到了子节点的位置,17继续要和孙子节点比较,17大于15 不需要交换;
-
继续对下一个节点比较,10交换到了子节点的位置,还要继续将其与孙节点进行比较。10 小于27,交换位置。
-
10和50 交换位置后,10 交换到了子节点的位置,还需要继续将其与孙子节点进行比较。10小于于27,交换位置。
-
此时,一个大顶堆构造完成,满足了堆积的性质:每个节点(叶节点除外)都大于他的的子节点;
-
大顶堆构造完成后,将堆顶与堆维交换位置,然后将堆尾从队中取出。将50和21交换位置,21到了堆顶,50(最大的数据)到了堆尾,然后将50从堆尾取出。
-
将50从堆中取出,找到了排序列表中的最大值,50添加到已排序序列中,第一轮堆排序完成,队中的元素个数减1.
-
取出最大数据后,重复将二叉树构建成大顶堆,交换堆顶和堆尾,取出堆尾。这样每次都是取出当前堆中最大数据,添加到已排序序列中,直到队中的数据全部被取出。
-
循环n轮堆排序后,列表排序完成,排序结果如下图。
构造堆:
寻找最后一个非叶子节点,找节点5的父亲节点,如果
父亲的小标是i
孩子的下标就是2i+1
末尾孩子的下标是n-1
父亲的下标是 n-1 = 2i+1
i= (n-2)//2
在这里插入代码片 https://www.jb51.net/article/222484.htm
# coding=utf-8
def heap_sort(array):
first = len(array) // 2 - 1
for start in range(first, -1, -1):
# 从下到上,从右到左对每个非叶节点进行调整,循环构建成大顶堆
big_heap(array, start, len(array) - 1)
for end in range(len(array) - 1, 0, -1):
# 交换堆顶和堆尾的数据
array[0], array[end] = array[end], array[0]
# 重新调整完全二叉树,构造成大顶堆
big_heap(array, 0, end - 1)
return array
def big_heap(array, start, end):
root = start
# 左孩子的索引
child = root * 2 + 1
while child <= end:
# 节点有右子节点,并且右子节点的值大于左子节点,则将child变为右子节点的索引
if child + 1 <= end and array[child] < array[child + 1]:
child += 1
if array[root] < array[child]:
# 交换节点与子节点中较大者的值
array[root], array[child] = array[child], array[root]
# 交换值后,如果存在孙节点,则将root设置为子节点,继续与孙节点进行比较
root = child
child = root * 2 + 1
else:
break
if __name__ == '__main__':
array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
print(heap_sort(array))
堆排序——topk问题
现在有n个数,设计算法得到前k大的数。K<n
解决思路:
排序后切片 O(nlogn)
排序Low B 三人组 O(mn)
堆排序思路 O(mlogn)
解决思路:
- 取列表前K元素建立一个小堆栈。堆顶就是目前第k大的数
- 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且堆堆进行一次调整;
- 遍历列表所有元素后,倒序弹出堆顶;
需要求列表的前5个最大元素
取下面列表前5个元素组成小根堆,然后在一次取剩余列表的数,更新小根堆:
[10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
代码实现:
在这里插入代码片
def sift(li,low,high):
"""
li:列表
low: 堆的根节点位置
high: 堆的最后一个元素的位置
"""
i = low #最开始的位置
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 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):
#i指向当前堆的最后一个元素
heap[0],heap[i] = heap[i],heap[0]
sift(heap,0,i-1) #i-1是洗呢high
# 3 出数
return heap
import random
li = list(range(1000))
# print(li)
random.shuffle(li)
# print(li)
print(topk(li,10))
归并排序—归并
分解:将列表月份越小,直至分成一个元素。
终止条件:一个元素是有序的。
合并:将两个有序列表归并,列表越来越大。
在这里插入代码片
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)
print(li[low:high+1])
merge(li,low,mid,high)
li = list(range(16))
import random
random.shuffle(li)
print(li)
merge_sort(li,0,len(li)-1)
print(li)
NB 三人组
三种排序算法的时间复杂度都是O(nlogn)
一般情况下,就运行时间而言:
快速排序《归并排序《堆排序
三种算法的有缺点:
快速排序:极端情况下排序效率第
冰柜排序:需要额外的内存开销
堆排序:在块的排序算法中相对较慢
希尔排序
计数排序
对列表进行排序,已知列表中的范围都在0到100 之间。设计时间复杂度为O(n)算法;
计数排序是一种非常快捷稳定性强的排序方法,时间复杂度0(n+k),其中n为要排序的数的个数,k为要排序的数的最大值。计数排序对一定量的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序局限性比较大,只能对整数进行排序。计数排序是消耗空间复杂度来获取快捷的排序方法,其空间发展都为O(k)同理K为要排序的最大值。
计数排序的基本思想为一组数在排序之前先统计这组数中其他数小于这个数的个数,则可以确定这个数的位置。
在这里插入代码片
def count_sort(li,max_count):
count = [0 for _ in range(max_count+1)]
print('count',count)
for val in li:
count[val] += 1
print('count',count)
li.clear()
for ind,val in enumerate(count):
for i in range(val):
li.append(ind)
import random
li = [random.randint(0,10) for _ in range(20)]
print('li',li)
count_sort(li,10)
print('li',li)
D:\Pycherm_python\python39_venv\Scripts\python.exe D:/Pycherm_python/foamy/清华大学博士讲解Python数据结构与算法/count_sort.py
li [7, 7, 2, 5, 9, 3, 10, 1, 9, 9, 0, 6, 6, 4, 4, 5, 7, 7, 6, 0]
count [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
count [2, 1, 1, 1, 2, 2, 3, 4, 0, 3, 1]
li [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, 7, 9, 9, 9, 10]
桶排序
算法原理及介绍
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
算法过程描述
1/设置一个定量的数组当作空桶;
2/遍历输入数据,并且把数据一个一个放到对应的桶里去;
3/对每个不是空的桶进行排序;
4/从不是空的桶里把排好序的数据拼接起来。
算法排序图解如下
下图分为10个桶,每一个桶的范围为【iX10, (i+1)X10】,将每一个数放入指定桶的范围内。
时间复杂度 O(kn)
空间复杂度 O(k +n)
k表示数字额位数
在这里插入代码片
#!/usr/bin/env python
# encoding: utf-8
def radix_sor(li):
max_num = max(li) #最大值9-》1 99 >2 999>3 10000>5
it = 0
while 10 ** it <= max_num:
buckets = [[] for _ in range(10)]
for var in li:
# 987 it = 1 987//10-98 98%10 -> 8 it=2 987//100 ->9 0%10=9
digit = (var // 10 ** it) % 10 #10**it 先做平方运算在左 // 模运算
buckets[digit].append(var)
li.clear()
for buc in buckets:
li.extend(buc)
#把数重新写回li
it += 1
import random
li = list(range(999,1020))
random.shuffle(li)
radix_sor(li)
print(li)
练习题目
1 242. 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
示例 1:
示例 2:
提示:
- 1 <= s.length, t.length <= 5 * 104
- s 和 t 仅包含小写字母
2 给定一个m*n 的二维列表,查找一个数是否存在。列表有下列特性:
每一行的列表从左到右以及排序好
每一行第一个数比上一行最后一个数大。
[
[1,3,5,7],
[10,11,16,20],
[23,30,34,50]
]
3给定一个列表和一个整数,设计算法找到两个数的下标,使得两个数之和为给定的整数。保证肯定仅有一个结果;
例如 列表[1,2,5,4] 与目标整数3,1+2=3 结果为(0,1)