算法和数据结构
算法:一个计算过程,解决问题的办法
递归
递归的两个必须条件
- 递推(调用自身)
- 回溯(结束条件)
来看几个例子:
eg1:该函数不是递归,没有结束条件
def func1(n): print (n) func1(n - 1 ) |
eg2:该函数亦不是递归,虽有条件,但条件是无穷的
def func2(n): if n > 0 : print (n) func2(n + 1 ) |
eg3:该函数是递归,满足递归的两个条件
def func3(n): if n > 0 : print (n) func3(n - 1 ) |
eg4:该函数亦是递归,满足递归的两个条件
def func4(n): if n > 0 : func4(n - 1 ) print (n) |
那么,eg3 和 eg4的输出结果是一样的吗?如果不一样,为什么?
解释:
因为,func3执行的时候,print是在调用自身之前,所以当n是5传入函数时,会先打印5,再调用自身,这时候n是4,,依次循环递归。。。。到最后n=0,所以回溯时没有任何输出,所以func3 输出的结果是:5,4,3,2,1;而func4执行时,print是在调用自身之后,当n是5传入函数执行时,此时的n已经被减了1变成了4,再依次循环递归。。。。,再回溯的时候func4才有输出,的结果是:1,2,3,4,5
递归的简单使用,给出一个列表:[1, [2, [3, [4, [5, [6, [7, ]]]]]]],需求:拿到列表中的元素(纯数字)
# coding=utf-8 li = [ 1 , [ 2 , [ 3 , [ 4 , [ 5 , [ 6 , [ 7 , ]]]]]]] def tell(li): for item in li: if type (item) is list : tell(item) else : print (item) tell(li) |
列表查找
顺序查找:最常用的就是for循环,挨个对比查找,效率极低!
二分查找:把一个列表一分为二(切片),判断要找的数大于还是小于中间值,大于则在右侧查找,小于在左侧查找。每次都是将列表一切为二进行查找!
原始的二分法(切片),时间复杂度为O(n)
def find(find_num, ll): print (ll) if len (ll) = = 0 : print ( 'not find' ) return mid_index = len (ll) / / 2 if find_num > ll[mid_index]: ll = ll[mid_index + 1 :] find(find_num, ll) elif find_num < ll[mid_index]: ll = ll[:mid_index] find(find_num, ll) else : print ( 'find' , ll[mid_index]) l = [ 1 , 3 , 5 , 8 , 12 , 34 , 45 , 56 , 67 , 78 , 89 , 123 , 234 , 345 , 456 , 566 , 789 ] find( 566 , l) |
改进后的二分法(不用切片),时间复杂度为O(logn)
def num_search(num_list, num): start = 0 end = len (num_list) - 1 while start < = end: mid = (end + start) / / 2 if num_list[mid] = = num: return mid elif num_list[mid] < num: start = mid + 1 else : end = mid - 1 return num_l = [i for i in range ( 1000 )] print (num_search(num_l, 666 )) |
排序
冒泡排序
思路:比较列表相邻的两个数,如果前边的大于后边的,就交换这两个数。。。,(升序)
代码实现,时间复杂度:O(n*n)
import random def sort_list(list1): for i in range ( len (list1) - 1 ): for j in range ( len (list1) - i - 1 ): if list1[j] > list1[j + 1 ]: list1[j], list1[j + 1 ] = list1[j + 1 ], list1[j] print ( '遍历次数: {}' . format (i)) return list1 data = list ( range ( 1000 )) random.shuffle(data) print (sort_list(data)) |
上面的代码虽然实现了冒泡排序,但是有个效率优化的问题,假设一个极端的例子,一个列表:[1,2,3,4,5,6,7,8,9],按照上述的冒泡排序方法,程序会循环8次结束,但是在这个过程中,列表中的数字位置并未发生改变,那怎么解决这个问题呢?加一个变量来判断一次循环后数字的位置是否有改变,有就继续循环,没有就结束循环

import random def sort_list(list1): for i in range(len(list1) - 1): change = 0 for j in range(len(list1) - i - 1): if list1[j] > list1[j + 1]: list1[j], list1[j + 1] = list1[j + 1], list1[j] change = 1 if change == 0: break print('遍历次数: {}'.format(i)) return list1 data = list(range(1000)) random.shuffle(data) print(sort_list(data))
选择排序
思路:循环列表,找到最小的值放到列表第一位,在遍历一次剩余数中的最小值,继续往后放。。。。
代码实现,时间复杂度:O(n*n)
def select_sort(li): for i in range ( len (li) - 1 ): min_num = i for j in range (i + 1 , len (li)): if li[j] < li[min_num]: min_num = j li[i], li[min_num] = li[min_num], li[i] data = list ( range ( 1000 )) random.shuffle(data) select_sort(data) print (data) |
插入排序
思路:列表分为无序区和有序区,最初的有序区只有一个值,每次从无序区选择一个值,插入到有序区的位置,直到无序区为空!
代码实现,时间复杂度:O(n*n)
def insert_sort(li): for i in range ( 1 , len (li)): tmp = li[i] j = i - 1 while j > = 0 and li[j] > tmp: li[j + 1 ] = li[j] j = j - 1 li[j + 1 ] = tmp data = list ( range ( 1000 )) random.shuffle(data) insert_sort(data) print (data) |
快排
思路:一个列表先取一个元素x(第一个元素),然后使元素x归位,此时列表被元素x分为左右两半,左边都会比元素x小,右边的都会比元素x大,最后用递归完成排序!
代码实现
def quick_sort(data, left, right): if left < right: mid = partition(data, left, right) quick_sort(data, left, mid - 1 ) quick_sort(data, mid + 1 , right) def partition(data, left, right): # 用变量保存第一个元素 tmp = data[left] # 左右碰不到时循环 while left < right: # 找到左边比右边小的数,大于tmp的数放在右边不动 while left < right and data[right] > = tmp: right - = 1 # 将right放在左边的left空位上 data[left] = data[right] # 找到左边比右边小的数,小于tmp的数放在左边不动 while left < right and data[left] < = tmp: left + = 1 # 将left放在左边的right空位上 data[right] = data[left] # 左右碰到时 data[left] = tmp return left data = list ( range ( 10000 )) random.shuffle(data) quick_sort(data, 0 , len (data) - 1 ) print (data) |
堆排序
前言
1、树与二叉树
数是一种可以递归定义的数据结构,是由N个节点组成的集合
- 如果N=0,就是一颗空树
- 如果N>0,那就存在1个节点作为数的根节点,其他节点可以分为M个集合,每个集合本身又是一棵树
2、两种特殊的二叉树
- 满二叉树:最后一层的节点都是满的(a)
- 完全二叉树:满二叉树只去掉最后一层的后面几个节点(b)
3、二叉树的存储方式
- 链式存储
- 顺序存储(列表)
顺序存储就是从上往下依次按顺序存入列表,如下图:
那么,这样存储后父节点与子节点的编号下标(i)有什么关系?
- 父节点与左子节点的编号下标关系:2i + 1
- 父节点与右子节点的编号下标关系:2i + 2
堆排序
1、堆分为大根堆和小根堆
- 大根堆:一颗完全二叉树,满足任一节点都要比其子节点大
- 小根堆:一颗完全二叉树,满足任一节点都要比其子节点小
2、堆排序的过程
- 建立堆
- 得到堆顶元素,为最大元素
- 去掉对顶,将堆最后一个元素放在堆顶,此时可通过一次调整重新使堆变得有序
- 堆顶元素为第二大元素,重复步骤3,直到堆变空
3、代码实现
# coding:utf-8 import random # 调整 def sift(arr, start, end): i = start j = 2 * i + 1 tmp = arr[i] while j < = end: # 孩子在堆里 # 升序排序 if j + 1 < = end and arr[j] < arr[j + 1 ]: # 如果存在右孩子且大于左边的孩子 j + = 1 # j指向右孩子 if arr[j] > tmp: # 子比父大 arr[i] = arr[j] # 子放在父的空位上 i = j # 子成为新的父 j = 2 * i + 1 # 新孩子 else : break arr[i] = tmp # 堆排序 def heap_sort(arr): length = len (arr) for i in range (length / / 2 - 1 , - 1 , - 1 ): # 循环每个小堆,做一次sift sift(arr, i, length - 1 ) # 堆建好之后,挨个出数 for i in range (length - 1 , - 1 , - 1 ): # i 指向堆的最后 arr[ 0 ], arr[i] = arr[i], arr[ 0 ] # 将调换下来的父节点数放在最后一位 sift(arr, 0 , i - 1 ) return arr arr = list ( range ( 555 )) random.shuffle(arr) print (heap_sort(arr)) |
归并排序
假设有一个列表,分为有序的两段,怎么让它合并为一个有序的列表
先实现一次归并,从左往右依次取左右两边的第一个元素,依次比较大小,将小的添加到新的列表中,然后继续取值比较。。。
def merge(arr, start, mid, end): ''' :param arr: 一个两段有序的列表 :param start: 左段有序列表的最小元素 :param mid: 左段有序列表的最后一个元素 :param end: 右段有序列表的最小元素 ''' tmp = [] i = start j = mid + 1 while i < = mid and j < = end: # 两边都有数 if arr[i] < arr[j]: # 左边小时将左边的元素append到tmp tmp.append(arr[i]) i + = 1 else : tmp.append(arr[j]) # 右边小时将右边的元素append到tmp j + = 1 while i < = mid: tmp.append(arr[i]) i + = 1 while j < = end: tmp.append(arr[j]) j + = 1 arr[start:end + 1 ] = tmp |
归并的使用
分解:用递归将一个列表越分越小,直到分成一个元素,一个元素是有序的
合并:用归并将两个有序列表合并
def merge(arr, start, mid, end): ''' :param arr: 一个两段有序的列表 :param start: 左段有序列表的最小元素 :param mid: 左段有序列表的最后一个元素 :param end: 右段有序列表的最小元素 ''' tmp = [] i = start j = mid + 1 while i < = mid and j < = end: # 两边都有数 if arr[i] < arr[j]: # 左边小时将左边的元素append到tmp tmp.append(arr[i]) i + = 1 else : tmp.append(arr[j]) # 右边小时将右边的元素append到tmp j + = 1 while i < = mid: tmp.append(arr[i]) i + = 1 while j < = end: tmp.append(arr[j]) j + = 1 arr[start:end + 1 ] = tmp def merge_sort(arr, start, end): if start < end: mid = (start + end) / / 2 # 分解 merge_sort(arr, start, mid) merge_sort(arr, mid + 1 , end) # 合并 merge(arr, start, mid, end) return arr arr = list ( range ( 555 )) random.shuffle(arr) print (merge_sort(arr, 0 , len (arr) - 1 )) |
计数排序
条件:假设有一个长度为10万的列表,其中的元素都在0-100之间,将该列表内的元素进行排序
思路:先定义一个0-100的列表(下标),让每个下标对应的数全部等于0,然后循环需要排序的列表,将循环出现的数对应到定义好下标的列表中进行个数统计
代码实现
def count_sort(arr, max_num): count = [ 0 for i in range (max_num + 1 )] for num in arr: count[num] + = 1 i = 0 for num, m in enumerate (count): for j in range (m): arr[i] = num i + = 1 return arr data = [random.randint( 0 , 100 ) for i in range ( 100 )] print ( 'before: ' , data) print ( 'after: ' , count_sort(data, 100 )) |
本文来自博客园,仅供参考学习,如有不当之处还望不吝赐教,不胜感激!转载请注明原文链接:https://www.cnblogs.com/rong-z/p/10526890.html
作者:cnblogs用户
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人