探索数据结构:解锁计算世界的密码

news2024/11/16 8:59:10


✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty‘s blog

前言

随着应用程序变得越来越复杂和数据越来越丰富,几百万、几十亿甚至几百亿的数据就会出现,而对这么大对数据进行搜索、插入或者排序等的操作就越来越慢,人们为了解决这些问题,提高对数据的管理效率,提出了一门学科即:数据结构与算法

1. 什么是数据结构

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

下标是常见的数据结构:

名称定义
数组(Array)数组是一种聚合数据类型,它是将具有相同类型的若干变量有序地组织在一起的集合。
链表(Linked List)链表是一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构具有在物理上存在非连续的特点。
栈(Stack)栈是一种特殊的线性表,它只能在一个表的一个固定端进行数据结点的插入和删除操作
队列(Queue)队列和栈类似,也是一种特殊的线性表。和栈不同的是,队列只允许在表的一端进行插入操作,而在另一端进行删除操作。
树(Tree)树是典型的非线性结构,它是包括,2 个结点的有穷集合 K
堆(Heap)堆是一种特殊的树形数据结构,一般讨论的堆都是二叉堆。
图(Graph)图是另一种非线性数据结构。在图结构中,数据结点一般称为顶点,而边是顶点的有序偶对

2. 什么是算法

**算法(Algorithm)😗*就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

算法一般分为:排序,递归与分治,回溯,DP,贪心,搜索算法

  • 算法往往数学密切相关,就如数学题一样,每道数学题都有不同的解法,算法也是同理。

3. 复杂度分析

3.1 算法评估

我们在进行算法分析时,常常需要完成两个目标**。一个是找出问题的解决方法,另一个就是找到问题的最优解**。而为了找出最优解,我们就需要从两个维度分析:

  • 时间效率:算法运行的快慢
  • 空间效率:算法所占空间的大小

3.2 评估方法

评估时间的方法主要分为两种,一种是实验分析法,一种是理论分析法

(1) 实验分析法

实验分析法简单来说就是将不同种算法输入同一台电脑,统计时间的快慢。但是这种方法有两大缺陷:

  1. 无法排查实验自身条件与环境的条件的影响:比如同一种算法在不同配置的电脑上的运算速度可能完全不同,甚至结果完全相反。我们很难排查所有情况。
  2. 成本太高:同一种算法可能在数据少时表现不明显,在数据多时速率较快
(2) 理论分析法

由于实验分析法的局限性,就有人提出了一种理论测评的方法,就是渐近复杂度分析(asymptotic complexity analysis),简称复杂度分析

这种方法体现算法运行所需的时间(空间)资源与输入数据大小之间的关系,能有效的反应算法的优劣。

4. 时间复杂度与空间复杂度

4.1 时间复杂度

一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

为了准确的表述一段代表所需时间,我们先假设赋值(=)与加号(+)所需时间为1ns,乘号(×)所需时间为2ns,打印所需为3ns。

让我们计算如下代码所需总时间:

int main()
{
	int i = 1;//1ns
	int n = 0;//1ns
	scanf("%d", &n);
	for (i = 0; i < n; i++)
	{
		printf("%d ", i);//3ns
	}
	return 0;
}

计算时间如下:
T ( n ) = 1 + 1 + 3 × n = 3 n + 2 T(n)=1+1+3×n=3n+2 T(n)=1+1+3×n=3n+2

但是实际上统计每一项所需时间是不现实的,并且由于是理论分析,当n—>∞时,其余项皆可忽略,T(n)的数量级由最高阶决定。所以我们计算时间复杂度时,可以简化为两个步骤:

  1. 忽略除最高阶以外的所有项。
  2. 忽略所有系数。

而上述代码时间可以记为O(n),这种方法被称为大O的渐进表示法。如果计算机结果全是常数,则记为O(1)。

  • 并且计算复杂度时,有时候会出现不同情况的结果,这是应该以最坏的结果考虑。

4.2 空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度的表示也遵循大O的渐进表示法

让我们计算一下冒泡排序的空间复杂度

// 计算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;
	}
}
  • 通过观察我们可以看出,冒泡排序并没有开辟多余的空间,所以空间复杂度为O(1).

5. 复杂度分类

算法的复杂度有几个量级,表示如下:
O ( 1 ) < O ( l o g N ) < O ( N ) < O ( N l o g N ) < O ( N 2 ) < O ( 2 N ) < O ( N ! ) O(1) < O( log N) < O(N) < O(Nlog N) < O(N 2 ) < O(2^N) < O(N!) O(1)<O(logN)<O(N)<O(NlogN)<O(N2)<O(2N)<O(N!)

  • 从左到右复杂度依次递增,算法的缺点也就越明显

图示如下:

5.1 常数O(1)阶

常数阶是一种非常快速的算法,但是在实际应用中非常难实现

以下是一种时间复杂度与空间复杂度皆为O(1)的算法:

int main()
{
	int a = 0;
	int b = 1;
	int c = a + b;
	printf("两数之和为%d\n", c);
	return 9;
}

5.2 对数阶O(logN)

对数阶是一种比较快的算法,它一般每次减少一半的数据。我们常用的二分查找算法的时间复杂度就为O(logN)

二分查找如下:

int binary_search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
{
    int left = 0;
    int right = size - 1;	// 定义了target在左闭右闭的区间内,[left, right]
    while (left <= right) {	//当left == right时,区间[left, right]仍然有效
        int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
        if (nums[middle] > target) {
            right = middle - 1;	//target在左区间,所以[left, middle - 1]
        } else if (nums[middle] < target) {
            left = middle + 1;	//target在右区间,所以[middle + 1, right]
        } else {	//既不在左边,也不在右边,那就是找到答案了
            return middle;
        }
    }
    //没有找到目标值
    return -1;
}

空间复杂度为O(logN)的算法,一般为分治算法

比如用递归实现二分算法:

int binary_search(int ar[], int low, int high, int key)
{
    if(low > high)//查找不到
        return -1;
    int mid = (low+high)/2;
    if(key == ar[mid])//查找到
        return mid;
    else if(key < ar[mid])
        return Search(ar,low,mid-1,key);
    else
        return Search(ar,mid+1,high,key);
}

每一次执行递归都会对应开辟一个空间,也被称为栈帧

5.3 线性阶O(N)

线性阶算法,时间复杂度与空间复杂度随着数量均匀变化。

遍历数组或者链表是常见的线性阶算法,以下为时间复杂度为O(N)的算法:

int main()
{
	int n = 0;
	int count = 0;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		count += i;//计算0~9的和
	}
	return 0;
}

以下为空间复杂度为O(N)的算法

int main()
{
	int n = 0;
	int count = 0;
	scanf("%d", &n);
	int* p = (int*)malloc(sizeof(int) * n);
	//开辟大小为n的空间
	if (p == NULL)
	{
		perror("malloc fail");
		return -1;
          }
       free(p);
       p=NULL;
	return 0;
}

5.4 线性对数阶O(NlogN)

无论是时间复杂度还是空间复杂度,线性对数阶一般出现在嵌套循环中,即一层的复杂度为O(N),另一层为O(logN)

比如说循环使用二分查找打印:

int binary_search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
{
    int left = 0;
    int right = size - 1;	// 定义了target在左闭右闭的区间内,[left, right]
    while (left <= right) {	//当left == right时,区间[left, right]仍然有效
        int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
        if (nums[middle] > target) {
            right = middle - 1;	//target在左区间,所以[left, middle - 1]
        }
        else if (nums[middle] < target) {
            left = middle + 1;	//target在右区间,所以[middle + 1, right]
        }
        else {	//既不在左边,也不在右边,那就是找到答案了
            printf("%d ", nums[middle]);
        }
    }
    //没有找到目标值
    return -1;
}
void func(int nums[], int size, int target)
{
	for (int i = 0; i < size; i++)
	{
		binary_search(nums, size, target);
	}
}

空间复杂度为O(NlogN)的算法,最常见的莫非归并排序

void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex){
    int i = startIndex, j=midIndex+1, k = startIndex;
    while(i!=midIndex+1 && j!=endIndex+1) {
        if(sourceArr[i] > sourceArr[j])
            tempArr[k++] = sourceArr[j++];
        else
            tempArr[k++] = sourceArr[i++];
    }
    while(i != midIndex+1)
        tempArr[k++] = sourceArr[i++];
    while(j != endIndex+1)
        tempArr[k++] = sourceArr[j++];
    for(i=startIndex; i<=endIndex; i++)
        sourceArr[i] = tempArr[i];
}
 
//内部使用递归
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) {
    int midIndex;
    if(startIndex < endIndex) {
        midIndex = startIndex + (endIndex-startIndex) / 2;//避免溢出int
        MergeSort(sourceArr, tempArr, startIndex, midIndex);
        MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
        Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
    }
}

5.5 平方阶O(N2)

平方阶与线性对数阶相似,常见于嵌套循环中,每层循环的复杂度为O(N)

时间复杂度为O(N2),最常见的就是冒泡排序

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;
	}
}

计算过程如下;
T ( N ) = 1 + 2 + 3 + . . . . . . + n − 1 = ( n 2 − n ) / 2 = O ( n 2 ) T(N)=1+2+3+......+n-1=(n^2-n)/2=O(n^2) T(N)=1+2+3+......+n1=(n2n)/2=O(n2)

空间复杂度为O(N2),最简单的就是动态开辟。

{
	int n = 0;
	int count = 0;
	scanf("%d", &n);
	int* p = (int*)malloc(sizeof(int) * n*n);
	//开辟大小为n的空间
	if (p == NULL)
	{
		perror("malloc fail");
		return -1;
          }
       free(p);
       p=NULL;
	return 0;
}

5.6 指数阶O(2N)

指数阶的算法效率低,并不常用。

常见的时间复杂度为O(2N)的算法就是递归实现斐波拉契数列:

int Fib1(int n)
{
	if (n == 1 || n == 2)
	{
		return 1;
	}
	else
	{
		return Fib1(n - 1) + Fib1(n - 2);
	}
}

粗略估计
T ( n ) = 2 0 + 2 1 + 2 2 + . . . . . + 2 ( n − 1 ) = 2 n − 1 = O ( 2 N ) T(n)=2^0+2^1+2^2+.....+2^(n-1)=2^n-1=O(2^N) T(n)=20+21+22+.....+2(n1)=2n1=O(2N)

  • 值得一提的是斐波拉契的空间复杂度为O(N),因为在递归至最深处后往回归的过程中,后续空间都在销毁的空间上建立的,这样能大大提高空间的利用率。

空间复杂度为O(2N)的算法一般与树有关,比如建立满二叉树

TreeNode* buildTree(int n) {
	if (n == 0)
		return NULL;
	TreeNode* root = newTreeNode(0);
	root->left = buildTree(n - 1);
	root->right = buildTree(n - 1);
	return root;
}

5.7 阶乘阶O(N!)

阶乘阶的算法复杂度最高,几乎不会采用该类型的算法。

这是一个时间复杂度为阶乘阶O(N!)的算法

int func(int n)
{
	if (n == 0)
		return 1;
	int count = 0;
	for (int i = 0; i < n; i++) 
	{
		count += func(n - 1);
	}
	return count;
}

示意图:

  • 空间复杂度为阶乘阶O(N!)的算法并不常见,这里就不在一一列举。

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

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

相关文章

LabVIEW最佳传输系统设计

LabVIEW最佳传输系统设计 介绍了基于LabVIEW软件开发的最佳基带传输系统和最佳带通传输系统的设计。通过软件仿真实现了脉冲成形滤波器和匹配滤波器的设计&#xff0c;证明了系统在消除码间干扰和抗噪声方面的优异性能。此设计不仅激发了学生的学习兴趣&#xff0c;还有助于提…

kubernetes最新版安装单机版v1.21.5

k8s集群由Master节点和Node&#xff08;Worker&#xff09;节点组成。 1.环境 环境&#xff1a;centos 7资源配置&#xff1a;2c4g &#xff08;CPU最少2c&#xff0c;不然k8s起不来&#xff09;docker&#xff1a;25.0.3k8s&#xff1a;1.21.5 2.安装前置环境 [rootbertra…

【Android开发】01-第一个Android APP

一、改MainActivity class MainActivity : AppCompatActivity() {/*因Android的app有生命周期&#xff0c;故入口是OnCreate而不是main函数*/override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main…

Kubernetes的五大开源存储项目

在Kubernetes中&#xff0c;关于数据的持久化管理是一种挑战&#xff0c;对此&#xff0c;社区提供了多种存储的解决方案&#xff0c;这些方案旨在简化和优化容器化应用程序的持久化数据管理。 现介绍 Kubernetes 的五大开源存储项目&#xff0c;带你了解开源存储解决方案的多…

java常用应用程序编程接口(API)——LocalDateTime,LocalDate,LocalTime,ZoneId,ZonedDateTime概述

前言&#xff1a; 在jdk8以后&#xff0c;出现了更多好用的时间相关的api&#xff0c;整理下使用心得。打好基础&#xff0c;daydayup! jdk8以前使用的时间api可以看这篇&#xff1a; java常用应用程序编程接口&#xff08;API&#xff09;——Date&#xff0c;SimpleDateForm…

学校机房Dev c++解决中文乱码问题

工具->编译选项->勾选 编译时加入以下命令 -fexec-charsetGBK -finput-charsetUTF-8 显示中文&#xff1a;工具->编辑器选项->去掉第一个的勾勾。

【多线程】常见锁策略详解(面试常考题型)

目录 &#x1f334; 乐观锁 vs 悲观锁&#x1f38d;重量级锁 vs 轻量级锁&#x1f340;自旋锁&#xff08;Spin Lock&#xff09;&#x1f38b;公平锁 vs ⾮公平锁&#x1f333;可重⼊锁 vs 不可重⼊锁&#x1f384;读写锁⭕相关面试题 常⻅的锁策略 注意: 接下来讲解的锁策略不…

docker报错 fatal error: runtim: out of memory

fatal error: runtim: out of memory 真无语了 系统内存也够用 原来是虚拟机的不够用了 &#xff08;原本1g已经加到2g还是会报错&#xff09; 直接3台虚拟机都加到4g

Maven-install报错:Malformed \uxxxx encoding

把仓库中所有的resolver-status.properties文件全部干掉。 直接ctrla全选中delete全部删除 然后再清idea缓存重启试试

巴塞罗那世界移动大会:华为构建电信公司AI模型——MWC 2024

在巴塞罗那世界移动大会的舞台上&#xff0c;华为宣布推出了一款专为电信公司设计的基础模型——MWC 2024电信基础模型。这一创新技术旨在通过自然语言交互、场景化代理等功能&#xff0c;提升电信行业员工的工作效率&#xff0c;增强用户满意度&#xff0c;进而推动整个行业的…

拦截器Interceptor(黑马学习笔记)

学习完了过滤器Filter之后&#xff0c;接下来我们继续学习拦截器Interceptor。 拦截器我们主要分为三个方面进行讲解&#xff1a; 1.介绍下什么是拦截器&#xff0c;并通过快速入门程序上手拦截器 2.拦截器的使用细节 3.通过拦截器Interceptor完成登录校验功能 我们先学习第一…

基于springboot实现图书馆管理系统项目【项目源码+论文说明】

基于springboot实现图书馆管理系统演示 摘要 电脑的出现是一个时代的进步&#xff0c;不仅仅帮助人们解决了一些数学上的难题&#xff0c;如今电脑的出现&#xff0c;更加方便了人们在工作和生活中对于一些事物的处理。应用的越来越广泛&#xff0c;通过互联网我们可以更方便地…

《Spring Security 简易速速上手小册》第4章 授权与角色管理(2024 最新版)

文章目录 4.1 理解授权4.1.1 基础知识详解授权的核心授权策略方法级安全动态权限检查 4.1.2 主要案例&#xff1a;基于角色的页面访问控制案例 Demo 4.1.3 拓展案例 1&#xff1a;自定义投票策略案例 Demo测试自定义投票策略 4.1.4 拓展案例 2&#xff1a;使用方法级安全进行细…

【打工日常】使用docker部署在线PDF工具

一、Stirling-PDF介绍 Stirling-PDF是一款功能强大的本地托管的基于 Web 的 PDF 操作工具&#xff0c;使用 docker部署。该自托管 Web 应用程序最初是由ChatGPT全权制作的&#xff0c;现已发展到包含广泛的功能来处理您的所有 PDF 需求。允许对 PDF 文件执行各种操作&#xff0…

最详细的Nginx安装【自测可用】

1.Nginx介绍 Nginx是一款高性能的http 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。由俄罗斯的程序设计师Igor Sysoev所开发&#xff0c;官方测试nginx能够支支撑5万并发链接&#xff0c;并且cpu、内存等资源消耗却非常低&#xff0c;运行非…

2024年【A特种设备相关管理(电梯)】考试总结及A特种设备相关管理(电梯)证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年A特种设备相关管理&#xff08;电梯&#xff09;考试总结为正在备考A特种设备相关管理&#xff08;电梯&#xff09;操作证的学员准备的理论考试专题&#xff0c;每个月更新的A特种设备相关管理&#xff08;电梯…

centos7 安装 docker-compose

1、直接参考官方&#xff1a; Install Compose standalone | Docker Docs 1、安装命令 curl -SL https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose 2、修改 docker-compose 执行权限 不修改执行权…

JavaScript高级程序设计

前言 《JavaScript高级程序设计》 第1章——什么是JavaScript DOM将整个页面抽象为一组分层节点。 BOM用于支持访问和操作浏览器的窗口。 第2章——HTML中的JavaScript 2.1 < script >元素 元素描述async立即开始下载脚本&#xff0c;但不能阻止其他页面动作&#…

力扣2月最后三天的每日一题

力扣2月最后三天的每日一题 前言2867.统计树中的合法路径数目思路确定1e5中的质数统计每个点的连接情况开始对质数点进行处理完整代码 2673.使二叉树所有路径值相等的最小代价思路完整代码 2581.统计可能的树根数目思路建立连通关系将猜测数组变为哈希表&#xff0c;方便查询利…

RTF文件格式解析(二)图像问题

图片 一个RTF文件可以包含由其他应用创建的图象。这些图象可以是16进制(默认的)或2进制格式。图象属于目标引用&#xff0c;由\pict 控制字开始。如后面的例子中将描述的&#xff0c;\pict关键字应在\*\shppict引用控制关键字之后。一个图象引用具有如下语法&#xff1a; <p…