diff --git a/README.md b/README.md index 73c30eb..04c1a8e 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,13 @@ 数据结构和算法是每个程序员需要掌握的基础知识之一,也是面试中跨不过的槛。目前关于 Python 算法和数据结构的中文资料比较欠缺, 笔者尝试录制视频教程帮助 Python 初学者掌握常用算法和数据结构,提升开发技能。 本教程是付费教程(文字内容和代码免费),因为笔者录制的过程中除了购买软件、手写板等硬件之外,业余需要花费很多时间和精力来录制视频、查资料、编写课件和代码,养家糊口不容易,希望大家体谅。 -(视频未完成,完成后会在本页面放出链接) ## 链接 +视频教程已经发布在网易云课堂和 csdn 学院,内容一致,推荐使用网易云课堂。 + +[网易云课堂: Python数据结构与算法教程](http://study.163.com/course/introduction.htm?courseId=1005526003) 视频教程 + +[csdn 学院:Python数据结构与算法教程](https://edu.csdn.net/course/detail/8332) [网上阅读《Python 算法与数据结构教程 》](http://ningning.today/python_data_structures_and_algorithms/) @@ -20,7 +24,7 @@ - 讲 Python 数据结构和算法的资料很少,中文资料更少 - 很多自学 Python 的工程师对基础不够重视,面试也发现很多数据结构和算法不过关,很多人挂在了基础的数据结构和算法上 - 缺少工程应用场景下的讲解,很多讲算法的资料太『教科书化』。本书实现的代码工程上可用 -- 网上很多视频教程比较水,收费还很不合理,纯属智商税 +- 网上很多视频教程不够循序渐进,不成系统 ## 作者简介 目前就职于[知乎](https://www.zhihu.com/people/pegasus-wang/activities),任后端工程师,多年 Python 开发经验。 @@ -73,20 +77,21 @@ - 面试笔试常考算法 ## 编程语言 -我们这里使用最近很火的Python。Python 入门简单而且是个多面手,在爬虫、web 后端、运维、数据分析、AI 方面领域都有 Python 的身影。 +我们这里使用最近很火的Python。Python 入门简单而且是个多面手,在爬虫、web 后端、运维、数据分析、AI、量化投资等领域都有 Python 的身影, +无论是否是专业程序员, Python 都是一门学习性价比非常高的语言。 知乎、豆瓣、头条、饿了么、搜狐等公司都有广泛使用 Python。笔者日常工作使用也是 Python,有一定实践经验, 在知乎上维护了一个专栏[《Python 学习之路》](https://zhuanlan.zhihu.com/c_85234576)。 Python 抽象程度比较高, 我们能用更少的代码来实现功能,同时不用像 C/C++ 那样担心内存管理、指针操作等底层问题, 把主要心思放在算法逻辑本身而不是语言细节上,Python 也号称伪代码语言。所有代码示例使用 Python2/3 兼容代码, -不过只在 python3.5 下测试过,推荐用相同版本 Python。 +不过只在 python3.5 下测试过,推荐用相同版本 Python 进行代码编写和测试。 ## 受众 想要学习 Python 算法和数据结构的初、中级同学,包括自学的同学和本科低年级学生等。需要掌握 Python 的基本语法和面向对象编程的一些概念,我们这里只使用最基本的 Python 语法,不会再去介绍用到的 Python 语法糖。 # 预备知识 -- 掌握 Python 基本语法,有过使用 Python 的经验。知道 class、module、yield 等 +- 有一定的 python 基础,掌握 Python 基本语法,了解 python 内置数据结构的使用方式。有过使用 Python 的经验。知道 class、module、yield 等。如果是 python 初学者,建议先补补基础,否则有些部分看着会比较吃力。 - 基本的面向对象编程知识,会定义和使用 Python 中的类 (class) - 知道 Python 中的魔术方法,比如 `__len__` `__contains__` - 无需太多数学基础,仅在算法时间复杂度分析的时候会用到一些简单数学知识。对于学习基础算法,逻辑思维可能更重要一些 @@ -94,10 +99,10 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同 ## 教材 这里我参考过三本书: -[《算法图解》](https://book.douban.com/subject/26979890/): 图解的形式很适合新手,示例使用的是 python +[《算法图解》](https://book.douban.com/subject/26979890/): 图解的形式很适合新手,示例使用的是 python。建议基础较少的同学看这本书入门 [《Data Structures and Algorithms in Python》]( https://book.douban.com/subject/10607365/): 适合对 Python -和算法比较熟悉的同学,或者是有其他语言编程经验的同学。英文版,缺点是书中错误真的很多,代码有些无法运行 +和算法比较熟悉的同学,或者是有其他语言编程经验的同学。本书是英文版,缺点是书中错误真的很多,代码有些无法运行而且不够 Pythonic。 [《算法导论》第三版]( https://book.douban.com/subject/20432061/): 喜欢数学证明和板砖书的同学可以参考,有很多高级主题。使用伪代码 @@ -128,7 +133,7 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同 这是很多看了几本书没有太多业界实践经验就敢讲课的培训班老师教不了的。**知识廉价,经验无价** - 每个实现都会有单测来验证,培养良好的编码和测试习惯,传授工程经验 - 结合 cpython 底层实现讲解(比如list 内存分配策略等),避免一些使用上的坑。并且会用 python 来模拟内置 dict 等的实现 -- 每篇讲义后有思考题和延伸阅读链接,帮助大家加深思考和理解 +- 每篇讲义后有思考题和延伸阅读链接,帮助大家加深思考和理解。大部分题目答案都可以网络上搜索到 ## 资料 @@ -152,9 +157,10 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同 - Python 实现方式 - 时间、空间复杂度 - 使用场景,什么时候用 -- 自己尝试实现,如果抛开视频自己写起来有困难可以多看几次视频,一定要自己手动实现。很多面试可能会让手写 +- 自己尝试实现,如果抛开视频自己写起来有困难可以反复多看几次视频,一定要自己手动实现。很多面试可能会让手写 - 每章讲义后边都会有我设计的几个小问题,最好能够回答上来。同时还有代码练习题,你可以挑战下自己的掌握程度。 -- 最好按照顺序循序渐进,每章都会有铺垫和联系 +- 最好按照顺序循序渐进,每章都会有铺垫和联系,后边的章节可能会使用到前面提到的数据结构 +- 根据自己的基础结合我列举的教材和视频学习,第一次理解不了的可以反复多看几次,多编写代码练习到熟练为止 ## 课程目标 掌握基本的算法和数据结构原理,能独立使用 Python 语言实现,能在日常开发中灵活选用数据结构。 @@ -163,7 +169,7 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同 ## 工具 -推荐使用以下工具进行开发,如果使用编辑器最好装对 应 Python 插件: +推荐使用以下工具进行开发,如果使用编辑器最好装对 应 Python 插件,笔者视频演示中使用了 vim,读者可以自己挑选自己喜欢的开发工具: - Pycharm - Sublime @@ -171,14 +177,49 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同 - Vscode - Vim/Emacs +代码中使用到了 pytest 测试框架和 when-changed 文件变动监控工具(方便我们修改完代码保存后自动执行测试),你需要用 pip 安装 + +```py +pip install pytest +pip install when-changed +``` + +视频演示里我使用到了一个简单的 test.sh 脚本文件,内容如下: + +```sh +#!/usr/bin/env bash + +# pip install when-changed + when-changed -v -r -1 -s ./ "py.test -s $1" +``` +将以上内容放到 test.sh 文件后加上可执行权限, `chmod +x test.sh`,之后就可以用 + +``` +'./test.sh somefile.py' +``` +每次我们改动了代码,就会自动执行代码里的单元测试了。pytest 会自动发现以 test 开头的函数并执行测试代码。 + ## 勘误 -输出其实也是一种再学习的过程,中途需要查看大量资料、编写讲义、视频录制、代码编写等,难免有疏漏之处。 -有出版社找过笔者想让我出书,一来自己对出书兴趣不大,另外感觉书籍相对视频不够直观,有错误也不能及时修改,打算直接把所有 -文字内容讲义放到 github 上,供大家免费查阅。 +输出其实也是一种再学习的过程,中途需要查看大量资料、编写讲义、视频录制、代码编写等,难免有疏漏甚至错误之处。 +有出版社找过笔者想让我出书,一来自己对出书兴趣不大,另外感觉书籍相对视频不够直观,有错误也不能及时修改,打算直接把所有文字内容讲义和代码等放到 github 上,供大家免费查阅。 + +如果你发现文字内容、代码内容、视频内容有错误或者有疑问,欢迎在 github 上提 issue 讨论(或者网易公开课评论区),或者直接提 Merge Request,我会尽量及时修正相关内容,防止对读者产生误导。 +同时非常感谢认真学习并及时发现书中错误的同学,非常欢迎针对知识本身的交流和讨论,任何建议和修正我都会认真求证。 +对于提出修正意见或者提交代码的同学,由于人数比较多这里就不一一列举了,可以在以下列表查看,再次感谢你们。笔者信奉开源精神,『眼睛足够多,bug 无处藏』。 -如果你觉得文字内容或者视频内容有错误,欢迎在 github 上提 issue 讨论,我会修正相关内容,防止产生误导。 +[issue](https://github.com/PegasusWang/python_data_structures_and_algorithms/issues?q=is%3Aissue+is%3Aclosed) + +[contributors](https://github.com/PegasusWang/python_data_structures_and_algorithms/graphs/contributors) + +## 如何提问? +如果读者关于代码、视频、讲义有任何疑问,欢迎一起讨论 +请注意以下几点: + +- 优先在网易云课堂的讨论区提问,方便别的同学浏览。如果未购买视频,也可以直接在 github 里提出 issue,笔者会有空会给大家解答。 +- 描述尽量具体,视频或者代码哪一部分有问题? +- 如果涉及到代码,提问时请保持代码的格式 ## 本电子书制作和写作方式 @@ -203,7 +244,6 @@ mkdocs serve # 修改自动更新,浏览器打开 http://localhost:8000 # 数学公式参考 https://www.zybuluo.com/codeep/note/163962 mkdocs gh-deploy # 部署到自己的 github pages ``` +扫码加入课程: -您的打赏就是我写作的最大动力,呵呵哒! - -![微信打赏](http://7ktuty.com1.z0.glb.clouddn.com/weixin_dashang.png) +![扫码加入课程返现30%](http://7ktuty.com1.z0.glb.clouddn.com/Screen%20Shot%202018-06-02%20at%2020.37.46.png) diff --git "a/docs/00_\350\257\276\347\250\213\347\256\200\344\273\213\344\271\213\347\254\250\346\226\271\346\263\225\345\255\246\347\256\227\346\263\225/why_and_how_to_learn.md" "b/docs/00_\350\257\276\347\250\213\347\256\200\344\273\213\344\271\213\347\254\250\346\226\271\346\263\225\345\255\246\347\256\227\346\263\225/why_and_how_to_learn.md" index 60f7bb0..96eb53c 100644 --- "a/docs/00_\350\257\276\347\250\213\347\256\200\344\273\213\344\271\213\347\254\250\346\226\271\346\263\225\345\255\246\347\256\227\346\263\225/why_and_how_to_learn.md" +++ "b/docs/00_\350\257\276\347\250\213\347\256\200\344\273\213\344\271\213\347\254\250\346\226\271\346\263\225\345\255\246\347\256\227\346\263\225/why_and_how_to_learn.md" @@ -4,7 +4,8 @@ 程序 = 算法 + 数据结构 -算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。 +算法(Algorithm):是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。 + 数据结构(Data Structures):是计算机存储和组织数据的一种方式,可以用来高效地处理数据。 举个例子:二分查找就是一个非常经典的算法,而二分查找经常需要作用在一个有序数组上。这里二分就是一种折半的算法思想, diff --git "a/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.md" "b/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.md" index b417c43..21b5d90 100644 --- "a/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.md" +++ "b/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.md" @@ -24,7 +24,7 @@ Python 的 array 是内存连续、存储的都是同一数据类型的结构, 操作 | 平均时间复杂度 | --------------------------------------|----------------| -list[index]| O(1) | +list[index] | O(1) | list.append | O(1) | list.insert | O(n) | list.pop(index), default last element | O(1) | @@ -48,3 +48,7 @@ list.remove | O(n) | [Python list implementation](https://www.laurentluce.com/posts/python-list-implementation/) [https://github.com/python/cpython/blob/master/Objects/listobject.c](https://github.com/python/cpython/blob/master/Objects/listobject.c) + + +# 勘误 +视频里的 Array.clear 方法有误。应该是 `for i in range(len(self._items))`,已经在后续所有使用到 Array 的代码里修正 diff --git "a/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.py" "b/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.py" index e88a1e4..11072a4 100644 --- "a/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.py" +++ "b/docs/02_\346\225\260\347\273\204\345\222\214\345\210\227\350\241\250/array_and_list.py" @@ -27,7 +27,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): diff --git "a/docs/03_\351\223\276\350\241\250/linked_list.md" "b/docs/03_\351\223\276\350\241\250/linked_list.md" index 5a65b2c..32813dd 100644 --- "a/docs/03_\351\223\276\350\241\250/linked_list.md" +++ "b/docs/03_\351\223\276\350\241\250/linked_list.md" @@ -13,6 +13,7 @@ # 单链表 和线性结构不同,链式结构内存不连续的,而是一个个串起来的,这个时候就需要每个链接表的节点保存一个指向下一个节点的指针。 +这里可不要混淆了列表和链表(它们的中文发音类似,但是列表 list 底层其实还是线性结构,链表才是真的通过指针关联的链式结构)。 看到指针你也不用怕,这里我们用的 python,你只需要一个简单赋值操作就能实现,不用担心 c 语言里复杂的指针。 先来定义一个链接表的节点,刚才说到有一个指针保存下一个节点的位置,我们叫它 next, 当然还需要一个 value 属性保存值 @@ -85,3 +86,7 @@ cdll.tailnode() | O(1) | # 相关阅读 [那些年,我们一起跪过的算法题- Lru cache[视频]](https://zhuanlan.zhihu.com/p/35175401) + +# 勘误: + +视频中 LinkedList.remove 方法讲解有遗漏, linked_list.py 文件已经修正,请读者注意。具体请参考 [fix linked_list & add gitigonre](https://github.com/PegasusWang/python_data_structures_and_algorithms/pull/3)。视频最后增加了一段勘误说明。 diff --git "a/docs/03_\351\223\276\350\241\250/linked_list.py" "b/docs/03_\351\223\276\350\241\250/linked_list.py" index b2c7ffc..f1ba137 100644 --- "a/docs/03_\351\223\276\350\241\250/linked_list.py" +++ "b/docs/03_\351\223\276\350\241\250/linked_list.py" @@ -31,7 +31,7 @@ def __len__(self): return self.length def append(self, value): # O(1) - if self.maxsize is not None and len(self) > self.maxsize: + if self.maxsize is not None and len(self) >= self.maxsize: raise Exception('LinkedList is Full') node = Node(value) # 构造节点 tailnode = self.tailnode @@ -43,6 +43,8 @@ def append(self, value): # O(1) self.length += 1 def appendleft(self, value): + if self.maxsize is not None and len(self) >= self.maxsize: + raise Exception('LinkedList is Full') headnode = self.root.next node = Node(value) self.root.next = node @@ -59,7 +61,8 @@ def iter_node(self): while curnode is not self.tailnode: # 从第一个节点开始遍历 yield curnode curnode = curnode.next # 移动到下一个节点 - yield curnode + if curnode is not None: + yield curnode def remove(self, value): # O(n) """ 删除包含值的一个节点,将其前一个节点的 next 指向被查询节点的下一个即可 @@ -67,13 +70,16 @@ def remove(self, value): # O(n) :param value: """ prevnode = self.root # - curnode = self.root.next for curnode in self.iter_node(): if curnode.value == value: prevnode.next = curnode.next + if curnode is self.tailnode: # NOTE: 注意更新 tailnode + self.tailnode = prevnode del curnode self.length -= 1 return 1 # 表明删除成功 + else: + prevnode = curnode return -1 # 表明删除失败 def find(self, value): # O(n) @@ -97,6 +103,9 @@ def popleft(self): # O(1) self.root.next = headnode.next self.length -= 1 value = headnode.value + + if self.tailnode is headnode: # 勘误:增加单节点删除 tailnode 处理 + self.tailnode = None del headnode return value @@ -105,6 +114,7 @@ def clear(self): del node self.root.next = None self.length = 0 + self.tailnode = None def test_linked_list(): @@ -113,29 +123,48 @@ def test_linked_list(): ll.append(0) ll.append(1) ll.append(2) + ll.append(3) - assert len(ll) == 3 + assert len(ll) == 4 assert ll.find(2) == 2 - assert ll.find(3) == -1 + assert ll.find(-1) == -1 assert ll.remove(0) == 1 - assert ll.remove(3) == -1 + assert ll.remove(10) == -1 + assert ll.remove(2) == 1 assert len(ll) == 2 + assert list(ll) == [1, 3] assert ll.find(0) == -1 - assert list(ll) == [1, 2] - ll.appendleft(0) - assert list(ll) == [0, 1, 2] + assert list(ll) == [0, 1, 3] assert len(ll) == 3 headvalue = ll.popleft() assert headvalue == 0 assert len(ll) == 2 - assert list(ll) == [1, 2] + assert list(ll) == [1, 3] + + assert ll.popleft() == 1 + assert list(ll) == [3] + ll.popleft() + assert len(ll) == 0 + assert ll.tailnode is None ll.clear() assert len(ll) == 0 + assert list(ll) == [] + + +def test_linked_list_remove(): + ll = LinkedList() + ll.append(3) + ll.append(4) + ll.append(5) + ll.append(6) + ll.append(7) + ll.remove(7) + print(list(ll)) if __name__ == '__main__': diff --git "a/docs/04_\351\230\237\345\210\227/array_queue.py" "b/docs/04_\351\230\237\345\210\227/array_queue.py" index 0b31925..4b45b94 100644 --- "a/docs/04_\351\230\237\345\210\227/array_queue.py" +++ "b/docs/04_\351\230\237\345\210\227/array_queue.py" @@ -18,7 +18,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): @@ -40,7 +40,7 @@ def __init__(self, maxsize): def push(self, value): if len(self) >= self.maxsize: raise FullError('queue full') - self.array[self.head] = value + self.array[self.head % self.maxsize] = value self.head += 1 def pop(self): @@ -49,7 +49,7 @@ def pop(self): return value def __len__(self): - return self.head-self.tail + return self.head - self.tail def test_queue(): @@ -68,10 +68,13 @@ def test_queue(): assert q.pop() == 0 assert q.pop() == 1 - assert len(q) == 3 + q.push(5) + + assert len(q) == 4 assert q.pop() == 2 assert q.pop() == 3 assert q.pop() == 4 + assert q.pop() == 5 assert len(q) == 0 diff --git "a/docs/04_\351\230\237\345\210\227/queue.py" "b/docs/04_\351\230\237\345\210\227/queue.py" index 5ca6e35..9e4c49f 100644 --- "a/docs/04_\351\230\237\345\210\227/queue.py" +++ "b/docs/04_\351\230\237\345\210\227/queue.py" @@ -69,12 +69,15 @@ def remove(self, value): # O(n) """ prevnode = self.root # curnode = self.root.next - while curnode.next is not None: + for curnode in self.iter_node(): if curnode.value == value: prevnode.next = curnode.next del curnode self.length -= 1 - return + return 1 # 表明删除成功 + else: + prevnode = curnode + return -1 # 表明删除失败 def find(self, value): # O(n) """ 查找一个节点,返回序号,从 0 开始 diff --git "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" index 7d8ab94..50a0ae7 100644 --- "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" +++ "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" @@ -129,7 +129,7 @@ size 2**3 the order of indices is: # 哈希函数 到这里你应该明白哈希表插入的工作原理了,不过有个重要的问题之前没提到,就是 hash 函数怎么选? 当然是散列得到的冲突越来越小就好啦,也就是说每个 key 都能尽量被等可能地散列到 m 个槽中的任何一个,并且与其他 key 被散列到哪个槽位无关。 -如果你感兴趣,可以阅读后边提到的一些参考资料。 +如果你感兴趣,可以阅读后边提到的一些参考资料。视频里我们使用二次探查函数,它相比线性探查得到的结果冲突会更少。 # 装载因子(load factor) @@ -147,7 +147,7 @@ GROWTH_RATE 这个关键字,你会发现不同版本的 cpython 使用了不 实践是检验真理的唯一标准,这里我们来实现一个简化版的哈希表 ADT,主要是为了让你更好地了解它的工作原理,有了它,后边实现起 dict 和 set 来就小菜一碟了。 这里我们使用到了定长数组,还记得我们在数组和列表章节里实现的 Array 吧,这里要用上了。 -解决冲突我们使用二次探查法。我们来实现三个哈希表最常用的基本操作,这实际上也是使用字典的时候最常用的操作。 +解决冲突我们使用二次探查法,模拟 cpython 二次探查函数的实现。我们来实现三个哈希表最常用的基本操作,这实际上也是使用字典的时候最常用的操作。 - add(key, value) - get(key, default) @@ -162,7 +162,7 @@ class Slot(object): 3.槽正在使用 Slot 节点 """ def __init__(self, key, value): - self.key, self.value = self.key, self.value + self.key, self.value = key, value class HashTable(object): pass @@ -176,5 +176,5 @@ class HashTable(object): # 延伸阅读 - 《Data Structures and Algorithms in Python》11 章 Hash Tables -- 《算法导论》第三版 11 章散列表 +- 《算法导论》第三版 11 章散列表,了解几种哈希冲突的解决方式,以及为什么我们选择二次探查而不是线性探查法? - 介绍 c 解释器如何实现的 python dict对象:[Python dictionary implementation](http://www.laurentluce.com/posts/python-dictionary-implementation/) diff --git "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" index 966e2c2..2904b06 100644 --- "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" +++ "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" @@ -19,7 +19,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): diff --git "a/docs/08_\345\255\227\345\205\270/dict_adt.py" "b/docs/08_\345\255\227\345\205\270/dict_adt.py" index fb35c51..55b392d 100644 --- "a/docs/08_\345\255\227\345\205\270/dict_adt.py" +++ "b/docs/08_\345\255\227\345\205\270/dict_adt.py" @@ -19,7 +19,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): diff --git "a/docs/09_\351\233\206\345\220\210/set_adt.py" "b/docs/09_\351\233\206\345\220\210/set_adt.py" index b8c6f41..3efbe78 100644 --- "a/docs/09_\351\233\206\345\220\210/set_adt.py" +++ "b/docs/09_\351\233\206\345\220\210/set_adt.py" @@ -19,7 +19,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): @@ -150,9 +150,6 @@ def __and__(self, other_set): for element_a in self: if element_a in other_set: new_set.add(element_a) - for element_b in other_set: - if element_b in self: - new_set.add(element_b) return new_set def __sub__(self, other_set): diff --git "a/docs/12_\345\237\272\346\234\254\346\216\222\345\272\217\347\256\227\346\263\225/basic_sort.md" "b/docs/12_\345\237\272\346\234\254\346\216\222\345\272\217\347\256\227\346\263\225/basic_sort.md" index 1028b05..9804502 100644 --- "a/docs/12_\345\237\272\346\234\254\346\216\222\345\272\217\347\256\227\346\263\225/basic_sort.md" +++ "b/docs/12_\345\237\272\346\234\254\346\216\222\345\272\217\347\256\227\346\263\225/basic_sort.md" @@ -3,7 +3,7 @@ # 冒泡排序 -bubble sort 可以说是最简单的一种排序算法了,它的思想如下。对一个数组进行 n 轮迭代,每次比较相邻两个元素, +bubble sort 可以说是最简单的一种排序算法了,它的思想如下。对一个数组进行 n-1 轮迭代,每次比较相邻两个元素, 如果相邻的元素前者大于后者,就交换它们。因为直接在元素上操作而不是返回新的数组,所以是一个 inplace 的操作。 这里冒泡的意思其实就是每一轮冒泡一个最大的元素就会通过不断比较和交换相邻元素使它转移到最右边。 diff --git "a/docs/14_\346\240\221\344\270\216\344\272\214\345\217\211\346\240\221/tree.md" "b/docs/14_\346\240\221\344\270\216\344\272\214\345\217\211\346\240\221/tree.md" index 1e3d792..6ea1cca 100644 --- "a/docs/14_\346\240\221\344\270\216\344\272\214\345\217\211\346\240\221/tree.md" +++ "b/docs/14_\346\240\221\344\270\216\344\272\214\345\217\211\346\240\221/tree.md" @@ -121,7 +121,7 @@ class BinTree(object): root = node node.left = node_dict.get(node_data['left']) node.right = node_dict.get(node_data['right']) - return cls(root) + return cls(root) btree = BinTree.build_from(node_list) ``` diff --git "a/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/heap_and_heapsort.py" "b/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/heap_and_heapsort.py" index c4fa201..aae9922 100644 --- "a/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/heap_and_heapsort.py" +++ "b/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/heap_and_heapsort.py" @@ -19,7 +19,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): diff --git "a/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" "b/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" index 1c8a2c6..039885c 100644 --- "a/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" +++ "b/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" @@ -19,7 +19,7 @@ def __len__(self): return self._size def clear(self, value=None): - for i in range(self._items): + for i in range(len(self._items)): self._items[i] = value def __iter__(self): diff --git "a/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/binary_search_tree.md" "b/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/binary_search_tree.md" index 307f187..d13d99e 100644 --- "a/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/binary_search_tree.md" +++ "b/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/binary_search_tree.md" @@ -185,7 +185,7 @@ bst = BST.build_from(NODE_LIST) - 找到待删除节点 N(12) 的后继节点 S(23) - 复制节点 S 到节点 N -- 删除节点 S +- 从 N 的右子树中删除节点 S,并更新其删除后继节点后的右子树 说白了就是找到后继并且替换,这里之所以能保证这种方法是正确的,你会发现替换后依旧是保持了 BST 的性质。 有个问题是如何找到后继节点呢?待删除节点的右子树的最小的节点不就是后继嘛,上边我们已经实现了找到最小 key 的方法了。 @@ -214,9 +214,9 @@ bst = BST.build_from(NODE_LIST) return subtree.left # 返回它的孩子并让它的父亲指过去 else: return subtree.right - else: # 俩孩子,寻找后继节点替换 + else: # 俩孩子,寻找后继节点替换,并从待删节点的右子树中删除后继节点 successor_node = self._bst_min_node(subtree.right) - subtree.key, subtree.value = successor_node.key, subtree.value + subtree.key, subtree.value = successor_node.key, successor_node.value subtree.right = self._bst_remove(subtree.right, successor_node.key) return subtree @@ -242,4 +242,4 @@ bst = BST.build_from(NODE_LIST) # 延伸阅读 - 《Data Structures and Algorithms in Python》14 章,树的概念和算法还有很多,我们这里介绍最基本的 -- 了解 mysql 索引使用的 Btree 结构 +- 了解 mysql 索引使用的 B-Tree 结构 diff --git "a/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/bst.py" "b/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/bst.py" index c65f44e..e1ceef3 100644 --- "a/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/bst.py" +++ "b/docs/17_\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/bst.py" @@ -104,9 +104,9 @@ def _bst_remove(self, subtree, key): return subtree.left # 返回它的孩子并让它的父亲指过去 else: return subtree.right - else: # 俩孩子,寻找后继节点替换 + else: # 俩孩子,寻找后继节点替换,并删除其右子树的后继节点,同时更新其右子树 successor_node = self._bst_min_node(subtree.right) - subtree.key, subtree.value = successor_node.key, subtree.value + subtree.key, subtree.value = successor_node.key, successor_node.value subtree.right = self._bst_remove(subtree.right, successor_node.key) return subtree