跳到主要内容

2024/算法的时间复杂度和空间复杂度

· 阅读需 8 分钟

算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。

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

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

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

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

因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。

下面我来分别介绍一下「时间复杂度」和「空间复杂度」的计算方式。

时间复杂度

我们想要知道一个算法的「时间复杂度」,很多人首先想到的的方法就是把这个算法程序运行一遍,那么它所消耗的时间就自然而然知道了。

这种方式可以吗?当然可以,不过它也有很多弊端。这种方式非常容易受运行环境的影响,在性能高的机器上跑出来的结果与在性能低的机器上跑的结果相差会很大。而且对测试时使用的数据规模也有很大关系。再者,并我们在写算法的时候,还没有办法完整的去运行呢。

因此,另一种更为通用的方法就出来了:「 大O符号表示法 」,即 T(n) = O(f(n))

我们先来看个例子:

for(int i \= 0; i <= n; i++){  j \= i; j ++; }

通过「 大O符号表示法 」,这段代码的时间复杂度为:O(n) ,为什么呢?

在大O符号表示法中,时间复杂度的公式是: T(n) = O( f(n) ),其中f(n) 表示每行代码执行次数之和,而 O 表示正比例关系,这个公式的全称是:算法的渐进时间复杂度

我们继续看上面的例子,假设每行代码的执行时间都是一样的,我们用1颗粒时间来表示,那么这个例子的第一行耗时是1个颗粒时间,第三行的执行时间是 n个颗粒时间,第四行的执行时间也是 n个颗粒时间(第二行和第五行是符号,暂时忽略),那么总时间就是 1颗粒时间 + n颗粒时间 + n颗粒时间 ,即 (1+2n)个颗粒时间,即: T(n) = (1+2n)*颗粒时间,从这个结果可以看出,这个算法的耗时是随着n的变化而变化,因此,我们可以简化的将这个算法的时间复杂度表示为:T(n) = O(n)

为什么可以这么去简化呢,因为大O符号表示法并不是用于来真实代表算法的执行时间的,它是用来表示代码执行时间的增长变化趋势的。

所以上面的例子中,如果n无限大的时候,T(n) = time(1+2n)中的常量1就没有意义了,倍数2也意义不大。因此直接简化为T(n) = O(n) 就可以了。

常见的时间复杂度量级有:

  • 常数阶O(1)

  • 对数阶O(logN)

  • 线性阶O(n)

  • 线性对数阶O(nlogN)

  • 平方阶O(n²)

  • 立方阶O(n³)

  • K次方阶O(n^k)

  • 指数阶(2^n)

上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。

下面选取一些较为常用的来讲解一下(没有严格按照顺序):

  1. 常数阶O(1)
    无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:

    int i \= 1; int j \= 2; ++i; j++; int m \= i + j;

    上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。

  2. 线性阶O(n)
    这个在最开始的代码示例中就讲解过了,如:

    for(i\=1; i<=n; ++i) {  j \= i; j++; }

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

  3. 对数阶O(logN)

还是先来看代码:


int i \= 1; while(i<n) { i \= i \* 2; }

从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)

  1. 线性对数阶O(nlogN)

线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是 O(nlogN)。

就拿上面的代码加一点修改来举例:

for(int m\=1; m < n; m++) {  i \= 1; while(i<n) { i \= i \* 2; } }
  1. 平方阶O(n²)

平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
举例:

for(int x\=1; i<=n; x++) {  for(i\=1; i<=n; i++) { j \= i; j++; } }

这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²)
如果将其中一层循环的n改成m,即:


for(int x\=1; i<=m; x++) { for(i\=1; i<=n; i++) { j \= i; j++; } }

那它的时间复杂度就变成了 O(m*n)

  1. 立方阶O(n³)K次方阶O(n^k)

参考上面的O(n²) 去理解就好了,O(n³)相当于三层n循环,其它的类似。

除此之外,其实还有平均时间复杂度、均摊时间复杂度、最坏时间复杂度、最好时间复杂度的分析方法,有点复杂,这里就不展开了。

空间复杂度

既然时间复杂度不是用来计算程序具体耗时的,那么我也应该明白,空间复杂度也不是用来计算程序实际占用的空间的。

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。

空间复杂度比较常用的有:O(1)、O(n)、O(n²),我们下面来看看:

  1. 空间复杂度 O(1)
    如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
    举例:
 int i \= 1; int j \= 2; ++i; j++; int m \= i + j;

代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)

  1. 空间复杂度 O(n)
    我们先看一个代码:
   int\[\] m \= new int\[n\] for(int i\=1; i<=n; ++i) {  j \= i; j++; }

这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)

常用算法时间复杂度和空间复杂度查询

排序算法

平均时间复杂度

最好情况

最坏情况

空间复杂度

稳定性

冒泡排序

O(n2)

O(n)

O(n2)

O(1)

稳定

选择排序

O(n2)

O(n2)

O(n2)

O(1)

不稳定

插入排序

O(n2)

O(n)

O(n2)

O(1)

稳定

希尔排序

O(nlogn)

O(nlog2n)

O(nlog2n)

O(1)

不稳定

归并排序

O(nlogn)

O(nlogn)

O(nlogn)

O(n)

稳定

快速排序

O(nlogn)

O(nlogn)

O(n2)

O(logn)

不稳定

堆排序

O(nlogn)

O(nlogn)

O(nlogn)

O(1)

不稳定

计数排序

O(n+k)

O(n+k)

O(n+k)

O(k)

稳定

桶排序

O(n+k)

O(n+k)

O(n2)

O(n+k)

稳定

基数排序

O(n * k)

O(n * k)

O(n * k)

O(n+k)

稳定

开启远程控制

· 阅读需 1 分钟
  • 控制面板
  • 系统和安全
  • 系统
  • 允许远程访问
  • 在弹窗中勾选 允许远程协助连接这台计算机

本地账户

  1. 如果没有帐户,添加本地账户
  2. 双击本地账户,选择【隶属于】,点击【添加】》〉》【高级】〉》〉【立即查找】》〉》在下方结果页找到 【Remote Desktop。Users】。
  3. 选择然后点击确认,保存修改
连接的设置如下,本地用户直接是用户名和本地用户密码

网关一般是内网网段的第一个 如 192.168.31.1

Microsoft 帐户

  1. 设置成管理员
  2. 删除PIN ,设置密码,关闭仅允许
  3. 使用 Microsoft 帐户和密码登录,
  4. 登录成功后可以再把PIN 设置回来

远程用户连接,直接说远程的邮箱和密码

2025-07/Git

· 阅读需 8 分钟

git 分远程仓库 ,本地仓库,本地暂存区

git add ->暂存区 git commit -> 本地仓库 git push -->远程仓库

git init

当前文件夹内创建git仓库


git add .
-- 或 git add 文件名
-- 或 git add 文件夹

添加所有文件进版本控制(暂存区)


git commit -m ' 提交的信息'


git push origin main

提交到 origin 的main分支 可以缩写 git push

git pull

远程同步到本地仓库和 暂存区

git fetch
远程同步到仓库 ,但不和暂存区比对

git merge


git status

本地文件状态

远程相关命令

添加远程仓库

git remote add origin 仓库 或 git remote add github 仓库名

origin 可作为默认

修改远程仓库

git remote set-url origin 地址

删除远程仓库

git remote rm origin 或git remote rm github


提交到远程 git push origin main 缩写 git push

git 文件重命名/移动

git mv 原始 新名

Git分支管理

git branch -M main 切换主分支

Git账户相关

配置 用户名 和邮箱

git  config  --global user.name  "用户名"
git config --global user.email "你的邮箱"


查看配置列表

git config --list

Git相关问题

每次都要输入密码

git config credential.helper store

Git回滚到某个版本或提交

git commit后,如何撤销commit

修改了本地的代码,然后使用:

git add file
git commit -m '修改原因'

执行commit后,还没执行push时,想要撤销这次的commit,该怎么办?

解决方案:
使用命令:

git reset --soft HEAD^

这样就成功撤销了commit,如果想要连着add也撤销的话,--soft改为--hard(删除工作空间的改动代码)。

命令详解:

HEAD^ 表示上一个版本,即上一次的commit,也可以写成HEAD1
如果进行两次的commit,想要都撤回,可以使用HEAD
2

--soft
不删除工作空间的改动代码 ,撤销commit,不撤销git add file

--hard
删除工作空间的改动代码,撤销commit且撤销add

另外一点,如果commit注释写错了,先要改一下注释,有其他方法也能实现,如:

git commit --amend
这时候会进入vim编辑器,修改完成你要的注释后保存即可

git reset 命令

本地代码回滚主要围绕着 git reset 命令,该命令会把版本库和工作目录改变为已知状态。具体来讲,git reset 调整 HEAD 引用指向指定的提交,默认情况下还会更新索引以匹配该提交。根据需要,git reset 命令也可以修改工作目录以呈现指定提交代表的项目修订版本。

git reset 命令有三个主要选项:--soft--mixed--hard

soft 提交

--soft 会将 HEAD 引用指向指定提交。索引和工作目录的内容保持不变。这个版本的命令有“最小”影响,只改变一个符号引用的状态使其指向一个新提交。

mixed 提交

--mixed 会将 HEAD 指向指定提交。索引内容也跟着改变以符合指定提交的树结构,但是工作目录中的内容保持不变。这个版本的命令将索引变成你刚刚暂存该提交全部变化时的状态,它会显示工作目录中还有什么修改。--mixed 是 git reset 的默认模式。

hard 提交

这条命令将 HEAD 引用指向给定提交。索引的内容也跟着改变以符合给定提交的树结构。此外,工作目录的内容也随之改变以反映给定提交表示的树的状态。当改变工作目录的时候,整个目录结构都改成给定提交对应的样子。做的修改都将丢失,新文件将被删除。在给定提交中但不在工作目录中的文件将恢复回来。

git reset 选项影响

选项 HEAD 索引 工作目录 --soft 是 否 否 --mixed 是 是 否 --hard 是 是 是

本地代码回滚

首先通过 git log 查找要回退到的提交标记(commit id),该命令显示从最近到最远的提交日志;

$ git log

或者只显示提交的 commit id 和对应的注释的选项,如下:

$ git log --pretty=oneline
36915978c5b7e6cf4364b0b778409ba375e14289 all jar change to release
32989905ae07a92741cfedb0391544666df45885 del jacoco
......

其次,通过 git reset 回滚到指定 commit id

$ git reset --hard <commit_id>

hard 选项,表示彻底将工作区、暂存区和版本库记录恢复到指定的版本库。

远程仓库代码回滚

将本地回滚的代码推送到远程仓库,这里需要加强制的选项 -f 或 --force

$ git push -f origin <branch_name>

在强制推送本地回滚的代码到远程仓库时,如针对 master 分支操作很有可能提示未有强制推送的权限,如下提示:

git@MacBook-Pro xxx-xx (master) $ git ps origin head --force
Total 0 (delta 0), reused 0 (delta 0)
remote: GitLab: You are not allowed to force push code to a protected branch on this project.
To http://gitlab.xxx.com/xxxx/xxx-xx.git
! [remote rejected] head -> master (pre-receive hook declined)
error: failed to push some refs to 'http://gitlab.xxx.com/xxxx/xxx-xx.git'

以 gitlab 为例,一般 master 分支都会设置成保护分支,不允许 push --force,需要取消设置

Github示例



echo "# snow-framework-java" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/snow-projects/snow-framework-java.git
git push -u origin main

或者



git remote add origin https://github.com/snow-projects/snow-framework-java.git
git branch -M main
git push -u origin main


git其他信息,

1.下载路径

https://git-scm.com/downloads

2.使用git之前需要做的最小配置

配置user.name和user.email,在仓库中,如果local和golable配置不同,local优先起作用

git config --global user.name 'dcc'

git config --global user.email '123@qq.com'

git config --global user.password "123456(新的密码)"


其中global代表当前用户。

3.config的三个作用域

缺省等同于local

git config --local # local只对某个仓库有效

git config --global #global对当前用户所有仓库有效

git config --system #system对系统所有登录的用户有效

显示config的配置,加 --list

git config --list --local #显示当前仓库的配置

git config --list --global # 显示当前用户的配置

git config --list --system #当前系统的所有用户的配置

建git仓库

两种场景:

1.把已有的项目代码纳入git管理

cd 项目代码所在文件夹

git init

2.新建的项目直接用git管理

cd 某个文件夹

git init your_project # 会在当前路径下创建和项目名称同名的文件夹

cd you_project

创建一个文件,然演示提交

clear 清理命令行

git 往仓库里添加文件

工作目录-git add-》暂存区-git commit-》版本历史

git add files 提交文件到暂存区,可以加多个文件,空格分开

git add . 表示提交所有

git add -u 表示提交更新过的,update

git commit -m '提交信息描述' # 提交文件到本地版本历史

git status

查看文件状态

git log 查看提交记录

查看最近n次提交的记录

git log -n 

查看提交的内容:git log --oneline

$ git log --oneline
656ab27 (HEAD -> master) image 改名
d8635c8 update
0a9fdf6 修改
140b101 css
9e2b38c js
d373bb1 add index.html
011164e add image
dfa30a4 add me

查看所有分支记录

git log --all

图形化查看

git log --all --graph -n5

按q推出git log

通过图形界面查看提交历史

gitk

给文件重命名

方式一: 相当于移动文件

mv  旧文件 新文件名
git add 新文件
git rm 旧文件

方式二: 只能修改git管理的文件

git mv 旧文件名  新文件名

git reset --hard

暂存区和工作目录都会被清理掉,最好不要执行;

git 分支命令

查看分支

 git branch

创建分支

创建dev分支,会自动切换到dev

git checkout -b dev

切换分支

git checkout master

.git目录

cd .git

ls -al

HEAD文件

cat HEAD # 说明当前所在分支

config存放配置信息

refs目录

引用分支信息

heads目录

存放分支信息

比如master

ccDuan@DESKTOP-F9J8CSE MINGW64 /e/gittest/git_learning/.git (GIT_DIR!)
$ cd refs/

ccDuan@DESKTOP-F9J8CSE MINGW64 /e/gittest/git_learning/.git/refs (GIT_DIR!)
$ ls
heads/ tags/

ccDuan@DESKTOP-F9J8CSE MINGW64 /e/gittest/git_learning/.git/refs (GIT_DIR!)
$ cd heads/

ccDuan@DESKTOP-F9J8CSE MINGW64 /e/gittest/git_learning/.git/refs/heads (GIT_DIR!)
$ ls
dev master

ccDuan@DESKTOP-F9J8CSE MINGW64 /e/gittest/git_learning/.git/refs/heads (GIT_DIR!)
$ cat master
656ab27ee9799dca5d5fbbd2e64d54fb6f5e07fd

git cat-file -t 查看文件类型

git cat-file -t   656ab27ee

commit类型

git cat-file -p 查看内容

tag目录

存放tag类型

objects目录

存放tree类型

pack存放打包文件

commit,tree,bolob对象的关系

commit指每次的变更

一个commit对应一个tree,tree代表文件夹

blob代表文件

Git源码

git/git at v1.0.0 (github.com)

2025-07/PNPM

· 阅读需 1 分钟
  • 包管理工具

npm install -g pnpm

创建vue

注意带上sudo

pnpm create vite <project-name> -- --template vue
cd <project-name>
pnpm install
pnpm dev

cd universe-dust-web pnpm install pnpm run dev

2025-07/deno

· 阅读需 2 分钟

Deno Land (github.com) 安装

deno --version

deno run main.js

Deno 目前提供了以下内置工具,在使用 JavaScript 和 TypeScript 时非常有用,只需要执行以下命令即可:

  • deno bundler (自带打包和 tree shaking功能,可以将我们的代码打包成单文件)
  • deno compile (将 Deno 项目构建为完全独立的可执行文件)
  • deno installe (可以将我们的代码生成可执行文件进行直接使用)
  • deno info (查看所有模块的依赖关系树)
  • deno doc (将源代码中的注释生成文档)
  • deno fmt (递归地格式化每个子目录中的每个文件)
  • deno repl (启动一个 read-eval-print-loop,它允许您在全局上下文中交互式地构建程序状态)
  • deno test (对名为 .test 的文件进行单元测试)
  • deno lint (代码检测器)

2025-07/shell工具

· 阅读需 1 分钟

mobaxterm

finalshell

vscode + remote-development

oh my zsh + iterm2

2026/上瘾式学习:科学的自学成才法-安川康介

· 阅读需 1 分钟

第一章 不够科学有效的学习方法

第二章 科学高效的学习方法

  1. 分散学习 隔一段时间学习一次,艾宾浩斯记忆曲线

  2. 精细提问与自我解释 在脑中自问自答来学习 精细提问: 针对所学的内容,自问“为什么会这样”,以及“这是如何发生的”等问题

  3. 交叉学习 间隔一端时间来学习

第三章 古老的记忆术,帮你搞定难记内容

第四章 调整身心和环境,进入最佳学习状态

  1. 学习的窍门 改变学习地点,利用零碎时间
  2. 睡眠的重要性
  3. 运动的重要性
  4. 缓解学习焦虑