Code war of Angel


  • Home

  • Archives

机器学习基石学习笔记(二)

Posted on 2019-01-14 | In 机器学习

Why can machines learn?

No Free Lunch定理

No Free Lunch定理表明,没有一个学习算法可以在任何领域总是产生最准确的学习器。不管采用何种学习算法,至少存在一个目标函数,能够使得随机猜测算法是更好的算法。
NFL说明了无法保证一个机器学习算法在D以外的数据集上一定能分类或预测正确,除非加上一些假设条件。

机器学习中假设与目标函数相等的可能性

  1. 机器学习中hypothesis与目标函数相等的可能性,类比于罐子中橙色球的概率问题;罐子里的一颗颗弹珠类比于机器学习样本空间的x;橙色的弹珠类比于h(x)与f不相等;绿色的弹珠类比于h(x)与f相等;从罐子中抽取的N个球类比于机器学习的训练样本D,且这两种抽样的样本与总体样本之间都是独立同分布的。所以呢,如果样本N够大,且是独立同分布的,那么,从样本中h(x)!=f(x)的概率就能推导在抽样样本外的所有样本中h(x)!=f(x)的概率是多少。
  2. $E_{in}(h)$表示在抽样样本中,h(x)与$y_n$不相等的概率;$E_{out}(h)$表示实际所有样本中,h(x)与f(x)不相等的概率是多少。如果$E_{in}$很小,那么他们错误概率满足PAC,他们很接近。
  3. M是hypothesis的个数,N是样本D的数量,ϵ是参数。该union bound表明,当M有限,且N足够大的时候,Bad Data出现的概率就更低了,即能保证D对于所有的h都有$E_{in}~~E_{out}$,满足PAC,演算法A的选择不受限制。那么满足这种union bound的情况,我们就可以和之前一样,选取一个合理的演算法(PLA/pocket),选择使$E_{in}$最小的$h_m$作为矩g,一般能够保证$g≈f$,即有不错的泛化能力。
    所以,如果hypothesis的个数M是有限的,N足够大,那么通过演算法A任意选择一个矩g,都有$E_{in}≈E_{out}$成立;同时,如果找到一个矩g,使
    $E_{in}≈0$,PAC就能保证$E_{out}≈0$。至此,就证明了机器学习是可行的。
    PAC
    learning possible

M对机器学习可行性的影响

  1. 从之前课程可以归纳出机器学习可行需要满足:
    • $E_{in}(g)≈E_{out}(g)$
    • $E_{in}(g)$ 足够小
  2. hypothesis set的个数M是有限的,但是当M很小时,不一定找到满足(2)的M,当M很大时,同样由霍夫丁不等式,$E_{in}(g)$与$E_{out}(g)$可能相差很大
  3. shatter : $m_H(H) = 2^N$ 即,exists N input can be shatter.也就是说对于inputs的所有情况都能列举出来。例如对N个输入,如果能够将$2^N$种情况都列出来,则称该N个输入能够被假设集H shatter。
  4. 二分类(dichotomy),dichotomy就是将空间中的点(例如二维平面)用一条直线分成正类(蓝色o)和负类(红色x),它的上界是$2^N$。
  5. 成长函数(growth function),记为$m_H(H)$,成长函数的定义是:对于由N个点组成的不同集合中,某集合对应的dichotomy最大,那么这个dichotomy值就$m_H(H)$,它的上界是$2^N$。
  6. break point,满足$m_H(H)=2^K$的k的最小值就是break point。那么令有k个点,如果k大于等于break point时,它的成长函数一定小于2的k次方。
  7. Vapnik-Chervonenkis(VC) bound
    Vapnik-Chervonenkis(VC) bound

VC Dimension

  1. 假设空间H有break point k,那么它的成长函数是有界的,它的上界称为Bound function。根据数学归纳法,Bound function也是有界的,且上界为$N^{k-1}$
    image.png
    那么 vc bound可以转化为
    vc bound
    这个式子的值只与k、N有关。
  2. VC Dimension:就是某假设集H能够shatter的最多inputs的个数,即最大完全正确的分类能力(注意,只要存在一种分布的inputs能够正确分类也满足)。根据之前break point的定义:假设集不能被shatter任何分布类型的inputs的最少个数。则VC Dimension等于break point的个数减一。即:
    $$ d_{vc} = ‘min k’-1 $$
  3. $ d_{vc} = d+1$
  4. VC Dimension代表了假设空间的分类能力,即反映了H的自由度,产生dichotomy的数量,也就等于features的个数,但也不是绝对的。
  5. 将$d_{vc}$代表k,此时vc bound 为
    vc bound
    ϵ的值
    ϵ表现了假设空间H的泛化能力,ϵ越小,泛化能力越大。
    那么,可以得到
    ein
    上述不等式的右边第二项称为模型复杂度,其模型复杂度与样本数量N、假设空间$H(d_{vc})$、ϵ有关。$E_{out}$由$E_{in}$共同决定。下面绘出$E_{out}$、model complexity、$E_{in}$随$d_{vc}$变化的关系:
    eout
  6. 样本复杂度(Sample Complexity),如果选定$d_{vc}$,样本D的数量N大约是$d_{vc}$的10000倍。这个数值太大了,实际中往往不需要这么多的样本数量,大概只需要$d_{vc}$的10倍就够了。

Noise and Error

错误衡量

Noise

  1. 错误衡量三个特性

    • out-of-sample:样本外的未知数据
    • pointwise:对每个数据点x进行测试
    • classification:看prediction与target是否一致,classification error通常称为0/1 error
  2. PointWise error实际上就是对数据集的每个点计算错误并计算平均
    PointWise
    pointwise error一般可以分成两类:0/1 error和squared error。
    PointWise

  3. Ideal Mini-Target由P(y∣x)和err共同决定。
    0/1 error中的mini-target是取P(y|x)最大的那个类,而squared error中的mini-target是取所有类的加权平方和。
    Ideal

Algorithmic Error Measure

  1. Error有两种:false accept和false reject。false accept意思是误把负类当成正类,false reject是误把正类当成负类。
  2. 机器学习演算法A的cost function error估计有多种方法,真实的err一般难以计算,常用的方法可以采用plausible或者friendly
    err

Weighted Classification

机器学习的Cost Function即来自于这些error,也就是算法里面的迭代的目标函数,通过优化使得Error(Ein)不断变小。
cost function中,false accept和false reject赋予不同的权重,在演算法中体现。对不同权重的错误惩罚,可以选用virtual copying的方法。
Weighted Classification

hw

机器学习基石学习笔记(一)

Posted on 2019-01-10 | In 机器学习

When can machines learn?

机器学习应用的三个条件:

- 事物本身存在某种潜在规律
- 某些问题难以使用普通编程解决
- 有大量的数据样本可供使用

流程

![流程](https://i.loli.net/2018/07/24/5b55fd7c69d0c.png)

术语:

  • 输入x
  • 输出y
  • 目标函数f,即最接近实际样本分布的规律
  • 训练样本data
  • 假设hypothesis,一个机器学习模型对应了很多不同的hypothesis,通过演算法A,选择一个最佳的hypothesis对应的函数称为矩g,g能最好地表示事物的内在规律,也是我们最终想要得到的模型表达式。

感知机(Perceptron)

$$ h(x) = sign ((\sum_{i=1}^n w_i x_i) - threshold)$$
其中 h(x) = 1/ -1, w是权重,threshold是阙值
把$threshold = w_0x_0 (x_0 =1)$,得到
$$ h(x) = sign ((\sum_{i=1}^n w_i x_i) - w_0x_0) $$
$$ = sign(\sum_{i=0}^n w_i x_i)$$
$$ = sign(W^TX) $$
假设只有二维,那么$ h(x) = w_0 + w_1x_1 + w_2x_2$,其中$w_0 + w_1x_1 + w_2x_2=0$是其中一条分类直线,权重w不同,代表不同的直线。
感知机

线性感知机 Perceptron Learning Algorithm(PLA)

  • 逐步修正:首先在平面上随意取一条直线,看看哪些点分类错误。然后开始对第一个错误点就行修正,即变换直线的位置,使这个错误点变成分类正确的点。接着,再对第二个、第三个等所有的错误分类点就行直线纠正,直到所有的点都完全分类正确了,就得到了最好的直线。
  • 做法:首先随机选择一条直线进行分类。然后找到第一个分类错误的点,如果这个点表示正类,被误分为负类,即$w_t ^t x_n(t)<0$,那表示w和x夹角大于90度,其中w是直线的法向量(垂直于这条直线)(ps:直线方程式的系数代表该直线的法向量)。所以,x被误分在直线的下侧(相对于法向量,法向量的方向即为正类所在的一侧),修正的方法就是使w和x夹角小于90度。通常做法是$w←w+yx, y=1$,如图右上角所示,一次或多次更新后的w+yx与x夹角小于90度,能保证x位于直线的上侧,则对误分为负类的错误点完成了直线修正。
    修正
    修正

  • 保证数据是线性可分:假设有这样一条直线,能够将正类和负类完全分开,令这时候的目标权重为$w_f$,对于每一个点,满足$y_n = sign(w_f^t x_n)$, 那么即每一个到这条直线的距离都大于0,即对于每一个点t,满足:
    $$ y_{n(t)} w_n^f x_{n(t)} >= min y_{n} w_n^f x_{n} > 0 $$

  • PLA会对每次错误的点进行修正,更新权重$w_{t+1}$的值,如果$w_{t+1}与w_f$越来越接近,数学运算上就是内积越大,那表示证明PLA是有学习效果
    的。
    推导:
    $$ w_f^T w_{t+1} = w_f^T(w_t + y_nx_n^t) $$
    $$ = w_f^Tw_t + w_f^Ty_nx_n^t $$
    $$ > w_f^Tw_t + 0 $$
    $$ > w_f^Tw_t $$

  • 为了证明内积的增长不是由于长度而是夹角,推导:
    当有错误的点的时候,即$ y_{n(t)} w_n^f x_{n(t)} <= 0 $
    那么
    $$ ||w_{t+1}||^2 = || w_t + y_{n(t)}x_{n(t)}||^2 $$
    $$ = || w_t||^2 + 2w_ty_{n(t)}x_{n(t)} + ||y_{n(t)}x_{n(t)}||^2 $$
    $$ <= || w_t||^2 + 0 + ||y_{n(t)}x_{n(t)}||^2 (y_n = 1/-1)$$
    $$ <= || w_t||^2 + ||maxx_{n(t)}||^2 $$
    所以 $||w_{t+1}||^2$的增长量不会超过$||maxx_{n(t)}||^2$

  • 如果$w_0 = 0$, 经过T次修正后,得 $ w_f^T \over ||wf||$$ w_T \over ||w_T||$$ >= \sqrt{T}*constant$
    推导:image.png
    image.png
    因​$\sqrt{T}⋅constant≤1$,也就是说,迭代次数T是有上界的。

口袋算法 Packet Algorithm

对于非线性可分的PLA,容忍有错误点,取错误点的个数最少时的权重w。

分类

1. 按输出y的分类
    - 监督学习
    - 非监督学习
    - 半监督学习
    - 增强学习(Reniforcement Learning): 我们给模型或系统一些输入,但是给不了我们希望的真实的输出y,根据模型的输出反馈,如果反馈结果良好,更接近真实输出,就给其正向激励,如果反馈结果不好,偏离真实输出,就给其反向激励。不断通过“反馈-修正”这种形式,一步一步让模型学习的更好。
![分类](5.png)

2. 按Data分类
    - Batch Learning: 一次性拿到整个Data,对其进行学习建模。
    - Online: 在线学习模型,数据是实时更新的,根据数据一个个进来,同步更新我们的算法。
        PLA算法。
        增强学习。
    - Active Learning: 让机器具备主动问问题的能力,例如手写数字识别,机器自己生成一个数字或者对它不确定的手写字主动提问。
![按协议分类](6.png)


3. 按输入X的类型
    - concrete features:具体的特征。
    - raw features:比如说手写数字识别中每个数字所在图片的mxn维像素值;比如语音信号的频谱。
    - abstract features:比如某购物网站做购买预测时,提供给参赛者的是抽象加密过的资料编号或者ID。
![输入X的类型](7.png)

Python实现PLA

参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def new_PLA(mat):
'''
rewrite the naive PLA algorithm using vectorazation.
'''
m,n = mat.shape
w = zeros(n)
bias = ones(m)
x_vec = c_[bias, mat] # add x0=1
update_cnt = 0
prepos = -1 # the previous position

while True:
out = sign(x_vec[:,0:-1] @ w)
out[out == 0] = -1 # sign(0) = -1 here

false_ind = where(out != sign(x_vec[:,-1]))[0] # indices where PLA is false
if not false_ind.any():
break # no false points
if not false_ind[false_ind > prepos].any():
prepos = -1

pos = false_ind[false_ind > prepos][0]
w += x_vec[pos, -1] * x_vec[pos, 0:-1] # updating the weight
prepos = pos
update_cnt += 1

return w, update_cnt

data = loadtxt(r'/vagrant/train.dat.txt')
w1, iteration1 = new_PLA(data)
print(w1,'\n', iteration1)

Python实现Pocket算法

参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def pocket(mat, iter_num = 50):
'''
pocket algorithm implement.
what is different from PLA is that pocket pick a random point each time
and update if the new point causes less mistakes
'''
m,n = mat.shape
w = zeros(n)
bias = ones(m)
x_vec = c_[bias,mat] # add the bias term

out = sign(x_vec[:,0:-1] @ w)
out[out == 0 ] = -1 # for sign(0) = -1 here
pre_error = sum((out - x_vec[:,-1]) != 0) # calculate the error

for i in range(iter_num):
false_id = where(out != x_vec[:,-1])[0] # the indices of mistakes
if not false_id.any():
break
rand_id = false_id[random.randint(len(false_id))] # randomly pick one false point
w += x_vec[rand_id,-1] * x_vec[rand_id,0:-1] # updating the weight
out = sign(x_vec[:,0:-1] @ w)
out[out == 0] = -1
new_error = sum((out - x_vec[:,-1]) != 0)
if new_error < pre_error:
w_pocket = w.copy() # w_pocket's base should not be w
pre_error = new_error

return w_pocket

Python PIL docker 环境部署问题

Posted on 2019-01-10 | In Python
  1. Python3中需要pip install Pillow
  2. docker 部署中 dockerfile 文件需要添加
    1
    2
    RUN apk add --no-cache libjpeg-turbo
    RUN apk add --no-cache --virtual ... jpeg-dev zlib-dev ...

如果不add jped-dev 在docker build 时会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Running setup.py install for Pillow: started
Running setup.py install for Pillow: finished with status 'error'
Complete output from command /usr/bin/python3.6 -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-x49fkhbc/Pillow/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-98mau5hk-record/install-record.txt --single-version-externally-managed --compile:
running install
running build
running build_py
creating build
creating build/lib.linux-x86_64-3.6
creating build/lib.linux-x86_64-3.6/PIL
....
running egg_info
writing src/Pillow.egg-info/PKG-INFO
writing dependency_links to src/Pillow.egg-info/dependency_links.txt
writing top-level names to src/Pillow.egg-info/top_level.txt
reading manifest file 'src/Pillow.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.c'
warning: no files found matching '*.h'
warning: no files found matching '*.sh'
no previously-included directories found matching 'docs/_static'
warning: no previously-included files found matching '.appveyor.yml'
warning: no previously-included files found matching '.coveragerc'
warning: no previously-included files found matching '.codecov.yml'
warning: no previously-included files found matching '.editorconfig'
warning: no previously-included files found matching '.landscape.yaml'
warning: no previously-included files found matching '.readthedocs.yml'
warning: no previously-included files found matching '.travis'
warning: no previously-included files found matching '.travis/*'
warning: no previously-included files found matching 'tox.ini'
warning: no previously-included files matching '.git*' found anywhere in distribution
warning: no previously-included files matching '*.pyc' found anywhere in distribution
warning: no previously-included files matching '*.so' found anywhere in distribution
writing manifest file 'src/Pillow.egg-info/SOURCES.txt'
running build_ext


The headers or library files could not be found for jpeg,
a required dependency when compiling Pillow from source.

Please see the install instructions at:
https://pillow.readthedocs.io/en/latest/installation.html

Traceback (most recent call last):
File "/tmp/pip-build-x49fkhbc/Pillow/setup.py", line 800, in <module>
zip_safe=not (debug_build() or PLATFORM_MINGW), )
File "/usr/lib/python3.6/site-packages/setuptools/__init__.py", line 129, in setup
return distutils.core.setup(**attrs)
File "/usr/lib/python3.6/distutils/core.py", line 148, in setup
dist.run_commands()
File "/usr/lib/python3.6/distutils/dist.py", line 955, in run_commands
self.run_command(cmd)
File "/usr/lib/python3.6/distutils/dist.py", line 974, in run_command
cmd_obj.run()
File "/usr/lib/python3.6/site-packages/setuptools/command/install.py", line 61, in run
return orig.install.run(self)
File "/usr/lib/python3.6/distutils/command/install.py", line 545, in run
self.run_command('build')
File "/usr/lib/python3.6/distutils/cmd.py", line 313, in run_command
self.distribution.run_command(command)
File "/usr/lib/python3.6/distutils/dist.py", line 974, in run_command
cmd_obj.run()
File "/usr/lib/python3.6/distutils/command/build.py", line 135, in run
self.run_command(cmd_name)
File "/usr/lib/python3.6/distutils/cmd.py", line 313, in run_command
self.distribution.run_command(command)
File "/usr/lib/python3.6/distutils/dist.py", line 974, in run_command
cmd_obj.run()
File "/usr/lib/python3.6/distutils/command/build_ext.py", line 339, in run
self.build_extensions()
File "/tmp/pip-build-x49fkhbc/Pillow/setup.py", line 612, in build_extensions
raise RequiredDependencyException(f)
__main__.RequiredDependencyException: jpeg

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/tmp/pip-build-x49fkhbc/Pillow/setup.py", line 812, in <module>
raise RequiredDependencyException(msg)
__main__.RequiredDependencyException:

The headers or library files could not be found for jpeg,
a required dependency when compiling Pillow from source.

Please see the install instructions at:
https://pillow.readthedocs.io/en/latest/installation.html



----------------------------------------
Command "/usr/bin/python3.6 -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-x49fkhbc/Pillow/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-98mau5hk-record/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-build-x49fkhbc/Pillow/

成功打包后,如果不add libgjpeg-turbo
会在import PIL时报错
for detail to check this

1
libjpeg.so.8: cannot open shared object file: No such file or directory

Python灰度图转伪彩色图

Posted on 2019-01-10 | In Python

因项目中算法给出分割图是灰度图,而前端需要展示彩色图,搜了下网上资料,参考了opencv写的这篇,改成了Python代码
效果:
原灰度图
灰度图
伪彩色图
彩色图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from PIL import Image
import requests
from io import BytesIO
import numpy as np
def grayImageTrans(url):
# url
# 如果是本地图片
# img = Image.open('/tmp/test.jpg')
file = requests.get(url)
tmpIm = BytesIO(file.content)
img = Image.open(tmpIm)
# 图片转矩阵
img = np.array(img)
# nparray转list
imgAry = img.tolist()
tmp=0
# 加颜色
for x in range(img.shape[0]):
for y in range(img.shape[1]):
tmp = imgAry[x][y]
imgAry[x][y] = __grayColorTrans(tmp)
newImgAry = np.array(imgAry)
# 矩阵转图片
newImg = Image.fromarray(np.uint8(newImgAry))
# newImg.save('/vagrant/test.png')
# 转base64
output_buffer = BytesIO()
newImg.save(output_buffer, format='PNG')
byte_data = output_buffer.getvalue()
base64_str = base64.b64encode(byte_data)
return base64_str

参考:
关于Image与Base64转换可以看这里
Image图像的convert

Three三维世界初体验

Posted on 2019-01-03 | In 前端

教程:
WebGL中文网教程
three文档
碰撞检测
原理:
以物体的中心为起点,向各个顶点(vertices)发出射线,如果射线与其它的物体相交,则检查最近的一个交点与射线起点间的距离,如果这个距离比射线起点至物体顶点间的距离要小,则说明发生了碰撞。但是,当物体的中心在另一个物体内部时,是不能够检测到碰撞的。而且当两个物体能够互相穿过,且有较大部分重合时,检测效果也不理想。

代码示例
碰撞实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// 初始化Three
var renderer;
function initThree() {
renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

width = document.getElementById('canvas-frame').clientWidth;
height = document.getElementById('canvas-frame').clientHeight;
renderer = new THREE.WebGLRenderer({
antialias : true
});
renderer.setSize(width, height);
document.getElementById('canvas-frame').appendChild(renderer.domElement);
renderer.setClearColor(0xffffff, 1.0);
}

// 初始化Camera
var camera;
function initCamera() {
// 设置透视相机
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
// 设置相机位置
camera.position.x = 200;
camera.position.y = 500;
camera.position.z = 800;
// 设置相机哪里为上
// camera.up.x = 0;
// camera.up.y = 0;
// camera.up.z = 1;
// 设置相机看向哪
camera.lookAt(new THREE.Vector3(0, 0, 0));
}

// 初始化场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

// 初始化灯光
var light;
function initLight() {
light = new THREE.DirectionalLight(0xFF0000,1);
light.position.set(100,100,1);
scene.add(light);
}

// 初始化里面的元素
var cube;
var mesh;
function initObject() {
for(var k =0;k<2;k++){
// 一个几何体
var geometry = new THREE.BoxGeometry( 100,100,100);
// 不同面设置不同颜色
for ( var i = 0; i < geometry.faces.length; i += 2 ) {

var hex = Math.random() * 0xffffff;
geometry.faces[ i ].color.setHex( hex );
geometry.faces[ i + 1 ].color.setHex( hex );

}
// 设置纹理
// var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors} );
var material = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } )
// 把几何体跟纹理混合起来
mesh = new THREE.Mesh( geometry,material);
// 元素的位置
mesh.position.set(0,k*50,k * 400);
// 把元素加到场景里
scene.add(mesh);
}
}

// 渲染
var stats;
function render()
{
// renderer.clear();
// mesh.position.x =mesh.position.x +1;
renderer.render(scene, camera);
requestAnimationFrame(render);
stats.update();
// TWEEN.update();
}

// 性能检测
function initStats(){
stats = new Stats();
stats.setMode(1); // 0: fps, 1: ms
// 将stats的界面对应左上角
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild( stats.domElement );
setInterval( function () {
stats.begin();
// 你的每一帧的代码
stats.end();
}, 1000 / 60 );
}

// 初始化网格
function initGrid(){
// 网格的边长是1000,每个小网格的边长是50
var helper = new THREE.GridHelper( 1000, 50,0x0000ff, 0x808080 );
var helper2 = new THREE.GridHelper( 1000, 50,0x0000ff, 0x808080 );
helper2.rotation.x = Math.PI/2;
scene.add( helper );
scene.add(helper2);
}

// 初始化事件
var INTERSECTED;
function initEvent(){
var objects=[];
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
//监听全局点击事件,通过ray检测选中哪一个object
document.addEventListener("mousedown", (event) => {
console.log('mousedown');
event.preventDefault();
mouse.x = (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - (event.clientY / this.renderer.domElement.clientHeight) * 2 + 1;

raycaster.setFromCamera(mouse, this.camera);
this.scene.children.forEach(child => {
if (child instanceof THREE.Mesh) {//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
objects.push(child)
}
})
var intersects = raycaster.intersectObjects(objects);


if (intersects.length > 0) {
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );

INTERSECTED = intersects[ 0 ].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex( 0x0000ff );
}
else {
if ( INTERSECTED ) {
INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
// 碰撞检测
var originPoint = INTERSECTED.position.clone();
var crash = false;
for (var vertexIndex = 0; vertexIndex < INTERSECTED.geometry.vertices.length; vertexIndex++) {
// 顶点原始坐标
var localVertex = INTERSECTED.geometry.vertices[vertexIndex].clone();
// 顶点经过变换后的坐标
var globalVertex = localVertex.applyMatrix4(INTERSECTED.matrix);
// 获得由中心指向顶点的向量
var directionVector = globalVertex.sub(INTERSECTED.position);

// 将方向向量初始化
var ray = new THREE.Raycaster(originPoint, directionVector.clone().normalize());
// 检测射线与多个物体的相交情况

var collisionResults = ray.intersectObjects(objects);
// 如果返回结果不为空,且交点与射线起点的距离小于物体中心至顶点的距离,则发生了碰撞
if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
crash = true;

}
}
if(crash){
alert('crash');
INTERSECTED.position.set(0,0,400);
}
}
INTERSECTED = null;

}
}, false);

document.addEventListener("mousemove",(event) => {
event.preventDefault();
if(INTERSECTED){

var mouse = new THREE.Vector2();
console.log(( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );

raycaster.setFromCamera( mouse, camera );
var objects = [];
scene.children.forEach(child => {
if (child instanceof THREE.GridHelper) {
objects.push(child)
}
});

var intersects = raycaster.intersectObjects( objects );

if (intersects.length > 0) {
var selected = intersects[ 0 ];
// console.log(selected.point.x,selected.point.y,selected.point.z);
INTERSECTED.position.copy(selected.point);
INTERSECTED.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
}
}
},false);
}

// 动画组件
function initTween(){
new TWEEN.Tween( mesh.position)
.to( { x: -400,z:100,y:100 }, 3000 ).repeat( Infinity ).start();
}


function threeStart() {
initThree();
initCamera();
initScene();
initLight();
initObject();
initGrid();
initStats();
initEvent();
// initTween();
render();
}

threeStart();

拖拽优化:
使用three-drag-controller,替代mousemove时间监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import DragControls from 'three-dragcontrols';
import OrbitControls from 'three-orbitcontrols';
......
function initDragControls() {
// 初始化拖拽控件
var dragControls = new DragControls(meshList, camera, renderer.domElement);
var controls = new OrbitControls(camera, renderer.domElement);

// 鼠标略过事件
dragControls.addEventListener('hoveron', function (event) {

});
// 开始拖拽
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false;
});
// 拖拽结束
dragControls.addEventListener('dragend', function (event) {
controls.enabled = true;
// crashCheck();
});
}

加入webpack管理,
优化后完整代码:
3d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
var THREE = require('three');
import DragControls from 'three-dragcontrols';
import OrbitControls from 'three-orbitcontrols';
import TransformControls from 'threejs-transformcontrols';

// 初始化Three
var renderer;
var width,height;
function initThree() {
renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

width = document.getElementById('canvas-frame').clientWidth;
height = document.getElementById('canvas-frame').clientHeight;
renderer = new THREE.WebGLRenderer({
antialias : true
});
renderer.setSize(width, height);
document.getElementById('canvas-frame').appendChild(renderer.domElement);
renderer.setClearColor(0xffffff, 1.0);
}

// 初始化Camera
var camera;
function initCamera() {
// 设置透视相机
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
// 设置相机位置
camera.position.x = 800;
camera.position.y = 0;
camera.position.z = 1000;
// 设置相机哪里为上
// camera.up.x = 0;
// camera.up.y = 0;
// camera.up.z = 1;
// 设置相机看向哪
camera.lookAt(new THREE.Vector3(0, 0, 0));
}

// 初始化场景
var scene;
function initScene() {
scene = new THREE.Scene();
}

// 初始化灯光
var light;
function initLight() {
light = new THREE.DirectionalLight(0xFF0000,1);
light.position.set(100,100,1);
scene.add(light);
}

// 初始化里面的元素
var cube;
var bus;
var meshList = [];
function initObject() {
for(var k =0;k<2;k++){
// 一个几何体
var geometry = new THREE.BoxGeometry( 100,50,150);
// 不同面设置不同颜色
for ( var i = 0; i < geometry.faces.length; i += 2 ) {

var hex = Math.random() * 0xffffff;
geometry.faces[ i ].color.setHex( hex );
geometry.faces[ i + 1 ].color.setHex( hex );

}
// 设置纹理
// var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors} );
var material = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } )
// 把几何体跟纹理混合起来
var mesh = new THREE.Mesh( geometry,material);
meshList.push(mesh);
// 元素的位置
mesh.position.set(0,k*50,k * 400);
// 把元素加到场景里
scene.add(mesh);
}
var busGeometry = new THREE.BoxGeometry( 500,500,500);
var butMaterial = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff, transparent: true, opacity: 0.5} );
bus = new THREE.Mesh(busGeometry,butMaterial);
bus.position.set(0,0,0);
scene.add(bus);
var edges = new THREE.EdgesHelper( bus, 0x1535f7 );//设置边框,可以旋转
scene.add( edges );
}

// 渲染
var stats;
function render()
{
// renderer.clear();
// mesh.position.x =mesh.position.x +1;
renderer.render(scene, camera);
requestAnimationFrame(render);
// stats.update();
// TWEEN.update();
}

// 性能检测
function initStats(){
stats = new Stats();
stats.setMode(1); // 0: fps, 1: ms
// 将stats的界面对应左上角
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild( stats.domElement );
setInterval( function () {
stats.begin();
// 你的每一帧的代码
stats.end();
}, 1000 / 60 );
}

// 初始化网格
function initGrid(){
// 网格的边长是1000,每个小网格的边长是50

var helper = new THREE.GridHelper( 1000, 50,0x0000ff, 0x808080 );
var helper2 = new THREE.GridHelper( 1000, 50,0x0000ff, 0x808080 );
helper2.rotation.x = Math.PI/2;
scene.add( helper );
// scene.add(helper2);
}

// 初始化事件
var INTERSECTED;
function initEvent(){
var objects= meshList;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
//监听全局点击事件,通过ray检测选中哪一个object
// document.addEventListener("mousedown", (event) => {
// console.log('mousedown');
// event.preventDefault();
// mouse.x = (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1;
// mouse.y = - (event.clientY / this.renderer.domElement.clientHeight) * 2 + 1;
//
// raycaster.setFromCamera(mouse, this.camera);
// // this.scene.children.forEach(child => {
// // if (child instanceof THREE.Mesh) {//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
// // objects.push(child)
// // }
// // })
// var intersects = raycaster.intersectObjects(objects);
//
//
// if (intersects.length > 0) {
// if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
//
// INTERSECTED = intersects[ 0 ].object;
// INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
// INTERSECTED.material.emissive.setHex( 0x0000ff );
// }
// else {
// if ( INTERSECTED ) {
// INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
// // 碰撞检测
// if(crashCheck(objects)){
// alert('crash');
// INTERSECTED.position.set(0,0,400);
// }
// }
// INTERSECTED = null;
//
// }
// }, false);

// document.addEventListener("mousemove",(event) => {
// event.preventDefault();
// if(INTERSECTED){
//
// var mouse = new THREE.Vector2();
// mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
// // console.log(mouse.x,mouse.y);
// var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 ).unproject( camera );
// var raycaster = new THREE.Raycaster(camera.position, vector.sub( camera.position ).normalize());
// var objects = [bus];
// // scene.children.forEach(child => {
// // if (child instanceof THREE.GridHelper) {
// // objects.push(child)
// // }
// // });
//
// var intersects = raycaster.intersectObjects( objects );
//
//
// if (intersects.length > 0) {
// var selected = intersects[ 0 ];
// console.log(selected.point.x,selected.point.y,selected.point.z,INTERSECTED.position.z);
// INTERSECTED.position.set(selected.point.x,selected.point.y,INTERSECTED.position.z);
// INTERSECTED.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
//
// }
// }
// },false);

var an = 1;
document.getElementById('J_left').addEventListener('click',function(e){
e.preventDefault();
e.stopPropagation();
var radius = 1000;
an = an + 1;
// console.log(an);
camera.position.z = radius * Math.cos( (Math.PI / 180 ) * (an) );
camera.position.x = radius * Math.sin( (Math.PI / 180 ) * (an) );
camera.lookAt(new THREE.Vector3(0, 0, 0));
// console.log('left,x='+camera.position.x+',z='+camera.position.z);
});
document.getElementById('J_right').addEventListener('click',function(e){
e.preventDefault();
e.stopPropagation();
var radius = 1000;
an = an - 1;
// console.log(an);
camera.position.z = radius * Math.cos( (Math.PI / 180 ) * (an) );
camera.position.x = radius * Math.sin( (Math.PI / 180 ) * (an) );
camera.lookAt(new THREE.Vector3(0, 0, 0));
// console.log('left,x='+camera.position.x+',z='+camera.position.z);
});
}

function crashCheck(objects){
if(INTERSECTED){
var originPoint = INTERSECTED.position.clone();
var crash = false;
for (var vertexIndex = 0; vertexIndex < INTERSECTED.geometry.vertices.length; vertexIndex++) {
// 顶点原始坐标
var localVertex = INTERSECTED.geometry.vertices[vertexIndex].clone();
// 顶点经过变换后的坐标
var globalVertex = localVertex.applyMatrix4(INTERSECTED.matrix);
// 获得由中心指向顶点的向量
var directionVector = globalVertex.sub(INTERSECTED.position);

// 将方向向量初始化
var ray = new THREE.Raycaster(originPoint, directionVector.clone().normalize());
// 检测射线与多个物体的相交情况

var collisionResults = ray.intersectObjects(objects);
// 如果返回结果不为空,且交点与射线起点的距离小于物体中心至顶点的距离,则发生了碰撞
if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
crash = true;
}
}
if(crash){
return true;
}
}
return false;
}

// 动画组建
function initTween(){
new TWEEN.Tween( mesh.position)
.to( { x: -400,z:100,y:100 }, 3000 ).repeat( Infinity ).start();
}

function initDragControls() {
// 添加平移控件
// var transformControls = new TransformControls(camera, renderer.domElement);
// scene.add(transformControls);

// 初始化拖拽控件
var dragControls = new DragControls(meshList, camera, renderer.domElement);
var controls = new OrbitControls(camera, renderer.domElement);

// 鼠标略过事件
dragControls.addEventListener('hoveron', function (event) {
// 让变换控件对象和选中的对象绑定
// transformControls.attach(event.object);
});
// 开始拖拽
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false;
});
// 拖拽结束
dragControls.addEventListener('dragend', function (event) {
controls.enabled = true;
crashCheck();
});
}


function threeStart() {
initThree();
initCamera();
initScene();
initLight();
initObject();
initGrid();
initDragControls();
// initStats();
initEvent();
// initTween();
render();
}

threeStart();

后期打算添加物理引擎Physijs

数据结构与算法(三)

Posted on 2018-12-24 | In 后端

字符串匹配算法

BF算法(暴力匹配算法)
  1. 假设主串长度为n,模式串长度为m。我们在主串中,检查起始位置分别是0、1、2、3……n-m 且长度为m的n-m+1个子串,看看有没有跟模式传匹配的。
    BF算法
  2. 时间复杂度: 最坏情况下O(n*m)

数据结构与算法(二)

Posted on 2018-12-19 | In 后端

二叉树

  1. 概念
    二叉树
    • 节点的高度:结点到叶子结点的最长路径
    • 节点的深度:根节点到这个结点所经历边的个数
    • 节点的层级:结点的深度+1
    • 树的高度:根节点的高度

二叉树的类型

- 满二叉树:编号2,叶子节点都在最底层,除叶子节点外,每个节点都有左右两个子节点。
- 完全二叉树:编号3,叶子节点都在最下层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大。
  1. 存储二叉树

    • 链表存储法
      链表存储法
    • 顺序存储法
      顺序存储法
      如果是完全二叉树,数组存储是最节省内存的方法。
  2. 二叉树的遍历 O(n)
    二叉树的遍历

    • 前序遍历:对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
    • 中序遍历:对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
    • 后序遍历:对于树中的任意节点来说,线打印它的左子树,然后打印右子树,最后打印它本身。
    • 按层遍历
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 前序遍历
      function preOrder(root) {
      if (root == null) return;
      console.log(root.val)
      preOrder(root->left);
      preOrder(root->right);
      }

      // 中序遍历
      function inOrder(root) {
      if (root == null) return;
      inOrder(root->left);
      console.log(root.val)
      inOrder(root->right);
      }

      // 后序遍历
      function postOrder(root) {
      if (root == null) return;
      postOrder(root->left);
      postOrder(root->right);
      console.log(root.val)
      }
二叉查找树 O(logn)
  1. 要求:对于树中任意一个节点,其左子树的值都要小与这个节点的值,右子树的值都要大于这个节点的值。
    平衡时间复杂度:O(logn)
  2. 二叉查找树的查找:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function treeSearch(tree,value){
    if(tree.value === value){
    return tree.value;
    }
    else if(tree.value<value && tree.right){
    return treeSearch(tree.right,value);
    }
    else if(tree.value>value && tree.left){
    return treeSearch(tree.left,value);
    }
    return null;
    }
  3. 二叉树插入操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 假设插入的数据在叶子节点上
    function treeInsert(tree,value){
    var p = tree;
    while (p!=null){
    if(p.value === value){
    return 'repeat'
    }
    else if(p.value < value){
    if(!p.right){
    p.right = {value: value};
    return;
    }
    p = p.right;
    }
    else if(p.value > value){
    if(!p.left){
    p.left = {value: value};
    return;
    }
    p = p.left;
    }
    }
    }
  4. 二叉树删除操作

    • 如果该节点没有子节点,删除该节点,并将父节点指向Null。
    • 如果该节点有一个子节点,删除该节点,并将父节点指向子节点。
    • 如果该节点有两个及以上子节点,删除该节点,并将右子树中最小节点替换到该位置。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      function treeDelete(tree,value){
      var p = tree;
      var pp = null; // 父节点
      while (p!=null && p.value!= value){
      pp = p;
      if(value > p.value){
      p = p.right;
      }else {
      p = p.left;
      }
      }
      if( p == null){
      return null;
      }
      // 没有子节点
      if(!p.left && !p.right){
      if(pp.right == p){
      delete pp.right;
      }else {
      delete pp.left;
      }
      return;
      }
      // 有一个子节点
      else if(!p.left || !p.right){
      var child = p.left? p.left:p.right;
      if(pp.right == p){
      pp.right = child;
      }
      else{
      pp.left = child;
      }
      return;
      }
      // 有两个及以上子节点
      else{
      //找右子树中的最小值
      minP = p.right;
      minPP = p;
      while(minP.left !=null){
      minPP = minP;
      minP = minP.left;
      }
      p.value = minP.value;
      delete minPP.left;
      }
      }
  5. 快速查找最大、最小节点、前驱节点、后继结点

  6. 中序遍历查找二叉树,可以输出有序的数据,时间复杂度是O(n)
  7. 支持重复数据的二叉查找树:
    • 方法一:二叉查找树每个节点不止存储一个数据,存储一个链表,或者是动态扩容的数据。
    • 方法二:每个节点只存储一个数据,如果重复,将这个值放在右子树,当作大于这个节点处理。查找时,查到值相同节点不停止查找,继续在右子树中查找,知道找到叶子节点。删除时,先找到所有节点,再按正常删除操作处理。
  8. 与散列表对比:
    • 散列表数据是无序的,如需要有序的数据,先排列;二叉查找树只需要中序遍历,可以在O(n)时间复杂度内,输出有序的数据序列。
    • 散列表扩容需要耗时比较久,遇到散列冲突时,性能不稳定;平衡二叉树的性能稳定,时间复杂度在O(logn)。
    • 散列表查找操作时间时常数级,但因为哈希冲突存在,这个常量不一定比logn小,加上哈希函数的耗时。
    • 散列表的构造比二叉查找树要复杂;平衡二叉查找树只需要考虑平衡性这一个问题。
红黑树

红黑树
红黑树是一种性能非常稳定的平衡二叉树,它是为了解决普通二叉树在数据更新中,复杂度退化的问题产生的,红黑树的高度近似log2n,所以它是近似平衡,查找、插入、删除的时间复杂度都是O(logn)。

  1. 要求
    • 根节点是黑色
    • 每个叶子节点都是黑色空节点,也就是说叶子节点不存储数据
    • 任何相邻节点不同时为红色,也就是说,红色节点是被黑色节点隔开
    • 每个节点,从该节点到达其可达叶子节点所经过的所有路径,经过的黑色节点的数目相同
  2. 实现

    • 左旋&右旋
      左旋:围绕某个节点的左旋
      右旋:围绕某个节点的右旋
  3. 插入

    • 插入操作的平衡调整:
      规定:插入的节点必须是红色的,而且二叉查找树中新插入的节点都是放在叶子节点上。
      1. 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。
      2. 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。
      3. 除此外的情况需要左右旋转、改变颜色来满足规定。
    • case1: 如果关注节点是 a,它的叔叔节点 d 是红色
      case1
      1. 将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色
      2. 将关注节点 a 的祖父节点 c 的颜色设置成红色;
      3. 关注节点变成 a 的祖父节点 c;
      4. 跳到 CASE 2 或者 CASE 3。
    • case2: 如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a是其父节点 b 的右子节点
      case2
      1. 关注节点变成节点 a 的父节点 b;
      2. 围绕新的关注节点b 左旋;
      3. 跳到case3.
    • case3: 如果关注节点是 a,它的叔叔节点d是黑色,关注节点a 是其父节点b的左子节点
      case3
      1. 围绕关注节点 a 的祖父节点 c 右旋;
      2. 将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。
      3. 调整结束。
  4. 删除
    • Step1: 针对删除节点初步调整,保证整个红黑树在删除这个节点后,依然满足“每个节点,从该节点到达其可达叶子节点所经过的所有路径,经过的黑色节点的数目相同”。
      1. case1: 如果要删除的节点是 a,它只有一个子节点 b
        case1
        • 删除节点 a,并且把节点 b 替换到节点 a 的位置
        • 节点a只能是黑色,节点b只能是红色。在case1中,将节点b改成黑色。
        • 不需要二次调整
      2. case2: 如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是a的右子节点c
        case2
        • 如果节点 a 的后继节点就是右子节点 c,那右子节点 c 肯定没有左子树。我们把节点 a 删除,并且将节点 c 替换到节点a的位置。
        • 把节点c 的颜色设置为节点a的颜色
        • 如果节点c是黑色,为了不违反最后一条定义,给节点c的右子节点d增加一个黑色。
        • 此时关注节点变成d ,进行二次调整。
      3. case3: 如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的的后继节点不是右子节点
        case3
        • 找到后继节点 d,并将它删除,删除后继节点 d 的过程参照case1
        • 将节点 a 替换成后继节点 d
        • 把节点 d 的颜色设置为跟节点 a 相同的颜色
        • 如果节点 d 是黑色,为了不违反红黑树的最后一条定义,我们给节点d的右子节点c多加一个黑色
        • 此时关注节点变成c,进行二次调整。
    • Step2: 二次调整,以满足“即不存在相邻的两个红色节点“
      1. case1: 如果关注节点是 a,它的兄弟节点 c 是红色的
        case1
        • 围绕关注节点 a 的父节点 b 左旋
        • 关注节点 a 的父节点 b 和祖父节点 c 交换颜色
        • 关注节点不变
        • 继续从四种情况中选择适合的规则来调整
      2. case2: 如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c的左右子节点 d、e 都是黑色的
        case2
        • 将关注节点 a 的兄弟节点 c 的颜色变成红色
        • 从关注节点 a 中去掉一个黑色,这个时候节点 a 就是单纯的红色/黑色
        • 给关注节点 a 的父节点b增加一个黑色
        • 关注节点从 a 变成其父节点 b
        • 继续从四种情况中选择适合的规则来调整
      3. case3: 如果关注节点是 a,它的兄弟节点 c 是黑色,c 的左子节点d 是红色,c的右子节点d是黑色
        case3
        • 围绕关注节点 a 的兄弟节点 c 右旋
        • 节点 c 和节点 d 交换颜色
        • 关注节点不变
        • 继续从四种情况中选择适合的规则来调整
      4. case4: 如果关注节点 a 的兄弟节点 c 是黑色的,并且 c 的右子节点是红色
        case4
        • 围绕关注节点 a 的父节点 b 左旋
        • 将关注节点 a 的兄弟节点 c 的颜色,跟关注节点 a 的父节点 b 设置成相同的颜色
        • 将关注节点 a 的父节点 b 的颜色设置为黑色
        • 从关注节点 a 中去掉一个黑色,节点 a 就变成了单纯的红色/黑色
        • 将关注节点 a 的叔叔节点 e 设置为黑色
        • 调整结束

递归树

使用递归树分析时间复杂度

递归树

  1. 归并排序递归树
    归并排序递归树

    • 每一层耗时的合并操作耗时n
    • 满二叉树的高度是logn
    • 所以时间复杂度是 O(nlogn)
  2. 快排递归树
    快排递归树

    • 假设每次分区都是1:9
    • 每层操作数据之和为n
    • 根节点到叶子节点最短路径是log(10)n,最长路径是log(9/10)n,忽略常数,即logn
    • 所以时间复杂度是O(nlogn)
  3. 斐波那契数列的递归树

    1
    2
    3
    4
    5
    function feibo(n) {
    if (n == 1) return 1;
    if (n == 2) return 2;
    return feibo(n-1) + feibo(n-2);
    }

斐波那契数列的递归树

- 根节点到叶子节点最长路径是n, 最短路径是n/2
- 如果是n,总耗时就是 2^n - 1; 如果是n/2 总耗时就是 2^(n/2) -1;
- 所以时间复杂度是O(2^n) ~ O(2^(n/2))
  1. 全排列的时间复杂度
    全排列
    • 总和:n + n(n-1) + n(n-1)(n-2) +… + n(n-1)(n-2)…21 = O(n!) ~ O(n*n!)

堆

  1. 定义:
    • 堆是个完全二叉树。
    • 堆中每一个节点的值都必须大于等于(或者小于等于)其子树中每个节点的值,称为“大顶堆”(或小顶堆)。
  2. 实现

    • 用数组存储堆
      堆
    • 插入元素
      堆化:顺着节点所在的路径,向上或者向下,对比,然后交换。
      堆化
    • 删除堆顶元素
      把最后一个节点放到堆顶,然后利用父子节点对比方法,对于不满足父子节点大小关系的,互换位置,重复该过程直到满足所有大小关系。
      从上往下的堆化
  3. 基于堆实现排序

    • 时间复杂度:O(nlogn)
    • Step1: 建堆:
      建堆
      从下往上的堆化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      function buildHeap(a, n){
      // 对于完全二叉树来说,下标从n/2 + 1 到 n的节点都是叶子节点
      for(var i = n/2;i>=1; --i){
      heapify(a,n,i)
      }
      }
      function heapify(a,n,i){
      while (true){
      var maxPos = i;
      if(i*2 <= n && a[i] < a[i*2]){
      maxPos = i*2
      }
      if(i*2+1 <= n && a[maxPos] < a[i*2+1]){
      maxPos = i*2+1;
      }
      if(maxPos == i) break;
      var tmp = a[i];
      a[i] = a[maxPos];
      a[maxPos] = tmp;
      i = maxPos
      }
      }

      时间复杂度:O(n)

    • Step2: 排序:
      每次都将最大的元素放到后面

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function sort(a){
      buildHeap(a, a.length);
      var k = a.length-1;
      while (k>1){
      tmp = a[k];
      a[k] = a[1];
      a[1] = tmp;
      k--; //除去刚刚交换到末尾的元素,进行堆化
      heapify(a,k,1);
      }
      }

      时间复杂度:O(nlogn)

快速排序与堆排序对比
  1. 堆排序数据访问没有快排友好
  2. 对于同样的数据,在排序中,堆排序交换的次数多于快排
堆的应用
  1. 优先级队列
    • 合并有序小文件:
      假设我们有 100 个小文件,每个文件的大小是 100MB,每个文件中存储的都是有序的字符串。我们希望将这些 100 个小文件合并成一个有序的大文件。我们从这 100 个文件中,各取第一个字符串,放入数组中,然后比较大小,如果每次都遍历数据找出最小的一个,显然比较慢。使用堆,小文件中取出来的字符串放入到小顶堆中,那堆顶的元素,也就是优先级队列队首的元素,就是最小的字符串。
    • 高性能定时器:
      按照任务设定的执行时间,将这些任务存储在优先级队列中,队列首部(也就是小顶堆的堆顶)存储的是最先执行的任务。
      定时器拿队首任务的执行时间点,与当前时间点相减,得到一个时间间隔T。定时器就设定在T秒后,执行这个任务。
      这样就不用每个1s轮询一次。
  2. 利用堆求Top K
    • 静态数据:
      维护一个大小为K的小顶堆,遍历静态数据数组n,如果元素比堆顶大,删除堆顶元素,插入该元素。
    • 动态数据:
      维护一个大小为K的小顶堆,当有新数据时,如果元素比堆顶大,删除堆顶元素,插入该元素。
  3. 利用堆求中位数
    • 静态数据:
      直接数组排序
    • 动态数据:
      维护两个堆,一个大顶堆,一个小顶堆。大顶堆中存储前半部分数据,小顶堆存后半部分数据,且小顶堆中堆数据都大于大顶堆中堆数据。如果是奇数,大顶堆中存n/2+1个数据。中位数就是大顶堆的堆顶。
    • 求99% 响应时间:
      将响应时间从小到达排序后,第n99%的数据就是99%响应时间。
      同样维护两个堆,一个大顶堆,一个小顶堆。大顶堆中存储n
      99%的数据,小顶堆存储1%的数据,取大顶堆堆顶数据。

图

  1. 概念
    • 无向图:
      无向图
    • 有向图:
      有向图
    • 带权图:
      带权图
    • 顶点
    • 边
    • 度:跟顶点相连接的边的个数
    • 入度:多少个边指向这个顶点。出度:多少个边以这个顶点为起点。
  2. 存储
    • 邻接矩阵
      邻接矩阵
      简单直接,浪费空间。
    • 邻接表
      邻接表
      节省空间,使用耗时
      可以将链表改成平衡二叉树、红黑树提升效率。

广度优先算法、深度优先算法

  1. 基于“图”这种数据结构
  2. 广度优先算法(Breadth-First-Search)
    广度优先算法

    • 先查找离起始顶点最近的,然后是次近的,依次往外搜索。
    • 时间复杂度:O(E),E是边的个数
    • 空间复杂度:O(V),V是顶点的个数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      public void bfs(int s, int t) {
      if (s == t) return;
      boolean[] visited = new boolean[v];
      visited[s]=true;
      Queue<Integer> queue = new LinkedList<>();
      queue.add(s);
      int[] prev = new int[v];
      for (int i = 0; i < v; ++i) {
      prev[i] = -1;
      }
      while (queue.size() != 0) {
      int w = queue.poll();
      for (int i = 0; i < adj[w].size(); ++i) {
      int q = adj[w].get(i);
      if (!visited[q]) {
      prev[q] = w;
      if (q == t) {
      print(prev, s, t);
      return;
      }
      visited[q] = true;
      queue.add(q);
      }
      }
      }
      }

      private void print(int[] prev, int s, int t) { // 递归打印 s->t 的路径
      if (prev[t] != -1 && t != s) {
      print(prev, s, prev[t]);
      }
      System.out.print(t + " ");
      }
  3. 深度优先算法(Depth-Firsh-Search)
    深度优先算法

    • 时间复杂度 O(E),E是边数
    • 空间复杂度 O(V), V是顶点数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      boolean found = false; // 全局变量或者类成员变量

      public void dfs(int s, int t) {
      found = false;
      boolean[] visited = new boolean[v];
      int[] prev = new int[v];
      for (int i = 0; i < v; ++i) {
      prev[i] = -1;
      }
      recurDfs(s, t, visited, prev);
      print(prev, s, t);
      }

      private void recurDfs(int w, int t, boolean[] visited, int[] prev) {
      if (found == true) return;
      visited[w] = true;
      if (w == t) {
      found = true;
      return;
      }
      for (int i = 0; i < adj[w].size(); ++i) {
      int q = adj[w].get(i);
      if (!visited[q]) {
      prev[q] = w;
      recurDfs(q, t, visited, prev);
      }
      }
      }
  4. 广度优先搜索需要借助队列来实现,遍历得到的路径就是,起始顶点到终止顶点的最短路径。深度优先算法使用回溯的思想,非常适合递归实现。

字符串匹配算法

BF算法

数据结构与算法

Posted on 2018-12-10 | In 后端

数据结构与算法

名词

  • 大O表示法:指代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度,简称时间复杂度。
  • 空间复杂度:空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
  • 平均时间复杂度:即加权平均时间复杂度或者期望时间复杂度。
  • 均摊时间复杂度:就是一种特殊的平均时间复杂度,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度低的情况上。

时间复杂度分析

  1. 只关注循环执行次数最多的一段代码
  2. 加法法则:总复杂度等于量级最大的那段代码的复杂度
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

书单

入门

  • 《大话数据结构》
  • 《算法图解》
    不同编程语言
  • 《数据结构与算法分析:JavaScript(/Python/..)语言描述》
    经典
  • 《算法》
  • 《算法导论》
    殿堂级
  • 《计算机程序设计艺术》
    面试
  • 《编程之美》
  • 《剑指offer》
  • 《编程珠玑》
    闲暇
  • 《数学之美》
  • 《算法之美》
  • 《算法帝国》

数组

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
最大的特点就是支持随机访问,但插入、删除操作也因此变得比较低效,平均情况时间复杂度为 O(n),根据下标随机访问的时间复杂度为 O(1)。

链表

链表不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。其中,把内存块成为“结点”

  • 单链表:每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的的地址(即后续指针 next)
    单链表
    头结点记录了链表的基地址,尾节点的指针指向Null空地址。
  • 循环链表:与单链表区别是,尾节点的指针指向头节点。
  • 双向链表:每个结点不止有一个后继指针 next 指向后面的结点,还有一个前驱指针prev指向前一个节点。
链表数组性能对比

链表数组性能对比

  • 数组使用连续内存空间,可以借助cpu缓存机制,进行预读。
    [CPU缓存机制]CPU在从内存读取数据的时候,会先把读取到的数据加载到CPU的缓存中。而CPU每次从内存读取数据并不是只读取那个特定要访问的地址,而是读取一个数据块(这个大小我不太确定。。)并保存到CPU缓存中,然后下次访问内存数据的时候就会先从CPU缓存开始查找,如果找到就不需要再从内存中取。这样就实现了比内存访问速度更快的机制,也就是CPU缓存存在的意义:为了弥补内存访问速度过慢与CPU执行速度快之间的差异而引入。
  • 数据缺点是大小固定,而链表天然支持动态扩容。
基于链表LRU缓存淘汰策略
  • 维护一个有序单链表,越靠近尾部节点是越早之前访问
  • 插入数据(新增),缓存未满,将数据插入链表头部
  • 插入数据(已有),缓存未满,将改数据从原来节点迁移到头部节点
  • 插入数据(新增),缓存已满,删除尾节点数据,将新数据插入链表头部

栈

后进者先出,先进者后出。(比如垒起来的盘子)

  • 动态扩容:均摊复杂度O(1)
  • 函数调用栈:操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”的结构,用来存储函数调用时的临时变量。每进入一个函数,就会讲临时变量作为一个栈帧进入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
    应用:
  • 表达式求值:编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取2个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
    表达式求值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    function calculate(val){
    var cal = val.split(' ');
    var numAry = [];
    var utilAry = [];
    var level = {
    '+':1,
    '-':1,
    '*':2,
    '/':2
    }

    var toCalNow = function (){
    var sum = null;
    console.log('calnow',numAry,utilAry);
    for(var n=0,j=0;n<numAry.length,j<utilAry.length;n++,j++){
    var num1 = numAry[n+1];
    if(n == 0){
    var num2 = numAry[n];
    }
    else{
    var num2 = sum;
    }
    var handler = utilAry[j]
    num2 = parseInt(num2);
    num1 = parseInt(num1);
    switch (handler){
    case '+':
    sum = num1 + num2;
    break;
    case '-':
    sum = num1 - num2;
    break;
    case '*':
    sum = num1 * num2;
    break;
    case '/':
    sum = num1 / num2;
    }
    }
    console.log('sum',sum);
    numAry = [sum];
    utilAry = [];
    }
    cal.forEach(v => {
    if(v == '+' || v == '-' || v == '*' || v == '/'){
    util = utilAry[0]
    if(!!util & level[v] < level[util]){
    toCalNow();
    }
    utilAry.unshift(v)

    }
    else{
    numAry.unshift(v)
    }
    });
    toCalNow();
    return numAry[0];
    }
    >>> console.log(calculate('12 + 2 * 2 - 1 * 2 / 2'))

队列

  • 先进者先出
  • 基于数组顺序队列
  • 基于链表的链式队列
  • 循环队列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class CircularQueue { 
    // 数组:items,数组大小:n
    private $items = [];
    private $n = 0;
    private $head = 0;
    private $tail = 0;

    public function CircularQueue(int capacity){
    $item = [capacity]; //长度为capacity的数组
    $this->n = capacity;
    }
    // 入队
    public function enqueue(String item){
    //队列满了
    if (($this->tail + 1) % $this->n == $this->head){
    return false;
    }
    $this->items[$this->tail] = item;
    $this->tail = ($this->tail + 1) % $this->n;
    return true;
    }
    // 出队
    public function dequeue(){
    // 如果head == tail 代表队列为空
    if($this->head == $this->tail){
    return false;
    }
    ret = $this->items[$this->head];
    $this->head = ($this->head + 1) % n ;
    return ret;
    }
    }
  • 阻塞队列

  1. “消费者”-“生产者”模式
  2. 线程池没有空闲线程时,新的任务请求线程资源时,线程池讲新的请求放进数组队列(一定长度)阻塞等待,更多的请求拒绝。
  • 并发队列
    加锁

递归

  • 关键:找到如何将大问题分解为小问题的规律,并且基于此写出递归公式,然后在推敲出终止条件,最后将递归公式跟终止条件翻译成代码。
  • 警惕堆栈溢出:函数调用的时候,系统栈/虚拟机调用栈空间一般不大,如果递归求解规模很大,调用层次很深,就有可能造成堆栈溢出。
  • 警惕重复计算:可以通过一个数据结构(如散列表)记录已经求过值f(k)数据,以避免重复计算。
  • 警惕死循环:检查环的存在。
  • 空间复杂度高

排序

排序算法比较

  • 内存消耗
  • 稳定性:稳定排序算法可以保持金额相同的两个对象,在排序之后的前后顺序保持不变。
冒泡排序 O(n^2)

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。

  • 原地排序算法
  • 稳定排序算法
  • 时间复杂度:最好O(n),最差O(n^2),平均O(n^2)
    冒泡排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 冒泡排序,a 表示数组
function bubbleSort($a){
var $n = $a.length;
if($n<=1) return;
for($i=0; $i<$n; $i++){
var $flag = false;
for ($j=0; $j<$n-$i-1; $j++){
// 交换
if($a[$j] > $a[$j+1]){
var $tmp = $a[$j];
$a[$j] = $a[$j+1];
$a[$j+1] = $tmp;
// 有数据交换,可能可以继续交换
$flag = true;
}
// 如果没有交换
if(!$flag) break;
}
}
return $a;
}
插入排序 O(n^2)

插入排序

  • 将数组中数据分为已排序空间、未排序空间,初始已排序空间只有数组第一个元素。插入排序核心算法就是取未排序空间元素,在已排序空间中找到合适的位置插入,并保证已排序空间一直有序,重复这个过程,直到未排序空间为空。
  • 原地排序算法
  • 稳定排序算法
  • 时间复杂度:最好O(n),最坏O(n^2),平均O(n^2)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 插入排序 $a表示数组
    function insertionSort($a){
    if($a.length<=1) return;
    for(var i=1;i<$a.length;i++){
    var value = $a[i];
    var j = i-1;
    for(;j>=0;j--){
    if(value < $a[j]){
    $a[j+1] = $a[j] // 数据移动
    }
    else{
    break;
    }
    }
    $a[j+1] = value //移动完再插入数据到位置
    }
    return $a
    }
选择排序 O(n^2)

选择排序

  • 类似插入排序,有已排序空间、未排序空间,但是每次会从未排序空间选择最小的元素,插入到已排序空间的的末尾。
  • 原地排序算法
  • 不稳定排序算法
  • 时间复杂度:最好O(n^2),最坏O(n^2),平均O(n^2)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 选择排序 $a表示数组
    function selectionSort($a){
    if($a.length<=1) return;
    for(var i=0;i<$a.length;i++){
    value = i;
    for(var j=i;j<$a.length;j++){
    if($a[j]< $a[value]){ //找出最小的元素
    value = j;
    }
    }
    tmp = $a[value];
    $a.splice(value,1);
    $a.splice(i,0,tmp); //把最小的元素迁移到已排序的末尾
    }
    return $a
    }
O(n^2)中优选插入排序
  • 与冒泡排序对比:插入排序赋值操作更少,所以性能更优
  • 与选择排序对比:插入排序是稳定性排序算法
    1
    2
    3
    4
    5
    6
    7
    // 冒泡排序
    var $tmp = $a[$j];
    $a[$j] = $a[$j+1];
    $a[$j+1] = $tmp;

    // 插入排序
    $a[j+1] = $a[j] // 数据移动
归并排序 O(nlogn)

归并排序

  • 非原地排序算法,由于合并数组需要额外申请空间。
  • 稳定算法
  • 时间复杂度:O(nlogn)
  • 空间复杂度 O(n)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // 归并排序,a表示数组
    function mergeSort(a){
    return mergeSort_c(a,0,a.length-1);
    }
    function mergeSort_c(a,p,r){
    // 一个数返回自身
    if(r == p){
    return [a[r]]
    }
    // 两个数对比返回
    if(r-p<=1){
    return concatSort([a[p]],[a[r]])
    }
    // 多个数再拆分
    else{
    var q = parseInt((p + r) / 2);
    var ary1 = mergeSort_c(a,p,q);
    var ary2 = mergeSort_c(a,q+1,r);
    return concatSort(ary1,ary2)
    }
    }
    // 合并方法
    function concatSort(ary1,ary2){
    var tmp = [];
    while (ary1.length>0 && ary2.length>0) {
    if(ary1[0]<= ary2[0]){
    tmp.push(ary1[0]);
    ary1.splice(0,1);
    }
    else{
    tmp.push(ary2[0]);
    ary2.splice(0,1);
    }
    }
    if(ary1.length>0){
    tmp = tmp.concat(ary1);
    }
    if(ary2.length>0){
    tmp = tmp.concat(ary2);
    }
    return tmp;
    }
快速排序 O(nlogn)~O(n^2)

快速排序

  • 如果要排序数组下标p到r之间的一组数据,我们选择p到r之间任意一个数据为pivot(分区点)。遍历p到r之间的数据,将小于pivot的放在左边,大于pivot放在右边。根据分治递归的思想,可以用递归排序p到q-1之间的数据和q+1到r之间的数据,直到区间缩小为1,这样说明所有数据都是有序的。
  • 利用快排实现O(n)找到一个数组的第K大元素。
  • 原地排序算法
  • 不稳定算法
  • 时间复杂度:最好O(nlogn),最差O(n^2)
  • 优化分区点选择:
    1. 三数取中:取头、中、尾三个数,去中间值那个数作为pivot。
    2. 随机法。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      // 快速排序,A 是数组,n 表示数组的大小 
      function quickSort(a){
      return quickSort_c(a,0,a.length-1);
      }
      function quickSort_c(a,p,r){
      if(p>=r){
      return;
      }
      // 分区
      var partition =function (p,r){
      var pivot = a[r]; // 随便选一个作为pivot
      var i = p;
      for(var j=p;j<r;j++){
      if(a[j]<pivot){
      var tmp = a[j];
      a[j] = a[i];
      a[i] = tmp;
      i++;
      }
      }
      // 交换pivot的位置,放在两个区中间
      var tmp = a[i];
      a[i] = a[r];
      a[r] = tmp;
      return i;
      }

      var q = partition(p,r);
      quickSort_c(a,p,q-1); //整理左边
      quickSort_c(a,q+1,r); //整理右边
      return a;
      }
归并排序、快速排序对比
  • 都利用分治思想,利用迭代实现。
  • 归并排序:从下到上,先处理子问题再合并。稳定但非原地算法。
  • 快速排序:从上到下,先分区,再处理子问题。不稳定但是原地算法。
桶排序 O(n)

桶排序

  • 核心思想:将要排序的数据分到几个有序的桶里,每个桶的数据再单独进行排序,排完后,再把每个桶里的数据按照顺序依次取出。
  • 时间复杂度:O(n)
  • 数据要求严苛:数据需要容易划分为几个桶,并且有天然的大小顺序,数据在每个桶分布需要均匀。
  • 适合使用在外部排序(数据存储在外部磁盘,数据量大,内存有限,无法将数据全部加载到内存中)。
计数排序 O(n)
  • 桶排序特殊情况,当数据范围不大的时候。
  • 时间复杂度:O(n)
    计数排序
    其中,A数组为原始数组,C数组为,下标对应分数,值为小于等于该分数的数量。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // 计数排序,a 是数组,n 是数组大小。假设数组中存储的都是非负整数。
    public void countingSort(int[] a, int n) {
    if (n <= 1) return;

    // 查找数组中数据的范围
    int max = a[0];
    for (int i = 1; i < n; ++i) {
    if (max < a[i]) {
    max = a[i];
    }
    }

    int[] c = new int[max + 1]; // 申请一个计数数组 c,下标大小 [0,max]
    for (int i = 0; i <= max; ++i) {
    c[i] = 0;
    }

    // 计算每个元素的个数,放入 c 中
    for (int i = 0; i < n; ++i) {
    c[a[i]]++;
    }

    // 依次累加
    for (int i = 1; i <= max; ++i) {
    c[i] = c[i-1] + c[i];
    }

    // 临时数组 r,存储排序之后的结果
    int[] r = new int[n];
    // 计算排序的关键步骤,有点难理解
    for (int i = n - 1; i >= 0; --i) {
    int index = c[a[i]]-1;
    r[index] = a[i];
    c[a[i]]--;
    }

    // 将结果拷贝给 a 数组
    for (int i = 0; i < n; ++i) {
    a[i] = r[i];
    }
    }
基数排序 O(n)

数据分割成位,按位来排序。

  • 数据要求:可分割成独立的位,位之间有递进关系,如果a数据高位比b数据大,那剩下的都不用比较。每一位数据范围不大,要可以用线性排序算法来排序。
  • 时间复杂度:O(n)
排序优化
  1. 排序算法对比
    排序算法对比
  2. 递归过深的堆栈溢出:限制递归深度;在堆上模拟实现函数调用栈,手动模拟递归压栈、出栈过程。
  3. 对于小规模数据(百级),O(n^2) 的排序算法并不一定比 O(nlogn) 排序算法执行时间长,可以选择简单、不需要递归的插入排序。

二分查找 O(logn)

  1. 每次都跟区间中的中间元素对比,将待查找的区间缩小一半,直到找到要找对元素,或者区间缩小为零。
  2. 时间复杂度:O(logn)
  3. 非递归实现(当数据中不存在重复数据时)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function bsearch(a,value){
    var low = 0;
    var high = a.length -1;
    // 如果low和high比较大,容易造成溢出,可以使用位运算 low+((high-low)>>1)
    var mid = parseInt(low +(high-low) /2);
    // 注意循环退出条件有相等对情况
    while (low<=high){
    if(a[mid] == value){
    return mid;
    }
    else if(a[mid] < value){
    low = mid+1;
    }
    else{
    high = mid-1;
    }
    }
    return -1;
    }
  4. 递归实现(当数据中不存在重复数据时)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function bsearch(a,value){
    return bsearchInternally(a,0,a.length-1,value);
    }
    function bsearchInternally(a,low,high,value){
    if(low>high) return -1;
    var mid = low+((high-low)>>1);
    if(a[mid] == value){
    return mid;
    }
    else if(a[mid] < value){
    return bsearchInternally(a,mid+1,high,value);
    }
    else{
    return bsearchInternally(a,low,mid-1,value);
    }
    }
  5. 局限性

  • 依赖顺序表结构(数组),因为数组根据下标查找是O(1),链表是O(n)。
  • 依赖有序数据,只适合用在一次排序,多次查找对场景。
  • 数据量太少不适合,顺序遍历更快。但是如果数据间比较操作比较耗时,不管数据量太少,推荐使用二分查找。
  • 数据量太大不合适,因为内存不足以存储这个数组。
二分查找变形
  1. 查找第一个值等于给定值对元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function bsearch(a,value){
    var low = 0;
    var high = a.length -1;
    while(low<=high){
    var mid = parseInt((low+high)/2);
    if(a[mid]<value){
    low = mid+1;
    }
    else if(a[mid]>value){
    high = mid-1;
    }
    else{
    // 如果是第一个元素或者前一个元素不等于value
    if(mid == 0 || a[mid-1] != value){
    return mid
    }
    // 否则要找到的元素还在[low,high-1]区间
    else{
    high = mid -1;
    }
    }
    }
    return -1;
    }
  2. 查找最后一个等于给定值的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function bsearch(a,value){
    var low = 0;
    var high = a.length -1;
    while(low<=high){
    var mid = parseInt((low+high)/2);
    if(a[mid]<value){
    low = mid+1;
    }
    else if(a[mid]>value){
    high = mid-1;
    }
    else{
    if(mid == a.length-1 || a[mid+1] != value){
    return mid
    }
    else{
    low = mid + 1;
    }
    }
    }
    return -1;
    }
  3. 查找第一个大于等于给定值的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function bsearch(a,value){
    var low = 0;
    var high = a.length -1;
    while(low<=high){
    var mid = parseInt((low+high)/2);
    if(a[mid]<value){
    low = mid+1;
    }
    else{
    if(mid == 0 || a[mid-1] < value){
    return mid
    }
    else{
    high = mid - 1;
    }
    }
    }
    return -1;
    }
  4. 查找最后一个小于等于给定值的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function bsearch(a,value){
    var low = 0;
    var high = a.length -1;
    while(low<=high){
    var mid = parseInt((low+high)/2);
    if(a[mid]>value){
    high = mid-1;
    }
    else{
    if(mid == a.length-1 || a[mid+1] > value){
    return mid
    }
    else{
    low = low + 1;
    }
    }
    }
    return -1;
    }

跳表SkipList O(logn)

  1. 链表加多重索引的结构,就是跳表。空间换时间的设计思路。
    跳表
  2. 时间复杂度:O(logn)
  3. 空间复杂度:O(n)
  4. 不更新索引的情况下,动态插入、删除时间复杂度也是O(logn)
  5. 更新索引:通过一个随机函数,来决定这个结点插入到哪几级索引中,比如随机函数生成K,将这个结点插入到第一级到第K级这K级索引中。
    更新索引
  6. Redis中使用跳表实现有序集合,相比于红黑树,“根据区间[100,990]查到元素”的操作跳表可以做到O(logn)的时间复杂度定位区间起点,然后在起点往后遍历就可以了,效率比较高。实现比较简单。

散列表HashTable O(k)

散列表

  1. 散列表用的就是数组支持按照下标随机访问的时候,时间复杂度是O(1)的特性。通过散列函数将元素的键值映射为下标,将数据存储在数组中对应下标的位置。查询的时候,通过同样的散列函数将键值转为下标,根据下标在数组中取值。
  2. 散列函数设计的基本要求:
    • 散列函数计算的散列值是个非负整数。
    • 如果key1 == key2,那hash(key1) == hash(key2)
    • 如果key1 != key2,那hash(key1) != hash(key2)
  3. 散列冲突:

    • 开放寻址法
    • 链表法,链表长度影响了散列表查询时的时间复杂度
      链表法
  4. 装载因子= 填入表中元素个数/散列表的长度。装载因子越大,冲突越多,性能下降越多。

  5. JS中Object基于哈希表,JSON只是约定的字符串格式,可以转为JavaScript的object。更多
  6. 加密中应用:哈希算法就是将任意数据转换成一定范围数据的算法,这种算法的副作用就是会产生冲突。但是在快速查找中出现的副作用,却是加密功能中的核心,因为有冲突,所以从结果就无法逆推出输入值,这样就实现了数据的单向加密。而输入数据的变化却又会影响到哈希串的值,所以我们可以用哈希串来进行数据的校验。
工业级别散列表
  1. 要求:
  • 支持快速查询、插入、删除操作;
  • 内存占用合理,不能浪费过多内存空间;
  • 性能稳定,极端情况下,散列表性能不会退化到无法接受的情况
  1. 设计要点:
  • 设计合适的散列函数:生成的值随机、分布均匀。
  • 定义装在因子阈值,设计动态扩容策略;
    避免低效(一次性)的扩容:当装载因子达到阈值时,只申请新空间,不迁移数据。当有一个新数据插入时,将新数据插入到新的散列表中,并从老数据拿出一个迁移到新散列表。迁移期间如果需要查询,先从新散列表查询,没有再从旧散列表查询。
  • 选择合适的散列冲突解决方法;
    开放寻址法:适合数据量小,装载因子小(<1)的时候。
    链表法:大装在因子容忍度高,适合存储大对象(此时指针占用内存可以忽略)、大数据量。
散列表与链表结合

散列表支持非常高效插入、删除、查找,但是顺序是打乱的,没有办法按照某种顺序快速遍历,将链表结合使用可以解决这个问题。

  1. LRU淘汰算法
    LRU淘汰算法
  • 散列表+双向链表存储结构,时间复杂度O(1)。
  • 查找:散列表中时间复杂度接近O(1),找到数据后,迁移到双向链表尾部。
  • 删除:借助散列表,在O(1)复杂度中找到要删除的结点,双向链表通过O(1)找到前驱结点。
  • 插入:先查找这个数据是否在缓存中,如果在,迁移到双向链表尾部,如果不在,如果缓存满了,将双向链表头部结点删除,再将数据放在双向链表尾部,如果缓存没满,直接将数据放在双向链表尾部。
  1. Redis有序集合
  • 散列表+跳表
  • 按照分值将对象组织成跳表,在按键值构建一个散列表。
  1. Java LinkedHashMap
哈希算法
  1. 要求:
    • 从哈希值不能反向推导出原始数据。
    • 输入的数据不同,哈希值不同。
    • 散列冲突概率很小。
    • 执行效率要高。
  2. 应用:
    • 安全加密:MD5、SHA、DES、AES
      字典攻击:加盐
    • 唯一标识:从图片二进制编码取某一些字节,进行加密,使用加密值代表这样图片的唯一标识
    • 数据校验:文件切割,分别对各个文件求哈希值,用户下载完各个切割文件后,先校验哈希值是否一致,以检查文件是否被篡改/完整。
    • 散列函数
  3. 区块链:区块链是一块块区块组成的,每个区块分为两部分:区块头和区块体。区块头保存着[自己区块体]和[上一个区块头]的哈希值。因为这种链式关系和哈希值的唯一性,只要区块链上任意一个区块被修改过,后面所有区块保存的哈希值就不对了。区块链使用的是 SHA256 哈希算法,计算哈希值非常耗时,如果要篡改一个区块,就必须重新计算该区块后面所有的区块的哈希值,短时间内几乎不可能做到。
哈希算法在分布式系统中应用
  1. 负载均衡
    • 实现会话粘滞的负载均衡算法:通过哈希算法,将客户端ip/用户session id进行计算,将哈希值与服务器数量进行取模计算,得到的值就是服务器编号。
  2. 数据分片
    • 数据量非常大的时候,将数据进行分片分开存储到n台服务器上,将数据进行哈希计算,取模,获得服务器编号。
  3. 分布式存储
    • 分布式缓存:一致性哈希算法,一致性算法图解
      假设k台机器,数据范围[0~MAX],分成m(m>>k)个区间,每个机器负责m/k个小区间。当有新机器加入的时候,将某几个小区间的数据从原来服务器迁移到新服务器中。

二叉树

  1. 概念
    二叉树
    • 节点的高度:结点到叶子结点的最长路径
    • 节点的深度:根节点到这个结点所经历边的个数
    • 节点的层级:结点的深度+1
    • 树的高度:根节点的高度

二叉树的类型

- 满二叉树:编号2,叶子节点都在最底层,除叶子节点外,每个节点都有左右两个子节点。
- 完全二叉树:编号3,叶子节点都在最下层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大。
  1. 存储二叉树

    • 链表存储法
      链表存储法
    • 顺序存储法
      顺序存储法
      如果是完全二叉树,数组存储是最节省内存的方法。
  2. 二叉树的遍历 O(n)
    二叉树的遍历

    • 前序遍历:对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
    • 中序遍历:对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
    • 后序遍历:对于树中的任意节点来说,线打印它的左子树,然后打印右子树,最后打印它本身。
    • 按层遍历
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 前序遍历
      function preOrder(root) {
      if (root == null) return;
      console.log(root.val)
      preOrder(root->left);
      preOrder(root->right);
      }

      // 中序遍历
      function inOrder(root) {
      if (root == null) return;
      inOrder(root->left);
      console.log(root.val)
      inOrder(root->right);
      }

      // 后序遍历
      function postOrder(root) {
      if (root == null) return;
      postOrder(root->left);
      postOrder(root->right);
      console.log(root.val)
      }
二叉查找树 O(logn)
  1. 要求:对于树中任意一个节点,其左子树的值都要小与这个节点的值,右子树的值都要大于这个节点的值。
    平衡时间复杂度:O(logn)
  2. 二叉查找树的查找:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function treeSearch(tree,value){
    if(tree.value === value){
    return tree.value;
    }
    else if(tree.value<value && tree.right){
    return treeSearch(tree.right,value);
    }
    else if(tree.value>value && tree.left){
    return treeSearch(tree.left,value);
    }
    return null;
    }
  3. 二叉树插入操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 假设插入的数据在叶子节点上
    function treeInsert(tree,value){
    var p = tree;
    while (p!=null){
    if(p.value === value){
    return 'repeat'
    }
    else if(p.value < value){
    if(!p.right){
    p.right = {value: value};
    return;
    }
    p = p.right;
    }
    else if(p.value > value){
    if(!p.left){
    p.left = {value: value};
    return;
    }
    p = p.left;
    }
    }
    }
  4. 二叉树删除操作

    • 如果该节点没有子节点,删除该节点,并将父节点指向Null。
    • 如果该节点有一个子节点,删除该节点,并将父节点指向子节点。
    • 如果该节点有两个及以上子节点,删除该节点,并将右子树中最小节点替换到该位置。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      function treeDelete(tree,value){
      var p = tree;
      var pp = null; // 父节点
      while (p!=null && p.value!= value){
      pp = p;
      if(value > p.value){
      p = p.right;
      }else {
      p = p.left;
      }
      }
      if( p == null){
      return null;
      }
      // 没有子节点
      if(!p.left && !p.right){
      if(pp.right == p){
      delete pp.right;
      }else {
      delete pp.left;
      }
      return;
      }
      // 有一个子节点
      else if(!p.left || !p.right){
      var child = p.left? p.left:p.right;
      if(pp.right == p){
      pp.right = child;
      }
      else{
      pp.left = child;
      }
      return;
      }
      // 有两个及以上子节点
      else{
      //找右子树中的最小值
      minP = p.right;
      minPP = p;
      while(minP.left !=null){
      minPP = minP;
      minP = minP.left;
      }
      p.value = minP.value;
      delete minPP.left;
      }
      }
  5. 快速查找最大、最小节点、前驱节点、后继结点

  6. 中序遍历查找二叉树,可以输出有序的数据,时间复杂度是O(n)
  7. 支持重复数据的二叉查找树:
    • 方法一:二叉查找树每个节点不止存储一个数据,存储一个链表,或者是动态扩容的数据。
    • 方法二:每个节点只存储一个数据,如果重复,将这个值放在右子树,当作大于这个节点处理。查找时,查到值相同节点不停止查找,继续在右子树中查找,知道找到叶子节点。删除时,先找到所有节点,再按正常删除操作处理。
  8. 与散列表对比:
    • 散列表数据是无序的,如需要有序的数据,先排列;二叉查找树只需要中序遍历,可以在O(n)时间复杂度内,输出有序的数据序列。
    • 散列表扩容需要耗时比较久,遇到散列冲突时,性能不稳定;平衡二叉树的性能稳定,时间复杂度在O(logn)。
    • 散列表查找操作时间时常数级,但因为哈希冲突存在,这个常量不一定比logn小,加上哈希函数的耗时。
    • 散列表的构造比二叉查找树要复杂;平衡二叉查找树只需要考虑平衡性这一个问题。
红黑树

红黑树
红黑树是一种性能非常稳定的平衡二叉树,它是为了解决普通二叉树在数据更新中,复杂度退化的问题产生的,红黑树的高度近似log2n,所以它是近似平衡,查找、插入、删除的时间复杂度都是O(logn)。

  1. 要求
    • 根节点是黑色
    • 每个叶子节点都是黑色空节点,也就是说叶子节点不存储数据
    • 任何相邻节点不同时为红色,也就是说,红色节点是被黑色节点隔开
    • 每个节点,从该节点到达其可达叶子节点所经过的所有路径,经过的黑色节点的数目相同
  2. 实现

    • 左旋&右旋
      左旋:围绕某个节点的左旋
      右旋:围绕某个节点的右旋
  3. 插入

    • 插入操作的平衡调整:
      规定:插入的节点必须是红色的,而且二叉查找树中新插入的节点都是放在叶子节点上。
      1. 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。
      2. 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。
      3. 除此外的情况需要左右旋转、改变颜色来满足规定。
    • case1: 如果关注节点是 a,它的叔叔节点 d 是红色
      case1
      1. 将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色
      2. 将关注节点 a 的祖父节点 c 的颜色设置成红色;
      3. 关注节点变成 a 的祖父节点 c;
      4. 跳到 CASE 2 或者 CASE 3。
    • case2: 如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a是其父节点 b 的右子节点
      case2
      1. 关注节点变成节点 a 的父节点 b;
      2. 围绕新的关注节点b 左旋;
      3. 跳到case3.
    • case3: 如果关注节点是 a,它的叔叔节点d是黑色,关注节点a 是其父节点b的左子节点
      case3
      1. 围绕关注节点 a 的祖父节点 c 右旋;
      2. 将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。
      3. 调整结束。
  4. 删除
    • Step1: 针对删除节点初步调整,保证整个红黑树在删除这个节点后,依然满足“每个节点,从该节点到达其可达叶子节点所经过的所有路径,经过的黑色节点的数目相同”。
      1. case1: 如果要删除的节点是 a,它只有一个子节点 b
        case1
        • 删除节点 a,并且把节点 b 替换到节点 a 的位置
        • 节点a只能是黑色,节点b只能是红色。在case1中,将节点b改成黑色。
        • 不需要二次调整
      2. case2: 如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是a的右子节点c
        case2
        • 如果节点 a 的后继节点就是右子节点 c,那右子节点 c 肯定没有左子树。我们把节点 a 删除,并且将节点 c 替换到节点a的位置。
        • 把节点c 的颜色设置为节点a的颜色
        • 如果节点c是黑色,为了不违反最后一条定义,给节点c的右子节点d增加一个黑色。
        • 此时关注节点变成d ,进行二次调整。
      3. case3: 如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的的后继节点不是右子节点
        case3
        • 找到后继节点 d,并将它删除,删除后继节点 d 的过程参照case1
        • 将节点 a 替换成后继节点 d
        • 把节点 d 的颜色设置为跟节点 a 相同的颜色
        • 如果节点 d 是黑色,为了不违反红黑树的最后一条定义,我们给节点d的右子节点c多加一个黑色
        • 此时关注节点变成c,进行二次调整。
    • Step2: 二次调整,以满足“即不存在相邻的两个红色节点“
      1. case1: 如果关注节点是 a,它的兄弟节点 c 是红色的
        case1
        • 围绕关注节点 a 的父节点 b 左旋
        • 关注节点 a 的父节点 b 和祖父节点 c 交换颜色
        • 关注节点不变
        • 继续从四种情况中选择适合的规则来调整
      2. case2: 如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c的左右子节点 d、e 都是黑色的
        case2
        • 将关注节点 a 的兄弟节点 c 的颜色变成红色
        • 从关注节点 a 中去掉一个黑色,这个时候节点 a 就是单纯的红色/黑色
        • 给关注节点 a 的父节点b增加一个黑色
        • 关注节点从 a 变成其父节点 b
        • 继续从四种情况中选择适合的规则来调整
      3. case3:

极客时间版权所有: https://time.geekbang.org/column/article/68976?code=dywldOC0YOf1iEE9qMZlHT8Yv8MdAXs9bSGuF1qs7mQ%3D

hmac加密算法

Posted on 2018-12-06 | In 后端

密钥散列消息认证码(Keyed-hash message authentication code)
它是一种通过特别计算方式之后产生的消息认证码(MAC),使用密码散列函数,同时结合一个加密密钥。它可以用来保证数据的完整性,同时可以用来作某个消息的身份验证。

使用方法(Python):

1
2
3
4
5
6
7
8
import hmac
# 传入的key和message都是bytes类型
message = b'Hello, world!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
# 如果消息很长,可以多次调用h.update(msg)
h.hexdigest()
# output: 'fa4ee7d173f2d97ee79022d1a7355bcf'

参考

利用Tensorflow简单线性拟合实践

Posted on 2018-12-04 | In Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import tensorflow as tf
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split

print(tf.__version__)

# 流程:
# 1. 数据处理
# 2. 选择模型
# 3. 训练模型
# 4. 模型评估
# 5. 调整参数

iris = datasets.load_iris() # sk自带了的鸢尾花数据集(含有三种鸢尾花,有四种特征数据)

# 载入数据,划分训练/测试集(20%)
train_X, test_X, train_y, test_y = train_test_split(iris.data, iris.target, test_size = 0.2, random_state = 0)

# 所有特征都是实数值
# shape是特征值数量
feature_name = "flower_features"
feature_columns = [tf.feature_column.numeric_column(feature_name,
shape=[4])]

classifier = tf.estimator.LinearClassifier(
feature_columns=feature_columns,
n_classes=3,
model_dir="/tmp/iris_model")


# 输入函数,讲导入的数据转换为TensorFlow数据类型
def input_fn(set_split='train'):
def _fn():
if set_split == "test":
features = {feature_name: tf.constant(test_X)}
label = tf.constant(test_y)
else:
features = {feature_name: tf.constant(train_X)}
label = tf.constant(train_y)
return features, label
return _fn


# 训练(拟合)模型
classifier.train(input_fn=input_fn(),
steps=1000)
print('fit done')


# 评估准确率
accuracy_score = classifier.evaluate(input_fn=input_fn('test'),
steps=100)["accuracy"]
print('\nAccuracy: {0:f}'.format(accuracy_score))
1…678…10
Angel Teng

Angel Teng

97 posts
14 categories
37 tags
© 2021 Angel Teng
Powered by Hexo
|
Theme — NexT.Muse v5.1.4