# 手写冒泡算法

function bubbleSort(arr) {
  const len = arr.length;
  for (let i = 0; i < len; i++) {
    // 每次排序一轮,就排序好一个元素
    for(let j = 0; j < len - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        const temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
}

# 根据 value 获取叶子节点

/**
 * 获取树节点
 * @param {Array} tree 树结构
 * @param {*} value 要找的节点值
 * @param {String} keyName 要找的节点键名
 * @param {String} childrenKey 当前节点中子节点键名
 */
export function getTreeNode(tree, value, keyName, childrenKey) {
  const stack = [...tree];
  let tempNode;

  while (stack.length) {
    tempNode = stack.pop();
    if (tempNode[keyName] === value) {
      return tempNode
    }
    if (tempNode[childrenKey] && tempNode[childrenKey].length) {
      stack.push(...tempNode[keyName]);
    }
  }

  return null;
}

# 根据 value 获取叶子节点的路径

/**
 * 根据 value 获取叶子节点的路径
 * @param {Array} tree
 * @param {*} value
 * @param {String} keyName
 * @param {String} childrenKey
 */
export function getTreeKeyPath(tree, value, keyName, childrenKey = 'children') {
  const path = [];

  const parse = (subTree) => {
    for (let i = 0; i < subTree.length; i += 1) {
      const node = subTree[i];
      path.push(node[keyName]);
      if (node[keyName] === value) {
        return true;
      }
      if (node[childrenKey] && node[childrenKey].length) {
        if (parse(node[childrenKey])) {
          return true;
        }
      }
      path.pop();
    }
    return false;
  };

  parse(tree);

  return path;
}

# 列表结构转树结构

# 递归

使用该方法虽然易懂,但是当 source 列表比较大的时候性能会比较差。

export function listToTree(source, pid = 0) {
  const list = JSON.parse(JSON.stringify(source)); // 避免影响之前的数组
  const result = [];

  list.forEach(item => {
    if (item.parentId === pid) {
      item.children = listToTree(source, item.id);
      result.push(item);
    }
  });

  return result;
}

# 循环

该方法比上面的递归性能更好。

export function listToTree(source, rootId = 0) {
  const list = JSON.parse(JSON.stringify(source)); // 避免影响之前的数组
  const map = new Map();
  const result = [];

  list.forEach((item) => {
    // 如果父节点在后面出现,需要合并 children 属性
    item = { ...item, ...map.get(item.id) };
    map.set(item.id, item);

    if (item.id === rootId) {
      result.push(item);
    } else {
      const parentItem = map.get(item.parentId) || {};
      if (!parentItem.children) {
        parentItem.children = [];
      }
      parentItem.children.push(item);
      map.set(item.parentId, parentItem);
    }
  });
  
  return result;
}

# 实现洗牌算法

// 返回最小与最大值之间的随机数,包含最大最小值
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

//将数组的副本循环随机打乱重组一个新数组返回
//实现方法: 在[0~i]数组中,取出i的值,将他的值与随机一个小于i索引的值对换
export function shuffle(arr) {
  let _arr = arr.slice();

  for (let i = 0; i < _arr.length; i++) {
    let j = getRandomInt(0, i);
    let t = _arr[i];
    _arr[i] = _arr[j];
    _arr[j] = t;
  }

  return _arr;
}

# 二叉树遍历(前序、中序、后序、广度优先)

# 前序遍历

前序遍历是先输出节点的值,再递归遍历左右子树,前序遍历是一个深度优先的遍历方法。

function recursionTreeNode(root) {
  if (root != null) {
    console.log(root.val);
    recursionTreeNode(root.left);
    recursionTreeNode(root.right);
  }
}

# 中序遍历

中序遍历是先遍历左子树,再输出当前节点,最后遍历右子树。

function recursionTreeNode(root) {
  if (root != null) {
    recursionTreeNode(root.left);
    console.log(root.val);
    recursionTreeNode(root.right);
  }
}

# 后序遍历

后序遍历是先递归遍历左右子树,再输出节点的值。

function recursionTreeNode(root) {
  if (root != null) {
    recursionTreeNode(root.left);
    recursionTreeNode(root.right);
    console.log(root.val);
  }
}

# 广度优先遍历

广度优先遍历就是按树的深度,从根节点开始,一层一层向底部遍历。

function recursionTreeNode(root) {
  const stack = [root];

  while (stack.length) {
    let tempNode = stack.pop();
    console.log(tempNode.val)

    if (tempNode.children.length) {
      stack.push(...tempNode.children);
    }
  }

  return null;
}