Skip to content

Commit

Permalink
feat: 增加非递归遍历
Browse files Browse the repository at this point in the history
  • Loading branch information
yangjin committed Dec 25, 2019
1 parent c5bb710 commit 7abc7b3
Showing 1 changed file with 186 additions and 92 deletions.
278 changes: 186 additions & 92 deletions docs/algo/二叉树/二叉查找树.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@

这里有两种处理办法:

1. 把值相同的数据都存储在同一个节点上
**1. 把值相同的数据都存储在同一个节点上**

每个节点不仅会存储一个数据,因此可以通过链表和支持动态扩容的数组等数据结构,把值相同的数据都存储在同一个节点上。

2. 存储在不同节点
**2. 存储在不同节点**

插入的时候,如果碰到一个节点的值与要插入的数据值相同,**就将这个要插入的数据放到这个节点的右子树中,也就是把新插入的数据当作大于这个节点的值来处理**

Expand All @@ -69,11 +69,7 @@ class Node {
}
}

/**
* 二叉查找树
* 支持重复数据
*/
class BinarySearchTree {
class binarySearchTree {
constructor() {
this.root = null;
}
Expand All @@ -84,19 +80,22 @@ class BinarySearchTree {
this.root = node;
return;
}
let current = this.root;

let currentNode = this.root;
let parent;
while (true) {
parent = current;
if (val < current.value) {
current = current.left;
if (current === null) {
parent = currentNode;
if (val < currentNode.value) {
currentNode = currentNode.left;
// 并且左子树为空
if (currentNode === null) {
parent.left = node;
break;
}
} else {
current = current.right;
if (current === null) {
currentNode = currentNode.right;
// 并且右子树为空
if (currentNode === null) {
parent.right = node;
break;
}
Expand All @@ -105,103 +104,198 @@ class BinarySearchTree {
}

find(val) {
let current = this.root;
if (current === null) {
return null;
}
let res = [];
while (current != null) {
if (current.value === val) {
res.push(current.value);
current = current.right;
} else if (val < current.value) {
current = current.left;
let currentNode = this.root;

while (currentNode !== null) {
const { value } = currentNode;
if (value === val) {
return currentNode;
} else if (val < value) {
currentNode = currentNode.left
} else {
current = current.right;
currentNode = currentNode.right;
}
}
return res.length === 0
? null
: res.length === 1
? res[0]
: res;

return null;
}
// TODO: 可删除多个相同的节点
remove(val) {
const removeNode = (node, val) => {
if (node === null) return null;
if (val === node.value) {
// 没有子节点
if (node.left === null && node.right === null) {
return null;
}
// 没有左子节点
if (node.left === null) {
return node.right;
}
// 没有右子节点
if (node.right === null) {
return node.left;
}
// 有两个子节点
let smallestRightNode = this.getSmallest(node.right);
node.value = smallestRightNode.value;
node.right = removeNode(node.right, smallestRightNode.value);
if ()
return node;
} else if (val < node.value) {
node.left = removeNode(node.left, val);
return node;
} else {
node.right = removeNode(node.right, val)
return node;

delete(val) {
var p = this.root; // 指向要删除的节点,初始化指向根节点
var pp = null; // 记录 p 的父节点
while (p !== null && p.value !== val) {
pp = p;
if (val > p.value) p = p.right;
else p = p.left;
}
if (p === null) return; // 没有找到

// 要删除的节点有两个子节点
if (p.left !== null && p.right !== null) {
// 查找右子树中最小节点
var minP = p.right;
var minPP = p;
while (minP.left !== null) {
minPP = minP;
minP = minP.left;
}
// 将minP的数据替换到p中
p.value = minPP.value;
// 下面就变成了删除minP了
p = minP;
pp = minPP
}
this.root = removeNode(this.root, val);

// 删除的节点是叶子节点或者仅有一个子节点
var child; // p 的子节点
if (p.left !== null) child = p.left;
else if (p.right !== null) child = p.right;
else child = null;

if (pp === null) this.root = child; // 删除的是根节点,且根节点没有有两个子节点
else if (pp.left === p) pp.left = child;
else pp.right = child;
}
getSmallest(node) {
if (node.left === null) {
return node;
} else {
return this.getSmallest(node.left);

findMin() {
if (this.root === null) return null;
var currentNode = this.root;
while (currentNode.left !== null) {
currentNode = currentNode.left;
}
return currentNode;
}
getMin() {
let current = this.root;
while (current.left !== null) {
current = current.left;

findMax() {
if (this.root === null) return null;
var currentNode = this.root;
while (currentNode.right !== null) {
currentNode = currentNode.right;
}
return current.value;
return currentNode;
}
getMax() {
let current = this.root;
while (current.right !== null) {
current = current.right;

preOder() {
function print(node) {
if (node !== null) {
console.log(node.value);
print(node.left);
print(node.right);
}
}
return current.value;
print(this.root);
}
preOrder(node) {
if (node !== null) {
console.log(node.value);
this.preOrder(node.left);
this.preOrder(node.right);

inOrder() {
function print(node) {
if (node !== null) {
print(node.left);
console.log(node.value);
print(node.right);
}
}
print(this.root);
}
inOrder(node) {
if (node !== null) {
this.inOrder(node.left);
console.log(node.value);
this.inOrder(node.right);

postOrder() {
function print(node) {
if (node !== null) {
print(node.left);
print(node.right);
console.log(node.value);
}
}
print(this.root);
}
postOrder(node) {
if (node !== null) {
this.inOrder(node.left);
console.log(node.value);
this.inOrder(node.right);
// 非递归前序遍历
preOder1() {
if (this.root === null) return
var res = [];
var stack = [];
stack.push(this.root);
while (stack.length) {
let node = stack.pop();
res.push(node.value);
// 这里先放右边再放左边是因为取出来的顺序相反
if (node.right !== null) stack.push(node.right);
if (node.left !== null) stack.push(node.left);
}
return res;
}

// 非递归中序遍历
inOrder1() {
if (this.root === null) return;
var stack = [];
var res = [];
var node = this.root; // !!!
while (true) {
// 先把左边的全部放进栈中
while (node !== null) {
stack.push(node);
node = node.left;
}
if (stack.length === 0) break;
var temp = stack.pop();
res.push(temp.value);
// 处理右边
node = temp.right;
}
return res;
}

// 非递归后序遍历
postOrder1() {
if (this.root === null) return;
var res = [];
var stack = [];
stack.push(this.root);
while (stack.length) {
var node = stack.pop();
res.push(node.value);
if (node.left !== null) stack.push(node.left)
if (node.right !== null) stack.push(node.right)
}
return res.reverse();
}

// 按层遍历
levelOrder() {
if (this.root === null) return;
var res = [];
var arr = [];
arr.push(this.root);
while (arr.length) {
let node = arr.shift();
res.push(node.value);
if (node.left) arr.push(node.left);
if (node.right) arr.push(node.right);
}
return res;
}
}

function test() {
let searchTree = new binarySearchTree();
console.log('add: 4 1 2 5 ')
searchTree.insert(4)
searchTree.insert(1)
searchTree.insert(2)
searchTree.insert(5)

searchTree.preOder()
console.log(searchTree.preOder1())

searchTree.inOrder()
console.log(searchTree.inOrder1())

searchTree.postOrder()
console.log(searchTree.postOrder1())

console.log(searchTree.levelOrder())
}

test();
```

## 二叉树的时间复杂度分析
Expand Down Expand Up @@ -248,7 +342,7 @@ log2(n + 1) <= L <= log2n + 1
主要有这几方面的原因:

1. 散列表的数据是无序的,要想输出有序的数据,需要先进行排列。而对于二叉查找树来说,通过中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。
2. 散列表扩容耗时比较多,而且遇到散列冲突时,性能不稳定。尽管二叉查找树的性能也不稳定,但是工程中,最常用的平衡二叉查找树**性能非常稳定,时间复杂度稳定在 O(logn)。
2. 散列表扩容耗时比较多,而且遇到散列冲突时,性能不稳定。尽管二叉查找树的性能也不稳定,但是工程中,**最常用的平衡二叉查找树性能非常稳定,时间复杂度稳定在 O(logn)**
3. 笼统的说,尽管散列表的查找等操作都是常量级的,但是因为哈希冲突,这个常量不一定比 logn 小。所以实际的查找速度可能不一定比 O(logn) 快,加上哈希函数的耗时,也不一定比平衡二叉查找树效率高。
4. 散列表的构造比二叉查找树要复杂,考虑的东西很多。比如散列函数设计、冲突解决、扩容、缩容等。而平衡二叉树查找树只考虑平衡性这一个问题,而且这个问题的方案比较成熟、固定。
5. 散列表为了避免过多的散列冲突,散列表转载因子不能太大,特别是基于开放寻址法解决冲突的散列表,不然会浪费一定的存储空间。
Expand Down

0 comments on commit 7abc7b3

Please sign in to comment.