二叉树相关算法
-
二叉树的前中后序遍历:
前序遍历:根 左 右 中序遍历:左 根 右 后序遍历:左 右 根
递归写法(前序遍历)(空间复杂度与系统堆栈有关,系统栈需要记住每个节点的值,所以空间复杂度为O(n)。时间复杂度应该为O(n)):
var preorderTraversal = function(root) {
var res = [];
var preorderTraversalNode = (node)=>{
if(node == null){
return;
}
//通过节点的位置前中后序,前序即先输出中节点
res.push(node.val);
//遍历左子树
preorderTraversalNode(node.left);
//遍历右子树
preorderTraversalNode(node.right);
}
preorderTraversalNode(root);
return res;
};
用栈模拟递归写法,(由于每个节点都需要入栈和出栈,所以时间和空间复杂度都是O(n)):
前序遍历,因为栈先入后出,所以入栈顺序是先右后左:
var preorderTraversal = function(root) {
var res = [];
var stack = [];
stack.push(root);
if(!root){
return [];
}
while(stack.length > 0){
var node = stack.pop();
res.push(node.val);
if(node.right){
stack.push(node.right);
}
if(node.left){
stack.push(node.left);
}
}
return res;
};
中序遍历:
法1:非递归,颜色标记法(第一次进栈的节点是白色,第二次进栈的节点是灰色,当为灰色时输入值到res中):
注: 颜色遍历是一种很好的模板方法,如 计算二叉树最大深度
var inorderTraversal = function(root) {
// 颜色标记法
var res = [];
var stack =[];
if(!root)return [];
var rootObject = {
color: 'white',
node:root
};
stack.push(rootObject);
while(stack.length > 0 ){
var {node, color} = stack.pop();
if(color == 'gray'){
res.push(node.val);
}
else{
node.right && stack.push({color: 'white',node: node.right});
stack.push({color:'gray',node:node});
node.left && stack.push({color: 'white',node: node.left});
}
}
return res;
};
法2 : 无需使用颜色标记法,节省了空间,先一路向左下压如栈中,依次弹出,放入结果中,进入右子节点,继续将左边的压如栈中,(推荐第一种方法,因为这个方法有些特殊,无法形成模板方法):
var inorderTraversal = function(root) {
var res = [];
var stack = [];
while(root || stack.length > 0){
while(root){
//一路向左下,压入栈中
stack.push(root);
root = root.left;
}
root = stack.pop();
res.push(root.val);
// 依次检测是否有右节点,有的话就进入右子节点,然后继续一路向左下压如栈中。
root = root.right;
}
return res;
};
后序遍历:
var postorderTraversal = function(root) {
var res = [];
var stack = [];
if(root) stack.push(root);
while(stack.length > 0){
// 前序遍历是按照 中左右 打印,那么使用unshift即可逆前序打印, 即: 右左 中
// 再将前序遍历的左右节点入栈顺序交换,即完成后序遍历: 左 右 中
var node = stack.pop();
res.unshift(node.val);
if(node.left){
stack.push(node.left);
}
if(node.right){
stack.push(node.right);
}
}
return res;
};
- 广度遍历和深度遍历:
深度遍历: 深度遍历上述遍历,包括前序、中序、后序遍历:
使用分治法中序遍历:
var inorderTraversal = function(root) {
// 分治法
return mergeSort(root);
};
function mergeSort(node){
if(!node){
return [];
}
var res = [];
var left = mergeSort(node.left);
var right = mergeSort(node.right);
res = res.concat(left);
res = res.concat(node.val);
res = res.concat(right);
return res;
}
分治法步骤:
1.写递归返回条件
2.分段处理
3.合并
分治法同样可以用于归并排序:
//归并排序
var sortArray = function(nums) {
return mergeSort(nums);
};
function mergeSort(nums){
if(nums.length < 2){
//注意判断条件
return nums;
}
var mid = Math.floor(nums.length / 2);
var left = nums.slice(0,mid);
var right = nums.slice(mid);
return merge(mergeSort(left),mergeSort(right));
}
function merge(l, r){
//注意返回
var res = [];
while(l.length>0 && r.length>0){
if(l[0] <= r[0]){
res.push(l.shift());
}
else{
res.push(r.shift());
}
}
while(l.length>0){
res.push(l.shift());
}
while(r.length>0){
res.push(r.shift());
}
return res;
}
广度遍历/层序遍历:
var levelOrder = function(root) {
var res = [];
var queue = [];
var stack = [];
if(!root) return [];
stack.push(root);
// 通过人为植入一个标记,来判断某一层是否遍历完成。
stack.push('s');
while(stack.length > 0){
var node = stack.shift();
while(node != 's'){
node.left && stack.push(node.left);
node.right && stack.push(node.right);
queue.push(node.val);
node = stack.shift();
}
res.push(queue);
queue = [];
if(stack.length > 0){
stack.push('s');
}
}
return res;
};