数据结构——绪论、时间复杂度

数据结构笔记目录:

1. 绪论、时间复杂度
2. 线性表
3. 树
4. 图
5. 查找
6. 排序


0. 基本概念

在这里插入图片描述

0.1 数据类型

值+操作的集合

  • 原子类型
  • 结构类型
  • 抽象数据类型(ADT):可用于定义一个完整数据类型 、 数据结构

ADT

一个数学模型及定义在该模型上的一组操作

ADT{
	数据对象:<数据对象的定义> D
	数据关系:<数据关系的定义> S
	基本操作:<基本操作的定义> P
}

0.2 数据结构

存在一种或多种特定关系(逻辑结构)的数据元素集合

  • 逻辑结构 独立于 存储结构
数据结构
逻辑结构
存储结构
对数据的运算

四种逻辑结构

  • 集合
  • 线性结构
  • 树形结构
  • 网状结构

两种存储结构

  • 顺序存储
  • 链式存储
  • (哈希)
  • (索引)

在这里插入图片描述

0.3 算法

特定问题的求解步骤

特性

  • 有穷性
    • 程序 ≠ \neq = 算法
      • 程序不一定有穷:死循环,操作系统
  • 确定性
  • 可行性
  • 输入
  • (至少一个)输出

目标

  • 正确
  • 可读
  • 健壮
  • 高效低存储

时间复杂度的简单计算方法

参考数据结构实现的黑书

1. 时间复杂度

1.1 概念

算法的渐进时间复杂度,简称时间复杂度

事前分析

  1. 算法采用的策略和方案
  2. 编译产生代码质量
  3. 问题的输入规模
  4. 机器执行指令的速度

将核心操作的次数与输入规模关联

1.1.1 函数渐进增长

给定两个函数 f ( n ) f(n) f(n) g ( n ) g(n) g(n) ,如果存在一个整数 N N N 使得对于所有 n > N n >N n>N , f ( n ) f(n) f(n) 总是比 g ( n ) g(n) g(n) 大,那么称 f ( n ) f(n) f(n) 的渐进增长快于 g ( n ) g(n) g(n)

在这里插入图片描述

  • 算法函数中的常数、系数可以忽略
  • 算法函数中最高次幂越小,算法效率越高

1.1.2 大O记法

语句总的执行次数 T ( n ) T(n) T(n) 是关于问题规模 n n n 的函数,进而分析 T ( n ) T(n) T(n) 随着 n n n 的变化情况并确定 T ( n ) T(n) T(n) 的量级。记为 T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n))

表示随着问题规模 n n n 的增大,算法执行时间的增长率和 f ( n ) f(n) f(n) 的增长率相同

  • f ( n ) f(n) f(n) 表示问题规模 n 的某个函数

  • 执行次数    ⟺    \iff 执行时间

规则

  • 用1代替运行时间中的所有常数、系数
  • 在修改后的次数中,只保留高阶项

1-100求和

# 累加
int main(){
	int sum = 0;//执行一次
	int n = 100;//执行一次
	for(int i = 0; i < n;++i){//判断语句执行n+1次
		sum += i;//执行n次数
	}	

	printf("sum=%d",sum);
}
# 执行 2n+3 次
# 大O记法 O(n)
# -------------------------------------------
# 高斯公式
void main(){
	int sum = 0;//执行1次
	int n = 100;//执行1次
	sum = (n+1)n/2;//执行1次
	printf("sum=%d",sum);
}
# 执行3次
# 大O记法 O(1)
最坏情况

直接查找

int search(int num){
	int arr[] = [11,10,8,9,7,22,23,0];
	
	for(int i = 0;i < arr.length;++i){
		if(num == arr[i])
			return i;
	}

	return -1;
}

最好情况:

  • 查找的第一个数字就是期望数字—— O ( 1 ) O(1) O(1)

最坏情况:

  • 查找的最后一个数字,才是期望数字—— O ( n ) O(n) O(n)

平均情况:

  • 任何数字查找的平均代价为 —— O ( n ) O(n) O(n)

1.1.3 时间复杂度排序

O ( 1 ) < O ( l o g n ) < O ( n l o g n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1) < O(logn) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1)<O(logn)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

1.2 简单时间复杂度分析

1.2.1 非函数调用

  1. 线性阶

    非嵌套线性阶,随输入规模的扩大,呈 线性增长

    int sum = 0;//执行一次
    int n = 100;//执行一次
    for(int i = 0; i <= n;++i){//执行n+1次
    	sum += i;//执行n次数
    }
    
  2. 平方阶

    二重循环嵌套

    int sum = 0;
    int n = 100;
    for(int i = 1;i <= n;++i){//执行 n+1 次
    	for (j = 1;j <= n;++j){//执行 n*(n+1) 次
    		sum += i;//执行 n^2 次
    	}
    }
    
  3. 立方阶

    三重循环嵌套

  4. 对数阶

    对数阶,由于输入规模n的增大,不管底数多少,增长率都相同,所以忽略底数

    int i=1,n=100;
    while(i < n){
    	i = i*2;
    }
    

    循环次数: x = l o g ∗ 2 n x = log*2n x=log2n
    时间复杂度: O ( l o g n ) O(logn) O(logn)

  5. 常数阶

1.2.3 函数调用

void main(){
    int n = 100;
    for(int i = 0;i < n;++i){
	    show(i);	// O(n)
    }
}

void show(int i){
    printf("%d",i);
}
void main(){
	int n = 100;
	for(int i = 0;i < n;++i){
		show(i);	//O(n^2)
	}
}

void show(int i){
	for(int j = 0;j < i;++j){		
		printf("%d",i);
	}	
}

1.3 计算方法

1.3.0 常规

  • for 循环
  • 一次 for 循环至多是该 for 循环包含的 内语句运行次数迭代次数
  • 嵌套的 for 循环
  • 从里到外分析循环
  • 顺序语句
  • 各语句运行时间求和
  • if/else 语句
  • 两执行函数中运行时间长的

1.3.1 循环主体中的变量参与循环条件的判断

找出主体语句中与 T ( n ) T(n) T(n) 成正比的循环变量,将之代入条件运算

int i = 1;
while(i <= n){
	i = i*2;
}
  • 执行次数 t t t ,有 2 t ≤ n 2^t \leq n 2tn ,得 T ( n ) = O ( l o g ∗ 2 n ) T(n) = O(log*2n) T(n)=O(log2n)
int y = 5;
while((y+1)*(y+1)<=n){
	y = y+1;
}
  • 执行次数 t = y − 5 t = y-5 t=y5 ,有 y = t + 5 y=t+5 y=t+5,代入得 t < n − 6 t < \sqrt{n}-6 t<n 6,即 T(n) = O( n \sqrt{n} n )
运算时间中的对数
  • 如果一个算法用时间 O ( 1 ) O(1) O(1) 将问题的大小削减为原来的一部分,那么该算法就是 O ( l o g N ) O(logN) O(logN)
  • 如果用常数时间把问题减少一个常数,那么这种算法就是 O ( N ) O(N) O(N)
折半查找
int BinarySearch(const ElementType A[],ElementType X,int N){
	int Low,Mid,High;
	Low = 0,High = N-1;
	while(Low <= High){
		Mid = (Low+High)/2;
		if(A[Mid] < X){
			Low = Mid + 1;
		}else{
			if(A[Mid] > X){
				High = Mid-1;
			else
				return Mid; //Found
		}
	}
	return NotFound; //NotFound is defined as -1
}

分析:提供了 O ( l o g N ) O(logN) O(logN) 以内的查找操作

欧几里得算法

定理:如果 M > N M > N M>N ,则 KaTeX parse error: Undefined control sequence: \mbox at position 3: M \̲m̲b̲o̲x̲{ mod N} < \fra…

unsigned int Gcd(unsigned int M,unsigned int N){
	unSigned int Rem;
	while(N > 0){
		Rem = M % N;
		M = N;
		N = Rem;
	}
	return M;
}
幂运算 O ( l o g n ) O(logn) O(logn)
long int Pow(long int X,unsigned int N){
	if(N == 0)
		return 1;
	if(N == 1)
		return X;
	if(isEven(N))
		return Pow(X*X,N/2);
	else 
		return Pow(X*X,N/2) * X;
}

1.3.2 循环主体中的变量与循环条件无关

采用 归纳法累计循环次数

多层循环从内到外分析,忽略单步语句、条件判断语句,只关注主体语句的执行次数

递归程序——分治策略
递归程序四条准则
  • 基准情形:有结束条件
  • 不断推进:每次调用都朝向基准情形推进
  • 设计法则:假设所有的递归调用都能进行
  • 合成效益法则:避免重复计算
“分治”策略
  • ”分“:将大问题大致分为两大致相等的 幂次阶 子问题,用递归求解
  • “治”:将两个子问题的解合并到一起并可能再做些少量的附加工作(幂次阶),得到整个问题的解

主方法 :大小为 n n n 的原问题分成若干个大小为 n b \frac{n}{b} bn 的子问题,其中 a a a 个子问题需要求解,而 c n k cn^k cnk 是合并各个子问题需要的工作量。此时问题可表示为:
T ( N ) = { c n = 1 a T ( N b ) + c n k n > 1 T(N)=\begin{cases} c & n=1\\ aT(\frac{N}{b}) + cn^k & n>1 \end{cases} T(N)={caT(bN)+cnkn=1n>1

则其时间复杂度可相应的表示为:

T ( N ) = { O ( n l o g b a ) a > b k O ( n k l o g b n ) a = b k O ( n k ) a < b k T(N)=\begin{cases} O(n^{log_ba}) &a>b^k \\ O(n^klog_bn) &a=b^k \\ O(n^k) &a<b^k \end{cases} T(N)= O(nlogba)O(nklogbn)O(nk)a>bka=bka<bk

推导

已知

{ T ( 1 ) = 1 T ( N ) = 2 T ( N 2 ) + O ( N ) \begin{cases} T(1) = 1 \\ T(N) = 2T(\frac{N}{2}) + O(N) \end{cases} {T(1)=1T(N)=2T(2N)+O(N)

1. 等号右边连续代入递归关系
T ( N ) = 2 T ( N 2 ) + N = 2 [ 2 T ( N 4 ) + N 2 ] + N = 2 { 2 [ 2 T ( N 8 ) + N 4 ] + N 2 } + N = . . . = 2 k T ( N 2 k ) + k N \begin{align} T(N) & = 2T(\frac{N}{2})+N \notag \\ &= 2[2T(\frac{N}{4})+\frac{N}{2}]+N \notag \\ & =2\{2[2T(\frac{N}{8})+\frac{N}{4}]+\frac{N}{2}\}+N \notag \\ & = ... \notag \\ & = 2^kT(\frac{N}{2^k}) + kN \notag \end{align} T(N)=2T(2N)+N=2[2T(4N)+2N]+N=2{2[2T(8N)+4N]+2N}+N=...=2kT(2kN)+kN

k = l o g N k = logN k=logN

T ( n ) = N T ( 1 ) + N l o g N = N l o g ( N ) + N T(n) = NT(1)+NlogN = Nlog(N) + N T(n)=NT(1)+NlogN=Nlog(N)+N

2. 叠缩求和

N 去除递归关系中的两边,不断替换

T ( N ) N = T ( N 2 ) N 2 + 1 T ( N 2 ) N 2 = T ( N 4 ) N 4 + 1 T ( N 4 ) N 4 = T ( N 8 ) N 8 + 1 ⋮ ⋮ T ( 2 ) 2 = T ( 1 ) 1 + 1 \begin{align} \frac{T(N)}{N} &= \frac{T(\frac{N}{2})}{\frac{N}{2}} + 1 \notag \\ \notag \\ \frac{T(\frac{N}{2})}{\frac{N}{2}} &= \frac{T(\frac{N}{4})}{\frac{N}{4}} + 1 \notag \\ \notag \\ \frac{T(\frac{N}{4})}{\frac{N}{4}} &= \frac{T(\frac{N}{8})}{\frac{N}{8}} + 1 \notag \\ \notag \\ \vdots \notag \\ \vdots \notag \\ \notag \\ \frac{T(2)}{2} &= \frac{T(1)}{1} + 1\notag \\ \notag \\ \end{align} NT(N)2NT(2N)4NT(4N)2T(2)=2NT(2N)+1=4NT(4N)+1=8NT(8N)+1=1T(1)+1

将等号左边的所有相相加等于右边所有项的和,结果为

T ( N ) N = T ( 1 ) 1 + l o g N = N l o g ( N ) + N \begin{align} \frac{T(N)}{N} &= \frac{T(1)}{1} + logN \notag \\ \notag \\ &=Nlog(N) + N \notag \end{align} NT(N)=1T(1)+logN=Nlog(N)+N

1.4 常见算法时间复杂度总结

描述增长的数量级说明举例
常数级1普通语句将两个数相加
对数级 l o g N logN logN二分策略二分查找
线性级 N N N单层循环找出最大元素
线性对数级 N l o g N NlogN NlogN分治思想归并排序
平方级 N 2 N^2 N2双层循环检查所有元素对
立方级 N 3 N^3 N3三层循环检查所有三元组
指数级 2 N 2^N 2N穷举查找检查所有子集

1.5 最大子序列和问题的解

1.5.1 遍历所有子串,对子串的子序列依次求和

int MaxSubSequenceSum(const int A[],int N){
	int ThisSum,MaxSum,i,j,k;
	MaxSum = 0;
	for(i = 0;i < N;++i){
		for(j = i;j < N;++j){//遍历所有子串
			ThisSum = 0;
			for(k = i;k <= j;++k)//当前子串的所有子序列求和
				ThisSum += A[k];
				if(ThisSum > MaxSum)
					MaxSum = ThisSum;	
		}
	}
	return MaxSum;
}

∑ i = 0 N − 1 ∑ j = i N − 1 ∑ k = i j 1 ∑ k = i j 1 = j − i + 1 ∑ j = i N − 1 j − i + 1 = ( N − i + 1 ) ( N − i ) 2 ∑ i = 0 N − 1 ( N − i + 1 ) ( N − i ) 2 = ∑ i = 1 N ( N − i + 1 ) ( N − i ) 2 = 1 2 ∑ i = 1 N i 2 − ( N + 3 2 ) ∑ i = 1 N i + 1 2 ( N 2 + 3 N + 2 ) ∑ i = 1 N 1 = N 3 + 3 N 2 + 2 N 6 \begin{align} \sum_{i = 0}^{N-1}{\sum_{j = i}^{N-1}{\sum_{k = i}^{j}{1}}} \notag \\ \sum_{k = i}^{j}{1} &= j-i+1 \notag \\ \sum_{j = i}^{N-1}{ j-i+1} &= \frac{(N-i+1)(N-i)}{2} \notag \\ \notag \\ \sum_{i = 0}^{N-1}{ \frac{(N-i+1)(N-i)}{2}} & = \sum_{i = 1}^{N}{ \frac{(N-i+1)(N-i)}{2}} \notag \\ \notag \\ &= \frac{1}{2}\sum_{i=1}^{N}i^2-(N+\frac{3}{2})\sum_{i=1}^{N}i + \frac{1}{2}(N^2+3N+2)\sum_{i = 1}^{N}1 \notag \\ \notag \\ &= \frac{N^3+3N^2+2N}{6} \notag \end{align} i=0N1j=iN1k=ij1k=ij1j=iN1ji+1i=0N12(Ni+1)(Ni)=ji+1=2(Ni+1)(Ni)=i=1N2(Ni+1)(Ni)=21i=1Ni2(N+23)i=1Ni+21(N2+3N+2)i=1N1=6N3+3N2+2N

时间复杂度为 O ( N 3 ) O(N^3) O(N3)

1.5.2 记录中间累加量

∑ k = i j A k = A j + ∑ k = i j − 1 A k \sum_{k = i}^{j}{A_k} = A_j+\sum_{k = i}^{j-1}{A_k} k=ijAk=Aj+k=ij1Ak

int MaxSubSequenceSum(const int A[],int N){
	int ThisSum,MaxSum,i,j,k;
	
	MaxSum = 0;
	for(i = 0;i < N;++i){
		ThisSum = 0;
		for(j = i;j < N;++j){//遍历所有子串
			ThisSum += A[k];

			if(ThisSum > MaxSum){
				MaxSum = ThisSum;
			}				
	}
	return MaxSum;
}

时间复杂度为 O ( N 2 ) O(N^2) O(N2)

1.5.3 分治法

将序列分成大致相等的两部分。

最大子序列和可能在三处出现:

  • 数据的左半部分;
  • 递归求解
  • 数据的右半部分;
  • 递归求解
  • 中间部分
  • 分别求出前、后部分的最大和,相加中间部分最大和
int MaxSubSum(const int A[],int Left,int Right){
	int MaxLeftSum,MaxRightSum;
	int MaxLeftBorderSum,MaxRightBorderSum;
	int LeftBorderSum,RightBorderSum;
	int Center,i;

	if(Left == Right){//只有一个元素
		if(A[Left] > 0)//该元素非负即为最大和
			return A[Left];
		else{
			return 0;
		}
	}
	
	Center = (Left+Right) / 2;
	MaxLeftSum = MaxSubSum(A,Left,Center);
	MaxRightSum = MaxSubSum(A,Center,Right);

	MaxLeftBorderSum = 0,LeftBorderSum = 0;
	for(i = Center;i >= Left;i--){
		LeftBorderSum += A[i];
		if(LeftBorderSum > MaxLeftBorderSum)
			MaxLeftBorderSum = LeftBorderSum;
	}

	MaxRightBorderSum = 0,RightBorderSum = 0;
	for(i = Center;i >= Left;i--){
		RightBorderSum += A[i];
		if(RightBorderSum > MaxRightBorderSum)
			MaxRightBorderSum = RightBorderSum;
	}

	return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
}

int MaxSubSequenceSum(Const A[],int N){
	return MaxSubSum(A,0,N-1);
}

{ T ( 1 ) = 1 T ( N ) = 2 T ( N / 2 ) + O ( N ) \begin{cases} T(1) = 1 \\ T(N) = 2T(N/2) + O(N) \end{cases} {T(1)=1T(N)=2T(N/2)+O(N)

时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

分治法 时间复杂度一般都是若干个代价为 l o g N logN logN 的子问题与一个处理函数的代价和

1.5.4 最简

int MaxSubSequenceSum(const int A[],int N){
	int ThisSum,MaxSum,j;	
	ThisSum = MaxSum = 0;
		for(j = 0;j < N;++j){
			ThisSum += A[k];

			if(ThisSum > MaxSum){
				MaxSum = ThisSum;
			}else if(ThisSum < 0){
				ThisSum = 0;
			}				
	}
	return MaxSum;
}

时间复杂度为 O ( N ) O(N) O(N)

空间复杂度

算法所需的辅助空间数量级

  • 运行过程中变量占用的空间 辅助数组
  • 递归工作栈 递归深度

原地工作

算法所需的辅助空间为常量

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AmosTian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值