数据结构和算法简介

news2024/10/7 18:30:43

目录

1.认识数据结构

什么是数据结构

逻辑结构

物理结构

常见的数据结构

2.认识算法

什么是算法

如何衡量算法效率

时间复杂度

什么是时间复杂度

如何计算时间复杂度

大O渐进表示法

常见时间复杂度计算例子

空间复杂度

什么是空间复杂度

如何计算空间复杂度

常见空间复杂度计算例子

1.认识数据结构

对于数据结构的认识,讲解的都是一些概念,目前只需要有大概的了解即可,读者不用过度纠结,随着学习的深入,对于数据结构的理解自然会更上一层楼。

什么是数据结构

计算机在被发明之初,其目的就是用来处理数据的,而且要处理的数据通常不是一个一个的,而是多个,有多个数据要被处理,计算机就需要将这多个数据组织并存储起来,以便高效地使用。那么,计算机组织并存储数据的方式就显得尤为重要了。于是,数据结构这门学科便诞生了。所以,数据结构是计算机组织、存储数据的方式。

而计算机组织、存储数据的方式通常涉及到两个方面 —— 逻辑结构、物理结构。

逻辑结构

逻辑结构描述的是数据元素之间的逻辑关系,即数据元素之间的关联方式。比如:一对一、一对多、多对多。

常见的逻辑结构:

线性结构:数据元素之间是一对一的关系,如数组、链表、栈和队列等。

树形结构:数据元素之间存在一对多的关系,如二叉树、平衡树、B树等。

图结构:数据元素之间存在多对多的关系,如有向图、无向图等。

物理结构

物理结构描述数据元素在计算机中的存储方式,即数据在计算机内存中的表示和布局。物理结构也称为存储结构

常见的物理结构:

顺序存储结构:数据元素在内存中是连续存储的,如数组。

链式存储结构:数据元素通过指针(或引用)来链接,如链表。

散列存储结构:通过散列函数将数据元素映射到特定的存储位置,如哈希表。

常见的数据结构

数组:固定大小的线性结构,支持随机访问。

链表:由节点组成,每个节点包含数据和指向下一个节点的指针。

:后进先出的线性结构,支持入栈和出栈操作。

队列:先进先出的线性结构,支持入队和出队操作。

:具有层次关系的非线性结构,常用于表示分类和层次关系。

:由节点和边组成,用于表示复杂的关系网络。

哈希表:基于散列函数实现的高效查找结构,支持快速插入、删除和查找操作。

2.认识算法

什么是算法

所谓算法,其实就是定义良好的计算过程,取一个或一组的值为输入,并产生出一个或一组值作为输出。

简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

如何衡量算法效率

我们已经知道了算法其实就是一系列的计算步骤,也就是解决问题的方法,既然是方法,就有好的方法和坏的方法,那么如何衡量算法的好坏呢?

当算法被实现出来之后,其实就是程序,程序在运行的时候需要消耗时间资源空间资源,因此,衡量一个算法的好坏是从时间和空间两个方面来衡量的,也就是时间复杂度空间复杂度。

时间复杂度主要用来衡量一个算法的运行快慢

空间复杂度主要用来衡量一个算法运行所需要的额外空间

时间复杂度

什么是时间复杂度

一个算法所花费的时间与其中语句的执行次数成正比,但是算法中的语句往往比较多,所以,我们选择算法中的基本操作的执行次数,为算法的时间复杂度。

如何计算时间复杂度

找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

我们可以计算一下给func函数传递不同的值时,++count语句共执行了多少次?

void func(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;
	}
}

func执行的基本操作次数:func(n) = n^2 + 2*n + 10
n = 10        func(10) = 130
n = 100      func(100) = 10210
n = 1000    func(1000) = 1002010

在实际计算时间复杂度时,我们并不一定要计算精确的执行次数,只需要计算大概的执行次数,也就是抓大头取决定性结果的哪一项;这时,就需要使用大O渐进表示法了。

大O渐进表示法

大O符号:是用于描述函数渐进行为的数学符号。

推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。

使用大O渐进表示法之后,上面的func函数的时间复杂度为O(n^2)。

通过上述过程我们发现大O渐进表示法其实就是去掉了对结果影响不大的项,简洁明了的表示出了执行次数。

常见时间复杂度计算例子

例一:时间复杂度为O(n)

void func2(int n)
{
	int count = 0;
	
	for (int k = 0; k < 2 * n ; ++k)
	{
		++count;
	}
	
	int m = 10;
	while (m--)
	{
		++count;
	}
}

该函数中的基本语句是 ++count,如果我们精确计算基本语句的执行次数,则执行次数为2n+10;使用大O渐进表示法表示出的时间复杂度为O(n)。 

例二:时间复杂度为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;
	}
}

在该函数中,由于我们并不清楚m和n的具体情况,所以我们并不能确定m和n谁才是大头,所以,使用大O渐进表示法表示的时候,需要将二者相加。

如果:

  • m 等于 n,时间复杂度为O(m) 或 O(n)。
  • m 远大于 n,时间复杂度为O(m)。
  • m 远小于 n,时间复杂度为O(n)。

例三:时间复杂度为O(1)

void func4()
{
	int count = 0;
	
	for (int k = 0; k < 100; ++ k)
	{
		++count;
	}
}

基本语句++count 共执行100次,根据大O渐进表示法,用常数1取代运行时间中的所有加法常数,表示出来的时间复杂度为O(1)。

注意:O(1)不是一次,而是常数次。

例四:时间复杂度为O(N);

const char* strchr(const char* str, int character)
{
	while(*str)
	{
		if(*str == character)
		{
			return str;
		}
		
		++str;
	}
	
	return NULL;
}

该函数的算法思想是从前往后依次遍历:

最好情况:如果我们查找的字符是靠前的字符,查找的时间复杂度为O(1)。

平均情况:如果我们查找的字符是中间的字符,查找的时间复杂度为O(N/2)。

最坏情况:如果我们查找的字符是靠后的字符,查找的时间复杂度为O(N)。

对于这种具有最好情况、平均情况、最坏情况的算法,在实际中,一般关注的是最坏情况,所以该算法的时间复杂度为O(N)。可以看出,时间复杂度的计算是一种保守的估计

例五:时间复杂度为O(N^2)

void BubbleSort(int* arr, int n)
{
	for (size_t end = n; end > 0; --end)
	{
		int flag = 0;
		
		for (size_t i = 1; i < end; ++i)
		{
			if (arr[i-1] > arr[i])
			{
				Swap(&arr[i-1], &arr[i]);
				flag = 1;
			}
		}
		
		if (flag == 0)
			break;
	}
}

冒泡排序的思想是进行多趟两两比较, 当两个数的位置不符合预期就会进行交换,每趟都能将最后一个不正确的值放在正确的位置,比较的次数依次为:n-1、n-2 …… 3、2、1、0。

根据大O渐进表示法表示出来之后就是O(N^2)

例六:时间复杂度为O(logN)

int BinarySearch(int* arr, int n, int x)
{
	int begin = 0;
	int end = n-1;
	
	while (begin <= end)
	{
		int mid = begin + ((end-begin)>>1);
		
		if (arr[mid] < x)
			begin = mid+1;
		else if (arr[mid] > x)
			end = mid-1;
		else
			return mid;
	}
	
	return -1;
}

二分查找的思想是在有序空间上查找,每次和中间值作比较,每次都能排除一半的值,查找的效率非常高。同样,二分查找也具有最好、平均、最坏情况,我们只考虑最坏情况。

当查找的区间缩放只剩一个值的时候,此时就是最坏情况。最坏情况下,除了多少次2,就查找了多少次,假设查找了x次,2^x = N,x = logN(以2为底)。因此,二分查找的时间复杂度是O(logN)。

注意:一般logN(以2为底)可以简写成logN,以其他的数字为底的都要显示的写出,不可省略底数。

例七:时间复杂度为O(N)

long long fac(size_t n)
{
	if(0 == n)
		return 1;

	return fac(n-1)*n;
}

变形:时间复杂度为O(N^2)

long long fac(size_t n)
{
	if(0 == n)
		return 1;
	
	for(int i = 0; i < n; ++i)
	{
		printf("%d ",i);
	}
	
	return fac(n-1)*n;
}

递归算法是自己调用自己,这意味着当前函数会被执行多次,所以,递归函数的时间复杂度是函数被调用的次数与单次函数的时间复杂度之积。

总结:递归算法时间复杂度是多次调用的次数累加。

例八:时间复杂度为O(2^N)

long long fib(size_t n)
{
	if(n < 3)
		return 1;
		
	return fib(n-1) + fib(n-2);
}

与上面的递归不同,该递归函数为双路递归,我们可以简单地画画递归展开图

我们发现,下一层调用的次数是上一层的2倍,符合等比数列的性质,我们可以根据等比数列的求和公式求出大概的时间复杂度,使用大O渐进表示法表示之后,时间复杂度为O(2^N)。

空间复杂度

谈完时间复杂度,下面我们谈谈空间复杂度。

什么是空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时额外占用存储空间大小的度量

我们可以这样理解,解决同一个问题,可能有不同的算法, 但是不同的算法都不能避免解决该问题本身所需要的存储空间。比如排序问题,数据元素本身就要占用一定的内存空间,这是使用任何算法都不能避免的,而不同的算法在解决该问题时,所需要的额外的、临时的空间就是我们要计算的空间复杂度。

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定

如何计算空间复杂度

空间复杂度的计算和时间复杂度是差不多的,我们并不需要计算精确的空间大小,只需要计算大概额外使用的变量的个数即可。同样使用大O渐进表示法来表示。

复习一下大O渐进表示法:

1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。

常见空间复杂度计算例子

例一:空间复杂度为O(1)

void BubbleSort(int* arr, int n)
{
	for (size_t end = n; end > 0; --end)
	{
		int flag = 0;
		for (size_t i = 1; i < end; ++i)
		{
			if (arr[i-1] > arr[i])
			{
				Swap(&arr[i-1], &arr[i]);
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

在冒泡排序算法中,数组arr的空间是固定的,使用任何算法都不能避免的。在算法运行过程中,使用了end、flag变量,额外临时使用的空间是常数个,所以空间复杂度是O(1)。

例二:空间复杂度为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个空间,使用大O渐进表示法表示出来的空间复杂度为O(N)。

例三:空间复杂度为O(N)

long long Fac(size_t N)
{
    if(N == 0)
        return 1;

    return Fac(N-1)*N;
}

该函数递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间,空间复杂度为O(N)。

递归函数的空间复杂度的计算主要关注函数被调用的次数每次调用所开辟的额外空间即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2194679.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】深度解析堆排序

目录 &#x1f4af;引言 &#x1f4af;堆的概念 &#xff08;一&#xff09;什么是堆 &#xff08;二&#xff09;堆的表示 &#x1f4af;堆排序原理 &#xff08;一&#xff09;建堆 &#xff08;二&#xff09;排序 &#x1f4af;代码实现 &#x1f4af;代码分析 &…

【Sqlite】sqlite内部函数sqlite3_value_text特性

目录 ⚛️1 结论 ☪️2 说明 ☪️3 传入数值转成科学计数法 ♋3.1 只有整数部分 ♏3.2 只有小数部分 ♐3.3 整数小数 ⚛️1 结论 整数(sqlite视为int64)位数 > 20位&#xff0c;sqlite3_value_text 采用科学计数法。否则正常表示。 浮点数(sqlite视为double)的整数部…

STM32 通用同步/异步通信

一、串行通信简介 CPU与外围设备之间的信息交换称为通信。基本的通信方式有并行通信和串行通信两种。STM32单片机提供了功能强大的串行通信模块&#xff0c;即通用同步/异步收发器&#xff08;USART&#xff09;。 1.串行通信 串行通信是数据字节一位一位地依次传送的通信方式。…

HarmonyOS第一课 05 从简单的页面开始-习题

【习题】从简单的页面开始 通过/及格分80/ 满分100 判断题 1.Button作为容器使用时可以通过添加子组件实现包含文字、图片等元素的按钮&#xff0c;其类型包括胶囊按钮、圆形按钮、普通按钮。T 正确(True) 错误(False) 大部分前端框架的按钮都具有这几个类型,鸿蒙也不例外…

Ubuntu+VsCode++搭建C++开发环境

Ubuntu下使用VsCode搭建C开发环境 1、基本工具的安装 首先Ubuntu下安装好C开发的一个些基本工具g、gdb、make、cmake等&#xff0c;安装方式点这里 检查一下安装环境 $ g --version g (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation,…

位图的应用

目录 问题引入 位图概念 位图的实现 应用2&#xff1a;找到只出现一次的整数 应用三&#xff1a;找交集 STL中的位图 问题引入 面试题 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在 这40亿个数中。【腾讯】 解决…

幂,你去哪儿了-《分析模式》漫谈37

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第3章的图3.5&#xff0c;原文的图是&#xff1a; 2004&#xff08;机械工业出版社&#xff09;中译本的图是&#xff1a; direct翻译成分子&#xff0c;inv…

master节点k8s部署]33.ceph分布式存储(四)

总结ceph分布式存储&#xff08;三&#xff09;中提到的三种方法&#xff1a; 1.创建rbda&#xff0c;并且在创建pv的时候配置该rbda,以下代码仅展示关键信息。 [rootxianchaomaster1 ~]# cat pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: ceph-pv ...…

MySQL多表查询:行子查询

先看我的表数据 dept表 emp表 行子查询 子查询返回的结果是一行&#xff08;可以是多列&#xff09;, 这种子查询称为行子查询 常用的操作符: , <>, IN, NOT IN 例子1. 查询与“张无忌” 的薪资及直属领导相同的员工信息 拆解成两个问题 a. 查询"张无忌"…

基于SpringBoot+Vue+MySQL的汽车租赁系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着城市化和交通需求的不断增加&#xff0c;汽车租赁业务成为了现代社会的一个重要组成部分。汽车租赁服务为人们提供了一种灵活便捷的交通解决方案&#xff0c;让用户在无需购买车辆的情况下&#xff0c;根据实际需要租赁车辆…

端口冲突的解决方案以及SpringBoot自动检测可用端口demo

端口冲突的解决方案 端口冲突通常发生在尝试运行两个或多个应用程序或服务时&#xff0c;它们尝试使用同一个端口号&#xff0c;导致系统无法正确分配资源。 各种端口错误 你是否遇到过下面这些报错信息呢&#xff1f; Windows 系统报错&#xff1a; 系统错误 1004 套接字操作…

图像转3D视差视频:DepthFlow、kling

1、DepthFlow 参看: https://github.com/BrokenSource/DepthFlow 通过深度图实现图像3d效果 安装 https://brokensrc.dev/get/pypi/#installing pip insatll depthflow shaderflow broken-source pianola spectronote turbopipe 使用 1、下载项目 git clone https://gith…

约数个数约数之和

好久没发文章了.......不过粉丝还是一个没少...... 今天来看两道超级恶心的数论题目&#xff01; No.1 约数个数 No.2 约数之和 先来看第一道&#xff1a;约数个数 题目描述 给定 n 个正整数 ai​,请你输出这些数的乘积的约数个数,答案对 10^97 取模 输入格式 第一行包含…

Python_文件处理

一个完整的程序一般都包括数据的存储和读取&#xff1b;我们在前面写的程序数据都没有进行实际的存储&#xff0c;因此python解释器执行完数据就消失了。实际开发中&#xff0c;我们经常需要从外部存储介质&#xff08;硬盘、光盘、U盘等&#xff09;读取数据&#xff0c;或者将…

微信小程序开发-目录结构介绍

文章目录 一&#xff0c;目录结构介绍1&#xff0c;主体文件2&#xff0c;页面文件3&#xff0c;修改页面渲染模式 二&#xff0c;新增页面1&#xff0c;右键“pages”-新建文件夹2&#xff0c;右键文件夹-新建page3&#xff0c;新建页面的快捷方式 四&#xff0c;基础库设置 一…

①EtherNet/IP转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherNet/IP 转 Modbus TCP GW型号系列 MS-GW25 概述 简介 MS-GW25 是 EtherNet/IP 和 Modbus TCP 协议转换网关&#xff0c;为…

C语言 | 第十一章 | static 日期函数 数学函数

P 100 变量作用域基本规则 2023/1/9 一、基本介绍 概念&#xff1a;所谓变量作用域&#xff08;Scope&#xff09;&#xff0c;就是指变量的有效范围。 函数内部声明/定义的局部变量&#xff0c;作用域仅限于函数内部。 #include<stdio.h> void sayHello() {char nam…

【C++】—— 继承(上)

【C】—— 继承&#xff08;上&#xff09; 1 继承的概念与定义1.1 继承的概念1.2 继承定义1.2.1 定义格式1.2.2 继承父类成员访问方式的变化 1.3 继承类模板 2 父类和子类对象赋值兼容转换3 继承中的作用域3.1 隐藏规则3.2 例题 4 子类的默认成员函数4.1 构造函数4.1.1 父类有…

稀缺是否意味着价值

省流版&#xff1a;物以稀为贵。 稀少并不等于需求。 更新为&#xff1a;物以希为贵。 有需求就意味着有价值。 不管是20&#xff1a;80中的20&#xff0c;还是10&#xff1a;90中的10&#xff0c;还是2&#xff1a;98中的2。 所以&#xff0c;这个模型里一定会出现1这类人&a…

MambaAD 实验部分讲解

4 实验 4.1 设置&#xff1a;数据集、指标和细节 数据集&#xff08;6个&#xff09; 1.MVTec-AD&#xff1a; 包含5种类型的纹理和10种类型的对象&#xff0c;总共5,354张高分辨率图像。 实验&#xff1a; 3,629张正常图像被指定为训练。 剩下的 1,725 张图像被保留用于测试…