数据结构第一讲:复杂度

news2024/9/22 7:25:07

数据结构第一讲:复杂度

  • 1.数据结构前言
    • 1.1什么是数据结构
    • 1.2算法
  • 2.算法效率
    • 2.1复杂度的概念
  • 3.时间复杂度
    • 3.1案例1
    • 3.2案例2
    • 3.3案例3
    • 3.4案例4
    • 3.5案例5
    • 3.6案例6
    • 3.7案例7
  • 4.空间复杂度
    • 4.1案例1
    • 4.2案例2
  • 5.常见复杂度对比
  • 6.轮转数组题目分析
    • 6.1优化1
    • 6.2优化2

博客简介:
1.该博客是数据结构第一讲,是数据结构初阶的内容,后续会继续输出数据结构高阶的内容
2.数据结构初阶内容全部由C语言来编写

1.数据结构前言

1.1什么是数据结构

将数据比作成一群在草原上的绵羊,绵羊是无序的,如果我们要查找一只叫做“贝蒂”的绵羊,一只一只查找是很耗时耗力的,所以我们要将所有的绵羊组织起来,对他们进行管理,以便于后边的增删查改等操作

像这样的将数据组织、存储的方式就叫做数据结构

1.2算法

算法其实就是解决问题的方法

2.算法效率

首先我们先看一道例题,然后在研究算法效率

案例:轮转数组链接: 力扣链接
我们直接将所写的代码拿上来:

void rotate(int* nums, int numsSize, int k) {
    //思路:每一次循环就将一个数字进行轮转
    while(k--)
    {
        int begin = *(nums+numsSize-1);
        for(int i = numsSize-1; i>0; i--)
        {
            *(nums+i) = *(nums+i-1);
        }
        *nums = begin;
    }
}

当我们提交时,我们可以发现:出错了!原因为:
在这里插入图片描述
那么为什么会出现超出时间限制这个错误呢,我们往下看:

2.1复杂度的概念

算法的运行需要时间,也需要空间,衡量一个算法的好坏通常是从时间和空间两个方面来看,也就是时间复杂度和空间复杂度

3.时间复杂度

时间复杂度判断总结:

1.只保留高阶项,保留高阶项后要省略掉低阶项
2.高阶项前的系数可以忽略
3.所有的常数项都要被替换成1
4.对于以log形式的函数都可以将底数忽略,写成是logN或者lgN
5.按照最坏的哪一种情况的复杂度作为算法的时间复杂度

下面我们来看具体的分析案例来加深对时间复杂度的理解:

3.1案例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);
}
// 计算Func2的时间复杂度?
void Func2(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N; ++k)
    {
        //for循环,时间复杂度为2*N
        ++count;
    }
    int M = 10;
    while (M--)
    {
        //时间复杂度为M,M为10,那么时间复杂度就是10
        ++count;
    }
    printf("%d\n", count);
}
//所以说:该算法的时间复杂度为2*N+10
//表示方法为:O(N)【保留了高阶项2*N,将高阶项前的系数可以忽略】

3.2案例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);
}
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; ++k)
    {
        //时间复杂度为M
        ++count;
    }
    for (int k = 0; k < N; ++k)
    {
        //时间复杂度为N
        ++count;
    }
    printf("%d\n", count);
}
//所以它的时间复杂度就是M+N
//表示为O(N)即可

3.3案例3

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

3.4案例4

// 计算strchr的时间复杂度?
const char* strchr(const char * str, int character)
{
    const char* p_begin = s;
    while (*p_begin != character)
    {
        if (*p_begin == '\0')
            return NULL;
        p_begin++;
    }
    return p_begin;
}
// 计算strchr的时间复杂度?
//该函数的作用是查找一个字符在字符串中的位置
const char* strchr(const char * str, int character)
{
    const char* p_begin = s;
    while (*p_begin != character)
    {
        if (*p_begin == '\0')
            return NULL;
        p_begin++;
    }
    return p_begin;
}
//我们假设字符串中一共有N个字符
//第一种情况(最好的情况):
//查找的第一次就查找到了目标字符,那么这时的时间复杂度就是O(1)
//第二种情况(中间情况):
//目标字符组在字符串的中间位置,那么这时的时间复杂度就是N/2,表示为O(N)
//第三种情况(最坏的情况):
//当目标字符在我们要查找的字符串末尾时,这时的时间复杂度为O(N)
//
//取最坏的情况作为算法最终的时间复杂度,也就是O(N)

3.5案例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;
    }
}
// 计算BubbleSort的时间复杂度?
//冒泡排序函数
void BubbleSort(int* a, int n)
{
    //这个函数将循环嵌套使用
    for (size_t end = n; end > 0; --end)
    {
        //外层循环的次数为N
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            //外层每循环一次,内层都要交换N-1次
            if (a[i - 1] > a[i])
            {
                Swap(&a[i - 1], &a[i]);
                exchange = 1;
            }
        }
        if (exchange == 0)
            break;
    }
}
//所以说该算法的时间复杂度为一个等差数列进行求和的结果:
//N N-1 ...... 2 1
//结果为N(N+1)/2 , 表示为O(N^2)

3.6案例6

void func5(int n)
{
    int cnt = 1;
    while (cnt < n)
    {
        cnt *= 2;
    }
}
void func5(int n)
{
    int cnt = 1;
    while (cnt < n)
    {
        cnt *= 2;
    }
}
//当N取10时,我们来看这个算法是怎么运行的:
//cut 1 2 4 8  16
//ret 2 4 8 16 32
//由于cnt并不是从0开始,一直顺序增加到10的,而是不停的*2做到的,所以此时的时间复杂度并不是O(n)
//当N为10时,循环了4次就做到了,通过我们的分析可以看出,该算法的时间复杂度为O(logN)

3.7案例7

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
    if (0 == N)
        return 1;
    return Fac(N - 1) * N;
}
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
    if (0 == N)
        return 1;
    return Fac(N - 1) * N;
}
//这里是计算递归函数的时间复杂度
//不难看出,该函数是从Fac(N-1)递归到了Fac(0)
//也就是从0到N递增运行,时间复杂度为O(N)

我们已经了解到了时间复杂度的计算方法,那么分析一个算法的好坏是否已经是了然于胸了呢,其实不是,判断一个算法的好坏还需要判断它的空间复杂度
当前来说,内存空间是相对而言十分廉价的,我们经常会使用空间来换取时间,那是不是就说明空间可以随意地使用呢,肯定不对!所以我们仍然需要了解空间复杂度的计算方法:

4.空间复杂度

空间复杂度计算针对的仅仅是算法进行过程中临时开辟的内存

4.1案例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;
    }
}
// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
    for (size_t end = n; end > 0; --end)
    {
        //size_t end = n  时间复杂度为1
        int exchange = 0;
        //int exchange = 0  时间复杂度为2(1+1=2)
        for (size_t i = 1; i < end; ++i)
        {
            //size_t i = 1  时间复杂度为3
            if (a[i - 1] > a[i])
            {
                Swap(&a[i - 1], &a[i]);
                exchange = 1;
            }
        }
        if (exchange == 0)
            break;
    }
}
//程序运行时只对三个变量进行了初始化,所以时间复杂度为O(1)

4.2案例2

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
    if (N == 0)
        return 1;
    return Fac(N - 1) * N;
}
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
    if (N == 0)
        return 1;
    return Fac(N - 1) * N;
}
//每一次函数递归时都要创建一次函数
//所以它的时间复杂度为O(N)

5.常见复杂度对比

在这里插入图片描述
在这里插入图片描述

6.轮转数组题目分析

我们有了上述的知识之后,再来看我们一开始写的题目:

void rotate(int* nums, int numsSize, int k) {
    while (k--)
    {
        int end = nums[numsSize - 1];
        for (int i = numsSize - 1; i > 0; i--)
        {
            nums[i] = nums[i - 1];
        }
        nums[0] = end;
    }
}

对它进行时间复杂度的分析:

void rotate(int* nums, int numsSize, int k) {
    while (k--)
    {
        //外层循环k次
        int end = nums[numsSize - 1];
        for (int i = numsSize - 1; i > 0; i--)
        {
            //外层每循环一次,内层进行N-1次操作
            nums[i] = nums[i - 1];
        }
        nums[0] = end;
    }
}
//时间复杂度为O(N^2)
//是一个等差数列

通过复杂度对比表格可以看出,O(n^2)复杂度需要的时间很长,那么我们是否可以将这个算法换成是O(N)或者是O(1)这些算法呢?优化如下:

6.1优化1

思路分析:
申请新数组空间,先将后k个数据放到新数组中,再将剩下的数据挪到新数组中

void rotate(int* nums, int numsSize, int k)
{
    int newArr[numsSize];
    for (int i = 0; i < numsSize; ++i)
    {
        newArr[(i + k) % numsSize] = nums[i];
    }
    for (int i = 0; i < numsSize; ++i)
    {
        nums[i] = newArr[i];
    }
}

时间复杂度分析:

void rotate(int* nums, int numsSize, int k)
{
    int newArr[numsSize];
    for (int i = 0; i < numsSize; ++i)
    {
        //时间复杂度为numsSize
        newArr[(i + k) % numsSize] = nums[i];
    }
    for (int i = 0; i < numsSize; ++i)
    {
        //时间复杂度为numsSize
        nums[i] = newArr[i];
    }
}
//所以说该算法的时间复杂度为O(N)
//但是它的空间复杂度为O(N),因为创建了一个新的数组,这个数组的大小为numsSize
//这里就是一个空间换取时间的案例

6.2优化2

思路分析:
在这里插入图片描述

void reverse(int* nums, int begin, int end)
{
	while (begin < end) {
		int tmp = nums[begin];
		nums[begin] = nums[end];
		nums[end] = tmp;
		begin++;
		end--;
	}
}
void rotate(int* nums, int numsSize, int k)
{
	k = k % numsSize;
	reverse(nums, 0, numsSize - k - 1);
	reverse(nums, numsSize - k, numsSize - 1);
	reverse(nums, 0, numsSize - 1);
}

时间复杂度分析:

void reverse(int* nums, int begin, int end)
{
	while (begin < end) {
		int tmp = nums[begin];
		nums[begin] = nums[end];
		nums[end] = tmp;
		begin++; 
		end--;
	}
}
void rotate(int* nums, int numsSize, int k)
{
	k = k % numsSize;
	reverse(nums, 0, numsSize - k - 1);
	//时间复杂度为O(N)
	reverse(nums, numsSize - k, numsSize - 1);
	//时间复杂度为O(N)
	reverse(nums, 0, numsSize - 1);
	//时间复杂度为O(N)
}
//所以该函数的时间复杂度为O(N)
//但是它的空间复杂度只有O(1)

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

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

相关文章

爬虫的深度爬取

爬虫的深度爬取和爬取视频的方式 深度爬取豆瓣读书 import time import fake_useragent import requests from lxml import etree head {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 …

SpringBoot 介绍和使用(详细)

使用SpringBoot之前,我们需要了解Maven,并配置国内源(为什么要配置这些,下面会详细介绍),下面我们将创建一个SpringBoot项目"输出Hello World"介绍. 1.环境准备 ⾃检Idea版本: 社区版: 2021.1 -2022.1.4 专业版: ⽆要求 如果个⼈电脑安装的idea不在这个范围, 需要…

C++编程逻辑讲解step by step:用MFC类库开发一个小游戏-军官能力测验

先给出最终效果 代码&#xff08;没法一点一点讲了&#xff0c;太长&#xff09; checkvw.h checkvw.cpp // checkvw.h : interface of the CCheckerView class // /class CCheckerView : public CView { protected: // create from serialization onlyCCheckerView();DECL…

掌握这4种翻译方式,阅读外语文件不再困难

如果你作为学生需要学习或者研究外国文件&#xff0c;或者出国旅游前也需要了解一些外国文件。如果掌握文件翻译工具&#xff0c;那这些问题就不是问题啦。这里我给你介绍几个效果不错的文件翻译工具吧。 1.福.昕文献翻译网站 这个工具只要在线就能使用&#xff0c;而且在线丝…

腾讯技术创作特训营 -- SUPERWINNIE -- AI重塑社交内容

目录 1 什么是AI社交内容 2 案例拆解 3 用LLM做爆文选题 4 用LLM出爆文脚本提示词 1 什么是AI社交内容 任何一个因素被AI取代都是AI社交内容 2 案例拆解 数字人 资讯素材 录屏产品的素材&#xff08;小红书测试AI产品&#xff09; 脚本 素材 剪辑 3 用LLM做爆文选题 &…

突破•指针二

听说这是目录哦 复习review❤️野指针&#x1fae7;assert断言&#x1fae7;assert的神奇之处 指针的使用和传址调用&#x1fae7;数组名的理解&#x1fae7;理解整个数组和数组首元素地址的区别 使用指针访问数组&#x1fae7;一维数组传参的本质&#x1fae7;二级指针&#x…

mq基础入门

前言 黑马商城导入了mq依赖 但是没有改service发消息 因为下单业务一直有问题 所以先没改 作业时间不够也没处理 1.异步调用 就是所谓的发短信 可以不用立即恢复 比如下单业务 下了单更新信息 就相当于发个消息通知一下 不用立即更改 但是支付就比较重要 不需要因为故障导…

数据结构——队列(顺序结构)

一、队列的定义 队列是一种线性数据结构,具有先进先出(FIFO)的特性。它可以理解为排队的队伍,先到先得,后到后得。队列有两个基本操作:入队(enqueue)和出队(dequeue)。入队在队列的末尾插入新元素,出队则是从队列的头部移除元素。这种数据结构常用于需要按照顺序处…

C语言中的运算符(二)

文章目录 &#x1f34a;自我介绍&#x1f34a;位运算符&#x1f34a;赋值复合运算符&#x1f34a;逗号运算符和赋值运算符&#x1f34a;运算符优先级 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ …

Qt开发网络嗅探器03

数据包分析 想要知道如何解析IP数据包&#xff0c;就要知道不同的IP数据包的包头结构&#xff0c;于是我们上⽹查查资料&#xff1a; 以太网数据包 ARP数据包 IPv4 IPv6 TCP UDP ICMP ICMPv6 根据以上数据包头结构&#xff0c;我们就有了我们的protocol.h文件&#xff0c;声明…

HTML+CSS+JS精美气泡提示框

源代码在效果图后面 点赞❤️关注&#x1f49b;收藏⭐️ 主要实现&#xff1a;提示框出现和消失两种丝滑动画 弹出气泡提示框后延迟2秒自动消失 效果图 错误框 正确 警告 提示 源代码 <!DOCTYPE html> <html lang"en"> <head><meta cha…

聚焦IOC容器刷新环节prepareBeanFactory专项

目录 一、IOC容器的刷新环节快速回顾 二、prepareBeanFactory源码展示分析 三、设置基本属性深入分析 &#xff08;一&#xff09;设置类加载器 &#xff08;二&#xff09;设置表达式解析器 &#xff08;三&#xff09;设置属性编辑器 &#xff08;四&#xff09;设计目…

快速排序、快速选择算法、找最近公共祖先

快速排序&#xff08;用i和j双指针&#xff0c;左部分值小&#xff0c;当ij时&#xff0c;两部分按基准值已经排序好&#xff0c;将基准值放到j即可。 class Solution {public int[] sortArray(int[] nums) {sort(nums,0,nums.length-1);return nums;}void sort(int[] nums,int…

初阶数据结构的实现1 顺序表和链表

顺序表和链表 1.线性表1.1顺序表1.1.1静态顺序表&#xff08;不去实现&#xff09;1.1.2动态顺序表1.1.2.1 定义程序目标1.1.2.2 设计程序1.1.2.3编写代码1.1.2.3测试和调试代码 1.1.2 顺序表的问题与思考 1.2链表1.2.1链表的概念及结构1.2.1.1 定义程序目标1.2.1.2 设计程序1.…

51单片机嵌入式开发:15、STC89C52RC操作蜂鸣器实现一个music音乐播放器的音乐盒

STC89C52RC操作蜂鸣器实现一个music音乐播放器的音乐盒 1 概述2 蜂鸣器操作方法3 蜂鸣器发出音声4 硬件电路5 软件实现6 整体工程&#xff1a;7 总结 1 概述 要实现一个基于STC89C52RC单片机的音乐盒&#xff0c;可以按照以下步骤进行&#xff1a; &#xff08;1&#xff09;硬…

Golang | Leetcode Golang题解之第274题H指数

题目&#xff1a; 题解&#xff1a; func hIndex(citations []int) int {// 答案最多只能到数组长度left,right:0,len(citations)var mid intfor left<right{// 1 防止死循环mid(leftright1)>>1cnt:0for _,v:range citations{if v>mid{cnt}}if cnt>mid{// 要找…

ISO 14001:企业环境管理的黄金标准

在全球可持续发展潮流中&#xff0c;企业的环境责任愈发重要。ISO 14001环境管理体系&#xff0c;为各类企业提供了一条优化环境绩效的明路。无论企业规模大小&#xff0c;业务类型如何&#xff0c;ISO 14001都能帮助企业有效管理环境影响&#xff0c;实现绿色发展。 ISO 14001…

QT样式美化 之 qss入门

样例一 *{font-size:13px;color:white;font-family:"宋体"; }CallWidget QLineEdit#telEdt {font-size:24px;}QMainWindow,QDialog{background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #1B2534, stop: 0.4 #010101,stop: 0.5 #000101, stop: 1.0 #1F2B…

【LeetCode】day17:654 - 最大二叉树, 617 - 合并二叉树, 700 - 二叉树搜索树中的搜索, 98 - 验证二叉搜索树

LeetCode 代码随想录跟练 Day17 654.最大二叉树617.合并二叉树700.二叉搜索树中的搜索98.验证二叉搜索树 654.最大二叉树 题目描述&#xff1a; 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的…

PyCharm创建一个空的python项目

1.设置项目路径 2.配置python解释器 右下角可以选择always