数据结构时间复杂度空间复杂度

发布于:2024-12-21 ⋅ 阅读:(13) ⋅ 点赞:(0)

数据结构前言

1.什么是数据结构

数据结构(Data Structure)是计算机存储,组织数据的方式,指相互之间存在一种或多种特性关系的数据元素的集合

2.什么是算法

算法(Algorithm)就是定义良好的计算过程,它取一个或一组的值为输入,并产生出一个或一组的值作为输出,简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果3.数据结构和算法的重要性

3.数据结构和算法的重要性

在校园招聘的笔试中:

  • 目前校园招聘笔试一般采用Online Judge形式, 一般都是20-30道选择题+2道编程题,或者3-4道
    编程题。
    2020奇安秋招C/C++
    美团2021校招
    网易2021校招C++
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以看出,现在公司对学生代码能力的要求是越来越高了,大厂笔试中几乎全是算法题而且难度大,中小长的笔试中才会有算法题。算法不仅笔试中考察,面试中面试官基本都会让现场写代码。而算法能力短期内无法快速提高了,至少需要持续半年以上算法训练积累,否则真正校招时笔试会很艰难,因此算法要早早准备。

在校园招聘的面试中:

  • CVTE面试1:
    1.怎么计算一个类到底实例化了多少对象?
    2.如果还有一个派生类继承了这个类,那么如何计算这两个类,各自实例化了多少对象?
    3.了解联合体和结构体吗?
    4.如何测试一个机器是大端还是小端?
    5.了解队列和栈吗?
    6.怎么用两个栈实现一个队列。
    7.使用过模版吗?
    8.写一个比较两个数大小的模板函数。
    9.使用过容器吗?
    10.判断两个链表是否相交。
    11.Vector和数组的区别。
    12.在学校里做的最满意的一个项目是什么?简述一下这个项目。
    腾讯的面试1:
    1、自我介绍
    2、学习STL具体是怎么开展的?
    3、如果一款产品给你怎么检测内存泄露?
    4、进程间通信方式,共享内存是怎么实现的,会出现什么问题,怎么解决?
    5、TCP为什么是可靠的?可靠是怎么保证的?为什么要三次握手?为什么三次握手就可以可靠?
    6、Http数据分包问题;
    7、Vector相关;
    8、Hashmap相关;
    9、红黑树的原理、时间复杂度等;
    10、Memcpy和memmove的区别;
    11、客户端给服务器发送数据,意图发送aaa,然后再发bbb,但是可能会出现aaabbb这种情
    况,如何处理?
    12、游戏的邮件服务器中每天会有玩家频繁的创建邮件和删除邮件,海量数据、大小不一,会有
    哪些场景,怎么存储,邮件是怎么到内存的?
    13、写一道算法题
    百度的面试1:
    1.**手写五道题,三道编程题,**一道数据库,一道linux
    2.数据库的题两问
    3.算法了解的如何,插入排序编程
    4.说一下IP,TCP,ARP
    5.内核是什么
    6.IP层主要功能
    7.map和set底层
    8.bootstrap的用法,html,html的全称
    9.觉得框架和库有啥区别
    10.代码优化
    11.哈希表
    12.shell脚本
    13.快速排序思想
    14.递归是什么
    15.分治是什么,与递归区别是什么
    16.web平台是怎么做的
    17.linux命令
    18.了解些什么前沿的技术,英语怎么样,了解过什么英语的文献

在未来工作中:
[数据结构与算法对一个程序员来说的重要性?
优化程序性能
1.提高运行效率:

  • 数据结构与算法能够帮助程序员设计出高效的程序。例如,在搜索数据时,使用二分查找算法(基于有序数组的数据结构)比线性查找算法的时间复杂度更低。对于一个包含 n​ 个元素的数组,线性查找的时间复杂度为 O(n)​,而二分查找的时间复杂度为 O(logn)​。当 n​ 很大时,二分查找的效率会显著高于线性查找。再如,在处理图数据时,使用迪杰斯特拉算法(Dijkstra’s algorithm)可以高效地找到从一个顶点到其他所有顶点的最短路径。如果没有合适的算法,可能需要遍历所有可能的路径,这在大规模图数据中是非常耗时的。

2.优化资源利用

  • 合理的数据结构可以减少内存的占用。例如,使用哈希表(Hash Table)可以在常数时间内进行数据的插入、删除和查找操作,相比于使用数组或链表,哈希表在处理大规模数据时可以更有效地利用内存空间。另外,一些动态数据结构(如动态数组、链表等)可以根据实际数据量自动调整大小,避免了静态数据结构可能导致的内存浪费或溢出问题。

解决复杂问题
1.提供问题解决思路

  • 许多复杂的编程问题可以通过选择合适的数据结构和算法来解决。例如,在处理任务调度问题时,可以使用优先级队列(Priority Queue)来按照任务的优先级进行排序,确保高优先级的任务先被执行。在解决字符串匹配问题时,KMP算法(Knuth-Morris-Pratt algorithm)可以高效地在文本中查找模式串的出现位置,避免了暴力匹配算法的低效性。

2.应对大规模数据处理

  • 在大数据时代,程序员需要处理海量的数据。数据结构与算法可以帮助程序员设计出能够处理大规模数据的高效算法。例如,在处理海量数据的排序问题时,外部排序算法(External Sorting Algorithm)可以将数据分成多个小块,分别在内存中进行排序,然后再合并结果,从而避免了一次性将所有数据加载到内存中导致的内存不足问题。

提升代码质量
1.增强代码可读性和可维护性

  • 使用合适的数据结构和算法可以使代码的逻辑更加清晰。例如,使用栈(Stack)来实现函数调用栈、表达式求值等功能,代码的逻辑会更加直观易懂。良好的数据结构和算法设计可以使代码的结构更加合理,易于维护和扩展。例如,在设计一个软件系统时,使用面向对象的设计模式(如工厂模式、单例模式等)结合合适的数据结构和算法,可以使代码的结构更加清晰,各个模块之间的耦合度更低,便于后续的维护和扩展。

2.遵循编程规范和最佳实践

  • 数据结构与算法是编程领域的基础知识,掌握它们可以帮助程序员遵循编程规范和最佳实践。例如,在编写代码时,应该选择合适的数据结构来存储和操作数据,避免使用过于复杂或低效的数据结构。同时,应该选择合适的算法来解决问题,避免使用暴力算法或低效的算法。

职业发展和竞争力提升
1.面试和求职必备

  • 在程序员的面试过程中,数据结构与算法是必考的内容之一。面试官通常会通过考察候选人对数据结构和算法的掌握程度来评估其编程能力和问题解决能力。例如,在面试中,可能会要求候选人实现一个特定的数据结构(如二叉树、哈希表等),或者解决一个算法问题(如排序、查找等)。
    掌握数据结构与算法可以增加程序员在求职市场上的竞争力,使他们更容易获得理想的工作机会。

2.技术进阶的基础

  • 对于想要在编程领域深入发展的程序员来说,数据结构与算法是必不可少的基础知识。例如,在学习计算机图形学、人工智能、机器学习等高级领域时,需要掌握复杂的数据结构和算法。只有具备扎实的数据结构和算法基础,才能更好地理解和应用这些高级技术。

4.如何学好数据结构和算法

1死磕代码
2画图和思考

5.数据结构和算法书籍及资料推荐

剑指offerOJ
LeetCode OJ

算法的时间复杂度和空间复杂度

1.算法效率

1.1如何衡量一个算法的好坏

long long Fib(int N)
{
 	if(N < 3)
 		return 1;
 
	return Fib(N-1) + Fib(N-2);
}

1.2算法的复杂度

算法在编写可执行程序后,运行时需要耗费时间资源和空间(内存)资源,因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,及时间复杂度和空间复杂度

**时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间,**在计算机发展早期,计算机的存储容量很小我,所以对空间复杂度很是在乎,但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的高度,所以已经不再需要特别关注一个算法的空间复杂度

1.3 复杂度在校招中的考察

在这里插入图片描述

2.时间复杂度

2.1时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间,一个算法执行所耗费的时间,从理论上说,是不能推算出来的晚,只有把程序放在机器上跑起来,才能知道,但是不需要每个算法都上机测试,很麻烦我,所以才有时间复杂度的分析方式,一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的复杂度
即:找到了某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度

// 请计算一下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);
}
  • Func1执行的基本操作次数:
    F(N)=N²+2*N+10

  • N=10 F(N)=130
    N=100 F(N)=10210
    N=1000 F(N)=1002010

  • 实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这
    里我们使用大O的渐进表示法。

2.2大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数字符号
推导大O阶方法:
1.用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数函数中,只保留最高阶项
3.如果最高阶项存在且不是1,Func1的时间复杂度为

  • O(N²)
  • N = 10 F(N) = 100
  • N = 100 F(N) = 10000
  • N = 1000 F(N) = 1000000

通过上面发现大O的渐进表示法去掉了那些对结果影响不大的项,简介明了的表示出路了执行次数
另外有些算法的时间复杂度存在最好,平均和最坏情况
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)

例如:在一个长度为N数组中搜索一个数据x
最好情况:一次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况只关注算法的最坏运行情况,所以在数组中搜索数据的时间复杂度为O(N)

2.3常见时间复杂度计算举例

实例1:

// 计算Func2的时间复杂度?
void Func2(int N)
{
 	int count = 0;
 	for (int k = 0; k < 2 * N ; ++ k)
 	{
 		++count;
 	}
 	int M = 10;
 	while (M--)
 	{
 		++count;
 	}
 	printf("%d\n", count);
}

实例2:

// 计算Func3的时间复杂度?
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;
 	}
 	printf("%d\n", count);
}

实例3:

// 计算Func4的时间复杂度?
void Func4(int N)
{
 	int count = 0;
 	for (int k = 0; k < 100; ++ k)
 	{
 		++count;
 	}
 	printf("%d\n", count);
}

实际4:

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

实例5:

// 计算BubbleSort的时间复杂度?
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;
 	}
}

实例6:

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
 	assert(a);
 	int begin = 0;
 	int end = n-1;
 	// [begin, end]:begin和end是左闭右闭区间,因此有=号
 	while (begin <= end)
 	{
 		int mid = begin + ((end-begin)>>1);
 		if (a[mid] < x)
 			begin = mid+1;
 		else if (a[mid] > x)
 			end = mid-1;
 		else
 			return mid;
 	}
 	return -1;
}

实例7:

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
 	if(0 == N)
 		return 1;
 
 	return Fac(N-1)*N;
}

实例8:

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
 	if(N < 3)
 		return 1;
 
 	return Fib(N-1) + Fib(N-2);
}

实例答案及分析:

  • 1.实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为O(N)
    2实例2基本操作执行了M+N次,有两个未知数,时间复杂度为O(M+N)
    3.实例3基本操作执行了100次,通过推导大O阶方法,时间复杂度为O(1)
    4.实例4基本操作执行了最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为O(N)
    5.实例5基本操作执行了最好N次,最坏执行了(N*(N+1)/2)次,大O阶推导+时间复杂度一般看最坏,时间复杂度为O(N²)
    6.实例6基本操作执行了最好1次,最坏O(logN)次,时间复杂度为O(logN)ps:logN在算法中表示底数为2,对数为N,有些地方会写成lgN(建议通过折纸查找方法理解logN)
    7.实例7通过计算分析发现基本操作递归了N次,时间复杂度为O(N)
    8.实例8通过计算分析发现基本操作递归了2N次,时间复杂度为O(2N)(建议画图递归栈桢的二叉树)

3.空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的亮度
空间复杂度不是程序占用了多少bytes的空间,因为这个没太大意义,所以空间复杂度算的是变量的个数
空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法
注:函数运行时所需要的栈空间(存储参数,局部变量,一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定

实例1:

// 计算BubbleSort的空间复杂度?
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;
 	}
}

实例2:

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前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;
}

实例3:

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
 	if(N == 0)
	 	return 1;
 
 	return Fac(N-1)*N;
}

实例答案及分析:

  • 1.实例1使用了常数个额外空间,空间复杂度为O(1)
    2.实例2开辟了N个空间,空间复杂度为O(N)
    3.实例3递归调用了N次,开辟了N个栈桢,每个栈桢使用了常数个空间,空间复杂度为O(N)

4.常见复杂度对比

一般算法的复杂度
在这里插入图片描述

5.复杂度的oj练习

消失的数字OJ链接
在这里插入图片描述
旋转数组OJ链接

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到