0%

算法复杂度分析,是从理论角度衡量一个算法的性能指标,包括时间复杂度和空间复杂度

大 O 表示法:渐进时间(空间)复杂度,表示代码运行时间(占用空间)随问题规模增长的变化趋势

时间复杂度

$O(\log n)$代码示例:

1
2
3
4
let i=1;
while (i <= n) {
i = i * 2;
}

常见时间复杂度大小关系:

$O(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)$

对比图

时间复杂度分类:

  1. 最好情况时间复杂度
  2. 最坏情况时间复杂度
  3. 平均情况时间复杂度
  4. 均摊时间复杂度:基本不用,略过

一般说时间复杂度,都指最坏情况时间复杂度

空间复杂度

我们常见的空间复杂度就是 $O(1)、O(n) 、O(n^2)$

基本概念

线性表是一对一关系的抽象,包括链表,队列,字符串等

  1. 栈:对线性表先进后出操作的限制
  2. 队列:先进先出的限制
  3. 字符串:元素只能是字符

基本概念

树,表示一对多的关系模型,比如父母和子女的关系,班级和学生的关系等

存储结构

  1. 双亲表示法,节点存储双亲,找父母容易,找孩子难
  2. 孩子表示法,节点存储孩子,找孩子容易,找父母难
  3. 孩子兄弟表示法,节点存储孩子和自己的兄弟,找孩子和兄弟容易,找父母难

二叉树

二叉树包括:

  1. 斜树,退化为一对一关系,此时为线性表
  2. 满二叉树,所有节点都有左子树和右子树,且所有叶子节点都在最下面一层
  3. 完全二叉树,按照从上到下,从左到右编号,不出现空档

二叉树的存储:对于完全二叉树,由于其定义严格,可用层序编号顺序存储,对于一般二叉树,可用孩子表示法:二叉链表存储

二叉树的遍历:

  1. 前序遍历:访问根节点,然后前序遍历左子树,和右子树
  2. 中序遍历:中序遍历左子树,访问根节点,中序遍历右子树
  3. 后序遍历:后序遍历左子树,和右子树,访问根节点
  4. 层序遍历:其实就是广度优先遍历

通过不同遍历方法,可以得到二叉树的次序,同样,通过特定遍历方法的次序,可以确定一棵树
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构

树、森林和二叉树的转换

待研究

特殊的树

1. 二叉查找树

定义:每个节点的值都大于左子树任意节点,且小于右子树任意节点

删除操作:

  • 叶子节点:直接删除
  • 只有左子树或右子树:子树向上移动
  • 同时有左子树和右子树:将右子树中最小的节点移动到删除节点

平均情况:插入和查找性能都是$O(\log n)$
最坏情况:如果按照顺序或者逆序插入,会退化为链表,插入和查找性能都变为$O(n)$

性质:中序遍历结果为从小到大排列的序列

2. 平衡二叉树(AVL)

定义:在二叉排序树之上,限制任意节点的左右子树高度差最大为 1

为解决二叉查找树最坏情况退化为链表的性能问题,引入平衡二叉树,只要保持树的高始终小于$\log n$,则就能保证查找在最坏情况依然为$O(\log n)$

最小失衡树:在新插入节点向上查找,第一个不能平衡的子树

通过对最小失衡树的左旋或者右旋操作使树重新平衡。

2. 红黑树(AVL)

2-3 树:有两种节点,2-节点和 3-节点
2-节点:包含一个元素和两个子节点
3-节点:包含两个元素和三个子节点

插入操作:
2-节点,2-节点直接变为 3-节点,树的高度不变
3-节点,多余节点向父节点移动,如果父节点是 2-节点,则父节点变为 3-节点,高度不变

当原来的树中所有元素都为 3-节点时,由上可知,根节点由一个 3-节点分化为 3 个 2-节点子树,树的高度加 1

由此可见,2-3 树是向上增长,插入操作最坏情况性能是$O(\log n)$,而且始终保持平衡,因此,查找性能依然为$O(\log n)$

红黑树:本质就是 2-3 树,用颜色代替 3-节点的功能,便于代码操作

所有的 3-节点拆分为 2 个 2-节点,用红色标注 2 个中较小的那个节点

红黑树的性质:

  1. 保持根节点为黑色
  2. 如果一个节点是红色的,则它的子节点必须是黑色的

插入操作:通过左旋、右旋和颜色变换,实现平衡

五种情况:

  • 插入到一个 2-节点,且节点小于该 2-节点
  • 插入到一个 2-节点,且节点大于该 2-节点
  • 插入到一个 3-节点,且插入节点小于 3-节点的两个节点
  • 插入到一个 3-节点,且插入节点大于 3-节点的两个节点
  • 插入到一个 3-节点,且插入节点在 3-节点的两个节点之间

红黑树和平衡二叉树比较:
增删方面:红黑树性能优于平衡二叉树,红黑树任何不平衡,都能在 3 次旋转内得到解决
查找方面:平衡二叉树优于红黑树,因为平衡二叉树是严格平衡的,而红黑树在最坏情况下(红黑相间的斜树),查找性能是$O(2\log n)$

  1. 红黑树

4) 线索二叉树
线索二叉树:由于二叉链表存储的二叉树,有很多空指针的空间浪费,所以利用这些空指针存储节点的前驱或者后继,此时二叉树实际就变为一个双向链表

  1. 二叉树
  2. 二叉查找树
  3. 平衡二叉树
    3.1 平衡查找树之 AVL 树
    3.2 平衡二叉树之红黑树
  4. B 树
  5. B+树
  6. B*树
  7. Trie 树

图的概述

图是一种很重要且很普遍的数学模型,现实生活当中很多问题都可以用图来刻画,然后利用图的相关算法加以解决,比如:

  1. 路径规划,城市之间的公路网,寻找 A 城市到 B 城市之间的最短路径
  2. 社交网络,分析共同爱好等

图的存储结构

简单图

三种存储结构:邻接矩阵,邻接表,边集数组

图的存储结构

无向图

单点连通性,找出与目标顶点所有连接的顶点
思路:DFS 遍历,用顶点数组变量标记是否访问

单点路径查找,找出与目标顶点的路径
思路:DFS 遍历,用顶点数组变量标记每个顶点的父顶点,然后从终止顶点向上查找直到找到起始节点

单点最短路径
思路:队列实现 BFS 遍历,其他跟路径查找一样

连通分量,求一幅图中所有的连通分量,可判断任意两点是否连通,常数时间的查询
思路:DFS 遍历,用顶点数组变量标记每个顶点所属的连通分量

判断是否有环
思路:DFS 遍历,一个顶点被访问 2 次,则有环

二分图(双色问题)
思路:DFS 遍历,每个顶点染相反的颜色

有向图

与无向图中的问题类似
单点可达性、多点可达性、单点路径查找、单点最短路径

DAG,检测有向图是否有环,并打印环

DFS 遍历的顶点排序:前序排列,后序排列,逆后序排列

拓扑排序定义:给定一副有向图,将所有顶点排序,使得所有的有向边均从排在前面的元素指向后面的元素
拓扑排序,就是 DFS 的逆后序排列
当且仅当有向图是有向无环图时,才有拓扑排序,所以需注意拓扑排序前检测是否有环

强连通性:如果两顶点互相可达,则为强连通的,一幅图中任意两点都是强连通的,则有向图也是强连通的
强连通分量:有向图中被分为几个子图,每个子图中的顶点均为强连通的
求强连通分量:kosaraju 算法,待研究

顶点对的可达性:判断任意两顶点是否可达,要求常数时间的查询
传递闭包算法,待研究

最小生成树(加权无向图)

生成树:包含所有顶点的无环连通子图
加权无向图:权值最小的生成树为最小生成树
加权有向图:有向图的最小生成树为最小树形图,不研究

求最小生成树的算法

切分定理:在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。

  1. Prim 算法,加点法,每次选择跟树最近(权值最小)的非树顶点加入树中
  2. Kruskal 算法,加边法,所有边按权值排序,每次选择权值最小的边,并且边的两个顶点不属于同一棵树,添加到树中,直到 n-1 条边或所有边都属于同一颗树

最短路径(加权有向图)

最短路径树:给定起点,到其他所有顶点最短路径的树

  1. dijkstra 算法,同 prim 算法,每次选择跟树最近的边加入,一个数组保存起点到其他点的最短距离,一个数组保存当前顶点最短距离的边的信息,一个队列保存访问过的顶点
  2. 拓扑排序最短路径算法,按照拓扑排序的顺序添加边,得到拓扑排序中第一个顶点到其他顶点的最短路径,对于给定的起点的最短路径,拓扑排序中起点前的顶点都是不可达的。
  3. bellman-ford 算法,待研究

最长路径:权值取反求最短路径

优先级限制下的任务调度问题:
单处理器:有向图无环图的拓扑排序
多处理器:关键路径法,求有向图中的最长路径

dijkstra 算法只适用权值非负的有向图
拓扑排序最短路径算法 适用于无环有向图,可处理负权值
bellman-ford 算法,不能存在负权重环

以上为算法(第 4 版)读书笔记
算法 4 官网
课本代码和习题代码
git 地址

基本概念

  1. 工作区(working tree)
  2. 暂存区(stage, 或 index)
  3. 仓库(repository)

基本操作

  1. 创建仓库
    git init
    git clone

  2. 工作区 <—> 暂存区
    git add: 在工作区添加文件或者修改文件后,执行 git add 将更新 暂存区
    git rm –cached : 从暂存区删除文件,工作区不变
    git checkout – : 用暂存区的文件覆盖工作区文件,危险

  3. 暂存区 <—> 仓库
    git commit: 暂存区里的改动提交到本地的版本库
    git reset HEAD – : 暂存区被仓库中的覆盖,工作区不变

  4. 工作区 <— 仓库
    git checkout HEAD : 仓库中的文件替换暂存区和工作区

  5. 本地仓库 <—> 远程仓库
    git push origin master: 本地仓库到远程仓库
    git fetch origin master: 远程 origin 的 master 替换到本地仓库,工作区不变
    git merge origin/master: 合并内容到本地 master 分支
    git pull: 远程仓库到本地仓库和本地工作区

  6. 比较不同
    git diff: 比较工作区和暂存区
    git diff –staged: 比较暂存区和仓库
    git diff HEAD: 比较工作区和仓库区别

  7. 查看状态
    git status -s: 文件状态的简写(M - 修改, A - 添加, D - 删除, R - 重命名,?? - 未追踪)

javascript 语言特性:动态语言,动态类型,解释执行

  1. 数据类型
    原始类型:boolean、number、string、undefined、null
    引用类型:object 和(es6 新增类型)symbol、bigint
    注:引用类型变量里存储的是指针

  2. 执行环境和作用域
    js 函数执行环境是一个对象,此对象包含了这个函数中声明的变量或函数,函数的执行环境串联起来组成了类似 c 语言中的调用堆栈。
    理解执行环境,也便理解变量作用域,也就明白所谓变量或者函数提升。
    在浏览器中,最外层的执行环境就是 windows 对象

  3. new 和 this
    this 是动态变化的,始终指向调用此函数的对象
    new 先创建一个空对象,然后在空对象上调用构造函数

    1
    2
    3
    4
    5
    6
    function test() {
    console.log(this);
    }
    var t2 = new test();
    t2.aa = 3;
    test();

    调用 test(),在全局对象上,所以 this 指向 windows 对象
    而使用 new test(),在新创建的 t2 对象,所以 this 指向 t2 对象

  4. 构造函数,原型对象,实例的关系
    通过 new 调用的函数就是构造函数,如果没有通过 new 操作符来调用的,就是普通函数
    原型对象和实例的关系如图,此图来自于 JavaScript 高级编程
    JavaScript原型对象关系
    Person 是定义的函数对象,默认有一个原型对象,所有通过 new Person 构造的对象实例内部也有原型对象,共享 Person 函数的原型对象。

  5. 两个操作符
    typeof 操作符,区别原生类型或者 Object 类型,但如果想具体分辨是 Array 还是 Number 等引用类型,则需要 instanceof 操作符。
    instanceof 原理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function _instanceof(L, R) {
    //L为instanceof左表达式,R为右表达式
    let Ro = R.prototype; //原型
    L = L.__proto__; //隐式原型
    while (true) {
    if (L === null) {
    //当到达L原型链顶端还未匹配,返回false
    return false;
    }
    if (L === Ro) {
    //全等时,返回true
    return true;
    }
    L = L.__proto__;
    }
    }

js 手册:
MDN 手册

glibc 库封装了 linux 系统调用,并提供 c 语言接口
所以学习 linux 系统编程,主要参考 glibc 库系统调用相关 api

一、进程控制

api 描述
fork 创建一个新进程
clone 按指定条件创建子进程
execve 运行可执行文件
exit 中止进程
_exit 立即中止当前进程
getdtablesize 进程所能打开的最大文件数
getpgid 获取指定进程组标识号
setpgid 设置指定进程组标志号
getpgrp 获取当前进程组标识号
setpgrp 设置当前进程组标志号
getpid 获取进程标识号
getppid 获取父进程标识号
getpriority 获取调度优先级
setpriority 设置调度优先级
modify_ldt 读写进程的本地描述表
nanosleep 使进程睡眠指定的时间
nice 改变分时进程的优先级
pause 挂起进程,等待信号
personality 设置进程运行域
prctl 对进程进行特定操作
ptrace 进程跟踪
sched_get_priority_max 取得静态优先级的上限
sched_get_priority_min 取得静态优先级的下限
sched_getparam 取得进程的调度参数
sched_getscheduler 取得指定进程的调度策略
sched_rr_get_interval 取得按 RR 算法调度的实时进程的时间片长度
sched_setparam 设置进程的调度参数
sched_setscheduler 设置指定进程的调度策略和参数
sched_yield 进程主动让出处理器,并将自己等候调度队列队尾
vfork 创建一个子进程,以供执行新程序,常与 execve 等同时使用
wait 等待子进程终止
wait3 参见 wait
waitpid 等待指定子进程终止
wait4 参见 waitpid
capget 获取进程权限
capset 设置进程权限
getsid 获取会晤标识号
setsid 设置会晤标识号

二、文件系统控制

1、文件读写操作

api 描述
fcntl 文件控制
open 打开文件
creat 创建新文件
close 关闭文件描述字
read 读文件
write 写文件
readv 从文件读入数据到缓冲数组中
writev 将缓冲数组里的数据写入文件
pread 对文件随机读
pwrite 对文件随机写
lseek 移动文件指针
_llseek 在 64 位地址空间里移动文件指针
dup 复制已打开的文件描述字
dup2 按指定条件复制文件描述字
flock 文件加/解锁
poll I/O 多路转换
truncate 截断文件
ftruncate 参见 truncate
umask 设置文件权限掩码
fsync 把文件在内存中的部分写回磁盘

2、文件系统操作

api 描述
access 确定文件的可存取性
chdir 改变当前工作目录
fchdir 参见 chdir
chmod 改变文件方式
fchmod 参见 chmod
chown 改变文件的属主或用户组
fchown 参见 chown
lchown 参见 chown
chroot 改变根目录
stat 取文件状态信息
lstat 参见 stat
fstat 参见 stat
statfs 取文件系统信息
fstatfs 参见 statfs
readdir 读取目录项
getdents 读取目录项
mkdir 创建目录
mknod 创建索引节点
rmdir 删除目录
rename 文件改名
link 创建链接
symlink 创建符号链接
unlink 删除链接
readlink 读符号链接的值
mount 安装文件系统
umount 卸下文件系统
ustat 取文件系统信息
utime 改变文件的访问修改时间
utimes 参见 utime
quotactl 控制磁盘配额

三、系统控制

api 描述
ioctl I/O 总控制函数
_sysctl 读/写系统参数
acct 启用或禁止进程记账
getrlimit 获取系统资源上限
setrlimit 设置系统资源上限
getrusage 获取系统资源使用情况
uselib 选择要使用的二进制函数库
ioperm 设置端口 I/O 权限
iopl 改变进程 I/O 权限级别
outb 低级端口操作
reboot 重新启动
swapon 打开交换文件和设备
swapoff 关闭交换文件和设备
bdflush 控制 bdflush 守护进程
sysfs 取核心支持的文件系统类型
sysinfo 取得系统信息
adjtimex 调整系统时钟
alarm 设置进程的闹钟
getitimer 获取计时器值
setitimer 设置计时器值
gettimeofday 取时间和时区
settimeofday 设置时间和时区
stime 设置系统日期和时间
time 取得系统时间
times 取进程运行时间
uname 获取当前 UNIX 系统的名称、版本和主机等信息
vhangup 挂起当前终端
nfsservctl 对 NFS 守护进程进行控制
vm86 进入模拟 8086 模式
create_module 创建可装载的模块项
delete_module 删除可装载的模块项
init_module 初始化模块
query_module 查询模块信息
*get_kernel_syms 取得核心符号,已被 query_module 代替

四、内存管理

api 描述
brk 改变数据段空间的分配
sbrk 参见 brk
mlock 内存页面加锁
munlock 内存页面解锁
mlockall 调用进程所有内存页面加锁
munlockall 调用进程所有内存页面解锁
mmap 映射虚拟内存页
munmap 去除内存页映射
mremap 重新映射虚拟内存地址
msync 将映射内存中的数据写回磁盘
mprotect 设置内存映像保护
getpagesize 获取页面大小
sync 将内存缓冲区数据写回硬盘
cacheflush 将指定缓冲区中的内容写回磁盘

五、网络管理

api 描述
getdomainname 取域名
setdomainname 设置域名
gethostid 获取主机标识号
sethostid 设置主机标识号
gethostname 获取本主机名称
sethostname 设置主机名称

六、socket 控制

api 描述
socketcall socket 系统调用
socket 建立 socket
bind 绑定 socket 到端口
connect 连接远程主机
accept 响应 socket 连接请求
send 通过 socket 发送信息
sendto 发送 UDP 信息
sendmsg 参见 send
recv 通过 socket 接收信息
recvfrom 接收 UDP 信息
recvmsg 参见 recv
listen 监听 socket 端口
select 对多路同步 I/O 进行轮询
shutdown 关闭 socket 上的连接
getsockname 取得本地 socket 名字
getpeername 获取通信对方的 socket 名字
getsockopt 取端口设置
setsockopt 设置端口参数
sendfile 在文件或端口间传输数据
socketpair 创建一对已联接的无名 socket

七、用户管理

api 描述
getuid 获取用户标识号
setuid 设置用户标志号
getgid 获取组标识号
setgid 设置组标志号
getegid 获取有效组标识号
setegid 设置有效组标识号
geteuid 获取有效用户标识号
seteuid 设置有效用户标识号
setregid 分别设置真实和有效的的组标识号
setreuid 分别设置真实和有效的用户标识号
getresgid 分别获取真实的,有效的和保存过的组标识号
setresgid 分别设置真实的,有效的和保存过的组标识号
getresuid 分别获取真实的,有效的和保存过的用户标识号
setresuid 分别设置真实的,有效的和保存过的用户标识号
setfsgid 设置文件系统检查时使用的组标识号
setfsuid 设置文件系统检查时使用的用户标识号
getgroups 获取后补组标志清单
setgroups 设置后补组标志清单

八、进程间通信

api 描述
ipc 进程间通信总控制调用

1、信号

api 描述
sigaction 设置对指定信号的处理方法
sigprocmask 根据参数对信号集中的信号执行阻塞/解除阻塞等操作
sigpending 为指定的被阻塞信号设置队列
sigsuspend 挂起进程等待特定信号
signal 参见 signal
kill 向进程或进程组发信号
*sigblock 向被阻塞信号掩码中添加信号,已被 sigprocmask 代替
*siggetmask 取得现有阻塞信号掩码,已被 sigprocmask 代替
*sigsetmask 用给定信号掩码替换现有阻塞信号掩码,已被 sigprocmask 代替
*sigmask 将给定的信号转化为掩码,已被 sigprocmask 代替
*sigpause 作用同 sigsuspend,已被 sigsuspend 代替
sigvec 为兼容 BSD 而设的信号处理函数,作用类似 sigaction
ssetmask ANSI C 的信号处理函数,作用类似 sigaction

2、消息

api 描述
msgctl 消息控制操作
msgget 获取消息队列
msgsnd 发消息
msgrcv 取消息

3、管道

api 描述
pipe 创建管道

4、信号量

api 描述
semctl 信号量控制
semget 获取一组信号量
semop 信号量操作

5、共享内存

api 描述
shmctl 控制共享内存
shmget 获取共享内存
shmat 连接共享内存
shmdt 拆卸共享内存

文档参考:

  1. glibc 库的官方手册:标准库和系统调用都有讲解,地址:https://www.gnu.org/software/libc/manual/html_mono/libc.html

  2. linux man page:第 2 节是系统调用,第 3 节是 glibc 库函数,地址:https://www.kernel.org/doc/man-pages/

  3. 相关书籍:unix 环境高级编程,The Linux Programming Interface(作者是 linux kernel man page 维护者)

引用备注:

以上分类来自于 ibm 社区:https://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html