Skip to content

Commit dcb703b

Browse files
committed
docs:新增算法大纲
1 parent 2449570 commit dcb703b

File tree

6 files changed

+434
-1
lines changed

6 files changed

+434
-1
lines changed

docs/.vuepress/config.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,14 @@ module.exports = {
457457

458458
{
459459
text: '数据结构与算法',
460+
items: [{
461+
text: '数据结构与算法专栏大纲',
462+
link: '/md/algorithm/logic/basic/00-数据结构与算法专栏大纲.md'
463+
}, ]
464+
},
465+
466+
{
467+
text: '大厂算法面试',
460468
items: [{
461469
text: '00-阿里秋招高频算法题汇总-基础篇',
462470
link: '/md/algorithm/logic/leetcode/00-阿里秋招高频算法题汇总-基础篇.md'
@@ -621,6 +629,9 @@ module.exports = {
621629
sidebarDepth: 0,
622630
children: [
623631
"01-DMP系统简介",
632+
"05-用户画像是什么?",
633+
"06-构建高质量的用户画像",
634+
"07-用户画像和特征工程",
624635
]
625636
},
626637
],
@@ -786,7 +797,7 @@ module.exports = {
786797
},
787798
],
788799
"/md/algorithm/logic/leetcode/": [{
789-
title: "数据结构与算法",
800+
title: "大厂算法面试",
790801
collapsable: false,
791802
sidebarDepth: 0,
792803
children: [
@@ -798,6 +809,17 @@ module.exports = {
798809
"05-字节秋招高频算法题汇总-进阶篇",
799810
]
800811
}, ],
812+
813+
"/md/algorithm/logic/basic/": [{
814+
title: "数据结构与算法",
815+
collapsable: false,
816+
sidebarDepth: 0,
817+
children: [
818+
"00-数据结构与算法专栏大纲",
819+
"【图解数据结构与算法】LRU缓存淘汰算法面试时到底该怎么写",
820+
]
821+
}],
822+
801823
"/md/spring/spring-cloud/": [{
802824
title: "SpringCloudAlibaba",
803825
collapsable: false,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# 00-数据结构与算法专栏大纲
2+
3+
## 你将获得
4+
5+
- 20 个经典数据结构与算法
6+
- 100 个真实项目场景案例
7+
- 文科生都能懂的算法手绘图解
8+
- 轻松搞定 BAT 的面试通关秘籍
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+
![](https://codeselect.oss-cn-shanghai.aliyuncs.com/1b969cfe410a43d5f2b148c255beb3ae.jpg)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# 【图解数据结构与算法】LRU缓存淘汰算法面试时到底该怎么写
2+
3+
链表实现的LRU缓存淘汰算法的时间复杂度O(n),通过散列表可以将这个时间复杂度降低到O(1)。
4+
5+
Redis有序集合即使用跳表实现,跳表可以看作一种改进版的链表。Redis有序集合不仅使用了跳表,还用到了散列表。
6+
7+
LinkedHashMap也用到了散列表和链表两种数据结构。散列表和链表都是如何组合起来使用的,以及为什么散列表和链表会经常放到一块使用。
8+
9+
## 1 LRU缓存淘汰算法
10+
11+
### 1.1 链表实现LRU
12+
13+
需维护一个按访问时间从大到小有序排列的链表结构。因为缓存大小有限,当缓存空间不够,需淘汰数据时,直接将链表头部结点删除。
14+
15+
当要缓存某数据,先在链表查找这数据:
16+
17+
- 没找到,数据放到链表尾部
18+
- 找到了,就把它移动到链表尾部
19+
20+
因为查找数据需遍历链表,所以单纯用链表实现LRU缓存淘汰算法需O(n)。
21+
22+
#### 缓存(cache)系统的主要操作
23+
24+
- 往缓存中添加一个数据
25+
- 从缓存中删除一个数据
26+
- 在缓存中查找一个数据
27+
28+
都涉及“查找”,如单纯采用链表,只能O(n)。如将散列表和链表组合,可将这三个操作的时间复杂度都降到O(1)。
29+
30+
#### 结构
31+
32+
33+
34+
![](https://codeselect.oss-cn-shanghai.aliyuncs.com/%E6%95%A3%E5%88%97%2B%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.png)
35+
使用双向链表存储数据,链表中的每个结点除了存储:
36+
37+
- 数据(data)
38+
- 前驱指针(prev)
39+
- 后继指针(next)
40+
- 还新增一个特殊字段hnext
41+
42+
因为通过链表法解决哈希冲突,所以每个结点在两条链中:
43+
44+
- 双向链表
45+
前驱和后继指针,将结点串在双向链表
46+
- 散列表中的拉链
47+
hnext指针,将结点串在散列表的拉链
48+
49+
### 1.2 查找
50+
51+
散列表中查找数据的时间复杂度接近O(1),所以通过散列表,我们可以很快地在缓存中找到一个数据。当找到数据之后,我们还需要将它移动到双向链表的尾部。
52+
53+
### 1.3 删除
54+
55+
需要找到数据所在的结点,然后将结点删除。借助散列表,我们可以在O(1)时间复杂度里找到要删除的结点。因为我们的链表是双向链表,双向链表可以通过前驱指针O(1)时间复杂度获取前驱结点,所以在双向链表中,删除结点只需要O(1)的时间复杂度。
56+
57+
### 1.4 添加
58+
59+
添加数据到缓存稍微有点麻烦,我们需要先看这个数据是否已经在缓存中。如果已经在其中,需要将其移动到双向链表的尾部;如果不在其中,还要看缓存有没有满。如果满了,则将双向链表头部的结点删除,然后再将数据放到链表的尾部;如果没有满,就直接将数据放到链表的尾部。
60+
61+
过程中的查找操作都可通过hash表。所以,这三个操作的时间复杂度都是O(1)。
62+
63+
通过散列表和双向链表的组合使用,实现了一个高效的、支持LRU缓存淘汰算法的缓存系统原型。
64+
65+
## 2 Redis有序集合
66+
67+
在有序集合中,每个成员对象有两个重要的属性,key(键值)和score(分值)。
68+
不仅会通过score来查找数据,还会通过key来查找数据。
69+
70+
举个例子,比如用户积分排行榜有这样一个功能:我们可以通过用户的ID来查找积分信息,也可以通过积分区间来查找用户ID或者姓名信息。这里包含ID、姓名和积分的用户信息,就是成员对象,用户ID就是key,积分就是score。
71+
72+
所以,如果我们细化一下Redis有序集合的操作,那就是下面这样:
73+
74+
- 添加一个成员对象
75+
- 按照键值来删除一个成员对象
76+
- 按照键值来查找一个成员对象
77+
- 按照分值区间查找数据,比如查找积分在[100, 356]之间的成员对象
78+
- 按照分值从小到大排序成员变量;
79+
80+
若仅按分值将成员对象组织成跳表的结构,那按照键删除、查询成员对象就会很慢,解决方法与LRU缓存淘汰算法的解决方法类似。
81+
可再按照键值构建一个散列表,这样按照key来删除、查找一个成员对象的时间复杂度就变成了O(1)。
82+
83+
Redis有序集合的操作还有另外一类,也就是查找成员对象的排名(Rank)或者根据排名区间查找成员对象。这个功能单纯用刚刚讲的这种组合结构就无法高效实现了。
84+
85+
## 3 Java LinkedHashMap
86+
87+
HashMap就是通过hash表这种数据结构实现的。而LinkedHashMap并不仅仅是通过链表法解决散列冲突的。
88+
89+
```java
90+
HashMap<Integer, Integer> m = new LinkedHashMap<>();
91+
m.put(3, 11);
92+
m.put(1, 12);
93+
m.put(5, 23);
94+
m.put(2, 22);
95+
96+
for (Map.Entry e : m.entrySet()) {
97+
System.out.println(e.getKey());
98+
}
99+
```
100+
101+
上面的代码会按照数据插入的顺序依次来打印。而hash表数据经过hash函数扰乱后是无规律存储的,它是如何实现按照数据的插入顺序来遍历打印的呢?
102+
103+
就是通过hash表和链表组合实现,可支持:
104+
105+
- 按照插入顺序遍历数据
106+
- 按访问顺序遍历数据
107+
108+
你可以看下面这段代码:
109+
![](https://img-blog.csdnimg.cn/5122f6333b8846159ce6d069b78d349b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_SmF2YUVkZ2U=,size_20,color_FFFFFF,t_70,g_se,x_16)打印结果
110+
![](https://img-blog.csdnimg.cn/bd93399936d348099db7d456fe377f37.png)
111+
112+
113+
每次调用 LinkedHashMap#put()添加数据时,都会将数据添加到链尾,前四个操作完成后,链表数据如下:
114+
![](https://img-blog.csdnimg.cn/6416549ba44749fa985aaf68fa63a7b0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_SmF2YUVkZ2U=,size_20,color_FFFFFF,t_70,g_se,x_16)
115+
第二次将键值为3的数据放入到LinkedHashMap时,会先查找该K是否已有,然后,再将已经存在的(3,11)删除,并将新的(3,26)放到链尾。
116+
这个时候链表中的数据就是下面这样:
117+
![](https://img-blog.csdnimg.cn/32c96b385f1c4c0893f153209b156b30.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_SmF2YUVkZ2U=,size_20,color_FFFFFF,t_70,g_se,x_16)
118+
访问K=5数据时,将被访问到的数据移动到链尾。此时,链表数据如下:
119+
![](https://img-blog.csdnimg.cn/a8362c3a9fae4f529306f0d79772a729.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_SmF2YUVkZ2U=,size_20,color_FFFFFF,t_70,g_se,x_16)
120+
121+
可见,按访问时间排序的LinkedHashMap本身就是个支持LRU缓存淘汰策略的缓存系统。
122+
LinkedHashMap中的“Linked”实际上是指的是双向链表,并非指用链表法解决哈希冲突。
123+
124+
## 4 为啥hash表和链表经常一块使用?
125+
126+
hash表虽支持高效插入、删除、查找,但hash表数据都是通过hash函数打乱后无规律存储。即无法支持按序快速遍历。
127+
128+
如想按序遍历散列表数据,需将散列表中的数据拷贝到数组,然后排序,再遍历。
129+
130+
因为散列表是动态数据结构,不断有数据插入、删除,所以想按顺序遍历散列表数据时,都要先排序,效率很低。为此,就将散列表和链表(或跳表)结合使用。
131+
132+
## 5 手写LRU
133+
134+
```java
135+
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
136+
137+
private final int CACHE_SIZE;
138+
139+
// 这里就是传递进来最多能缓存多少数据
140+
public LRUCache(int cacheSize) {
141+
// true指linkedhashmap将元素按访问顺序排序
142+
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
143+
CACHE_SIZE = cacheSize;
144+
}
145+
146+
@Override
147+
protected boolean removeEldestEntry(Map.Entry eldest) {
148+
// 当KV数据量大于指定缓存个数时,就自动删除最老数据
149+
return size() > CACHE_SIZE;
150+
}
151+
152+
}
153+
```
154+
155+
## 6 FAQ
156+
157+
Q:本文的散列表和链表结合使用的例子用的都是双向链表。若把双向链表改单链表,还正常工作吗?
158+
159+
删除一个元素时,虽 O(1) 找到目标结点,但要删除该结点需拿到前一个结点的指针,遍历到前一个结点复杂度会变为 O(N),所以用双链表实现比较合适。(硬要操作的话,单链表也可实现 O(1) 时间复杂度删除结点的)。
160+
161+
Q:假设有 10 万名猎头,每个猎头都可做任务(如发布职位)积累积分,然后通过积分下载简历。咋在内存存储这 10 万个猎头 ID 和积分信息,支持如下操作:
162+
163+
- 根据猎头的 ID 快速查找、删除、更新这个猎头的积分信息
164+
- 查找积分在某个区间的猎头 ID 列表
165+
- 查找按照积分从小到大排名在第 x 位到第 y 位之间的猎头 ID 列表
166+
167+
A:以积分排序构建一个跳表,再以猎头 ID 构建一个散列表:
168+
169+
- ID 在散列表中所以可以 O(1) 查找到这个猎头
170+
- 积分以跳表存储,跳表支持区间查询
171+
- 这点根据目前学习的知识暂时无法实现
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# 05-用户画像是什么?
2+
3+
## 1 什么是用户画像
4+
5+
用户画像是指根据用户的个人信息、行为数据、兴趣爱好等多方面数据构建出的用户的特征模型或描述。用户画像旨在更好地理解和洞察用户,从而为用户提供个性化的服务和体验。
6+
7+
通过数据建立描绘用户的标签,个性化推荐、广告系统、活动营销、都是基于用户画像的应用。
8+
9+
## 2 特点和作用
10+
11+
1. **多维数据:** 用户画像通常包括多种维度的数据,例如用户的基本信息(年龄、性别、地域)、行为数据(浏览记录、购买记录)、兴趣爱好(关注的话题、喜欢的产品类别)等。
12+
13+
2. **个性化服务:** 基于用户画像,企业可以针对不同用户群体提供个性化的服务和推荐,例如个性化推荐商品、定制化的营销活动等,提升用户满意度和忠诚度。
14+
15+
3. **精准营销:** 用户画像可以帮助企业更准确地了解用户的需求和偏好,从而进行精准的营销活动,提高营销效果和转化率。
16+
17+
4. **产品优化:** 通过分析用户画像,企业可以发现用户的使用习惯和痛点,从而优化产品设计和功能,提升产品的用户体验和市场竞争力。
18+
19+
5. **决策支持:** 用户画像还可以为企业提供数据支持,辅助决策制定和业务发展方向,帮助企业更好地了解市场和用户需求。
20+
21+
6. **跨平台应用:** 用户画像不仅可以应用于电子商务和在线服务领域,还可以在金融、医疗、教育等多个领域进行应用,为不同行业提供个性化服务和解决方案。
22+
23+
总的来说,用户画像是通过对用户数据进行分析和建模,形成对用户特征和行为的描述,从而为企业提供个性化服务、精准营销、产品优化等方面的支持,是数字化时代企业数据驱动和个性化服务的重要工具之一。
24+
25+
## 3 正确理解用户画像
26+
27+
- 不能把典型用户当作用户画像
28+
29+
典型用户是虚构的,每个真实用户都有自己的用户画像
30+
31+
- 用户画像不是用户标签的简单组合
32+
用户画像的标签要和业务相结合
33+
34+
- 用户画像的有效性
35+
用户的行为,偏好会随时间而变化
36+
37+
标签是用户画像的核心,只有真正有效的用户画像标签,才能提升运营效果。
38+
39+
## 4 用户画像如何生成
40+
41+
### 4.1 统一用户唯一标识
42+
43+
如手机号
44+
45+
### 4.2 给用户打标签
46+
47+
属性标签、消费标签、行为标签、内容分析
48+
49+
### 4.3 将用户画像与业务关联
50+
51+
获客、粘客、留客
52+
53+
## 5 用户画像的标签维度
54+
55+
### 5.1 标签类型,从标签主题的角度
56+
57+
- 用户属性
58+
- 用户行为
59+
- 用户消费
60+
- 风险控制
61+
- 内容分析
62+
63+
### 5.2 标签类型一从标签生成的角度
64+
65+
#### 5.2.1 统计类型
66+
67+
需要使用聚合函数计算后得到的标签。如总消费金额
68+
69+
#### 5.2.2 规则匹配类型
70+
71+
- 人口属性
72+
- 用户生命周期
73+
74+
#### 5.2.3 挖掘类型
75+
76+
用户偏好
77+
78+
### 5.3 标签类型一从数据提取的角度
79+
80+
#### 5.3.1 事实标签
81+
82+
从生产系统获取数据
83+
84+
#### 5.3.2 模型标签
85+
86+
对用户属性和行为进行聚类
87+
88+
#### 5.3.3 预测标签
89+
90+
基于用户属性和行为,挖掘用户潜在需求。
91+
92+
### 5.4 标签类型一从数据时效的角度
93+
94+
静态属性标签:长期甚至永久都不会发生变化,如性别
95+
动态属性标签:存在有效期,需要定期的更新,如用户活跃度
96+
97+
### 5.5 人群的标签组合
98+
99+
性别:有2个标签 男、女
100+
101+
年龄段维度:有7个标签,18-,20-30,30-40,40-50,50-60,60-70
102+
103+
月均消费维度:有7个标签 100-,100-500,500-1000 ...
104+
105+
```bash
106+
人群标签数量=2*7*7=98
107+
```

0 commit comments

Comments
 (0)