通信系统仿真

崔春雷

目录

  • 1 第一单元: MATLAB基础
    • 1.1 课程说明与资料
      • 1.1.1 作业参考答案
      • 1.1.2 移动22级作业答案
    • 1.2 MATLAB安装与运行环境
      • 1.2.1 MATLAB介绍
    • 1.3 基本数据类型:数值类型
    • 1.4 基本数据类型:字符类型
    • 1.5 数据类型转换与输出
    • 1.6 数组与矩阵基础
      • 1.6.1 矩阵运算进阶
    • 1.7 数组与矩阵常用函数
    • 1.8 matlab中的逻辑运算
    • 1.9 实验: MATLAB常用数学函数
      • 1.9.1 实验 作业答案
    • 1.10 元胞数组
    • 1.11 结构体数组
      • 1.11.1 结构体进阶
      • 1.11.2 元胞数组与结构体数组对比
      • 1.11.3 map 容器
    • 1.12 附录:MATLAB常用基础命令
    • 1.13 拓展内容:实时脚本
      • 1.13.1 实时脚本示例
    • 1.14 课程作业与答案
      • 1.14.1 《通信系统仿真》期末考试
  • 2 第二单元:Matlab 程序设计
    • 2.1 顺序结构程序
    • 2.2 分支结构—— if语句
    • 2.3 分支结构—— switch语句
    • 2.4 循环结构—— while语句
    • 2.5 循环结构—— for语句
    • 2.6 图像处理基础
    • 2.7 Matlab的函数
      • 2.7.1 函数内容的课外扩展
    • 2.8 本章实验:for循环的应用
      • 2.8.1 素数问题
        • 2.8.1.1 素数的螺旋线排列
      • 2.8.2 3X+1猜想
      • 2.8.3 7 行代码计算 π
    • 2.9 排序算法
      • 2.9.1 冒泡排序
      • 2.9.2 选择排序
      • 2.9.3 插入排序
      • 2.9.4 快速排序
      • 2.9.5 基数排序
      • 2.9.6 计数排序
      • 2.9.7 堆排序
    • 2.10 动态规划算法
      • 2.10.1 动态规划编程实例
      • 2.10.2 动态规划:01背包问题
      • 2.10.3 动态规划常见题目分析
      • 2.10.4 动态规划题目分析2
    • 2.11 常用算法简介
      • 2.11.1 剪枝算法
      • 2.11.2 二分查找
      • 2.11.3 递归算法
      • 2.11.4 回溯算法
        • 2.11.4.1 Leetcode回溯题目合集
        • 2.11.4.2 回溯算法总结
        • 2.11.4.3 回溯法解数独问题
        • 2.11.4.4 DFS与BFS
          • 2.11.4.4.1 DFS/BFS原理
          • 2.11.4.4.2 BFS的应用:Dijkstra算法
      • 2.11.5 n 皇后问题专题
      • 2.11.6 双指针算法
      • 2.11.7 数组模拟链表(约瑟夫环)
      • 2.11.8 Hash(哈希表)
      • 2.11.9 图论与路径规划
        • 2.11.9.1 迪杰斯特拉算法
        • 2.11.9.2 A*算法
          • 2.11.9.2.1 A*算法的MATLAB实现
        • 2.11.9.3 RRT路径规划算法
          • 2.11.9.3.1 RRT算法 MATLAB代码
          • 2.11.9.3.2 参考资料
      • 2.11.10 数据结构
        • 2.11.10.1 数据结构例题
      • 2.11.11 前缀和 差分 双指针
      • 2.11.12 位运算
      • 2.11.13 常用算法代码模板
    • 2.12 练习题库
    • 2.13 code
      • 2.13.1 简易计算器gui代码
      • 2.13.2 五子棋
      • 2.13.3 连连看小游戏
      • 2.13.4 递归算法与汉诺塔
      • 2.13.5 有理数的小数循环节
    • 2.14 MATLAB编程风格
      • 2.14.1 向量化编程专题
  • 3 第三单元:Matlab 图形图像处理
    • 3.1 二维图形绘图基础
    • 3.2 二维图形绘图进阶
    • 3.3 三维图形绘图
      • 3.3.1 MATLAB绘图小结
        • 3.3.1.1 用matlab绘制好看图像
    • 3.4 MATLAB高级绘图
    • 3.5 文件操作
    • 3.6 Matlab图像处理进阶
      • 3.6.1 补充:Matlab图像处理常用函数
      • 3.6.2 RGB/HSV/HSI颜色模型
      • 3.6.3 图片切换动画效果
      • 3.6.4 图像连通域标记
      • 3.6.5 图像旋转与插值
      • 3.6.6 图像的形态学
      • 3.6.7 空间滤波
        • 3.6.7.1 图像中常见的噪声类型与滤波方法
        • 3.6.7.2 matlab中的滤波函数
        • 3.6.7.3 BM3D 去噪算法
        • 3.6.7.4 双边滤波
      • 3.6.8 图像的频域处理
    • 3.7 本章总结
    • 3.8 实验 : matlab 绘图练习1
    • 3.9 实验: matlab 绘图练习2
    • 3.10 实验 :数学函数图像绘制
    • 3.11 实验:绘图综合练习
    • 3.12 实验:曲线拟合
    • 3.13 实验:牛顿法求解方程的根
    • 3.14 实验:信号的傅里叶变换
      • 3.14.1 傅里叶变换、小波变换、希尔伯特变换
      • 3.14.2 新建目录
    • 3.15 课外补充:图像处理基础1
    • 3.16 课外补充:图像处理基础2
    • 3.17 课外补充:图像处理基础3
    • 3.18 课外补充:PYTHON基础
  • 4 第五单元:MATLAB通信仿真
    • 4.1 现代通信系统的介绍
    • 4.2 模拟通信系统的仿真原理
    • 4.3 HDB3编解码的仿真实现
    • 4.4 SIMULINK和其模块简介
    • 4.5 数字通信系统的仿真原理
    • 4.6 模拟通信系统Simulink仿真
    • 4.7 数字通信系统Simulink仿真
    • 4.8 音频信号测处理与仿真
    • 4.9 图像数字水印技术
      • 4.9.1 三角函数到傅里叶变换再到语音识别与数字水印
    • 4.10 信息系统与算法
      • 4.10.1 递归算法
        • 4.10.1.1 递归与堆栈的关系
      • 4.10.2 哈希表
      • 4.10.3 双指针算法
        • 4.10.3.1 双指针算法实战
        • 4.10.3.2 双指针进阶:滑动窗口算法
      • 4.10.4 字符串匹配 KMP算法
        • 4.10.4.1 字符串匹配B-M算法
      • 4.10.5 快速傅里叶变换
      • 4.10.6 回溯算法
      • 4.10.7 动态规划
      • 4.10.8 分治算法
      • 4.10.9 Dijkstra算法
  • 5 第六单元: systemview通信仿真
    • 5.1 SystemView概述
    • 5.2 模拟通信系统 数字系统的仿真分析
    • 5.3 SystemView通信系统仿真进阶
    • 5.4 新建课程目录
  • 6 第四单元:MATLAB高级应用
    • 6.1 符号运算基础
      • 6.1.1 利用Matlab自动推导公式
    • 6.2 Matlab中的数值计算
      • 6.2.1 积分的计算
      • 6.2.2 龙格库塔:常微分方程的数值解法
      • 6.2.3 fmincon函数与非线性方程最小值
    • 6.3 统计、拟合、插值
      • 6.3.1 协方差与相关系数
    • 6.4 GUI设计初步
    • 6.5 matlab GUI界面编程
      • 6.5.1 gui实例
      • 6.5.2 gui编程中常用函数
      • 6.5.3 App Designer入门
    • 6.6 实验:GUI设计图像空间变换系统
    • 6.7 作业:利用GUI设计 计算器、信号发生器等
    • 6.8 MTALB数据导入方法
    • 6.9 课外补充:MATLAB的App会取代GUI吗?
    • 6.10 模拟退火算法matlab实现
    • 6.11 遗传算法的Matlab实现
      • 6.11.1 进化算法(Evolutionary Algorithm)及相关函数介绍
    • 6.12 粒子群算法 matlab实现
      • 6.12.1 粒子群算法及MATLAB实例仿真
    • 6.13 BP网络的应用
    • 6.14 matlab 结构体
    • 6.15 群智能算法合集
  • 7 拓展知识
    • 7.1 什么是算法的时间复杂度?
    • 7.2 Notepad++使用教程
    • 7.3 MATLAB常用函数总结
    • 7.4 MATLAB常用知识点总结
    • 7.5 MATLAB命令大全
    • 7.6 视频:MATLAB官方基础教程
    • 7.7 经典书籍:Matlab2012经典超强教程
    • 7.8 经典书籍:MATLAB揭秘(自学宝典)
    • 7.9 经典资料:MATLAB N个实用技巧
    • 7.10 Matlab编程小技巧
    • 7.11 寻优算法
      • 7.11.1 Dijkstra算法python实现
    • 7.12 PYTHON基础教程
      • 7.12.1 Python进阶
      • 7.12.2 Python小技巧
      • 7.12.3 Python总结
        • 7.12.3.1 Python循环语句总结
        • 7.12.3.2 24个顶级Python库
        • 7.12.3.3 魔法函数
      • 7.12.4 廖雪峰python
      • 7.12.5 正则表达式基础
      • 7.12.6 numpy
        • 7.12.6.1 101道Numpy习题
        • 7.12.6.2 Numpy简要语法教程
        • 7.12.6.3 Numpy实现全连接神经网络 (手写数字识别)
        • 7.12.6.4 图解NumPy
      • 7.12.7 matplotlib
        • 7.12.7.1 matplotlib练习50题
        • 7.12.7.2 Matplotlib速查表
        • 7.12.7.3 Matplotlib 实操指南
      • 7.12.8 Python3 模块 import
      • 7.12.9 Python 小项目
    • 7.13 参考资源:数据结构与算法
      • 7.13.1 十大经典排序算法总结
    • 7.14 机器学习概述
      • 7.14.1 反向传播算法
        • 7.14.1.1 反向传播的数学原理
      • 7.14.2 极大似然估计
        • 7.14.2.1 极大似然估计与最小二乘法
      • 7.14.3 Batch Normalization
        • 7.14.3.1 Batch Normalization&Dropout浅析
        • 7.14.3.2 ​BN层的梯度反向传播计算
        • 7.14.3.3 Batch Size的大小与神经网络的性能
        • 7.14.3.4 标准化和归一化
      • 7.14.4 主成分分析PCA与SVD奇异值分解
        • 7.14.4.1 岭回归 与 PCA
        • 7.14.4.2 PCA原理推导
        • 7.14.4.3 PCA原理新解
        • 7.14.4.4 svd
        • 7.14.4.5 PCA数学原理
      • 7.14.5 正则化
        • 7.14.5.1 L1、L2正则化和过拟合 总结
        • 7.14.5.2 L1 和 L2 正则化的直观解释
      • 7.14.6 SVM
        • 7.14.6.1 从零推导支持向量机(SVM)
        • 7.14.6.2 支持向量机(SVM)介绍
        • 7.14.6.3 SVM推导与实战
        • 7.14.6.4 支持向量机的直观理解
        • 7.14.6.5 浅显易懂的支持向量机SVM
      • 7.14.7 线性回归
      • 7.14.8 逻辑回归
      • 7.14.9 BP算法
        • 7.14.9.1 万能逼近——神经网络拟合任意函数原理
      • 7.14.10 激活与池化
        • 7.14.10.1 激活函数与损失函数 小结
      • 7.14.11 深度学习简述
        • 7.14.11.1 MATLAB2020深度学习实例
      • 7.14.12 损失函数与误差反向传播
        • 7.14.12.1 梯度下降与损失函数
      • 7.14.13 深度学习优化问题
      • 7.14.14 梯度下降法
        • 7.14.14.1 各类梯度下降算法的Python实现
        • 7.14.14.2 梯度下降的直观理解
        • 7.14.14.3 动量、RMSProp、Adam
      • 7.14.15 卷积的概念
        • 7.14.15.1 卷积的矩阵化算法
      • 7.14.16 局部连接
      • 7.14.17 RNN
      • 7.14.18 LSTM
      • 7.14.19 CNN-四大经典CNN技术浅析
      • 7.14.20 熵(Entropy)与交叉熵
      • 7.14.21 softmax函数详解
      • 7.14.22 自编码算法详细理解与代码实现
      • 7.14.23 pytorch
        • 7.14.23.1 ​PyTorch简介
          • 7.14.23.1.1 Pytorch快速入门资料
        • 7.14.23.2 CNN的PyTorch实现
        • 7.14.23.3 pytorch总结
        • 7.14.23.4 PyTorch trick 集锦
        • 7.14.23.5 在PyTorch上加载自定义数据集
        • 7.14.23.6 实战:Pytorch识别验证码
        • 7.14.23.7 实战:Transformer的最简洁pytorch实现
        • 7.14.23.8 使用PyTorch实现神经网络分类
      • 7.14.24 卷积神经网络CNN概述
        • 7.14.24.1 CNN 简易原理
        • 7.14.24.2 卷积神经网络CNN原理详解
        • 7.14.24.3 自己手写一个卷积神经网络
        • 7.14.24.4 CNN反向传播算法
        • 7.14.24.5 卷积计算、作用与思想
        • 7.14.24.6 用卷积神经网络CNN识别手写数字集
        • 7.14.24.7 卷积 池化 参数的计算
        • 7.14.24.8 im2col方法实现卷积算法
        • 7.14.24.9 卷积核的梯度计算
        • 7.14.24.10 卷积层反向传播推导及实现
        • 7.14.24.11 反向传输算法
          • 7.14.24.11.1 resnet残差网络
        • 7.14.24.12 CNN反向传播的MATLAB实现
      • 7.14.25 神经网络的调参技巧
      • 7.14.26 BP神经网络
        • 7.14.26.1 零开始搭建bp神经网络
        • 7.14.26.2 MATLAB自带的bp工具箱
        • 7.14.26.3 神经网络中偏置(bias)的作用
      • 7.14.27 聚类分析 k-means
        • 7.14.27.1 matlab做聚类分析(k-means)
        • 7.14.27.2 聚类模型探讨综述
        • 7.14.27.3 5种经典聚类算法
      • 7.14.28 深度学习的一些概念
      • 7.14.29 人工智能简述:AI的过去和现在
      • 7.14.30 k-NN(k近邻算法)
      • 7.14.31 神经网络中的优化器:BGD、SGD、MBGD、Momentum
      • 7.14.32 卷积神经网络的经典网络总结
        • 7.14.32.1 卷积神经网络中十大拍案叫绝的操作
      • 7.14.33 GAN 对抗样本攻击
      • 7.14.34 蒙特卡洛模拟
      • 7.14.35 dropout与随机部分连接
      • 7.14.36 Jupyter 等 IDE概览
      • 7.14.37 分类算法常用评价指标
      • 7.14.38 Inception 网络与不变性
      • 7.14.39 卷积神经网络的可视化
      • 7.14.40 隐马尔可夫模型HMM
        • 7.14.40.1 马尔科夫链
    • 7.15 MATLAB音频处理
      • 7.15.1 python处理音频信号
    • 7.16 图像处理
      • 7.16.1 图像处理中的指标
    • 7.17 代码集
    • 7.18 论文写作与阅读方法
      • 7.18.1 期刊投稿攻略
      • 7.18.2 论文排版教程
      • 7.18.3 SCI-HUB论文下载技巧
      • 7.18.4 几种论文写作神器,提高写作效率
      • 7.18.5 latex入门
      • 7.18.6 LaTeX教程
    • 7.19 机器学习常用的网站以及资源
      • 7.19.1 很详细的ML&DL学习博客
    • 7.20 SymPy 符号计算基本教程
  • 8 程序设计数学基础
    • 8.1 编程数学基础
      • 8.1.1 概率的历史
      • 8.1.2 概率
        • 8.1.2.1 常见概率分布
          • 8.1.2.1.1 二维正态分布
        • 8.1.2.2 蒙特卡罗方法
        • 8.1.2.3 置信区间
        • 8.1.2.4 协方差与相关系数
      • 8.1.3 矩阵 向量求导法则
      • 8.1.4 雅可比矩阵 海森矩阵
      • 8.1.5 矩阵的几种分解方式
      • 8.1.6 行列式和代数余子式
      • 8.1.7 向量
      • 8.1.8 矩阵的基本运算
      • 8.1.9 矩阵分析
      • 8.1.10 矩阵的LU分解
      • 8.1.11 矩阵奇异值分解(SVD)
        • 8.1.11.1 SVD分解2
        • 8.1.11.2 SVD分解逐步推导
        • 8.1.11.3 奇异值与特征值的意义
      • 8.1.12 随机向量
        • 8.1.12.1 随机过程简述
      • 8.1.13 投影矩阵和最小二乘
      • 8.1.14 知乎数学精选集
        • 8.1.14.1 高数问题集
      • 8.1.15 小波变换
      • 8.1.16 程序设计数学基础1:高等数学
      • 8.1.17 程序设计数学基础2:线性代数
      • 8.1.18 程序设计数学基础3:概率论和数理统计
      • 8.1.19 向量的距离与相似度计算
      • 8.1.20 复数
      • 8.1.21 高等数学——幂级数
      • 8.1.22 无穷小的本质
      • 8.1.23 数列极限和收敛性
      • 8.1.24 不定积分技巧总结
    • 8.2 有趣的数学题目
    • 8.3 高等数学
      • 8.3.1 泰勒级数
  • 9 路径规划与智能算法
    • 9.1 常见路径规划算法简介
    • 9.2 Dijkstra算法详细
  • 10 教学文档
    • 10.1 授课计划
    • 10.2 课程标准
什么是算法的时间复杂度?

                       什么是算法的时间复杂度?


算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,比如排序就有前面的十大经典排序和几种奇葩排序,虽然结果相同,但在过程中消耗的资源和时间却会有很大的区别,比如快速排序与猴子排序:)。


那么我们应该如何去衡量不同算法之间的优劣呢?

主要还是从算法所占用的「时间」和「空间」两个维度去考量。

  • 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。

  • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。

本小节将从「时间」的维度进行分析。

什么是大O

当看「时间」二字,我们肯定可以想到将该算法程序运行一篇,通过运行的时间很容易就知道复杂度了。

这种方式可以吗?当然可以,不过它也有很多弊端。

比如程序员小吴的老式电脑处理10w数据使用冒泡排序要几秒,但读者的iMac Pro 可能只需要0.1s,这样的结果误差就很大了。更何况,有的算法运行时间要很久,根本没办法没时间去完整的运行,还是比如猴子排序:)。

那有什么方法可以严谨的进行算法的时间复杂度分析呢?

有的!

「 远古 」的程序员大佬们提出了通用的方法:「 大O符号表示法 」,即 T(n) = O(f(n))

其中 n 表示数据规模 ,O(f(n))表示运行算法所需要执行的指令数,和f(n)成正比。

上面公式中用到的 Landau符号是由德国数论学家保罗·巴赫曼(Paul Bachmann)在其1892年的著作《解析数论》首先引入,由另一位德国数论学家艾德蒙·朗道(Edmund Landau)推广。Landau符号的作用在于用简单的函数来描述复杂函数行为,给出一个上或下(确)界。在计算算法复杂度时一般只用到大O符号,Landau符号体系中的小o符号、Θ符号等等比较不常用。这里的O,最初是用大写希腊字母,但现在都用大写英语字母O;小o符号也是用小写英语字母o,Θ符号则维持大写希腊字母Θ。

注:本文用到的算法中的界限指的是最低的上界。

常见的时间复杂度量级

我们先从常见的时间复杂度量级进行大O的理解:

  • 常数阶O(1)

  • 线性阶O(n)

  • 平方阶O(n²)

  • 对数阶O(logn)

  • 线性对数阶O(nlogn)

  • -----------------------------------------------------------------------------------------------------

O(1)

无论代码执行了多少行,其他区域不会影响到操作,这个代码的时间复杂度都是O(1)

1void swapTwoInts(int &a, int &b){
2  int temp = a;
3  a = b;
4  b = temp;
5}



O(n)

在下面这段代码,for循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此可以用O(n)来表示它的时间复杂度。

1int sum int n ){
2   int ret = 0;
3   for ( int i = 0 ; i <= n ; i ++){
4      ret += i;
5   }
6   return ret;
7}

特别一提的是 c * O(n) 中的 c 可能小于 1 ,比如下面这段代码:

1void reverse string &s ) {
2    int n = s.size();
3    for (int i = 0 ; i < n/2 ; i++){
4      swap ( s[i] , s[n-1-i]);
5    }
6}

O(n²)

当存在双重循环的时候,即把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。


1void selectionSort(int arr[],int n){
2   for(int i = 0; i < n ; i++){
3     int minIndex = i;
4     for (int j = i + 1; j < n ; j++ )
5       if (arr[j] < arr[minIndex])
6           minIndex = j;
7
8     swap ( arr[i], arr[minIndex]);
9   }
10}

这里简单的推导一下

  • 当 i = 0 时,第二重循环需要运行 (n - 1)  次

  • 当 i = 1 时,第二重循环需要运行 (n - 2)  次

  • 。。。。。。

不难得到公式:

1(n - 1) + (n - 2) + (n - 3) + ... + 0
2= (0 + n - 1) * n / 2
3= O (n ^2)

当然并不是所有的双重循环都是 O(n²),比如下面这段输出 30n 次 Hello,五分钟学算法:)的代码。

1void printInformation (int n ){
2   for (int i = 1 ; i <= n ; i++)
3        for (int j = 1 ; j <= 30 ; j ++)
4           cout<< "Hello,五分钟学算法:)"<< endl;
5}

O(logn)

 1int binarySearch( int arr[], int n , int target){

2  int l = 0, r = n - 1;
3  while ( l <= r) {
4    int mid = l + (r - l) / 2;
5    if (arr[mid] == target) return mid;
6    if (arr[mid] > target ) r = mid - 1;
7    else l = mid + 1;
8  }
9  return -1;
10}

在二分查找法的代码中,通过while循环,成 2 倍数的缩减搜索范围,也就是说需要经过 log2^n 次即可跳出循环。

同样的还有下面两段代码也是 O(logn) 级别的时间复杂度。

1  // 整形转成字符串
2  string intToString ( int num ){
3   string s = "";
4   // n 经过几次“除以10”的操作后,等于0
5   while (num ){
6    s += '0' + num%10;
7    num /= 10;
8   }
9   reverse(s)
10   return s;
11  }

1void hello (int n ) {
2   // n 除以几次 2 到 1
3   for ( int sz = 1; sz < n ; sz += sz) 
4     for (int i = 1; i < n; i++)
5        cout<< "Hello,五分钟学算法:)"<< endl;
6}

O(nlogn)

将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logn),也就是了O(nlogn)。

1void hello (){
2  for( m = 1 ; m < n ; m++){
3    i = 1;
4    while( i < n ){
5        i = i * 2;
6    }
7   }
8}



时间复杂度比较

O(1) < O(log2n) < O(n) < O(nlog2n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)





快速掌握算法时间复杂度与空间复杂度

前言

一个算法的优劣好坏,会决定一个程序运行的时间、空间。也许当小数据量的时候,这种影响并不明显,但是当有巨量数据的时候,算法的好坏带来的性能差异就会出天差地别。可以说直接影响了一个产品的高度和广度。每个程序员都想用最优的算法解决问题,我们期待自己写出的代码是简洁、高效的。但是如何评判一个算法的好坏呢?时间复杂度和空间复杂度就是一个很好的标准。

1. 时间复杂度

1.1 概念

执行算法所需要的计算工作量就是我们常说的时间复杂度。该值不一定等于接下来要介绍的基本执行次数。是一个大约的数值,具有统计意义。

1.2 基本执行次数T(n)

根据计算,得出的该算法在输入数据量为n时的,实际执行次数。该值为准确的,具体的数值,有数学意义。

1.3 时间复杂度

根据基本执行次数,去除系数、常数项等得到的渐进时间复杂度。用大O表示法。也就是说,随着数据量的剧增,不同常数项和系数,已经大致不能够影响该算法的基本执行次数。常数项和系数对于计算时间复杂度无意义

1.4 举例说明

  1. T(n) = 2: 该函数总共执行两条语句,所以基本执行次数为2;时间复杂度为O(1): 该函数的基本执行次数只有常数项,所以时间复杂度为O(1)

void test(int n)
{
    int a;
    a = 10;
}
  1. T(n) = 2n: 该函数共循环n次,每次执行2条语句,所以基本执行次数为2n。时间复杂度舍弃系数,为O(n)

void test(int n)
{
    int cnt;
    for (cnt = 0; cnt < n; cnt++) {
        int a;
        a= 10;
    }
}
  1. T(n) = 2 * (1 + 2 + 3 + 4 + ... + n) + 1 = 2 * (1 + n) * n / 2 + 1 = n^2 + n + 1。因为共执行(1 + 2 + 3 + 4 + ... + n) 次循环,每次循环执行2条语句,所有循环结束后,最后又执行了1条语句,所以执行次数如上;时间复杂度为O(n^2),因为n和常数项1忽略,它们在数据量剧增的时候,对于执行次数曲线几乎没有影响了

void test(int n)
{
    int cnt1, cnt2;
    for (cnt1 = 0; cnt1 < n; cnt1++) {
        for (cnt2 = cnt1; cnt2 < n; cnt2++) {
            int a;
            a = 10;            
        }
    }
    a = 11;
}
  1. T(n) = 2 * logn 因为每次循环执行2条语句,共执行logn次循环;时间复杂度为O(logn),忽略掉系数2

void test(int n)
{
    int cnt;
    for (cnt = 1; cnt < n; cnt *= 2) {
        int a;
        a = 10;
    }
}
  1. T(n) = n * logn * 2 因为每次循环2条语句,共执行n * logn次循环;时间复杂度为O(nlogn),忽略掉系数2

void test(int n)
{
    int cnt1, cnt2;
    for (cnt1 = 0; cnt1 < n; cnt1++) {
        for (cnt2 = 1; cnt2 < n; cnt2 *= 2) {
            int a;
            a = 10;
        }
    }
}
  1. T(n) = 2 * n^3 因为每次循环2条语句,共执行n^3 次循环;时间复杂度为O(n^3),忽略掉系数2

void test(int n)
{
    int cnt1, cnt2, cnt3;
    for (cnt1 = 0; cnt1 < n; cnt1++) {
        for (cnt2 = 0; cnt2 < n; cnt2++) {
            for (cnt3 = 0; cnt3 < n; cnt3++) {
                int a;
                a = 10;
            }
        }
    }
}
  1. 斐波那契数列的递归实现,每次调用该函数都会分解,然后要再调用2次该函数。所以时间复杂度为O(2^n)

int test(int n)
{
    if (n == 0 || n == 1) {
        return 1;
    }
    return (test(n-1) + test(n-2));
}

1.5 时间复杂度比较

O(1) < O(log2n) < O(n) < O(nlog2n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

2. 空间复杂度

2.1 概念

一个算法所占用的存储空间主要包括:

  • 程序本身所占用的空间

  • 输入输出变量所占用的空间

  • 动态分配的临时空间,通常指辅助变量

输入数据所占空间只取决于问题本身,和算法无关。我们所说的空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,即第三项。通常来说,只要算法不涉及到动态分配的空间以及递归、栈所需的空间,空间复杂度通常为0(1)。

2.2 举例说明

  1. S(n) = O(1).空间复杂度为O(1),因为只有a, b, c, cnt四个临时变量。且临时变量个数和输入数据规模无关。

int test(int n)
{
    int a, b, c;
    int cnt;
    for (cnt = 0; cnt < n; cnt++) {
        a += cnt;
        b += a;
        c += b;
    }
}
  1. S(n) = O(n).空间复杂度为O(n),因为每次递归都会创建一个新的临时变量a。且共递归n次。

int test(int n)
{
    int a = 1;
    if (n == 0) {
        return 1;
    }
    n -= a;
    return test(n);
}

3. 函数的渐进增长与渐进时间复杂度

在上面的例子中,我们通常都会舍弃掉系数和常数项。这是因为当输入量剧增,接近正无穷时,系数和常数项已经不能够影响执行次数曲线。不同的系数和常数项曲线会完全重合。我做了一个折线图用来比较当输入值n激增时,n^2 曲线和 2n^2 + 100 曲线。可以看到,当数据量剧增时,系数和常数项对于统计时间复杂度都不再有意义,两条曲线几乎完全重合。

4. 不同算法的时间复杂度 & 空间复杂度

下图是我做的一个表格,整理了不同的排序算法的时间复杂度和空间复杂度供大家参考:

感谢大家的阅读,大家喜欢的请帮忙点下推荐。后面会继续出精彩的内容,敬请期待!





                                       初识算法


如果说数学是皇冠上的一颗明珠,那么算法就是这颗明珠上的光芒。算法让这颗明珠更加熠熠生辉,为科技进步和社会发展照亮前进的路。数学是美学,算法是艺术。走进算法的人,才能体会它的魅力。

打开算法之门

瑞士著名的科学家N.Wirth教授曾提出:数据结构+算法=程序。

数据结构是程序的骨架,算法是程序的灵魂。

其实在我们的生活中,算法无处不在。我们每天早上起来,刷牙、洗脸、吃早餐,都在算着时间,以免上班或上课迟到;去超市购物,在资金有限的情况下,考虑先买什么、后买什么,算算是否超额;在家中做饭,用什么食材、调料,做法、步骤,还要品尝一下咸淡,看看是否做熟。所以,不要说你不懂算法,其实你每天都在用。

妙不可言——算法复杂性

我们首先看一道某公司的招聘试题。

写一个算法,求下面序列之和:





当你看到这个题目时,你会怎么想?for语句?while循环?

先看算法1-1:

//算法1-1
sum=0;
for(i=1;i<=n;i++)
{
sum=sum+pow(-1,i)        //pow(-1,i)表示-1的i次幂
}

这段代码可以实现求和运算,但是为什么不这样算呢?





再看算法1-2:

//算法1-2
if(n%2==0)      //判断n是不是偶数,%表示求余数
  sum=0;
else
  sum=-1;

有的人看到这个代码后恍然大悟,原来可以这样啊!这不就是数学家高斯使用的算法吗?





一共50对数,每对之和均为101,那么总和为:
(1+100)*50=5050
1787年,10岁的高斯用了很短的时间算出了结果,而其他孩子却要算很长时间。

可以看出,算法1-1需要运行n+1次,如果n=10000,就要运行10001次,而算法1-2仅仅需要运行1次!是不是有差别很大?!

问:高斯的方法我也知道,但遇到类似的题还是……我用的笨办法是算法吗?
答:是算法。

【算法】是指对特定问题求解步骤的一种描述。

算法只是对问题求解方法的一种描述,它不依赖于任何一种语言,既可以用自然语言、程序设计语言(C、C++、Java、Python等)描述,也可以用流程图、框图来表示。一般为了更清楚地说明算法的本质,我们去除了计算机语言的语法规则和细节,采用“伪代码”来描述算法。

“伪代码”介于自然语言和程序设计语言之间,它更符合人们的表达方式,容易理解,但不是严格的程序设计语言,如果要上机调试,需要转换成标准的计算机程序设计语言才能运行。

算法有哪些特性呢?

  • (1)有穷性:算法是由若干条指令组成的有穷序列,总是在执行若干次后结束,不可能永不停止。

  • (2)确定性:每条语句有确定的含义,无歧义。

  • (3)可行性:算法在当前环境条件下可以通过有限次运算实现。

  • (4)输入输出:有零个或多个输入,一个或多个输出。

问:算法1-2的确算得挺快的,但如何知道我写的算法好不好呢?

"好"算法的标准

  • (1)正确性:正确性是指算法能够满足具体问题的需求,程序运行正常,无语法错误,能够通过典型的软件测试,达到预期的需求。

  • (2)易读性:算法遵循标识符命名规则,简洁易懂,注释语句恰当适量,方便自己和他人阅读,便于后期调试和修改。

  • (3)健壮性:算法对非法数据及操作有较好的反应和处理。例如,在学生信息管理系统中登记学生年龄时,若将21岁误输入为210岁,系统应该提示出错。

  • (4)高效性:高效性是指算法运行效率高,即算法运行所消耗的时间短。算法时间复杂度就是算法运行需要的时间。现代计算机一秒钟能计算数亿次,因此不能用秒来具体计算算法消耗的时间,由于相同配置的计算机进行一次基本运算的时间是一定的,我们可以用算法基本运算的执行次数来衡量算法的效率。因此,将算法基本运算的执行次数作为时间复杂度的衡量标准。

  • (5)低存储性:低存储性是指算法所需要的存储空间低。对于像手机、平板电脑这样的嵌入式设备,算法如果占用空间过大,则无法运行。算法占用的空间大小称为空间的复杂度

除了(1)~(3)中的基本标准外,我们对好的算法的评判标准就是高效率、低存储

问:(1)~(3)中的标准都好办,但时间复杂度怎么算呢?


【时间复杂度】:算法运行需要的时间,一般将算法的执行次数作为时间复杂度的度量标准。

看算法1-3,并分析算法的时间复杂度。

//算法1-3
sum=0;                         //运行1次
total=0;                      //运行1次
for(i=1;i<=n;i++)            //运行n+1次
{
    sum=sum+i;                 //运行n次
    for(j=1;j<=n;j++)        //运行n*(n+1)次
        total=total+i*j;      //运行n*n次
}

把算法的所有语句的运行次数加起来:1+1+n+1+n+n*(n+1)+n*n,可以用一个函数T(n)表达:

[公式]

当n足够大时,我们可以看到算法运行时间主要取决于第一项,后面的甚至可以忽略不计。

用极限表示为:

[公式] ,C为不等于0的常数

如果用时间复杂度的渐近上界表示,如图1-1所示。





从图1-1中可以看出,当 [公式] 时, [公式] ,当n足够大时,T(n)和f(n)近似相等。因此,我们用 [公式] 来表示时间复杂度渐近上界,通常用这种表示法衡量算法时间复杂度。算法1-3的时间复杂度渐近上界为 [公式] ,用极限表示为:

[公式]

还有渐近下界符号 [公式] ,如图1-2所示。





从图1-2可以看出,当 [公式] 时, [公式] ,当n足够大时,T(n)和f(n)近似相等,因此,我们用 [公式] 来表示时间复杂度接近下界。

渐近精确界符号 [公式] ,如图1-3所示。





从图1-3中可以看出,当 [公式] 时, [公式] ,当n足够大时,T(n)和f(n)近似相等。这种两边逼近的方式,更加精确近似,因此,用 [公式] 来表示时间复杂度渐近精确界。

不过,我们通常使用时间复杂度接近上界 [公式] 来表示时间复杂度。

在算法分析中,渐近复杂度是对算法运行次数的粗略估计,大致反映问题规模增长趋势,而不必精确计算算法的运行时间。

在计算渐近时间复杂度时,可以只考虑对算法运行时间贡献大的语句,而忽略那些运算次数少的语句,循环语句中处在循环内层的语句往往运行次数最多,即为对运行时间贡献最大的语句。例如在算法1-3中,total=total+i*j是对算法贡献最大的语句,只计算该语句的运行次数即可。

注意:不是每个算法都能直接计算运行次数。

例如算法1-5中,在a[n]数组中顺序查找x,返回其下标i,如果没找到,则返回-1。





我们很难计算算法1-5中的程序到底执行了多少次,因为运行次数依赖于x在数组中的位置,如果第一个元素就是x,则执行1次(最好情况);如果最后一个元素是x,则执行n次(最坏情况);如果分布概率均等,则平均执行次数为(n+1)/2。

有些算法,如排序、查找、插入等算法,可以分为最好、最坏和平均情况分别求算法渐近复杂度,但我们考察一个算法通常考察最坏的情况,而不是考察最好的情况,最坏情况对衡量算法的好坏具有实际的意义。


【空间复杂度】是算法占用的空间大小。一般将算法的辅助空间作为衡量空间复杂度的标准。

空间复杂度的本意是指算法在运行过程中占用了多少存储空间。算法占用的存储空间包括:

  • 输入/输出数据;

  • 算法本身;

  • 额外需要的辅助空间。

输入/输出数据占用的空间是必需的,算法本身占用的空间可以通过精简算法来缩减,但这个压缩的量是很小的,可以忽略不计。而在运行时使用的辅助变量所占用的空间,即辅助空间是衡量空间复杂度的关键因素。




=======================================

=======================================


算法基础——时间复复杂度 - 知乎 (zhihu.com)