boutique-books/b00-阅读笔记/学习js数据结构与算法/学习js数据结构与算法.md
linghuam 78b2cfce3a m
2019-05-21 20:29:27 +08:00

417 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 学习js数据结构与算法
## 栈
### 十进制转二进制
除2取余直到整数为0反向输出余数。
```js
function divideBy2(num) {
var stack = [],
result = '';
while(num > 0) {
stack.push(Math.floor(num % 2));
num = Math.floor(num / 2);
}
while(stack.length) {
result += stack.pop().toString();
}
return result;
}
```
### 十进制转任意进制
```js
function baseConverter(decNumber, base) {
var stack = [],
digits = '0123456789ABCDEF',
result = '';
while(decNumber > 0) {
stack.push(Math.floor(decNumber % base));
decNumber = Math.floor(decNumber / base);
}
while(stack.length) {
result += digits[stack.pop()];
}
return result;
}
```
## 队列
循环队列
## 链表
* 单链表
* 双向链表
* 循环链表
## 集合
* 并集
* 交集
* 差集
* 子集
## 字典和散列表HashMap
### 散列表
#### 选择散列函数
一个表现良好的散列函数是由几个方面构成的:插入和检索元素的时间(即性能),当然也包括较低的冲突可能性。
也有一些为数字键值准备的散列函数,你可以在![http://goo.gl/VtdN2x](http://goo.gl/VtdN2x)找到一
系列的实现。
#### 处理散列值冲突
处理冲突有几种方法:分离链接、线性探查和双散列法。
* **分离链接法** 包括为散列表的每一个位置创建一个链表并将元素存储在里面。
* **线性探查** 当想向表中某个位置加入一个新元素的时候,如果索引
为index的位置已经被占据了就尝试index+1的位置。如果index+1的位置也被占据了就尝试
index+2的位置以此类推。
```text
在一些编程语言中,我们需要定义数组的大小。如果使用线性探查的话,需
要注意的一个问题是数组的可用位置可能会被用完。在JavaScript中我们不需
要担心这个问题,因为我们不需要定义数组的大小,它可以根据需要自动改变大
小——这是JavaScript内置的一个功能。
```
* **双散列法** 即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
## 树
### 基本术语
**结点:** 包含一个数据元素及若干指向子树的指针。
**结点的度(Degree)** 结点拥有的子树数。
**叶子(Leaf)(终端)结点:** 度为零的结点。
**分支(非终端)结点:** 度大于零的结点。
**树的度:** 树内各结点度的最大值。
**孩子(Child)** 结点的子树的根称为该结点的孩子。
**双亲(Parent)** 该结点称为孩子的双亲。
**兄弟(Sibling)** 同一双亲的孩子之间互称为兄弟。
**祖先:** 从根到该结点所经分支上的所有结点。
**子孙:** 以某结点为根的子树中的任一结点都称为该点的子孙。
**层次:** 从根开始定义,根为第一层,根的孩子为第二层,以此类推。
**堂兄弟:** 双亲在同一层的结点互为堂兄弟。
**深度(Depth)** 树中结点的最大层次称为树的深度或高度。
**有序树 & 无序树:** 如果将树中结点的各子树看成从左至右是有序的,则称该树为有序树,否则为无序树。
**森林(Forest)** m(m>=0)棵互不相交的树的集合。
**二叉树(BinaryTree)** 每个结点至多只有两棵子树且左右有序。
**二叉搜索树(BST)** 左边存储比父节点小,右边存储比父节点大的二叉树。
### 树的创建BST
``` js
function BST() {
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
this.parent = null;
}
this.root = null;
this.addNode = function(value) {
var node = new Node(value);
if (this.root == null) {
this.root = node;
} else {
var currentNode = this.root;
var isContinue = true;
while(isContinue) {
if (value < currentNode.value) {
if (currentNode.left) {
currentNode = currentNode.left;
} else {
currentNode.left = node;
isContinue = false;
}
} else if (value > currentNode.value) {
if (currentNode.right) {
currentNode = currentNode.right;
} else {
currentNode.right = node;
isContinue = false;
}
}
}
}
}
}
// 递归写法
function insertNode(root, newNode) {
if (root === null) {
root = newNode;
} else {
if (newNode.value < root.value) {
if (root.left === null) {
root.left = newNode;
} else {
insertNode(root.left, newNode);
}
} else {
if (root.right === null) {
root.right = newNode;
} else {
insertNode(root.right, newNode);
}
}
}
}
```
### 树的遍历
#### 中序遍历(左根右)
```js
// 递归写法
function traveseTree(node, callback) {
if (node !== null) {
traveseTree(node.left, callback);
callback(node);
traveseTree(node.right, callback);
}
}
// 非递归写法(借助栈)
function traveseTree(root, callback) {
var stack = [];
var p = root;
if (root == null)
return;
while(stack.length || p) {
// 第一步:遍历左子树,根节点入栈(为了后面根据根节点找到右子树)
while(p) {
stack.push(p);
p = p.left;
}
// 第二步出栈p指向栈顶元素取p的右子树重复以上过程直到栈为空且p为空
callback(p = stack.pop());
p = p.right;
}
}
```
#### 先序遍历(根左右)
```js
// 递归写法
function traveseTree(root, callback) {
if (root != null) {
callback(root);
traveseTree(root.left);
traveseTree(root.right);
}
}
// 非递归写法(借助栈)
function traveseTree(root, callback) {
var p = root,
stack = [];
if (root == null) return;
while(stack.length || p) {
// 第一步:先遍历左子树,边遍历边打印,并将根节点存入栈中,以后借助跟节点进入右子树开启新一轮遍历
while(p) {
callback(p);
stack.push(p);
p = p.left;
}
p = stack.pop();
p = p.right;
}
}
```
#### 后序遍历(左右根)
```js
// 递归写法
function traveseTree(root, callback) {
if (root != null) {
traveseTree(root.left);
traveseTree(root.right);
callback(root);
}
}
// 非递归写法(借助栈)
// 后序遍历的难点在于:需要判断上次访问的节点是位于左子树,还是右子树。若是位于左子树,则需跳过根节点,先进入右子树,再回头访问根节点;若是位于右子树,则直接访问根节点。
function traveseTree(root, callback) {
var pCur = root, pLast = null, stack = [];
if (root == null) return;
// 先把pCur移到左子树最下边
while(pCur) {
stack.push(pCur);
pCur = pCur.left;
}
while(stack.length) {
pCur = stack.pop();
//一个根节点被访问的前提是:无右子树或右子树已被访问过
if (pCur.right == null || pCur.right == pLast) {
callback(pCur);
pLast = pCur;
}
/*这里的else语句可换成带条件的else if:
else if (pCur->lchild == pLastVisit)//若左子树刚被访问过,则需先进入右子树(根节点需再次入栈)
因为:上面的条件没通过就一定是下面的条件满足。仔细想想!
*/
else {
// 根节点再次入栈
stack.push(pCur);
// 进入右子树,且可肯定右子树一定不为空
pCur = pCur.right;
while(pCur) {
stack.push(pCur);
pCur = pCur.left;
}
}
}
}
```
#### 层次遍历(利用队列实现)
1. 根节点入队
2. 出队
3. 如果有左孩子左孩子入队如果有右孩子右孩子入队
4. 重复步骤23直到队列为空
```js
function traveseTree(root, callback) {
var queue = [];
if (root == null) return null;
queue.push(root);
while(queue.length) {
var frontNode = queue.shift();
callback(frontNode);
if (frontNode.left) queue.push(frontNode.left);
if (frontNode.right) queue.push(frontNode.right);
}
}
```
### 树的查找
* 最小值左子树最下边
* 最大值右子树最下边
* 特定值先序遍历
### 树的删除
```js
// 删除值为value的节点
function removeNode(node, value) {
if (node == null) return null;
if (value < node.value) {
node.left = removeNode(node.left, value);
return node;
} else if (value > node.value) {
node.right = removeNode(node.right, value);
return node;
} else {
//情况1节点为叶节点有零个子节点的节点
if(node.left == null && node.right == null) {
node = null;
return node;
}
//情况2只有一个子节点的节点
if (node.left == null) {
node = node.right;
return node;
} else if(node.right == null) {
node = node.left;
return node;
}
//情况3有两个子节点的节点
// 先找到右边子树节点的最小值节点
// 再用最小值节点的值更新当前节点的值
// 最后删除右边子树最小值节点
var findMinNode = function(node) {
if (node) {
while(node && node.left !== null) {
node = node.left;
}
return node;
}
return null;
}
var aux = findMinNode(node.right);
node.value = aux.value;
node.right = removeNode(node.right, aux.value);
return node;
}
}
```
### 其他扩展
* 红黑树
* AVL平衡二叉搜索树
## 图
### 图的表示
* 邻接矩阵顶点用数组索引表示`a[i][j] = 1`来表示边缺点是浪费一些空间
* 邻接表每个顶点的相邻顶点列表组成
* 关联矩阵行表示顶点列表示边`a[i][j] = 1`表示边j的入射顶点为i
```js
function Graph() {
this.vertices = [];
this.vertexMap = new Map();
this.adjList = new Map();
this.addVertex = function(v) {
return this.vertexMap.has(v.id) ? null : (this.vertexMap.set(v.id, v),this.vertices.push(v),this.adjList.set(v.id, new Set()), v);
};
this.addEdge = function(sourceId, targetId) {
if (this.vertexMap.has(sourceId) && this.vertexMap.has(targetId)) {
this.adjList.get(sourceId).add(this.vertexMap.get(targetId));
this.adjList.get(targetId).add(this.vertexMap.get(sourceId));
}
return this;
};
this.toString = function() {
this.adjList.forEach((value, key) => {
console.log(key + ':' + Array.from(value).map(e => e.id).join(',') + '\n');
});
};
}
```
### 图的遍历
* 广度优先BFS****实现
* 深度优先DFS**队列**实现
## 排序和搜索算法
## 算法补充知识
## 时间复杂度速查表