Skip to content

Commit dac4683

Browse files
author
YuChengKai
committed
2 parents d6f4784 + 8dc896e commit dac4683

File tree

2 files changed

+186
-17
lines changed

2 files changed

+186
-17
lines changed

DataStruct/dataStruct-zh.md

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
2+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
3+
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4+
5+
- [](#%E6%A0%88)
6+
- [原理](#%E5%8E%9F%E7%90%86)
7+
- [实现](#%E5%AE%9E%E7%8E%B0)
8+
- [应用](#%E5%BA%94%E7%94%A8)
9+
- [队列](#%E9%98%9F%E5%88%97)
10+
- [原理](#%E5%8E%9F%E7%90%86-1)
11+
- [实现](#%E5%AE%9E%E7%8E%B0-1)
12+
- [单链队列](#%E5%8D%95%E9%93%BE%E9%98%9F%E5%88%97)
13+
- [循环队列](#%E5%BE%AA%E7%8E%AF%E9%98%9F%E5%88%97)
14+
- [链表](#%E9%93%BE%E8%A1%A8)
15+
- [原理](#%E5%8E%9F%E7%90%86-2)
16+
- [实现](#%E5%AE%9E%E7%8E%B0-2)
17+
- [](#%E6%A0%91)
18+
- [二叉树](#%E4%BA%8C%E5%8F%89%E6%A0%91)
19+
- [二分搜索树](#%E4%BA%8C%E5%88%86%E6%90%9C%E7%B4%A2%E6%A0%91)
20+
- [实现](#%E5%AE%9E%E7%8E%B0-3)
21+
22+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
23+
124
#
225

326
## 原理
@@ -313,9 +336,11 @@ class BST {
313336

314337
对于树的遍历来说,有三种遍历方法,分别是先序遍历、中序遍历、后序遍历。三种遍历的区别在于何时访问节点。在遍历树的过程中,每个节点都会遍历三次,分别是遍历到自己,遍历左子树和遍历右子树。如果需要实现先序遍历,那么只需要第一次遍历到节点时进行操作即可。
315338

339+
以下都是递归实现,如果你想学习非递归实现,可以 [点击这里阅读](../Algorithm/algorithm-ch.md#%E9%9D%9E%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0)
340+
316341
```js
317342
// 先序遍历可用于打印树的结构
318-
// 先序遍历表示先访问根节点,然后访问左节点,最后访问右节点。
343+
// 先序遍历先访问根节点,然后访问左节点,最后访问右节点。
319344
preTraversal() {
320345
this._pre(this.root)
321346
}
@@ -361,8 +386,13 @@ _back(node) {
361386
breadthTraversal() {
362387
if (!this.root) return null
363388
let q = new Queue()
389+
// 将根节点入队
364390
q.enQueue(this.root)
391+
// 循环判断队列是否为空,为空
392+
// 代表树遍历完毕
365393
while (!q.isEmpty()) {
394+
// 将队首出队,判断是否有左右子树
395+
// 有的话,就先左后右入队
366396
let n = q.deQueue()
367397
console.log(n.value)
368398
if (n.left) q.enQueue(n.left)
@@ -390,3 +420,140 @@ _getMax(node) {
390420
}
391421
```
392422

423+
**向上取整和向下取整**,这两个操作是相反的,所以代码也是类似的,这里只介绍如何向下取整。既然是向下取整,那么根据二分搜索树的特性,值一定在根节点的左侧。只需要一直遍历左子树直到当前节点的值不再大于等于需要的值,然后判断节点是否还拥有右子树。如果有的话,继续上面的递归判断。
424+
425+
```js
426+
floor(v) {
427+
let node = this._floor(this.root, v)
428+
return node ? node.value : null
429+
}
430+
_floor(node, v) {
431+
if (!node) return null
432+
if (node.value === v) return v
433+
// 如果当前节点值还比需要的值大,就继续递归
434+
if (node.value > v) {
435+
return this._floor(node.left, v)
436+
}
437+
// 判断当前节点是否拥有右子树
438+
let right = this._floor(node.right, v)
439+
if (right) return right
440+
return node
441+
}
442+
```
443+
444+
**排名**,这是用于获取给定值的排名或者排名第几的节点的值,这两个操作也是相反的,所以这个只介绍如何获取排名第几的节点的值。对于这个操作而言,我们需要略微的改造点代码,让每个节点拥有一个 `size` 属性。该属性表示该节点下有多少子节点(包含自身)。
445+
446+
```js
447+
class Node {
448+
constructor(value) {
449+
this.value = value
450+
this.left = null
451+
this.right = null
452+
// 修改代码
453+
this.size = 1
454+
}
455+
}
456+
// 新增代码
457+
_getSize(node) {
458+
return node ? node.size : 0
459+
}
460+
_addChild(node, v) {
461+
if (!node) {
462+
return new Node(v)
463+
}
464+
if (node.value > v) {
465+
// 修改代码
466+
node.size++
467+
node.left = this._addChild(node.left, v)
468+
} else if (node.value < v) {
469+
// 修改代码
470+
node.size++
471+
node.right = this._addChild(node.right, v)
472+
}
473+
return node
474+
}
475+
select(k) {
476+
let node = this._select(this.root, k)
477+
return node ? node.value : null
478+
}
479+
_select(node, k) {
480+
if (!node) return null
481+
// 先获取左子树下有几个节点
482+
let size = node.left ? node.left.size : 0
483+
// 判断 size 是否大于 k
484+
// 如果大于 k,代表所需要的节点在左节点
485+
if (size > k) return this._select(node.left, k)
486+
// 如果小于 k,代表所需要的节点在右节点
487+
// 注意这里需要重新计算 k,减去根节点除了右子树的节点数量
488+
if (size < k) return this._select(node.right, k - size - 1)
489+
return node
490+
}
491+
```
492+
493+
接下来讲解的是二分搜索树中最难实现的部分:删除节点。因为对于删除节点来说,会存在以下几种情况
494+
495+
- 需要删除的节点没有子树
496+
- 需要删除的节点只有一条子树
497+
- 需要删除的节点有左右两条树
498+
499+
对于前两种情况很好解决,但是第三种情况就有难度了,所以先来实现相对简单的操作:删除最小节点,对于删除最小节点来说,是不存在第三种情况的,删除最大节点操作是和删除最小节点相反的,所以这里也就不再赘述。
500+
501+
```js
502+
delectMin() {
503+
this.root = this._delectMin(this.root)
504+
console.log(this.root)
505+
}
506+
_delectMin(node) {
507+
// 一直递归左子树
508+
// 如果左子树为空,就判断节点是否拥有右子树
509+
// 有右子树的话就把需要删除的节点替换为右子树
510+
if ((node != null) & !node.left) return node.right
511+
node.left = this._delectMin(node.left)
512+
// 最后需要重新维护下节点的 `size`
513+
node.size = this._getSize(node.left) + this._getSize(node.right) + 1
514+
return node
515+
}
516+
```
517+
518+
最后讲解的就是如何删除任意节点了。对于这个操作,T.Hibbard 在 1962 年提出了解决这个难题的办法,也就是如何解决第三种情况。
519+
520+
当遇到这种情况时,需要取出当前节点的后继节点(也就是当前节点右子树的最小节点)来替换需要删除的节点。然后将需要删除节点的左子树赋值给后继结点,右子树删除后继结点后赋值给他。
521+
522+
你如果对于这个解决办法有疑问的话,可以这样考虑。因为二分搜索树的特性,父节点一定比所有左子节点大,比所有右子节点小。那么当需要删除父节点时,势必需要拿出一个比父节点大的节点来替换父节点。这个节点肯定不存在于左子树,必然存在于右子树。然后又需要保持父节点都是比右子节点小的,那么就可以取出右子树中最小的那个节点来替换父节点。
523+
524+
```js
525+
delect(v) {
526+
this.root = this._delect(this.root, v)
527+
}
528+
_delect(node, v) {
529+
if (!node) return null
530+
// 寻找的节点比当前节点小,去左子树找
531+
if (node.value < v) {
532+
node.right = this._delect(node.right, v)
533+
} else if (node.value > v) {
534+
// 寻找的节点比当前节点大,去右子树找
535+
node.left = this._delect(node.left, v)
536+
} else {
537+
// 进入这个条件说明已经找到节点
538+
// 先判断节点是否拥有拥有左右子树中的一个
539+
// 是的话,将子树返回出去,这里和 `_delectMin` 的操作一样
540+
if (!node.left) return node.right
541+
if (!node.right) return node.left
542+
// 进入这里,代表节点拥有左右子树
543+
// 先取出当前节点的后继结点,也就是取当前节点右子树的最小值
544+
let min = this._getMin(node.right)
545+
// 取出最小值后,删除最小值
546+
// 然后把删除节点后的子树赋值给最小值节点
547+
min.right = this._delectMin(node.right)
548+
// 左子树不动
549+
min.left = node.left
550+
node = min
551+
}
552+
// 维护 size
553+
node.size = this._getSize(node.left) + this._getSize(node.right) + 1
554+
return node
555+
}
556+
```
557+
558+
559+

JS/JS-en.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
33
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
44

5-
- [Built-in Types](#built-in-types)
6-
- [Typeof](#typeof)
7-
- [New](#new)
8-
- [This](#this)
9-
- [instanceof](#instanceof)
10-
- [scope](#scope)
11-
- [Closure](#closure)
12-
- [Prototypes](#prototypes)
13-
- [inheritance](#inheritance)
14-
- [Deep and Shallow Copy](#deep-and-shallow-copy)
15-
- [shallow copy](#shallow-copy)
16-
- [deep copy](#deep-copy)
17-
- [the differences between call, apply, bind](#the-differences-between-call-apply-bind)
18-
- [simulation to implement `call` and `apply`](#simulation-to-implement---call-and--apply)
19-
- [Promise implementation](#promise-implementation)
20-
- [Throttle](#throttle)
5+
- [Built-in Types](#built-in-types)
6+
- [Typeof](#typeof)
7+
- [New](#new)
8+
- [This](#this)
9+
- [instanceof](#instanceof)
10+
- [scope](#scope)
11+
- [Closure](#closure)
12+
- [Prototypes](#prototypes)
13+
- [inheritance](#inheritance)
14+
- [Deep and Shallow Copy](#deep-and-shallow-copy)
15+
- [shallow copy](#shallow-copy)
16+
- [deep copy](#deep-copy)
17+
- [the differences between call, apply, bind](#the-differences-between-call-apply-bind)
18+
- [simulation to implement `call` and `apply`](#simulation-to-implement---call-and--apply)
19+
- [Promise implementation](#promise-implementation)
20+
- [Throttle](#throttle)
21+
- [Map、FlapMap and Reduce](#mapflapmap-and-reduce)
22+
- [async and await](#async-and-await)
2123

2224
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
2325

0 commit comments

Comments
 (0)