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


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

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

所属专栏:数据结构与算法
贝蒂的主页: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;//1nsint n = 0;//1nsscanf("%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);elsereturn 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++];elsetempArr[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;//避免溢出intMergeSort(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.mzph.cn/news/712273.shtml

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

相关文章

600万订单每秒Disruptor +SpringBoot,如何解决消息不丢失?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; Disruptor 官方说能达到每秒600w OPS订单处理能力&…

Java——Object

1.Object万类之祖 1.1 Object类型的概述 Object类是所有类型的顶层父类&#xff0c;所有类型的直接或者间接的父类&#xff1b;所有的类型中都含有Object类中的所有方法。 随意定义一个类型,不手动显式定义其父类&#xff0c;那么这个类的父类就是Object类 public Object() …

【C语言】指针初阶2.0版本

这篇博文我们来继续学习指针的其他内容 指针2.0 传值调用与传址调用传值调用传址调用 一维数组与指针理解数组名使用指针深入理解一维数组 二级指针指针数组二维数组与指针 传值调用与传址调用 在开始之前&#xff0c;我们需要先了解这个概念&#xff0c;后面才能够正常的学习…

利用 Python 抓取数据探索汽车市场趋势

一、引言 随着全球对环境保护意识的增强和技术的进步&#xff0c;新能源汽车作为一种环保、高效的交通工具&#xff0c;正逐渐受到人们的关注和青睐。在这个背景下&#xff0c;对汽车市场的数据进行分析和研究显得尤为重要。 本文将介绍如何利用 Python 编程语言&#xff0c;结…

VSCode上搭建C/C++开发环境(vscode配置c/c++环境)Windows系统---保姆级教程

引言劝退 VSCode&#xff0c;全称为Visual Studio Code&#xff0c;是由微软开发的一款轻量级&#xff0c;跨平台的代码编辑器。大家能来搜用VSCode配置c/c&#xff0c;想必也知道VSCode的强大&#xff0c;可以手握一个VSCode同时编写如C&#xff0c;C&#xff0c;C#&#xff…

微服务day02-Ribbon负载均衡与Nacos安装与入门

一.Ribbon负载均衡 在上一节中&#xff0c;我们通过在RestTemplte实例中加上了注解 LoadBalanced,表示将来由RestTemplate发起的请求会被Ribbon拦截和处理&#xff0c;实现了访问服务时的负载均衡&#xff0c;那么他是如何实现的呢&#xff1f; 1.1 Ribbon负载均衡的原理 Rib…

链表的归并排序-LeetCode(Python版)

双指针归并排序&#xff01;图解排序链表&#xff01;-知乎 class ListNode(object):def __init__(self, val0, nextNone):self.val valself.next nextclass Solution(object):def find_mid(self, head): # 快慢指针slow, fast head, headwhile fast.next and fast.next.n…

linux 硬盘存储剩余容量自动化监控+报警通知

linux 硬盘存储剩余容量自动化监控报警通知 编写shell脚本 #!/bin/bash# 获取系统存储大小&#xff08;单位为GB&#xff09; storage_size$(df -h / | awk NR2 {print $4} | sed s/G//)# 阈值&#xff08;小于10GB触发报警&#xff09; threshold10# 钉钉机器人 Webhook UR…

LabVIEW非接触式电阻抗层析成像系统

LabVIEW非接触式电阻抗层析成像系统 非接触式电阻抗层析成像&#xff08;NEIT&#xff09;技术以其无辐射、非接触、响应速度快的特点&#xff0c;为实时监测提供了新的解决方案。基于LabVIEW的电阻抗层析成像系统&#xff0c;实现了数据的在线采集及实时成像&#xff0c;提高…

代码随想录算法训练营第四十四天|139.单词拆分、56.携带矿石资源

139.单词拆分 思路&#xff1a;将字符串s看作为背包容量&#xff0c;从字符串中获取物品&#xff0c;刚好满足背包容量的过程&#xff0c;因为可以从字符串中多次取值&#xff0c;相当于物品的数量是不限制&#xff0c;这就是一个完全背包的问题&#xff01;这个题有个关键点&a…

Python中的windows路径问题

在Python中处理Windows路径时,经常会遇到一些特殊的问题。这主要是因为Windows和大多数其他操作系统(如Linux和macOS)使用不同的路径分隔符。在Windows中,路径使用反斜杠(\)作为分隔符,而在其他操作系统中,路径使用正斜杠(/)作为分隔符。 以下是在Python中处理Windo…

Java SE:多线程(Thread)

1. 线程两个基本概念 并发&#xff1a;即线程交替运行多个指令并行&#xff1a;即多个线程同时运行指令 并发并行不矛盾&#xff0c;两者可同时发生&#xff0c;即多个线程交替运行指令 2. 多线程3种实现方式 2.1 直接创建线程对象 /*** 方式1&#xff1a;* 1. 创建thread类的…

mybatis plus 深入学习 【Base Mapper】的方法 【IService】的方法

mybatis plus 深入学习 常见注解 1.TableName 描述&#xff1a;表名注解&#xff0c;标识实体类对应的表使用位置&#xff1a;实体类 TableName("sys_user") public class User {private Long id;private String name;private Integer age;private String email;…

【Linux系统化学习】信号的保存

目录 阻塞信号 信号处理常见方式概览 信号的其他相关概念 在内核中的表示 sigset_t 信号集操作函数 sigprocmask函数 sigpending函数 信号的捕捉 内核如何实现信号的捕捉 sigaction函数 可重入函数 volatile 阻塞信号 信号处理常见方式概览 当信号来临时&#x…

c++算法入门教程(2)

C是一种功能强大且广泛应用的编程语言&#xff0c;对于想要深入学习编程和算法的人来说&#xff0c;掌握C是一个重要的里程碑。本文将带你逐步了解C编程的基础知识&#xff0c;并介绍一些常见的算法和编程技巧帮你入门c算法。 ​在c算法入门教程(1) 中&#xff0c;我讲解了什么…

GEE:使用Sigmoid激活函数对单波段图像进行变换(以NDVI为例)

作者:CSDN @ _养乐多_ 本文将介绍在 Google Earth Engine (GEE)平台上,对任意单波段影像进行 Sigmoid 变换的代码。并以对 NDVI 影像像素值的变换为例。 文章目录 一、Sigmoid激活函数1.1 什么是 Sigmoid 激活函数1.2 用到遥感图像上有什么用?二、代码链接三、完整代码一…

查询每个会话使用内存大小(DM8达梦数据库)

DM8达梦数据库查询每个会话使用内存大小 1 环境介绍2 查询每个sql会话使用内存大小3 达梦数据库学习使用列表 1 环境介绍 在某些环境数据库内存增长到服务器内存用完,发生OOM事件,可以分析sql会话使用内存大小; 2 查询每个sql会话使用内存大小 --创建SQL会话占用内存记录表 …

共享栈的C语言实现

共享栈&#xff1a;所谓共享栈就是为了节省空间&#xff0c;让两个栈共享一片连续的存储空间&#xff0c;两个栈从这片连续的共享空间的两端向中间扩充自己的存储空间&#xff0c;设这片存储空间的大小为maxSize&#xff0c;采用栈顶指针始终指向当前栈顶元素的方式来实现共享栈…

简单认识算法的复杂度

时间复杂度与空间复杂度 1.算法的复杂度 ​ 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏&#xff0c;一般是从时间和空间两个维度来衡量的&#xff0c;即时间复杂度和空间复杂度。 ​ 时间复杂度主要衡量一个算法…

MYSQL02高级_目录结构、默认数据库、表文件、系统独立表空间

文章目录 ①. MySQL目录结构②. 查看默认数据库③. MYSQL5.7和8表文件③. 系统、独立表空间 ①. MySQL目录结构 ①. 如何查看关联mysql目录 [rootmysql8 ~]# find / -name mysql /var/lib/mysql /var/lib/mysql/mysql /etc/selinux/targeted/tmp/modules/100/mysql /etc/seli…