算法(二叉樹-矩陣-堆排序)

最小和

位運算知識點java

12>>1 //6 a/2 等價爲 a>>1node

中間數 (L+R)/2 會出現溢出(溢出的意思就是超過了二進制)算法

L+(R-L)/2 最終改爲 l+((r-l)>>1)api

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塔問題),遞歸算法是一種天然且合乎邏輯的解決問題的方式,可是遞歸算法的執行效率一般比較差。所以,在求解某些問題時,常採用遞歸算法來分析問題,用非遞歸算法來求解問題數組

,遞歸會出問題的話,循環也必定會出問題,只不過遞歸是出了問題才告訴你,而循環則在執行前就能夠知道有問題函數

循環和遞歸有種逆向思惟關係, 循環一般來自底向上, 遞歸自頂向下。ui

堆排序

將數組轉化成二叉樹this

左節點 2*i+1 右節點 2*i+2 父節點 (i-1)/2.net

大根堆=>就是徹底二叉樹3d

// 堆
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;
}

能夠去查查先序,中序,後序,層序的實現,還有其中的遞歸版和非遞歸版

................................................................................................................................................................

相關文章
相關標籤/搜索