数据结构&复杂度

数据结构

什么是数据结构?

什么是算法?算法效率

数据结构和算法的重要性

时间&空间复杂度

什么是数据结构
  • 数据结构的本质就是在内存中以某一种组织管理数据(帮我们管理数据),这种组织其实就比如说是树形,图形等组织。管理数据其实就是进行增删改查的操作(数据结构其实就是计算机存储组织数据的方式,是指相互之间存在一种或者多种特定关系的数据元素的集合)
什么是算法
  • 算法:就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果(排序,二分查找,其实这些都是算法)
  • 数据结构和算法是相辅相成的,二者是我中有你、你中有我的关系:在一个数据结构中可能会用到算法来优化,一个算法中也可能用到数据结构来组织数据
算法效率
  • 算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,空间效率被称为空间复杂度。时间复杂度衡量的是一个算法的运行速度,而空间复杂度主要是衡量一个算法所需要的额外空间
  • 在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是在乎;但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度;所以我们如今已经不需要再特别关注一个算法的空间复杂度,而更注重于时间复杂度
时间复杂度
  • 时间复杂度,说白了,就是一个函数表达式
  • 写一个算法,其实最看重的就是这个算法的时间效率(快不快)和空间效率(占用的空间多不多)
  • 时间复杂度的定义:算法的时间复杂度是一个函数(这里的函数指的是数学中的函数表达式,不是C语言中的函数,表达式衡量的是跑了多少次),它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上讲是不能算出来的,只有把程序放在机器上跑起来的时候,才能知道,但是我们也不能每个程序都要上机去跑吧,很麻烦,所以出现了时间复杂度这个衡量的方法。一个算法所耗费的时间与其中语句的执行次数成正比,算法中的基本操作次数,为算法的时间复杂度
  • 为什么不用运行的时间去衡量算法的时间复杂度而用语句的执行次数去衡量算法的时间复杂度呢?------原因在于:算法运行的话,可能在不同的系统上去运行,每一台电脑的配置和环境都不太相同,那么运行同一个程序的时间也就会产生差别,那么放在一个系统下行不行呢?结果是也不行的,因为操作系统在运行的时候,也不是只在单纯的运行这一个程序,运行的程序不同的话,时间也会有差异的。所以时间复杂度还是用基本语句关于数据规模N的语句来表示,然后紧接着使用大O渐进表示法就可以了
递归算法时间复杂度求解的方法:
  • 单次调用中执行次数*总递归次数
下面的++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", count);
}

  • Func1执行的次数是:F(N)=N^2+2*N+10
  • N=10,结果为130
  • N=100,结果为10210
  • N=1000,结果为1002010
  • 在实际的计算中,我们并不一定要计算出精确的数字,只需要知道大概的数字就可以了,所以我们使用大O渐进表示法,大O表示的其实就是一个估算值
大O渐进表示法
  • 大O符号适用于描述函数渐进行为的数学符号(估算值)
推导大O阶方法:
  • 用常数1取代运行时间中的所有加法常数
  • 在修改后的运行次数函数中,只保留最高阶
  • 如果最高阶存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。
#include<stdio.h>
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i);
	}
	return 0;
}
  • 上述代码中,语句执行的次数是10次,是一个常量,那么这个程序的时间复杂度是O(1)
时间复杂度有三种情况
  • 最好情况:任意输入规模的最小运行次数(下界)
  • 最坏情况:任意输入规模的最大运行次数(上界),一般来说,都是看最坏情况,这样的话,可以在一定程度上降低预期
  • 平均情况:任意输入规模的期望运行次数
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int find = 0;//这里的数字可以随便给
	for (i = 0; i < sz; i++)
	{
		if (arr[i] == find)
			return i;
	}
	return -1;
}
  • 比如说我要找1,1是数组中的第一个元素,只需要比较一次就可以找到了,那么此时,就是最好的情况,因为只用了常数次,如果找遍了整个数组,都没有找到我希望找到的那个数字,那么就是最坏情况(因为已经比较了size次了)
  • 平均情况下:算出总的比较次数去除以数据的个数(1+2+3+…+N)/N;结果就是N/2;
  • 一般情况下都用最坏情况来看你时间复杂度,因为如果最坏情况我都能承受,那么最好情况和平均情况我也是可以承受的。所以数组中搜索数据的时间复杂度为O(N);
实例1:时间复杂度为O(N)
void Func2(int N)
{
	int count = 0;
	for (int k = 0; k < 2 * N; k++)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
	printf("%d", count);
}
实例2: 时间复杂度为O(M+N)
void Func3(int N, int M)
{
	int count = 0;
	for (int k = 0; k < M; k++)
	{
		++count;
	}
	for (int k = 0; k < N; k++)
	{
		++count;
	}
}
实例3:时间复杂度为O(1)
void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; k++)
	{
		++count;
	}
	printf("%d", count);
	return 0;
}
实例4: 冒泡排序,时间复杂度为O(n^2)
  • 冒泡排序的空间复杂度为O(1)的原因在于,虽然冒泡排序中有设计数组相关的东西,但是这个数组的存在,并不是和我排序强相关的,换一句话说,其实就是,这个数组并不是我为了排序而去开辟的空间,而是我排序是作用在这个空间上的,所以就是没有额外开辟其他的空间,所以冒泡排序的空间复杂度是O(1)
  • 具体执行次数:时间复杂度计算时以最坏情况为准,则假设数组开始是逆序的,那么第一次排序执行 n-1 次,第二次排序执行 n-2 次,第三次执行 n-3 次 … …,直到最后达成有序,所以冒泡排序的具体执行次数是一个等差数列,具体次数 = (首项+尾项)/2*项数 = (N^2-N)/2
  • 用大O的渐进表示法得出时间复杂度:O(N^2)
//冒泡排序的执行次数其实就是一个等差数列,算出来的结果其实就是N*(N-1)/2
//所以冒泡排序的复杂度是N方
#include<stdio.h>
int main()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,10 };
	int i = 0;
	int j = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
  • 冒泡排序的优化
#include<stdio.h>
int main()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,10 };
	int i = 0;
	int j = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = 0;
			}
		}
		if (flag == 1)
			break;
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
  • 封装成函数的冒泡排序
#include<stdio.h>
void BubbleSort(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
void Print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	BubbleSort(arr, sz);
	Print(arr,sz);
	return 0;
}

在这里插入图片描述

实例5:二分查找 时间复杂度为O(logn)
  • 具体执行次数:和上面一样,这里考虑最坏情况,即数组中没有想找的元素,数组会从中间开始查找,每次排除掉一半的元素,直到把所有元素排除完,第一次排除后剩下 1/2 的元素,第二次排除后剩下 1/4 元素,第三次排除后剩下 1/8 元素 … …,设元素个数为N,查找次数为X,则 X * (½)^N = 1 -> (½)^N = X -> 具体次数:X = log2N
  • 用大O的渐进表示法得出时间复杂度:O(logN),注:因为在键盘上无法表示出log的底数,所有在时间复杂度中把log的底数2省略掉了,直接用logN表示log2N的时间复杂度。
//二分查找的思想其实就是查找一次就会减少一半的数据,那么一直查找的话其实就相当于是
//N/2/2/2/2/2/...=1
//假设找了x次,那么其实就是除了x个2
//2的x次方=N
//x就是logN
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr)[0];
	int key = 8;
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{
		int mid = left + (right - left) / 2;
		if (arr[mid] > key)
		{
			right = mid - 1;
		}
		else if (arr[mid] < key)
		{
			left = mid + 1;
		}
		else
		{
			printf("找到了,下标为:%d", mid);
			break;
		}
	}
	return 0;
}
实例6:求阶乘 是一个递归的程序,递归程序一共递归了N+1次,所以时间复杂度为O(N)

在这里插入图片描述

  • 空间复杂度是O(N),因为其实相当于是要去涉及都栈帧的问题,所以空间复杂度是O(N)
int Fac(int N)
{
	if (0 == N || 1 == N)
		return 1;
	else
		return N * Fac(N - 1);
}
//单次比较总共进行了1次,递归一共是N+1次
  • 下面这个算法的时间复杂度是O(N方)
    在这里插入图片描述
  • 实例7: 斐波那契数列
    在这里插入图片描述
  • 具体次数:以上图为例,我们可以看到在数值大于2的时候,每一层的调用次数是以2的指数形式增长的,是一个等比数列
//时间复杂度为O(2^N)
int Fib(long long N)
{
	if (N < 3)
		return 1;
	else
		return Fib(N - 1) + Fib(N - 2);
}
  • 斐波那契的递归形式和循环形式(递归的时间复杂度为2的N次方,本质上其实就是一个等比数列,fib展开其实就是一个二叉树,算的就是一共执行了多少次fib,算下来的结果就是2的n次方)

循环形式 时间复杂度为O(N),循环一共走了N-3次

#include<stdio.h>
long long Fib(int N)
{
	long long a = 1;
	long long b = 1;
	long long c = 1;
	for (int i = 3; i <= N; i++)
	{
		c = a + b;
		a = b;
		b = c;
	}
	return c;
}
int main()
{
	printf("%lld", Fib(10));
	return 0;
}
  • 递归形式
#include<stdio.h>
long long Fib(long long a, long long b, int N)
{
	if (N < 3)
		return 1;
	else if (3 == N)
		return a + b;
	else
		return Fib(b, a + b, N - 1);
}
int main()
{
	printf("%lld", Fib(1,1,10));
	return 0;
}

在这里插入图片描述

时间复杂度和空间复杂度都是关于问题规模N的数学表达式,基本语句的执行次数—时间复杂度;创建变量(对象的个数)—空间复杂度
空间复杂度
  • 空间复杂度是对一个算法在运行过程中临时占用存储空间的大小(占用额外空间的大小)
  • 空间复杂度最常见的情况就是0(1)
合并两个有序数组,合并之后的数组依然有序
void Merge(int arr1[], int arr2[], int size1, int size2)
{
	int index1 = 0;
	int index2 = 0;
	int index = 0;
	int* temp = (int*)malloc(sizeof(int) * (size1 + size2));   //temp为动他开辟的辅助空间
	//保证index1和index2下标合法
	while (index1 < size1 && index2 < size2)
	{
		if (arr1[index1] < arr2[index2])
			temp[index++] = arr1[index1++];
		else
			temp[index++] = arr2[index2++];
	}
	while (index1 < size1)  //说明数组1还有剩余而数组2没有剩余了
	{
		temp[index++] = arr1[index1++];
	}
	while (index2 < size2)
	{
		temp[index++] = arr2[index2++];
	}
	return temp;
}
时间复杂度在实际中的应用
  • 这里题目的要求中,要求在O(N)的时间内完成算法
    在这里插入图片描述
  • 上面的题目一个很容易想到的思路其实就是先排序,然后再去遍历查找,但是这种思路不符合题目中要求的复杂度,所以暂时先不考虑这种思路去对题目进行解答,但是代码如下:
class Solution {
public:
    int missingNumber(vector<int>& nums) 
    {
        int i=0;
        sort(nums.begin(),nums.end());
        for(i=0;i<nums.size();)
        {
            if(nums[i]==i)
                i++;
            else
                break;
        }
        //return i;
        return i;
    }
};
  • 异或的解题思路
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int res = 0;
        int n = nums.size();
        for (int i = 0; i < n; i++) {
            res ^= nums[i];
        }
        for (int i = 0; i <= n; i++) {
            res ^= i;
        }
        return res;
    }
};
  • 还有一种思路,就是利用数学的思路,现有数组的求和为一个值,缺少一个元素的数组的值也为一个数,两个数字的差值其实就是缺少的那个数字
class Solution {
public:
    int missingNumber(vector<int>& nums) 
    {
        int n=nums.size();
        int ret=n*(n+1)/2;
        int res=0;
        for(int i=0;i<nums.size();i++)
        {
            res+=nums[i];
        }
        return ret-res;
    }
};
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值