本专栏使用C语言编写数据结构
C语言专栏:C语言基本语法、基本操作、相关库函数的编写,相关内存分析
C++专栏:C++基本语法、C++相关结构剖析,详细例题,相关小型Demo的编写
数据结构专栏:基本数据结构原理介绍,代码实现,相关Leetcode例题讲解剖析
干货满满,陆续更新ing
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
算法(Algorithm)就是定义良好的计算过程,它取一个或一组的值为输入,并产生出一个或一组的值作为输出。
算法的时间复杂度是一个函数,算法中的基本操作的执行次数,为算法的时间复杂度
时间复杂度取所有情况中最坏的情况,最坏预期的估算
//请计算Func1中++count共执行多少次
void Func1(int N){
int count = 0;
for(int i = 0; i < N; i++){
for(int j = 0; j < N;j++){
++count;
}
}
for(int k = 0;k < 2 * N;++k){
++count;
}
int M = 10;
while(M--){
++count;
}
printf("%d\n",count);
}
上述代码的时间复杂度函数:F(N)=N*N+2*N+10
,由函数可得:随着N的变大,后两项对整个结果的影响变小
大O渐进表示法:O(N^2)
1. 用常数1取代运行时间中的所有加法常数
2. 在修改后的运行次数函数中,只保留最高阶项
3. 如果最高阶项存在且不是1,则去掉与这个项相乘的常数,得到的结果就是大O阶
void Func2(int N){
int count = 0;
for(int i = 0;i < 100;i++){
++count;
}
printf("%d\n",count);
}//O(1) --- 常数次
上述函数中:变量i循环执行了100次,count自增100次,为常数项,故为O(1)的时间复杂度。
void Func3(int N){
int count = 0;
for(int i = 0;i < 2 * N;i++){
++count;
}
int M = 10;
while(M--){
++count;
}
printf("%d\n",count);
}//O(N)
上述函数中:变量i循环执行了2N次,count自增2N次,N为变量,故时间复杂度为线性阶,为O(N)的时间复杂度。
void Func4(int M,int N){
for(int i = 0;i < M;i++){
++count;
}
for(int j = 0;j < N;j++){
++count;
}
}//O(M+N)
//M远大于N:O(M)
//M与N差不多大:O(N)或者O(M)
//N远大于M:O(N)
上述函数中:变量i循环执行了M次,count自增M次,j循环执行了N次,count自增N次,M、N为变量,故时间复杂度为线性阶。
const char* strchr(const char * str,int character);
while(*str){
if(*str == character){
return str;
}else{
++str;
}
}
return NULL;
//O(N)
上述函数中:将str遍历一次,str长度未知,设为变量N,故时间复杂度为线性阶。为O(N)的时间复杂度。
void BubbleSort(int * a,int n){
assert(a);
for(size_t end = n;end > 0;--end){
int exchange = 0;
for(size_t i = 1; i < end;++i){
if(a[i-1] > a[i]){
Swap(&a[i-1],&a[i]);
exchange = 1;
}
}
if(exchange == 0){
break;
}
}
}
//n n-1 n-2 n-3 n-4...2 1
//F(n) = (n-1+1)*n/2 --- 最坏情况
//F(n) = n-1
//O(n^2)
上述冒泡排序函数中:时间复杂度函数为::F(N)=(n-1+1)*n/2
时间复杂度考虑算法的最坏情况,对于冒泡排序,举例:若期望达到一个从左到右递增的数组序列,当原始数组序列为从左到右递减序列时,算法时间复杂度最大,将下标为0位置的最大元素右移至下标为n-1的位置时,Swap函数执行n-1次,依次将下标为1位置的元素右移至下标为n-2的位置时,Swap函数执行n-2次…直至得到期望数组序列,故时间复杂度为等差数列:(n-1) (n-2) (n-3) … 1求和。为O(N^2)的时间复杂度。
int BinarySearch(int* a,int n,int x){
assert(a);
int begin = 0;
int end = n;
while(begin < end){
int mid = begin + ((end-begin)>>1);//右移=除2,防止溢出
if(a[mid] < x){
begin = mid+1;
}else if(a[mid] > x){
end = mid;
}else{
return mid;
}
}
return -1;
}
//二分查找
//最好情况:O(1)
//最坏情况:假设折半了x次 1*2*2*2*...*2=n 共x个2 x=log2(n)
//O(logN)
上述折半查找函数中:假设数组中共有n个数据,折半进行了x次,时间复杂度函数为:x=log2(n)
时间复杂度考虑算法的最坏情况,对于折半查找,最坏情况为折半查找未查找到数据,即数组中全部元素全部折半查找一遍未查找到期望数据,不停的折半(数据个数除以2),将一组数据最终折半剩下最后一个数据都未查找到,假设折半次数为x次 1*2*2*2*...*2=n 共x个2
x=log2(n)。为O(logN)的时间复杂度。
long long Fac(size_t N){
if(0 == N){
return i;
}
//for(size_t i = 0; i < N; i++){
// printf("%d\n",i);
//}O(N*N)
//N N-1 N-2 ...
return Fac(N-1)*N;
}//O(N)
上述递归函数中:每次Fac调用时,N简易,直至N==0,故N自减了N次,为O(N)的时间复杂度。
递归算法的时间复杂度:
- 如果每次函数调用是O(1),就看函数的递归次数
- 如果每次函数调用不是O(1),就看函数递归调用次数及函数内部调用次数的累加
//斐波那契
long long Fib(size_t N){
if(N < 3){
return 1;
}
return Fib(N-1)+Fib(N-2);
}
//1+2+4+8....+2^(N-2)
//级别为:2^N
空间复杂度也是一个数学函数表达式,是对一个算法在运行过程中临时占用存储空间大小的量度
空间复杂度算的是变量的个数
使用大O渐进表示法
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定,空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
void BubbleSort(int * a,int n){
assert(a);
for(size_t end = n;end > 0;--end){
int exchange = 0;
for(size_t i = 1; i < end;++i){
if(a[i-1] > a[i]){
Swap(&a[i-1],&a[i]);
exchange = 1;
}
}
if(exchange == 0){
break;
}
}
}
//3个空间:exchange end i
//O(1) --- 常数
上述冒泡排序函数中:函数执行过程中共开辟三个变量空间:变量i,变量exchange,变量end,3为常数,故为O(1)的时间复杂度。
int missingNumber(int* nums,int numsSize){
int a[numsSize];//变长数组
//...
}
//O(N) --- 线性变化,所以为O(N)量级
上述函数中:函数执行过程中共开辟numsSize个变量空间,但numsSize的值不确定,为线性变化,故为O(N)的时间复杂度。
long long* Fibonacci(size_t n){
if(n==0){
return NULL;
}
long long* fibArray = (long long*)malloc((n+1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for(int i = 2;i <= n;i++){
fibArray[i] = fibArray[i-1] + fibArray[i-2];
}
return fibArray;
}
//n+1 指针fibArray i
//O(N)
上述函数中:函数执行过程中共开辟n+1个变量空间,但n的值不确定,为线性变化,故为O(N)的空间复杂度。
long long Fac(size_t N){
if(N==1){
return 1;
}
return Fac(N-1)*N;
}
//建立栈帧
//O(N)
long long Fib(size_t N){
if(N < 3){
return 1;
}
return Fib(N-1) + Fib(N-2);
}
//O(N)
上述函数中:函数执行过程中建立栈帧,递归调用结束后释放空间,故为O(N)的空间复杂度。
时间是累计的,不可复用,空间可以重复利用
递归函数中空间使用栈帧,每次递归调用后空间释放
5201314 | O(1) | 常数阶 |
---|---|---|
3n+4 | O(N) | 线性阶 |
3n^2+4n+5 | O(N^2) | 平方阶 |
3log(2)n+4 | O(logN) | 对数阶 |
2n+3nlog(2)n+14 | O(NlogN) | NlogN阶 |
n^3 +2n^2+4n+6 | O(N^3) | 立方阶 |
2^n | O(2^N) | 指数阶 |