分治与排序

247 阅读5分钟

时间复杂度的第一性原理:

  1. 渐进分析
  2. 数学期望

分治与排序

渐进分析是时间复杂度分析的第一性原理,依托数学的渐进分析形成计算机程序复杂度分析系统。

渐进分析

计算机算法复杂度分析主要考虑最坏的运行时间

数学公式:

  1. 渐进上界
T(n)=O(f(n))当且仅当存在正常量cn0,使得对所有nn0T(n)cf(n)T(n)=O(f(n)) 当且仅当存在正常量c和n_0,使得对所有n \geq n_0 时 T(n) \leq c \cdot f(n)
  1. 渐进下界
T(n)=Ω(f(n))当且仅当存在正常量cn0,使得对所有nn0T(n)cf(n)T(n)=\Omega (f(n)) 当且仅当存在正常量c和n_0,使得对所有n \geq n_0 时 T(n) \geq c \cdot f(n)
  1. 渐进紧确界
T(n)=Θ(f(n))当且仅当存在正常量c1c2n0,使得对所有nn0T(n)=\Theta (f(n)) 当且仅当存在正常量c_1、c_2、n_0,使得对所有n \geq n_0 时
c1f(n)T(n)c2f(n),Ω(f(n))T(n)O(f(n))c_1 \cdot f(n) \leq T(n) \leq c_2 \cdot f(n),即\Omega (f(n)) \leq T(n) \leq O(f(n))

分治策略

  1. 分解:将一个问题划分成一些子问题
  2. 解决:递归解决这些子问题
  3. 合并:将子问题的解组合成原问题的解
乘法子问题
  1. 分解乘法子问题
123456781234 \cdot 5678
=(12100+34)(56100+78)=(12 \cdot 100 + 34) \cdot (56 \cdot 100 + 78)
[X1X2X3...Xn][Y1Y2Y3...Yn][X_1X_2X_3...X_n] \cdot [Y_1Y_2Y_3...Y_n]
=(a10n2+b)(c10n2+d)=(a \cdot 10^\frac{n}{2} + b) \cdot (c \cdot 10^\frac{n}{2} + d)
=(ac)10n+(ad+bc)10n2+(bd)=(a \cdot c)\cdot 10^n + (a \cdot d + b\cdot c)\cdot 10^\frac{n}{2} + (b\cdot d)
  1. 伪代码

    multiplication(x, y):
    	if (n==1):
        	return x*y
        make x as a*10^(n/2) + b
        make y as c*10^(n/2) + d
        
        a*c = multiplication(a,c)
        a*d = multiplication(a,d)
        b*c = multiplication(b,c)
        b*d = multiplication(b,d)
        
        return ac*10^n + (ad+bc)*10^(n/2) + bd
    
  2. 主定理

    LEVEL子问题个数子问题大小求解子问题操作次数该层总计操作次数
    01nc*nd1cnd
    1an/bc*(n/b)dac(n/b)d
    ...............
    tatn/(bt)c*(n/bt)datc*(n/bt)d
    ...............
    log2 nalog2 nn/(blogb n) = 1c*(n/blogb n)dalog2 n c*(n/blogb n)d

    总计

    T(n)=1cnd+ac(nb)d++atc(nbt)d++alogbnc(nblogbn)dT(n)=1*c*n^d+a*c*(\frac{n}{b})^d+\cdots+a^t*c*(\frac{n}{b^t})^d+\cdots+a^{\log_bn}*c*(\frac{n}{b^{\log_bn}})^d
    T(n)=cnd(1+abd++(abd)t++(abd)logbn)T(n)= c*n^d*(1+\frac{a}{b^d}+\cdots+(\frac{a}{b^d})^t+\cdots+(\frac{a}{b^d})^{\log_bn})
    T(n)=cndt=0logbn(abd)tT(n)= c*n^d*\sum_{t=0}^{\log_bn}{(\frac{a}{b^d})^t}
    =>T(n)={O(ndlnn)if:a=bdO(nd)if:a<bdO(nlogba)if:a>bd=> T(n)=\begin{cases} O(n^d*\ln n),if:a=b^d\\ O(n^d),if:a<b^d\\ O(n^{\log_ba}),if:a>b^d\\ \end{cases}
  3. 性能分析

    LEVEL子问题个数子问题大小求解子问题操作次数该层总计操作次数
    01n1O(1)
    14n/21O(4)
    ...............
    t4tn/(2t)1O(4t)
    ...............
    log2 n4log2 n =n211O(n2)

    总计 = O(1) + O(4) + ... O(n2) ==> O(n2)

    表达式:

    T(n)=4T(n2)+O(1) T(n) = 4*T(\frac{n}{2}) + O(1)
    a=4b=2c=0a>bd,按主定理得到如下: \forall a=4、b=2、c=0时a>b^d,按主定理得到如下:
    T(n)=O(nlogba)=O(nlog24)=O(n2) T(n)=O(n ^ {\log_ba}) = O(n ^ {\log_24}) = O(n ^ 2)
KAMTSUBA乘法子问题
  1. 子问题
[X1X2X3...Xn][Y1Y2Y3...Yn] [X_1X_2X_3...X_n] \cdot [Y_1Y_2Y_3...Y_n]
=(a10n2+b)(c10n2+d) =(a \cdot 10^\frac{n}{2} + b) \cdot (c \cdot 10^\frac{n}{2} + d)
=(ac)10n+(ad+bc)10n2+(bd) =(a \cdot c)\cdot 10^n + (a \cdot d + b\cdot c)\cdot 10^\frac{n}{2} + (b\cdot d)
=(ac)10n+((a+b)(c+d)acbd)10n2+(bd) =(a \cdot c)\cdot 10^n + ((a+b)(c+d)-ac-bd)\cdot 10^\frac{n}{2} + (b\cdot d)
  1. 伪代码

      multiplication(x, y):
      	if (n==1):
          	return x*y
          make x as a*10^(n/2) + b
          make y as c*10^(n/2) + d
          
          a*c = multiplication(a,c)
          b*d = multiplication(b,d)
          (a+b)*(c+d) = multiplication((a+b),(c+d))
          
          return ac*10^n + ((a+b)*(c+d)-ac-bd)*10^(n/2) + bd
    
  2. 性能分析

    LEVEL子问题个数子问题大小求解子问题操作次数该层总计操作次数
    01n1O(1)
    13n/21O(3)
    ...............
    t3tn/(2t)1O(3t)
    ...............
    log2 n3log2 n =n1.611O(n1.6)

    总计 = O(1) + O(3) + ... O(n1.6) ==> O(n2)

    表达式:

    T(n)=3T(n2)+O(1) T(n) = 3*T(\frac{n}{2}) + O(1)
    a=3b=2c=0a>bd,按主定理得到如下: \forall a=3、b=2、c=0时a>b^d,按主定理得到如下:
    T(n)=O(nlogba)=O(nlog23)=O(n1.6) T(n)=O(n ^ {\log_ba}) = O(n ^ {\log_23}) = O(n ^ {1.6})

排序

插入排序
  1. 维护一个不断增长的排序序列,每次循环将元素插入到指定正确的地方

  2. 伪代码

      InsertSort(Z):
    	for i in range(1,len(Z)):
    		curValue = Z[i]
    		j = i-1
    		while j>=0 and Z[j] > curValue:
    			Z[j+1] = Z[j]
    			j--
    		Z[j+1] = curValue
    
  3. 时间复杂度分析

    • for循环n次,while循环最多n次,所有最差为O(n2)
归并排序
  1. 使用了分治的思想

    • 每次将数组排序分为两个长度相等的数组排序的子问题
    • 递归求解子问题
    • 合并子问题的解(遍历两个数组,依次将最小值放入新的大数组中),并返回排好序的数组
  2. 伪代码

    mergerSort(Z):
    	n = len(Z)
    	if n <= 1:
    		return Z
    	L = mergerSort(Z[0:n/2])
    	R = mergerSort(Z[n/2:n])
    	return merger(L,R)
    merger(L,R):
    	newArray = array[len(L)+len(R)]
    	i=0,j=0
    	for t in newArray:
    		if L[i] < R[j]:
    			newArray[t] = L[i]
    			i++
    		else:
    			newArray[t] = R[i]
    			j++
    	return newArray;
    
  3. 性能分析

    LEVEL子问题个数子问题大小求解子问题操作次数该层总计操作次数
    01nc*nO(n)
    12n/2c*(n/2)O(n)
    ...............
    t2tn/(2t)c*(n/2t)O(n)
    ...............
    log2 n2log2 n =n1c*1O(n)

    总计 = O(n) + O(n) + ... O(n) ==> O(nlogn)

    表达式:

    T(n)=2T(n2)+O(n)T(n) = 2*T(\frac{n}{2}) + O(n)
    a=2b=2d=1a>bd,按主定理得到如下: \forall a=2、b=2、d=1时a>b^d,按主定理得到如下:
    T(n)=O(ndlog2n)=O(nlog2n)=O(nlog2n)T(n)=O(n ^ d*\log_2n) = O(n*\log_2n) = O(n\log_2n)
线性时间寻找第t大小数字
  1. 思路

    • 使用分治策略
      • 选择一个主元,围绕它划分子数组
      • 递归
      • 合并结果集
  2. 伪代码

    query (Z,t): //获取排序第t位数
    	if len(Z) == 1:
    		return Z[0]
    	pivot = medianOfmedian(Z) //获取中位数的中位数
    	L,R = grouping(Z,pivot) //将其进行分组
    	if len(L) == t-1: //尺判断过程会排除一部分(至少排除7/10),当层时间复杂度T(7n/10)
    		return pivot;
    	else if len(L) > t-1:
    		return query(L,t);
    	else if len(L) < t-1:
    		return query(R,t);
    grouping(Z,pivot)://按pivot将其进行分组,小在左,大在右,时间复杂度O(n)
    	L,R = [],[]
    	for i in Z:
    		if Z[i] == pivot:
    			continue
    		else if Z[i] < pivot:
    			add Z[i] to L
    		else :
    			add Z[i] to R
    medianOfmedian(Z)://递归获取中位数的中位数,每次分五组,当层时间复杂度T(5/n)
    	n = len(Z)
    	if n <= 5:
    		return getMedian[0]
    	one = medianOfmedian(Z[0:n/5])
    	two = medianOfmedian(Z[n/5:2n/5])
    	three = medianOfmedian(Z[2n/5:3n/5])
    	four = medianOfmedian(Z[3n/5:4n/5])
    	five = medianOfmedian(Z[4n/5:n])
    	getMedian(one,two,three,four,five);
    getMedian(A)://获取中位数
    	sort(A)
    	n = len (A)	
    	if n > 3 
    		return A[2];
    	else if n > 1 
    		return A[1];
    	else  if n > 0 
    		return A[0];
    
    1. 性能分析
      T(n)=T(15n)+T(710n)+O(n) T(n) = T(\frac{1}{5}n)+T(\frac{7}{10}n)+O(n)
      假设时间复杂度为O(n) 假设时间复杂度为O(n)
      T(n)=c(15n)+c(710n)+n T(n) = c*(\frac{1}{5}n)+c*(\frac{7}{10}n)+n
      T(n)=T(15n)+T(710n)+O(n) T(n) = T(\frac{1}{5}n)+T(\frac{7}{10}n)+O(n)
      T(n)cn T(n) \leq c*n
      ========> ========>
      c(15n)+c(710n)+ncn c*(\frac{1}{5}n)+c*(\frac{7}{10}n)+n\leq c*n
      c10 c \geq 10
      所以当c=10,n>1T(n)O(n) 所以当c=10,n>1时T(n)\leq O(n)
      时间复杂度为:O(n)时间复杂度为: O(n)

快速排序

统计基础知识

X是一个伯努利随机变量,其为1的概率为1/100,为0的概率为99/100

  • X的数学期望是多少

    E[X]=1(1100)+0(99100)=1100\Epsilon [X] = 1*(\frac{1}{100}) + 0*(\frac{99}{100}) = \frac{1}{100}
  • 抽取n个独立的随机变量,和的期望是多少?

    E[n=1nXi]=n=1nE[Xi]=n100\Epsilon [\sum_{n=1}^{n}{X_i}] = \sum_{n=1}^{n}{\Epsilon[X_i]} = \frac{n}{100}
  • 抽取n个随机变量,在看到第一个1的时候停止,令N为最后一次抽取的下标N的期望是多少?

    E[N]=1p=11100=100\Epsilon [N] = \frac{1}{p} = \frac{1}{\frac{1}{100}}=100
随机算法
  1. 算法中的某一部分涉及随机化操作
  2. 运行时间分析
    • A写了一个算法,B选择执行的输入,A执行这个算法
      • 运行时间是一个随机变量(依赖于算法执行过程中的随机性),我们可以求解期望运行时间
    • A写了一个算法,B选择执行的输入,同时B也决定算法运行过程中的随机性(每次都给同一个数)
      • 运行时间不是随机的(B每次都输入最坏的情况),我们可以求解最坏运行时间
快速排序
  1. 思路

    • 使用分治策略

      • 随机选择一个主元,围绕它划分子数组

      • 递归

      • 合并结果集

  2. 伪代码

quickSort(Z):
	if len(Z) = 1:
		return
	pivot = random(Z)
	L,R = grouping(Z,pivot) //将其进行分组
	Z = [L,pivot,R]
	quickSort(L)
	quickSort(R)
	return Z;
random(Z)://随机获取主元
	n = random(0,len(Z)-1)
	return Z[n];
grouping(Z,pivot)://按pivot将其进行分组,小在左,大在右,时间复杂度O(n)
	L,R = [],[]
	for i in Z:
		if Z[i] == pivot:
			continue
		else if Z[i] < pivot:
			add Z[i] to L
		else :
			add Z[i] to R
  1. 性能分析
    • 最坏
      • T(n) = T(n-1) + O(n) = O(n2)
    • 最好
      • T(n) = T(n/2) + T(n/2) + O(n) = O(nlogn)

排序算法下界

比较排序
  1. 归并排序 最坏情况 O(nlogn)
  2. 快速排序 最好情况 O(nlogn)
非比较排序
  1. 计数排序O(n)
    • 1、提前知道可能出现的值;2、输入的可能值数量有限
    • 1、按可能出现的值创建桶;2、遍历数组将值放入指定桶中;3、按顺序将指定桶中的值拿出来依次放入数组
  2. 基数排序O(n)
    • 1、待排序的是整数或者字符串;2、长度不超过一定的范围;
    • 1、使用计数排序按最低位开始放入指定的0~9的10个队列中;2、按顺序依次拿出所有的数;3、重复1和2直到最大位结束。