三大简单的排序算法:
1:冒泡排序
def bubble_sort(array): """冒泡排序 基本思想:和名称很像,每次比较两个数,较大的数字上浮,每进行一遍,最后的数总是最大的 接着遍历其它的数字 实现方式:和基本思想一样 时间复杂度:等差数列求和, O(N^2) 空间复杂度:交换两个数组的时候需要额外的空间,O(1) 稳定性: 是稳定的算法,因为当两个数相等的时候,可以控制不让它进行交换 """ array_lenght = len(array) for i in range(0,array_lenght): for j in range(1,array_lenght-i): if array[j] < array[j-1]: array[j], array[j-1] = array[j-1], array[j] return array
2:选择排序
def selection_sort(array): """选择排序 基本思想:遍历n次,每次从未排序的数字当中选择最大的,放到末尾, 实现方式:每次遍历的时候把最小的数放在开头,这样开头部分有序,然后遍历未排序的部分 时间复杂度:等差数列,O(N^2) 空间复杂度: O(1) 稳定性:稳定的, 在寻找最小数的时候,如果和当前数相等,那么可以跳过 """ array_length = len(array) for i in range(0, array_length): min = i for j in range(i+1, array_length): if array[j] < array[min]: min = j array[i], array[min] = array[min], array[i] return array
3:插入排序
def insert_sort(array): """插入排序 基本思想:从第一个元素开始,认为已经排好序了,然后取出下一个元素,和前面的元素比较 ,如果比前面元素小,那么交换,直到没有比它小的元素。 实现方式:从低到高不断遍历,第i位以前都被认为是排序好的,将第i+1位往前不断交换插入前面 时间复杂度: 依赖数据状况 最好状况O(N), 最坏状况O(N^2) 空间复杂度: O(1) 稳定性: 稳定的, 当遇到和自己想同的元素的时候,不往前交换 """ array_length = len(array) for i in range(1, array_length): for j in range(i, 0, -1): if array[j] < array[j-1]: array[j], array[j-1] = array[j-1], array[j] else: break return array
三大排序的算法复杂度都是O(N^2)
递归算法:
当使用递归算法的时候,在系统当中其实使用的是栈的形式来实现的。当遇到一个递归函数的时候,会把它压入栈中,包括函数执行的行数,还有临时变量等等。
递归算法的复杂度
递归算法的时间复杂度:使用master方法来进行估算 a表示递归发生的次数,b表示子问题相对于原来问题的规模,$O(N^d)$表示剩余子程序的规模 $T(N) = a * T(\frac{N}{b}) + O(N^d)$
当$\log_ba > d$ 时间复杂度为$O(N^{\log_ba })$
当$\log_ba = d$ 的时候 时间复杂度$O(N^d * logN) $
当$\log_ba < d$ 的时候 时间复杂度$O(N^d)$
比如下面一个Java代码,使用递归求最大值,我们可以分析它的压栈的情况,也可以分析它的复杂度。在下面问题当中,因为出现了两次递归,所以a=2,子问题规模是原来的一半,所以b=2,剩余问题的复杂度为O(1),d=0,所以这里$\log_2 2 >0 $,所以算法复杂度为O(N)
public class getMax { public static int getMaxValue(int[] arr, int L, int R){ if(L == R){ return arr[L]; } int mid = (L+R)/2; int maxLeft = getMaxValue(arr, L, mid); int maxRight = getMaxValue(arr, mid+1, R); return Math.max(maxLeft, maxRight); } public static void main(String[] args){ int[] arr = {4, 3, 2, 1}; System.out.println(getMaxValue(arr,0,3)); } }
归并排序:
使用分治法和递归的解法引出来递归问题,归并问题的思路是这样的:要求一个数组a的排序,可以将数组分成两部分,将这两部分排完序以后合并一下,合并的策略是将两个数组都从左又进行遍历,大的放在数组里面,然后指针加1.
def merge_sort(array): """归并排序 使用分治法的思想来进行排序, 使用递归的方法来进行实现 时间复杂度:使用master公式 a=2, b=2。除去递归,其它问题的复杂度,也就是merge的复杂度o(n), 所以,整体的复杂度为O(n*logn) 空间复杂度:归并排序每次递归需要用到一个辅助表,长度与待排序的表相等, 虽然递归次数是O(log2n),但每次递归都会释放掉所占的辅助空间, 所以下次递归的栈空间和辅助空间与这部分释放的空间就不相关了,因而空间复杂度还是O(n) 稳定性: 稳定性算法。在merge的时候,如果两个值相等,可以控制让左边先进来,然后右边再进来。 """ if len(array) <= 1: return array mid = int(len(array)/2) left = merge_sort(array[:mid]) right = merge_sort(array[mid:]) return merge(left, right) def merge(left, right): """合并算法 刚开始并没与价差两个数组的长度,而是直接相减,直到有一个为为止 时间复杂度:o(n) 需要遍历一边left和right 空间复杂度为 o(n) 需要一个result数组来存储结果 """ result = [] l, r = 0, 0 while(l < len(left) and r < len(right)): if left[l] < right[r]: result.append(left[l]) l += 1 else: result.append(right[r]) r += 1 result += (left[l:]) result += (right[r:]) return result
小和问题
在一个数组中, 每一个数左边比当前数小的数累加起来, 叫做这个数组的小和。 求一个数组的小和
暴力解法,将每个数都遍历一边,时间复杂度为$O(N^2)$
可以借用上面的一些思想,要计算一组数的小和,只需要计算左边的小和加上右边的小和再加上合并时候的小和,合并时候的小和求解的时候数组已经排好序了,计算小和时可以用一些策略加速。
Java版本的代码如下
public static int samllSumCount(int[] arr){ if (arr == null || arr.length <2){ return 0; } return mergeSort(arr, 0, arr.length-1); } public static int mergeSort(int[] arr, int l, int r){ if (l == r){ return 0; } int mid = l + ((r-l) >> 1); //注意当a的值大于0的时候,>>1表示除以2,a的值小于0的时候, >>1 比除2小1 return mergeSort(arr, l, mid) + mergeSort(arr, mid+1, r) + merge(arr, l, mid, r); } public static int merge(int[] arr, int l, int m, int r){ /** * 其实就是在排序的过程当中插入了一些计算小数的代码 * 在计算的过程当中,其实i-m 和 m-r之间是已经排序好的数字 * 利用这一特点可以批处理完成小和的计算 */ int[] help = new int[r-l+1]; int p0 = l; int p1 = m+1; int res = 0; int i = 0; while(p0 <=m && p1 <= r){ if(arr[p0] < arr[p1]){ res += arr[p0] * (r-p1+1); help[i++] = arr[p0++]; } else{ help[i++] = arr[p1++]; } } while(p0 <= m){ help[i++] = arr[p0++]; } while(p1 <= r){ help[i++] = arr[p1++]; } for(i=0; i<help.length; i++){ arr[l+i] = help[i]; } return res; }