算法(二叉树-矩阵-堆排序)

最小和

位运算知识点

12>>1 //6 a/2 等价为 a>>1

中间数 (L+R)/2 会出现溢出(溢出的意思就是超过了二进制)

L+(R-L)/2 最终改成 l+((r-l)>>1)

const smallSum = arr => {
  if (arr == null || arr.length < 2) {
    return 0;
  }
  return mergeSort(arr, 0, arr.length - 1)
}
const mergeSort = (arr, l, r) => {
  if (l == r) {
    return 0;
  }
  // let mid = Math.floor((l + r) / 2)
  let mid = l+((r-l)>>1)
  return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r)
}
const merge = (arr, l, m, r) => {
  let help = [];
  let i = 0;
  let p1 = l;
  let p2 = m + 1;
  let res=0;
  while (p1 <= m && p2 <= r) {
    //如果左边小于右边,r开始到p2的个数*p1
      //简单理解成 p1<p2  重复的加在一起
    res+=arr[p1]<arr[p2]?(r-p2+1)*arr[p1]:0;
    help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]
  }
  while (p1 <= m) {
    help[i+1]=arr[p1++]
  }
  while (p2 <= r) {
    help[i++]=arr[p2++]
  }
  for (let j = 0; j < help.length; j++) {
    arr[l + j] = help[j]
  }
  return res
}
console.log(smallSum([1, 2, 3]))

递归的理解

递归算法实际上是一种分而治之的方法,它把复杂问题分解为简单问题来求解。对于某些复杂问题(例如hanio塔问题),递归算法是一种自然且合乎逻辑的解决问题的方式,但是递归算法的执行效率通常比较差。因此,在求解某些问题时,常采用递归算法来分析问题,用非递归算法来求解问题

,递归会出问题的话,循环也一定会出问题,只不过递归是出了问题才告诉你,而循环则在执行前就可以知道有问题

循环和递归有种逆向思维关系, 循环通常来自底向上, 递归自顶向下。

堆排序

将数组转化成二叉树

左节点 2*i+1 右节点 2*i+2 父节点 (i-1)/2

大根堆=>就是完全二叉树

// 堆
let len; //数组长度
//建立大堆顶
function builddMaxHeap(arr) {
  len = arr.length;
  for (let i = Math.floor(len / 2); i >= 0; i--) {
    heapify(arr, i)
  }
}

//堆调整
const heapify = (arr, i) => {
  let left = 2 * i + 1,
    right = 2 * i + 2,
    largest = i;
  if (left < len && arr[left] > arr[largest]) {
    largest=left;
  }
  if (right < len && arr[right] > arr[largest]) {
    largest=right;
  }
  if (largest != i) {
    swap(arr, i, largest)
    heapify(arr, largest)
  }
}
function swap(arr, i, j) {
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
//排序
function heapSort(arr) {
  builddMaxHeap(arr)
  for (let i = arr.length-1; i >0 ; i--) {
    //0 i>0  最后一个和0交换  
    swap(arr, 0, i)
    len--;
    //0重新被排到最后  
    heapify(arr,0)
  }
  return arr;
}

排序

let arr=[
  {name:'张三',age:122,height:423},
  {name:'张三',age:14,height:223},
  {name:'张三',age:16,height:123},
]
console.log(arr.sort((a, b) => a.age - b.age))

矩阵算法

回型打印

let arr=[
  [1,2,3,4],
  [1,2,3,4],
  [1,2,3,4],
  [1,2,3,4]
]
const spiralOrder=(arr)=>{
  let x1=0;
  let y1=0;
  let x2=arr.length-1;
  let y2=arr[0].length-1;
    //这个代码是直接找外层循环后再找内层循环
   while (x1 <= x2 && y1 <= y2) {
    printEdge(arr,x1++,y1++,x2--,y2--)
   }
    //下面这层代码是直接找外层循环
  //printEdge(arr,x1,y1,x2,y2)
}
const printEdge = (arr,x1, y1, x2, y2) => {
  // x轴相等
  // 0  0 0 3
  if(x1==x2){
    for (let i = y1; i <=y2 ; i++) {
      // [0][0]   [0][1]   [0][2]  [0][3]
      console.log(arr[x1][i])
    }
    //y轴相等
    //0 0 3 0
  }else if (y1 == y2) {
    for (let i = x1; i <=x2 ; i++) {
      console.log(arr[i][y1])
    }
  }else{
    let cy1=y1;
    let cx1=x1;
    while (cy1 != y2) {
      //(0,0) (0,1) (0,2)
      console.log(arr[x1][cy1])
      cy1++
    }
    while (cx1 != x2) {
      //(0,3)(1,3)(2,3)
      console.log(arr[cx1][y2])
      cx1++
    }
    while (cy1 != y1) {
      //(3,3)(3,2)(3,1)
      console.log(arr[x2][cy1])
      cy1--
    }
    while (cx1 != x1) {
      //(3,0)(2,0)(1,0)
      console.log(arr[cx1][y1])
      cx1--
    }
  }
}
spiralOrder(arr)

打印Z形矩阵

宏观基础

一行是一横行

一列是一纵向

//虽然我懂了,但是我被这个行呀,列呀搞糊涂了

/**
 * 将   AB连线上的元素打印出来
 * @param {要打印的矩阵} m
 * @param {A的横坐标} x1
 * @param {A的纵坐标} y1
 * @param {B的横坐标} x2
 * @param {B的纵坐标} y2
 * @param {打印方向} f
 */
  printMatrizIGZag=(arr) =>{
    let x1 = 0;
    let y1 = 0;
    let x2 = 0;
    let y2 = 0;
    let enx2 = arr.length - 1,
      eny2 = arr[0].length - 1;
    let fromUp = false;
    // 判断条件:AB走到最后即结束循环
    while (x1 != enx2 + 1) {
      printLevel(arr, x1, y1, x2, y2, fromUp);
      x1 = y1 == eny2 ? x1 + 1 : x1;
      y1 = y1 == eny2 ? y1 : y1 + 1;
      y2 = x2 == enx2 ? y2 + 1 : y2;
      x2 = x2 == enx2 ? x2 : x2 + 1;
      fromUp = !fromUp;
    }
  }
  printLevel=(m, x1, y1, x2, y2, f)=> {
    if (f) {
      while (x1 != x2 + 1) {
        console.log(m[x1++][y1--])
      }
    } else {
      while (x2 != x1 - 1) {
        console.log(m[x2--][y2++])
      }
    }
  }
let arr = [
  [1, 2, 3, 4],
  [1, 2, 3, 4],
  [1, 2, 3, 4],
  [1, 2, 3, 4]
]
printMatrizIGZag(arr)

链表

链表是由一系列的节点组成的集合,每个节点都使用一个对象的引用指向他的后继,指向另一个节点的引用叫链

有点麻烦,先放放

二叉树遍历

定义一个初始化的二叉树

var nodes = {
  node: 6,
  left: {
    node: 5, 
    left: { 
      node: 4 
    }, 
    right: { 
      node: 3 
    }
  },
  right: { 
    node: 2, 
    right: { 
      node: 1 
    } 
  }
}

/*
*         6   
*     5       2  
*   4   3        1
* */

先序遍历

递归版

  • 若二叉树为空,则算法结束,否则:
  • 访问根节点
  • 前序遍历根节点的左子树
  • 前序遍历根节点的右子树
let result = [];
const dfs = nodes => {
if (nodes.node) {
 result.push(nodes.node)
 //先递归添加所有的左节点
 nodes.left && dfs(nodes.left)
   //再递归添加所有的右节点
 nodes.right && dfs(nodes.right)
}
}
dfs(nodes)
console.log(result)
// [6, 5, 4, 3, 2, 1]

非递归版

  • 初始化一个栈,将根节点压入栈中
  • 先判断右节点有没有,有就入栈,再判断左节点有没有,有就入栈
  • 然后再出栈(pop), 先出左节点,再出右节点
var dfs = function(nodes) {
  var result = []
  var stack = []
  stack.push(nodes)
  while (stack.length) {
    var item = stack.pop()
    result.push(item.node)
    item.right && stack.push(item.right)
    item.left && stack.push(item.left)
  }
  return result
}
console.log(dfs(nodes))
// [6, 5, 4, 3, 2, 1]

中序遍历

左 中 右

递归版

  • 先入栈6,5,4 出栈4,5,6再5节点的时候因为有右节点3,先入栈,添加到数组中,所以是4,5,3,6
  • 再右节点入栈的时候,因为入栈一个就添加到数组中,所以是2,1
var result = []
var dfs = function(nodes) {
  if(nodes.node) {
    //也就是先入栈6,5,4,所有出栈是4,5,6
    nodes.left && dfs(nodes.left)
    result.push(nodes.node)//(4,5) 3  6
    nodes.right && dfs(nodes.right)//因为5有右节点(3)  ,
    // 然后就是右节点2入栈的时候就添加到数组中,右节点1入栈也被添加了
  }
}
dfs(nodes)
console.log(result)
// [4, 5, 3, 6, 2, 1]

非递归版

var dfs = function(nodes) {
  var result = []
  var stack = []
  var item = nodes
  stack.push(nodes)
  while (stack.length) {
    if(item.left && !item.touched) {//因为4的item.left没有直接跳出
      item.touched = true
      item = item.left
      stack.push(item)  //(6,5,4)
      continue
    }
    item.touched && delete item.touched // 清理标记
    item = stack.pop()
    result.push(item.node) //4,5,
    item.right && stack.push(item.right) //然后把3入栈,因为3没有左节点直接出栈
  }
  return result
}
console.log(dfs(nodes))

后序遍历

左右中

递归版

不用解释,打印下你就懂了
var result = []
var dfs = function(nodes) {
  if(nodes.node) {
    nodes.left && dfs(nodes.left)
    nodes.right && dfs(nodes.right)
    result.push(nodes.node)
  }
}
dfs(nodes)
console.log(result)

非递归版

function Stack() {

  var items = [];     //用来保存栈里的元素

  this.push = function (element) {
    items.push(element);
  }

  this.pop = function () {
    return items.pop();
  }

  this.peek = function () {
    return items[items.length - 1];
  }

  this.isEmpty = function () {
    return items.length == 0;
  }

  this.size = function () {
    return items.length;
  }

  this.clear = function () {
    items = [];
  }

  this.print = function () {
    console.log(items.toString());
  }
}
//也就是先序遍历(中左右)换成中右左
const preOrder = (head) => {
  if (head != null) {
    const stack = new Stack()
    stack.push(head)
    while (!stack.isEmpty()) {
      head=stack.pop()
      console.log(head.node)
      if (head.right != null) {
        stack.push(head.right)
      }
      if (head.left != null) {
        stack.push(head.left)
      }
    }
  }
}
preOrder(nodes)

最简洁的方法
const preOrder = (head) => {
  if (head != null) {
    const stack = new Stack()
    stack.push(head)
    let c=null;
    while (!stack.isEmpty()) {
      //查看栈顶(就是最后一个)
     c=stack.peek()
      if (c.left != null && head != c.left && head != c.right) {
        stack.push(c.left)
      }else if (c.right != null && head != c.right) {
        stack.push(c.right)
      }else{
        console.log(stack.pop().node)
        head=c
      }
    }
  }
}
preOrder(nodes)

打印直观的二叉树

点我你就知道啦

给一个节点,找到这个节点的后继

直接用java代码吧比较直观

	public static class Node {
		public int value;
		public Node left;
		public Node right;
		public Node parent;

		public Node(int data) {
			this.value = data;
		}
	}

	public static Node getSuccessorNode(Node node) {
		if (node == null) {  
			return node;
		}
		if (node.right != null) { //如果当前节点的右孩子节点不为空,说明有右子树,
			return getLeftMost(node.right); //则找到并返回右子树上最左的节点
		} else {               //如果当前节点没有右子树
			Node parent = node.parent;
			while (parent != null && parent.left != node) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

	public static Node getLeftMost(Node node) { //在这个函数里面,node是某个节点的头部
		if (node == null) {
			return node;
		}
		while (node.left != null) { //左子树不为空的情况下,一路向左
			node = node.left;
		}
		return node;
	}

记录的过程叫做序列化,把一个内容还原出内存中的树结构,就是反序列化

序列化二叉树

定义一个如图的二叉树

const symmetricalTree = {
  val: 1,
  left: {
    val: 2,
    left: { val: 4, left: null, right: null },
    right: { val: 5, left: null, right: null }
  },
  right: {
    val: 3,
    left: { val: 6, left: null, right: null },
    right: { val: 7, left: null, right: null }
  }
}

先序序列化

//序列化
function Serialize(pRoot, arr = []) {
  if (!pRoot) {
    arr.push('#');
  } else {
    arr.push(pRoot.val);
    Serialize(pRoot.left, arr);
    Serialize(pRoot.right, arr);
  }
  return arr.join(',');
}

反序列化

//反序列化
function Deserialize(str) {
  if (!str) {
    return null;
  }
  return deserialize(str.split(','));
}

function deserialize (arr) {
  let node = null;
  const current = arr.shift();
  if (current !== '#') {
    node = { val: current };
    node.left = deserialize(arr);
    node.right = deserialize(arr);
  }
  return node;
}

可以去查查先序,中序,后序,层序的实现,还有其中的递归版和非递归版
###############################################################################################################################################################################................................................................................................................................................................

posted @ 2019-06-28 16:39  猫神甜辣酱  阅读(811)  评论(0编辑  收藏  举报