Python之常见算法介绍
一、算法介绍
1、 算法是什么
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
2、时间复杂度
在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号 ),简称时间复杂度。
常见时间复杂度单位:效率从上到下变低,
O(1) 简单的一次运算(常数阶)
O(n) 一次循环(线性阶)
O(n^2) 两个循环(平方阶)
O(logn) 循环减半
O(nlogn) 一个循环加一个循环减半
O(n^2logn)
O(n^3)
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法
O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n)
大O推导法:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数
比如:
- 这是一段C的代码
- #include "stdio.h"
- int main()
- {
- int i, j, x = , sum = , n = ; /* 执行1次 */
- for( i = ; i <= n; i++)
- {
- sum = sum + i; /* 执行n次 */
- for( j = ; j <= n; j++)
- {
- x++; /* 执行n*n次 */
- sum = sum + x; /* 执行n*n此 */
- }
- }
- printf("%d", sum); /* 执行1次 */
- }
分析:
执行总次数 = 1 + n + n*n + n*n + 1 = 2n2 + n + 2
根据大O推导法:
1.用常数 1 取代运行时间中的所有加法常数:执行总次数为: 2n2 + n + 1
2.在修改后的运行次数函数中,只保留最高阶项,这里的最高阶是 n 的二次方: 执行总次数为: 2n2
3.如果最高阶项存在且不是 1 ,则去除与这个项相乘的常数,这里 n 的二次方不是 1 所以要去除这个项的相乘常数:执行总次数为: n2
因此最后我们得到上面那段代码的算法时间复杂度表示为: O(n2)
3、空间复杂度
空间复杂度是用来评估算法内存占用大小的单位
空间换时间:如果需要增快算法的速度,需要的空间会更大
二、python实现常见的排序算法
前三种比较LowB,后三种比较NB
前三种时间复杂度都是O(n^2),后三种时间复杂度都是O(nlog(n))
1、冒泡(交换)排序
原理:列表中两个相邻的数,如果前一个数比后一个数大,就做交换。一共需要遍历列表的次数是len(lst)-1
时间复杂度:O(n^2)
- def bubble_sort(lst):
- for i in range(len(lst) - 1): # 这是需要循环遍历多少次
- for j in range(len(lst) - 1 - i): # 每次数组中的无序区
- if lst[j] > lst[j + 1]:
- lst[j], lst[j + 1] = lst[j + 1], lst[j]
- lst = [1, 2, 44, 3, 5]
- bubble_sort(lst)
- print(lst)
优化:如果在循环的时候,有一次没有进行交换,就表示数列中的数据已经是有序的
时间复杂度:最好情况是0(n),只遍历一次,一般情况和最坏情况都是O(n^2)
- def bubble_sort(lst):
- for i in range(len(lst)-1): # 这是需要循环遍历多少次
- change = False # 做一个标志变量
- for j in range(len(lst)-1-i): # 每次数组中的无序区
- if lst[j] >lst[j+1]:
- lst[j],lst[j+1] = lst[j+1],lst[j]
- change = True # 每次遍历,如果进来排序的话,就会改变change的值
- if not change: # 如果change没有改变,那就表示当前的序列是有序的,直接跳出循环即可
- return
- lst = [1, 2, 44, 3, 5]
- bubble_sort(lst)
- print(lst)
2、选择排序
原理:每次遍历找到当下数组最小的数,并把它放到第一个位置,下次遍历剩下的无序区,记录剩余列表中最小的数,继续放置
时间复杂度 O(n^2)
方法一:
- def select_sort(lst):
- for i in range(len(lst) - 1): # 当前需遍历的次数
- min_loc = i # 当前最小数的位置
- for j in range(i + 1, len(lst)): # 无序区
- if lst[j] < lst[min_loc]: # 如果有更小的数
- lst[min_loc], lst[j] = lst[j], lst[min_loc] # 把最小的数交换到当前最小数的位置(索引)
- lst = [1, 2, 44, 3, 5]
- select_sort(lst)
- print(lst)
方法二:
- def select_sort(lst):
- for i in range(len(lst) - 1): # 当前需遍历的次数
- min_loc = i # 当前最小数的位置
- for j in range(i + 1, len(lst)): # 无序区
- if lst[j] < lst[min_loc]: # 如果有更小的数
- min_loc = j # 最小数的位置改变
- if min_loc != i:
- lst[i], lst[min_loc] = lst[min_loc], lst[i] # 把最小数和无序区第一个数交换
- lst = [1, 2, 44, 3, 5]
- select_sort(lst)
- print(lst)
3、插入排序
原理:列表分为有序区和无序区,有序区是一个相对有序的序列,最初有序区只有一个元素,每次从无序区选择一个值,插入到有序区,直到无序区为空
时间复杂度:O(n^2)
- def insert_sort(lst):
- for i in range(1, len(lst)): # 从1开始遍历表示无序区从1开始,有序区初始有一个值
- tmp = lst[i] # tmp表示拿到的无序区的第一张牌
- j = i - 1 # j表示有序区的最后一个值
- while j >= 0 and lst[j] > tmp: # 当有序区有值,并且有序区的值比无序区拿到的值大就一直循环
- lst[j + 1] = lst[j] # 有序区的值往后移
- j -= 1 # 找到上一个有序区的值,然后再循环
- lst[j + 1] = tmp # 跳出循环之后,只有j+1的位置是空的,要把当下无序区的值放到j+1的位置
- lst = [1, 2, 44, 3, 5]
- insert_sort(lst)
- print(lst)
4、快速排序
思路:取第一个元素,让它归位,就是放到一个位置,使它左边的都比它小,右边的都比它大,然后递归完成排序
时间复杂度:O(nlog(n))
- import sys
- sys.setrecursionlimit(100000) # 设置默认递归次数
- def partition(lst, left, right):
- tmp = lst[left] # 找一个基准
- while left < right:
- # 从右边开始向左边遍历,大于基准的数不动,查找小于基准数的数赋值到左边
- while left < right and lst[right] >= tmp:
- right -= 1
- lst[left] = lst[right] # 找到小于基准的数,赋值到左边
- # 从左边开始向右边遍历,小于基准的数不动,查找大于基准数的数赋值到左边
- while left < right and lst[left] <= tmp:
- left += 1
- lst[right] = lst[left] # 找到大于基准的数,赋值到右边
- lst[left] = tmp # 归位的元素
- return left # 返回right也行,都是中间值
- def quick_sort(lst, left, right):
- if left < right:
- mid = partition(lst, left, right)
- quick_sort(lst, left, mid-1)
- quick_sort(lst, mid+1, right)
- lst = [5, 1, 6, 7, 7, 4, 2, 3, 6]
- quick_sort(lst, 0, len(lst) - 1)
- print(lst)
5、归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略,分是将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"归并"在一起,即分而治之。
- def merge_sort(li):
- # 不断递归调用自己一直到拆分成成单个元素的时候就返回这个元素,不再拆分了
- if len(li) == 1:
- return li
- # 取拆分的中间位置
- mid = len(li) // 2
- # 拆分过后左右两侧子串
- left = li[:mid]
- right = li[mid:]
- # 对拆分过后的左右再拆分 一直到只有一个元素为止
- # 最后一次递归时候ll和lr都会接到一个元素的列表
- # 最后一次递归之前的ll和rl会接收到排好序的子序列
- ll = merge_sort(left)
- rl = merge_sort(right)
- # 我们对返回的两个拆分结果进行排序后合并再返回正确顺序的子列表
- # 这里我们调用拎一个函数帮助我们按顺序合并ll和lr
- return merge(ll, rl)
- # 这里接收两个列表
- def merge(left, right):
- # 从两个有顺序的列表里边依次取数据比较后放入result
- # 每次我们分别拿出两个列表中最小的数比较,把较小的放入result
- result = []
- while len(left) > 0 and len(right) > 0:
- # 为了保持稳定性,当遇到相等的时候优先把左侧的数放进结果列表,因为left本来也是大数列中比较靠左的
- if left[0] <= right[0]:
- result.append(left.pop(0))
- else:
- result.append(right.pop(0))
- # while循环出来之后 说明其中一个数组没有数据了,我们把另一个数组添加到结果数组后面
- result += left
- result += right
- return result
- li = [1, 5, 2, 4, 7, 5, 3, 2, 1]
- li2 = merge_sort(li)
- print(li2)
6、堆排序
1.堆是一个完全二叉树
2.完全二叉树即是:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(2层),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
3.堆满足两个性质: 堆的每一个父节点数值都大于(或小于)其子节点,堆的每个左子树和右子树也是一个堆。
4.堆分为最小堆和最大堆。最大堆就是每个父节点的数值要大于孩子节点,最小堆就是每个父节点的数值要小于孩子节点。排序要求从小到大的话,我们需要建立最大堆,反之建立最小堆。
5.堆的存储一般用数组来实现。假如父节点的数组下标为i的话,那么其左右节点的下标分别为:(2*i+1)和 (2*i+2)。如果孩子节点的下标为j的话,那么其父节点的下标为(j-1)/2。
完全二叉树中,假如有n个元素,那么在堆中最后一个父节点位置为(n/2-1)。
- def swap(a, b): # 将a,b交换
- a, b = b, a
- return a, b
- def sift_down(array, start, end):
- """
- 调整成大顶堆,初始堆时,从下往上;交换堆顶与堆尾后,从上往下调整
- :param array: 列表的引用
- :param start: 父结点
- :param end: 结束的下标
- :return: 无
- """
- while True:
- # 当列表第一个是以下标0开始,结点下标为i,左孩子则为2*i+1,右孩子下标则为2*i+2;
- # 若下标以1开始,左孩子则为2*i,右孩子则为2*i+1
- left_child = 2 * start + 1 # 左孩子的结点下标
- # 当结点的右孩子存在,且大于结点的左孩子时
- if left_child > end:
- break
- if left_child + 1 <= end and array[left_child + 1] > array[left_child]:
- left_child += 1
- if array[left_child] > array[start]: # 当左右孩子的最大值大于父结点时,则交换
- array[left_child], array[start] = swap(array[left_child], array[start])
- start = left_child # 交换之后以交换子结点为根的堆可能不是大顶堆,需重新调整
- else: # 若父结点大于左右孩子,则退出循环
- break
- print(">>", array)
- def heap_sort(array): # 堆排序
- # 先初始化大顶堆
- first = len(array) // 2 - 1 # 最后一个有孩子的节点(//表示取整的意思)
- # 第一个结点的下标为0,很多博客&课本教材是从下标1开始,无所谓吧,你随意
- for i in range(first, -1, -1): # 从最后一个有孩子的节点开始往上调整
- print(array[i])
- sift_down(array, i, len(array) - 1) # 初始化大顶堆
- print("初始化大顶堆结果:", array)
- # 交换堆顶与堆尾
- for head_end in range(len(array) - 1, 0, -1): # start stop step
- array[head_end], array[0] = swap(array[head_end], array[0]) # 交换堆顶与堆尾
- sift_down(array, 0, head_end - 1) # 堆长度减一(head_end-1),再从上往下调整成大顶堆
- array = [1, 1, 16, 7, 2, 3, 20, 3, 17, 8]
- heap_sort(array)
- print("堆排序最终结果:", array)
三、查找算法
1、二分查找
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
- def search(lst, num):
- left = 0
- right = len(lst) - 1
- while left <= right: # 循环条件
- mid = (left + right) // 2 # 获取中间位置,数字的索引
- if num < lst[mid]: # 如果查询数字比中间数字小,那就去中间数的左边找,
- right = mid - 1 # 调整右边边界
- elif num > lst[mid]: # 如果查询数字比中间数字大,那么就去中间数的右边找
- left = mid + 1 # 调整左边边界
- elif num == lst[mid]:
- print('找到的数:%s, 索引是:%s' %(lst[mid], mid))
- return mid # 如果查询数字刚好为中间值,返回该值得索引
- print('没有找到该值')
- return -1 # 如果循环结束,左边大于了右边,代表没有找到
- lst = [1, 3, 4, 8, 11, 12, 13, 15, 17, 20, 21, 27, 42, 43, 49, 51, 52, 57, 58, 59, 62, 69, 71, 73, 74, 80, 83, 84, 89, 96, 100, 111]
- search(lst, 1) # 找到的数:1, 索引是:0
- search(lst, 51) # 找到的数:51, 索引是:15
- search(lst, 96) # 找到的数:96, 索引是:29
- search(lst, 120) # 没有找到该值
- def search(lst, num, left=None, right=None):
- left = left if left else 0
- right = len(lst)-1 if right is None else right
- mid = (right + left) // 2
- if left > right: # 循环结束条件
- print('没有找到该值')
- return None
- elif num < lst[mid]:
- return search(lst, num, left, mid - 1)
- elif num > lst[mid]:
- return search(lst, num, mid + 1, right)
- elif lst[mid] == num:
- print('找到的数:%s, 索引是:%s' % (lst[mid], mid))
- return mid
- lst = [1, 3, 4, 8, 11, 12, 13, 15, 17, 20, 21, 27, 42, 43, 49, 51, 52, 57, 58, 59, 62, 69, 71, 73, 74, 80, 83, 84, 89, 96, 100, 111]
- search(lst, 1) # 找到的数:1, 索引是:0
- search(lst, 51) # 找到的数:51, 索引是:15
- search(lst, 96) # 找到的数:96, 索引是:29
- search(lst, 120) # 没有找到该值
Python之常见算法介绍的更多相关文章
- Python之基础算法介绍
一.算法介绍 1. 算法是什么 算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.也就是说,能够对一定规范的输入,在有限时间内获得所要求的输 ...
- 算法与设计模式系列1之Python实现常见算法
preface 常见的算法包括: 递归算法 二分法查找算法 冒泡算法 插入排序 快速排序 二叉树排序 下面就开始挨个挨个的说说原理,然后用Python去实现: 递归算法 一个函数(或者程序)直接或者间 ...
- Python实现常见算法[2]——快速排序
#!/usr/bin/python # module: quik_sort.py def PARTION(L,m,n): base = L[n] i = m-1 j = m while j<n: ...
- Python实现常见算法[1]——冒泡排序
#!/usr/bin/python def BUBBLE_SORT(L, x, y): j = y while j>x: i = x while i<j: if L[i] > L[i ...
- Python实现常见算法[3]——汉罗塔递归
#!/usr/bin/python # define three list var. z1 = [1,2,3,4,5,6,7,"1st zhu"] z2 = ["2st ...
- H2O中的随机森林算法介绍及其项目实战(python实现)
H2O中的随机森林算法介绍及其项目实战(python实现) 包的引入:from h2o.estimators.random_forest import H2ORandomForestEstimator ...
- 如何用Python实现常见机器学习算法-1
最近在GitHub上学习了有关python实现常见机器学习算法 目录 一.线性回归 1.代价函数 2.梯度下降算法 3.均值归一化 4.最终运行结果 5.使用scikit-learn库中的线性模型实现 ...
- python 的常见排序算法实现
python 的常见排序算法实现 参考以下链接:https://www.cnblogs.com/shiluoliming/p/6740585.html 算法(Algorithm)是指解题方案的准确而完 ...
- Python的十种常见算法
十种排序算法 1. 常见算法分类 十种常见排序算法一般分为以下几种: (1)非线性时间比较类排序: a. 交换类排序(快速排序.冒泡排序) b. 插入类排序(简单插入排序.希尔排序) c. ...
随机推荐
- 【面向对象设计原则】之单一职责原则(SRP)
单一职责原则是面向对象原则五大原则中最简单,也是最重要的一个原则, 他的字面定义如下: 单一职责原则(Single Responsibility Principle, SRP): 一个类只负责一个功能 ...
- Android + https 实现 文件上传
package com.example.wbdream.zigvine; import android.annotation.SuppressLint; import android.app.Acti ...
- Android Picasso最详细的使用指南
Picasso 是Square 公司开源的Android 端的图片加载和缓存框架.Square 真是一家良心公司啊,为我们Android开发者贡献了很多优秀的开源项目有木有!像什么Rerefoit . ...
- 基于Git项目管理客户端SourceTree的免注册安装及远程连接方法
作为程序员,不可避免的要在github上查询代码,而在企业项目中,为了使得项目好管理需要使用项目管理客户端,所以接下来详细讲解一下基于git的sourceTree在windows系统下的安装及与Git ...
- idea连接服务器上传jar并运行
.打开idea tools-deployment-configuration.. .打开如图 点击 + 号 .如 ...
- WinServer-FTP搭建
FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务. FTP是File Transfer Protocol ...
- c/c++ linux epoll系列1 创建epoll
linux epoll系列1 创建epoll 据说select和poll的弱点是,随着连接(socket)的增加,性能会直线下降. epoll不会随着连接(socket)的增加,性能直线下降. 知识点 ...
- OV摄像头图像采集基础知识总结
目前FPGA用于图像采集 传输 处理 显示应用越来越多,主要原因是图像处理领域的火热以及FPGA强大的并行处理能力.本文以OV7725为例,对摄像头使用方面的基础知识做个小的总结,为后续做个铺垫. 下 ...
- 重装助手教你如何禁用Windows 10快速启动
快速启动是首先在Windows 8中实现并延续到Windows 10的功能,可在启动PC时提供更快的启动时间.它是一个方便的功能,也是大多数人在不知情的情况下使用的功能,但还有一些功能会在他们掌握新P ...
- 什么是tomcat集群?
什么是tomcat集群? 利用nginx对请求进行分流,将请求分配给不同的tomcat去处理,减少每个tomcat的负载量,提高服务器的响应速度. 目标 实现高性能负载均衡的tomcat集群. 工具 ...