数据结构与算法「鱼C笔记一,谈谈算法,时间复杂度」

简介: 数据结构与算法「鱼C笔记一,谈谈算法,时间复杂度」教程参考自小甲鱼数据结构与算法视频,是个很不错的视频,感谢小甲鱼的付出相关代码已经上传到仓库的arithmetics文件夹为什么要学习算法因为好的算法可以提高程序的运行效率,我们从运行效率的方面来举例,1加到100。

数据结构与算法「鱼C笔记一,谈谈算法,时间复杂度」

教程参考自小甲鱼数据结构与算法视频,是个很不错的视频,感谢小甲鱼的付出

相关代码已经上传到仓库的arithmetics文件夹

为什么要学习算法

因为好的算法可以提高程序的运行效率,我们从运行效率的方面来举例,1加到100。案例使用的是Kotlin语言

1)不使用算法

我们不使用算法优化,直接用for循环进行相加

fun main(args: Array<String>) {
    var sum: Int = 0//总数
    for (i in 1..100) {//遍历,从1加到100
        sum + =i//sum和i相加
    }
    println("$sum")//输出
}

我们来看看执行次数

fun main(args: Array<String>) {
    var sum: Int = 0//执行1次
    for (i in 1..100) {
        sum += i//执行了100次
    }
    println("$sum")//输出
}

在for循环里面,执行了100次

2)使用算法

这里我们使用高斯算法

fun main(args: Array<String>) {
    var sum: Int = 0//执行1次
    val n = 100//执行1次
    sum = (1 + n) * n / 2//执行1次
    println("$sum")//执行1次
}

原本需要执行100次的任务,被我们1次就解决了

3)结论

当然,如果仅仅是这个案例,其实你觉得运行时间是差不多。我们稍微修改下代码,拿出证据。

fun main(args: Array<String>) {
    //普通循环
    val startTime = System.currentTimeMillis()   //获取开始时间毫秒值
    var sum = 0L//总数
    for (i in 1..1000000000) {//遍历,从1加到10亿
        sum += i//sum和i相加
    }
    println("$sum")//执行1次
    val endTime = System.currentTimeMillis() //获取结束时间毫秒值
    System.out.println("程序运行时间: " + (endTime - startTime) + "ms")
}

我们获取开始时间,结束时间,算出程序运行的时间。运行结果未使用高斯算法的程序运行时间为: 542毫秒。

fun main(args: Array<String>) {
    //使用高斯算法
    val startTime = System.currentTimeMillis()   //获取开始时间
    var sum= 0L//执行1次
    val n = 1000000000000//执行1次
    sum = (1 + n) * n / 2//执行1次
    println("$sum")//执行1次
    val endTime = System.currentTimeMillis() //获取结束时间
    System.out.println("程序运行时间: " + (endTime - startTime) + "ms")
}

运行使用了高斯算法的程序运行时间为:1毫秒

结论是使用算法确实能让我们的程序提高运行效率

时间复杂度

一,大O计法

我们用时间复杂度来评估程序运行时间的需求,如上小节的程序运行时间。

上小节为证明算法价值测试了程序运行时间,但是很多时候我们是要把算法先设计好,才开始写代码。这也符合我们的正常思维,毕竟只有设计时觉得是个好算法你才会去把它写出来嘛。

这也就是我们说的事前分析估算方法

1)常数阶

我们用一种叫做大O计法来度量算法的时间优劣,大O计法使用了程序的执行次数来衡量程序的运行时间。

fun main(args: Array<String>) {
    //输入字符串变量
    val n = readLine()//执行一次
    /**
     * 1,常数阶
     * 将输入字符串n通过toInt方法转化为Int类型
     * 而后面的?:1看起来是三元运算符,其实不是的,这是Kotlin的ELvis操作符。
     * 它的作用是当检测到n变量的值为空时,使用默认值1
     */
    a(n?.toInt() ?: 1)
}

fun a(n: Int) {
    println("$n")//执行1次
}

在该程序中,我们整个程序只执行了两次,一次是在输入字符串变量时,一次是在输出变量时。那么这样的算法程序,我们可以称它的时间复杂度为O(1)。

这种执行次数不会随着输入规模增长的算法我们称为常量阶。

输入规模 1 100 1000 10000
执行次数 2 2 2 2

那为什么明明是执行了两次,却称它的的时间复杂度是O(1)呢?因为在大O计数法里,常量阶不管执行多少次,都是写作1,比如

fun b(i: Int) {
    println("$i")
    println("$i")
    println("$i")
    println("$i")
    println("$i")
    println("$i")
    println("$i")
    println("$i")
}

在这段程序中,虽然我们输出了8次,但是使用大O计法仍然是O(1)。

这个可以先记着,后面我们与线性阶,平方阶对比时会讲解为什么这样,其实就是大O计法里面有一个思想,忽略少数的误差,有点像是高中物理测试时的忽略误差。

当n变得很大,很大的时候,常数阶因为执行次数并不会增长,和其他会随着输入规模的增长而变大的阶对比来说,实在太小了,就近似看成是1

常量阶的计法永远是O(1)

2)线性阶

线性阶其实就是我们的一元一次函数,y=kx+b

/**
 * 线性阶
 */
fun main(args: Array<String>) {
    //输入字符串变量
    val n = readLine()//执行次数为1
    /**
     * 1,线性阶
     * 将字符串n通过toInt方法转化为Int类型
     * 而后面的?:1看起来是三元运算符,其实不是的,这是Kotlin的ELvis操作符。
     * 它的作用是当检测到n变量的值为空时,使用默认值1
     */
    c(n?.toInt() ?: 1)//常量阶1
}

fun c(n: Int) {
    //循环,从1到n
    for (i in 1..n) {
        println(i)//执行次数是1
    }
}
输入规模 1 100 1000 10000
执行次数 2 101 1001 10001

在这个函数中,输出执行次数为1n,输入执行次数为1,我们会下意识得以为计法是O(1n+1)。

还记得之前说过忽略吗,我们可以通过高中数学知识来帮助记忆,我们把线性阶看成是一元一次函数,y=kx+b,x是我们的输入规模,y为执行次数,kb为常数,在大O技数法中,kb不管多少,都是忽略b,把k看做1。我们的程序可以看做是y=1n+1,最后的计法是O(n)。

/**
 * 线性阶
 */
fun main(args: Array<String>) {
    //输入字符串变量,这里相当于y=kx+b的b
    val n = readLine()
    val n2 = readLine()
    val n3 = readLine()
    val n4 = readLine()
    /**
     * 1,线性阶
     * 将字符串n通过toInt方法转化为Int类型
     * 而后面的?:1看起来是三元运算符,其实不是的,这是Kotlin的ELvis操作符。
     * 它的作用是当检测到n变量的值为空时,使用默认值1
     */
    d(n?.toInt() ?: 1)//常量阶1
}

fun d(n: Int) {
    //循环,从1到n,这里的n相当于1元一次函数的y=kx+b的x
    for (i in 1..n) {
        //这里的输出次数相当于1元一次函数y=kx+b的k
        println(i)//执行次数是1
        println(i)//执行次数是1
        println(i)//执行次数是1
        println(i)//执行次数是1
        println(i)//执行次数是1
        println(i)//执行次数是1
    }
}

还是用以上程序作为例子,y=6x+4,但是由于b要被忽略,k看做是,所以简化后还是y=x,记作时间复杂度为O(n)的算法

线性阶的计法永远是O(n)

3)平方阶

平方阶类似我们的一元二次方程,y=kx^2+bx+c

/**
 * 平方阶
 */
fun main(args: Array<String>) {
    //输入字符串变量
    val n = readLine()//执行了一次
    /**
     * 1,常数阶
     * 将字符串n通过toInt方法转化为Int类型
     * 而后面的?:1看起来是三元运算符,其实不是的,这是Kotlin的ELvis操作符。
     * 它的作用是当检测到n变量的值为空时,使用默认值1
     */
    a(n?.toInt() ?: 1)//常量阶1

}

fun a(n: Int) {
    for (i in 1..n) {
        for (j in 1..n) {
            println("$i,$j")//执行了n*n次
        }
    }
}

可以看做y=x2+1,记做O(n2),忽略输入次数c

执行次数函数 记作 用语 解释
13 O(1) 常数阶 常数阶不管执行次数多少,都看做1
2n+3 O(n) 线性阶 线性阶可以看做y=kx+b,永远忽略b,k记做1
3n^2+2n+1 O(n^2) 平方阶 平方阶看做y=kx^n+bx+c,忽略bx+c,k记做1
目录
相关文章
|
13天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
23 1
|
16天前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
58 4
|
14天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
13天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
21天前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
82 23
|
21天前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
56 20
|
13天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
37 1
|
21天前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
43 0
|
21天前
|
算法 vr&ar 计算机视觉
数据结构之洪水填充算法(DFS)
洪水填充算法是一种基于深度优先搜索(DFS)的图像处理技术,主要用于区域填充和图像分割。通过递归或栈的方式探索图像中的连通区域并进行颜色替换。本文介绍了算法的基本原理、数据结构设计(如链表和栈)、核心代码实现及应用实例,展示了算法在图像编辑等领域的高效性和灵活性。同时,文中也讨论了算法的优缺点,如实现简单但可能存在堆栈溢出的风险等。
35 0
|
2月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
40 4