Skip to content

Commit 02a377b

Browse files
committed
emoji testing
1 parent d54ac39 commit 02a377b

File tree

39 files changed

+337
-337
lines changed

39 files changed

+337
-337
lines changed
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
# 什么是算法和數據結構
1+
# 什麼是算法和數據結構
22

33
你可能會在一些教材上看到這句话:
44

55
程序 = 算法 + 數據結構
66

7-
算法(Algorithm):是指解题方案的准确而完整的描述,是一系列解决問題的清晰指令,算法代表着用系统的方法描述解决問題的策略机制。也就是說,能够對一定规范的输入,在有限時間内获得所要求的输出。
7+
算法(Algorithm):是指解题方案的准确而完整的描述,是一系列解决問題的清晰指令,算法代表着用系统的方法描述解决問題的策略機制。也就是說,能够對一定規範的输入,在有限時間内获得所要求的输出。
88

9-
數據結構(Data Structures):是计算机存储和组织數據的一種方式,可以用來高效地处理數據
9+
數據結構(Data Structures):是计算機存儲和組織數據的一種方式,可以用來高效地處理數據
1010

11-
举個例子:二分查找就是一個非常經典的算法,而二分查找經常需要作用在一個有序数组上。這裡二分就是一種折半的算法思想,
12-
而数组是我們最常用的一種數據結構,支持根據下标快速訪問。很多算法需要特定的數據結構來實現,所以經常把它们放到一块講
11+
舉個例子:二分查找就是一個非常經典的算法,而二分查找經常需要作用在一個有序数組上。這裡二分就是一種折半的算法思想,
12+
而数組是我們最常用的一種數據結構,支持根據下標快速訪問。很多算法需要特定的數據結構來實現,所以經常把它們放到一塊講
1313

14-
實际上,在真正的项目開發中,大部分時間都是 从數據库取數據 -> 數據操作和結構化 -> 返回给前端,在數據操作過程中需要合理地抽象,
15-
组织、处理數據,如果选用了错誤的數據結構,就會造成代碼运行低效。這也是我們需要学习算法和數據結構的原因
14+
實際上,在真正的項目開發中,大部分時間都是 從資料庫取數據 -> 數據操作和結構化 -> 返回给前端,在數據操作過程中需要合理地抽象,
15+
組織、處理數據,如果選用了错誤的數據結構,就會造成代碼運行低效。這也是我們需要學習算法和數據結構的原因
1616

17-
# 笨方法学算法
18-
這裡我們用一種很原始的『笨』方法來学习算法:纸笔模擬
17+
# 笨方法學算法
18+
這裡我們用一種很原始的『笨』方法來學習算法:紙筆模擬
1919

20-
- 閱讀资料了解算法思想
21-
- 纸笔模擬嘗試理解
20+
- 閱讀資料了解算法思想
21+
- 紙筆模擬嘗試理解
2222
- 用自己熟悉的编程语言來實現
2323
- 單測
2424

25-
# 小問題
25+
# 小問題 :smile:
2626

2727
- 你還知道哪些經典的算法和數據結構?
28-
- 学习算法你覺得需要哪些预备知识
28+
- 學習算法你覺得需要哪些预备知识
2929
- 我們的業務代碼開發中會涉及到算法嗎?
3030
- 你了解 redis 嗎,你知道它有哪几個常用的數據結構嗎?你知道它的底层實現方式嗎?

docs/01_抽象数据类型和面向对象编程/ADT_OOP.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Python 一切皆對象
22

3-
举個例子,在 python 中我們經常使用的 list
3+
舉個例子,在 python 中我們經常使用的 list
44

55
```py
66
l = list() # 實例化一個 list 對象 l
@@ -12,14 +12,14 @@ print(len(l)) # 调用對象的 `__len__` 方法
1212

1313
在後面實現新的數據类型时,我們将使用 python 的 class 實現,它包含属性和方法。
1414
属性一般是使用某種特定的數據类型,而方法一般是對属性的操作。
15-
這裡你只需了解這么多就行了, 我們不會使用继承等特性。
15+
這裡你只需了解這麼多就行了, 我們不會使用继承等特性。
1616

1717

18-
# 什么是抽象數據类型 ADT
18+
# 什麼是抽象數據类型 ADT
1919

20-
實际上 python 内置的 list 就可以看成一種抽象數據类型。
20+
實際上 python 内置的 list 就可以看成一種抽象數據类型。
2121

22-
ADT: Abstract Data Type,抽象數據类型,我們在组合已有的數據結構來實現一種新的數據类型, ADT 定義了类型的數據和操作。
22+
ADT: Abstract Data Type,抽象數據类型,我們在組合已有的數據結構來實現一種新的數據类型, ADT 定義了类型的數據和操作。
2323

2424
我們以抽象一個背包(Bag) 數據类型來說明,背包是一種容器类型,我們可以给它添加東西,也可以移除東西,并且我們想知道背包里
2525
有多少東西。于是我們可以定義一個新的數據类型叫做 Bag.
@@ -35,15 +35,15 @@ class Bag:
3535
影片中我們将使用 python 的 class 來實現一個新的容器类型叫做 Bag。
3636

3737

38-
# 實現 ADT 我們应该注意什么
39-
- 如何选用恰當的數據結構作為存储
40-
- 选取的數據結構能否满足 ADT 的功能需求
38+
# 實現 ADT 我們应该注意什麼
39+
- 如何選用恰當的數據結構作為存儲
40+
- 選取的數據結構能否满足 ADT 的功能需求
4141
- 實現效率如何?
4242

4343

4444
# 小問題:
45-
- 你了解 python 的魔術方法嗎? 比如 `__len__` ,调用 len(l) 的时候發生了什么
46-
- 你了解單測嗎?我們以後将使用 pytest 运行單元測试,保证我們實現的數據結構和算法是正确的。你可以网上搜索下它的簡單用法
45+
- 你了解 python 的魔術方法嗎? 比如 `__len__` ,调用 len(l) 的时候發生了什麼
46+
- 你了解單測嗎?我們以後将使用 pytest 運行單元測试,保证我們實現的數據結構和算法是正确的。你可以网上搜索下它的簡單用法
4747

4848
# 延伸閱讀:
4949

docs/02_数组和列表/array_and_list.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
# 線性結構
2-
本節我們从最簡單和常用的線性結構開始,并結合 Python 语言本身内置的數據結構和其底层實現方式來講解。
2+
本節我們從最簡單和常用的線性結構開始,并結合 Python 语言本身内置的數據結構和其底层實現方式來講解。
33
雖然本质上數據結構的思想是语言无關的,但是了解 Python 的實現方式有助于你避免一些坑。
44

55
我們會在代碼中注释出操作的時間複雜度。
66

77

8-
# 数组 array
8+
# 数組 array
99

10-
数组是最常用到的一種線性結構,其實 python 内置了一個 array 模块,但是大部人甚至从來没用過它
11-
Python 的 array 是内存连续、存储的都是同一數據类型的結構,而且只能存数值和字符。
10+
数組是最常用到的一種線性結構,其實 python 内置了一個 array 模塊,但是大部人甚至從來没用過它
11+
Python 的 array 是内存连续、存儲的都是同一數據类型的結構,而且只能存数值和字符。
1212

1313
我建議你课下看下 array 的文档:https://docs.python.org/2/library/array.html
1414

1515
你可能很少會使用到它(我推荐你用 numpy.array),我将在影片里簡單介绍下它的使用和工作方式,最常用的還是接下來要說的 list,
16-
本章最後我們會用 list 來實現一個固定长度、并且支持所有 Python 數據类型的数组 Array.
16+
本章最後我們會用 list 來實現一個固定长度、并且支持所有 Python 數據类型的数組 Array.
1717

1818

1919
# 列表 list
20-
如果你学過 C++,list 其實和 C++ STL(标准模板库)中的 vector 很类似,它可能是你的 Python 学习中使用最频繁的數據結構之一
20+
如果你學過 C++,list 其實和 C++ STL(標准模板库)中的 vector 很类似,它可能是你的 Python 學習中使用最频繁的數據結構之一
2121
這裡我們不再去自己實現 list,因為這是個 Python 提供的非常基础的數據类型,我會在影片中講解它的工作方式和内存分配策略,
2222
避免使用過程中碰到一些坑。當然如果你有毅力或者興趣的了解底层是如何實現的,可以看看 cpython 解释器的具體實現。
2323

@@ -33,14 +33,14 @@ list.remove | O(n) |
3333
![](./list.png)
3434

3535
# 用 list 實現 Array ADT
36-
講完了 list 让我們來實現一個定长的数组 Array ADT,在其他一些语言中,内置的数组結構就是定长的
36+
講完了 list 让我們來實現一個定长的数組 Array ADT,在其他一些语言中,内置的数組結構就是定长的
3737
這裡我們會使用 list 作為 Array 的一個成员(代理)。具體請参考影片講解和代碼示例,後面我們會使用到這個 Array 类。
3838

3939

4040
# 小問題
4141
- 你知道線性結構的查找,删除,訪問一個元素的平均時間複雜度嗎?(後面我們會介绍這個概念,现在你可以簡單地理解為一個操作需要的平均步骤)
42-
- list 内存重新分配的时候為什么要有冗余?不會浪费空間嗎?
43-
- 當你频繁的pop list 的第一個元素的时候,會發生什么?如果需要频繁在兩头增添元素,你知道更高效的數據結構嗎?後面我們會講到
42+
- list 内存重新分配的时候為什麼要有冗余?不會浪费空間嗎?
43+
- 當你频繁的pop list 的第一個元素的时候,會發生什麼?如果需要频繁在兩头增添元素,你知道更高效的數據結構嗎?後面我們會講到
4444

4545

4646
# 延伸閱讀

docs/03_链表/double_link_list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def appendleft(self, value):
6060

6161
def remove(self, node): # O(1),傳入node 而不是 value 我們就能實現 O(1) 删除
6262
"""remove
63-
:param node # 在 lru_cache 里實际上根據key 保存了整個node:
63+
:param node # 在 lru_cache 里實際上根據key 保存了整個node:
6464
"""
6565
if node is self.root:
6666
return

docs/03_链表/linked_list.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# 鏈式結構
22

3-
上一節講到了支持随机訪問的線性結構,這次我們開始講鏈式結構, 影片里我會說下這兩種結構的區别,然後講解最常見的單鏈表和双鏈表。
3+
上一節講到了支持随機訪問的線性結構,這次我們開始講鏈式結構, 影片里我會說下這兩種結構的區别,然後講解最常見的單鏈表和双鏈表。
44
之前在专栏文章[那些年,我們一起跪過的算法题[影片]](https://zhuanlan.zhihu.com/p/35175401)里實現過一個 lru_cache,
55
使用到的就是循環双端鏈表,如果感覺這篇文章有點难理解,我們這裡将會循序渐进地來實現。
66
後面講到哈希表的冲突解决方式的时候,我們會再次提到鏈表。
77

88
上一節我們分析了 list 的各種操作是如何實現的,如果你還有印象的话,list
9-
在头部进行插入是個相當耗时的操作(需要把後面的元素一個一個挪個位置)。假如你需要频繁在数组兩头增删,list 就不太合适。
10-
今天我們介绍的鏈式結構将摆脱這個缺陷,當然了鏈式結構本身也有缺陷,比如你不能像数组一样随机根據下标訪問,你想查找一個元素只能老老實實从头遍历
11-
所以嘛,学习和了解數據結構的原理和實現你才能准确地选择到底什么时候该用什么數據結構,而不是瞎选导致代碼性能很差
9+
在头部进行插入是個相當耗时的操作(需要把後面的元素一個一個挪個位置)。假如你需要频繁在数組兩头增删,list 就不太合适。
10+
今天我們介绍的鏈式結構将摆脱這個缺陷,當然了鏈式結構本身也有缺陷,比如你不能像数組一样随機根據下標訪問,你想查找一個元素只能老老實實從头遍历
11+
所以嘛,學習和了解數據結構的原理和實現你才能准确地選择到底什麼时候该用什麼數據結構,而不是瞎選导致代碼性能很差
1212

1313

1414
# 單鏈表
1515
和線性結構不同,鏈式結構内存不连续的,而是一個個串起來的,這個时候就需要每個鏈接表的節點保存一個指向下一個節點的指針。
16-
這裡可不要混淆了列表和鏈表(它们的中文發音类似,但是列表 list 底层其實還是線性結構,鏈表才是真的通過指針關联的鏈式結構)。
16+
這裡可不要混淆了列表和鏈表(它們的中文發音类似,但是列表 list 底层其實還是線性結構,鏈表才是真的通過指針關联的鏈式結構)。
1717
看到指針你也不用怕,這裡我們用的 python,你只需要一個簡單赋值操作就能實現,不用担心 c 语言里复杂的指針。
1818

1919
先來定義一個鏈接表的節點,刚才說到有一個指針保存下一個節點的位置,我們叫它 next, 當然還需要一個 value 属性保存值
@@ -32,7 +32,7 @@ class LinkedList(object):
3232
[root] -> [node0] -> [node1] -> [node2]
3333
"""
3434
```
35-
實現我們會在影片中用画图來模擬并且手動代碼實現,代碼里我們會标识每個步骤的時間複雜度。這裡請高度集中精力,
35+
實現我們會在影片中用画图來模擬并且手動代碼實現,代碼里我們會標识每個步骤的時間複雜度。這裡請高度集中精力,
3636
雖然鏈表的思想很簡單,但是想要正确写對鏈表的操作代碼可不容易,稍不留神就可能丢失一些步骤。
3737
這裡我們還是會用簡單的單測來验证代碼是否按照预期工作。
3838

@@ -48,7 +48,7 @@ linked_list.remove(value) | O(n) |
4848

4949
# 双鏈表
5050
上邊我們亲自實現了一個單鏈表,但是能看到很明显的問題,單鏈表雖然 append 是 O(1),但是它的 find 和 remove 都是 O(n)的,
51-
因為删除你也需要先查找,而單鏈表查找只有一個方式就是从头找到尾,中間找到才退出。
51+
因為删除你也需要先查找,而單鏈表查找只有一個方式就是從头找到尾,中間找到才退出。
5252
這裡我之前提到過如果要實現一個 lru 缓存(訪問時間最久的踢出),我們需要在一個鏈表里能高效的删除元素,
5353
并把它追加到訪問表的最後一個位置,這個时候單鏈表就满足不了了,
5454
因為缓存在 dict 里查找的時間是 O(1),你更新訪問顺序就 O(n)了,缓存就没了優势。
@@ -67,10 +67,10 @@ class Node(object):
6767

6868
對, 就多了 prev,有啥優势嘛?
6969

70-
- 看似我們反過來遍历双鏈表了。反過來从哪里開始呢?我們只要让 root 的 prev 指向 tail 節點,不就串起來了嗎?
70+
- 看似我們反過來遍历双鏈表了。反過來從哪里開始呢?我們只要让 root 的 prev 指向 tail 節點,不就串起來了嗎?
7171
- 直接删除節點,當然如果给的是一個值,我們還是需要查找這個值在哪個節點? - 但是如果给了一個節點,我們把它拿掉,直接让它的前後節點互相指過去不就行了?哇欧,删除就是 O(1) 了,兩步操作就行啦
7272

73-
好,废话不多說,我們在影片里介绍怎么實現一個双鏈表 ADT。你可以直接在本项目的 `docs/03_鏈表/double_link_list.py` 找到代碼。
73+
好,废话不多說,我們在影片里介绍怎麼實現一個双鏈表 ADT。你可以直接在本項目的 `docs/03_鏈表/double_link_list.py` 找到代碼。
7474
最後让我們看下它的時間複雜度:(這裡 CircularDoubleLinkedList 取大写字母缩写為 cdll)
7575

7676
循環双端鏈表操作 | 平均時間複雜度 |

docs/03_链表/linked_list.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ def __iter__(self):
5959
yield node.value
6060

6161
def iter_node(self):
62-
"""遍历 head 節點到 tail 節點"""
62+
"""遍历 head 節點到 tail 節點"""
6363
curnode = self.root.next
64-
while curnode is not self.tailnode: # 从第一個節點開始遍历
64+
while curnode is not self.tailnode: # 從第一個節點開始遍历
6565
yield curnode
6666
curnode = curnode.next # 移動到下一個節點
6767
if curnode is not None:
@@ -86,7 +86,7 @@ def remove(self, value): # O(n)
8686
return -1 # 表明删除失败
8787

8888
def find(self, value): # O(n)
89-
""" 查找一個節點,返回序号, 0 開始
89+
""" 查找一個節點,返回序号, 0 開始
9090
9191
:param value:
9292
"""
@@ -107,7 +107,7 @@ def popleft(self): # O(1)
107107
self.length -= 1
108108
value = headnode.value
109109

110-
if self.tailnode is headnode: # 勘誤:增加單節點删除 tailnode 处理
110+
if self.tailnode is headnode: # 勘誤:增加單節點删除 tailnode 處理
111111
self.tailnode = None
112112
del headnode
113113
return value
@@ -122,7 +122,7 @@ def clear(self):
122122
def reverse(self):
123123
"""反转鏈表"""
124124
curnode = self.root.next
125-
self.tailnode = curnode # 记得更新 tailnode,多了這個属性处理起來經常忘记
125+
self.tailnode = curnode # 记得更新 tailnode,多了這個属性處理起來經常忘记
126126
prevnode = None
127127

128128
while curnode:

docs/03_链表/lru_cache.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def fib(n):
1313

1414

1515
"""
16-
下邊就來写一個缓存装饰器來優化它。傳统方法是用個数组记录之前计算過的值,但是這種方式不够 Pythonic
16+
下邊就來写一個缓存装饰器來優化它。傳统方法是用個数組记录之前计算過的值,但是這種方式不够 Pythonic
1717
"""
1818

1919

@@ -40,13 +40,13 @@ def f(n):
4040

4141

4242
"""
43-
問題來了,假如空間有限怎么办,我們不可能一直向缓存塞東西,當缓存达到一定個数之後,我們需要一種策略踢出一些元素,
43+
問題來了,假如空間有限怎麼办,我們不可能一直向缓存塞東西,當缓存达到一定個数之後,我們需要一種策略踢出一些元素,
4444
用來给新的元素腾出空間。
4545
一般缓存失效策略有
46-
- LRU(Least-Recently-Used): 替换掉最近請求最少的對象,實际中使用最广。cpu缓存淘汰和虚拟内存效果好,web应用欠佳
46+
- LRU(Least-Recently-Used): 替换掉最近請求最少的對象,實際中使用最广。cpu缓存淘汰和虚拟内存效果好,web应用欠佳
4747
- LFU(Least-Frequently-Used): 缓存污染問題(一個先前流行的缓存對象會在缓存中驻留很长時間)
4848
- First in First out(FIFO)
49-
- Random Cache: 随机选一個删除
49+
- Random Cache: 随機選一個删除
5050
5151
LRU 是常用的一個,比如 redis 就實現了這個策略,這裡我們來模擬實現一個。
5252
要想實現一個 LRU,我們需要一種方式能够记录訪問的顺序,并且每次訪問之後我們要把最新使用到的元素放到最後(表示最新訪問)。
@@ -94,7 +94,7 @@ def __call__(self, func):
9494
9595
- 這裡為了简化默认参数只有一個数字 n,假如可以傳入多個参数,如何确定缓存的key 呢?
9696
- 這裡實現没有考虑線程安全的問題,要如何才能實現線程安全的 LRU 呢?當然如果不是多線程环境下使用是不需要考虑的
97-
- 假如這裡没有用内置的 dict,你能使用 redis 來實現這個 LRU 嗎,如果使用了 redis,我們可以存储更多數據到服务器。而使用字典實际上是缓存了Python进程里(localCache)。
97+
- 假如這裡没有用内置的 dict,你能使用 redis 來實現這個 LRU 嗎,如果使用了 redis,我們可以存儲更多數據到服务器。而使用字典實際上是缓存了Python进程里(localCache)。
9898
- 這裡只是實現了 lru 策略,你能同时實現一個超时 timeout 参数嗎?比如像是memcache 實現的 lazy expiration 策略
9999
- LRU有個缺點就是,對于周期性的數據訪問會导致命中率迅速下降,有一種優化是 LRU-K,訪問了次数达到 k 次才會将數據放入缓存
100100
"""
@@ -127,7 +127,7 @@ def test():
127127
print(time.time() - beg)
128128

129129

130-
# TODO 要怎么给 lru 写單測?
130+
# TODO 要怎麼给 lru 写單測?
131131

132132
if __name__ == '__main__':
133133
test()

docs/04_队列/array_queue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33

4-
# NOTE: array_and_list 第一章拷贝的代碼
4+
# NOTE: array_and_list 第一章拷贝的代碼
55
class Array(object):
66

77
def __init__(self, size=32):

0 commit comments

Comments
 (0)