力扣 (LeetCode)-對稱二叉樹,樹|刷題打卡

Github來源:力扣 (LeetCode)|刷題打卡 | 求星星 ✨ | 給個❤️關注,❤️點贊,❤️鼓勵一下做者前端

[已開啓]任務一:刷題打卡 * 10 篇node

哪吒人生信條:若是你所學的東西 處於喜歡 纔會有強大的動力支撐git

天天學習編程,讓你離夢想更新一步,感謝不負每一份熱愛編程的程序員,不論知識點多麼奇葩,和我一塊兒,讓那一顆四處流蕩的心定下來,一直走下去,加油,2021加油!歡迎關注加我vx:xiaoda0423,歡迎點贊、收藏和評論程序員

時間: 3 月 1 日 ~ 3 月 13 日github

前言

若是這篇文章有幫助到你,給個❤️關注,❤️點贊,❤️鼓勵一下做者,接收好挑戰了嗎?文章公衆號首發,關注 程序員哆啦A夢 第一時間獲取最新的文章web

❤️筆芯❤️~面試

棧,隊列,鏈表,集合,字典和散列表算法

樹是一種分層數據的抽象模型,最多見的樹的例子是家譜或是公司的組織架構圖編程

  • 一個樹結構包含一些列存在父子關係的節點
  • 位於樹頂部的節點叫作根節點,它沒有父節點
  • 樹中的每一個元素都叫做節點,節點份內部節點和外部節點
  • 至少有一個子節點的節點稱爲內部節點
  • 沒有子元素的節點稱爲外部節點或葉節點
  • 子樹由節點和它的後代構成
  • 節點的一個屬性是深度,節點的深度取決於它的祖先節點的數量
  • 樹的高度取決於全部節點深度的最大值

二叉樹和二叉搜索樹數組

  • 二叉樹中的節點最多隻能有兩個子節點:一個是左側子節點,另外一個是右側子節點
  • 二叉搜索樹是二叉樹的一種,可是它只容許你在左側節點存儲小的值,在右側節點存儲大的值

二叉搜索樹數據結構

建立BinarySearchTree

function BinarySearchTree() {
 var Node = function(key){
 // 聲明一個Node類來表示樹中的每一個節點
  this.key = key;
  this.left = null;
  this.right = null;
 };
 var root = null;
}
複製代碼
  • 鏈表:將經過指針來表示節點之間的關係
  • 雙向鏈表:每一個節點包含兩個指針,一個指向下一個節點,另外一個指向上一個節點
  • 樹,一個指向左側子節點,另外一個指向右側子節點
  • insert(key),向樹中插入一個新的鍵
  • search(key),在樹中查找一個鍵,若是節點存在,則返回true;若是不存在,則返回false
  • inOrderTraverse,經過中序遍歷方式遍歷全部節點
  • preOrderTraverse,經過先序遍歷方式遍歷全部節點
  • postOrderTraverse,經過後序遍歷方式遍歷全部節點
  • min,返回樹中最小的值/鍵
  • max,返回樹中最大的值/鍵
  • remove(key),從樹中移除某個鍵

向樹中插入一個鍵

示例:

// 向樹插入一個新鍵的算法
// 要向樹中插入一個新的節點
this.insert = function(key){ 
 var newNode = new Node(key); //建立用來表示新節點的Node類實例 
 // 只須要向構造函數傳遞咱們想用來插入樹的節點值,它的左指針和右指針的值會由構造函數自動設置爲null
 if (root === null){ //第二步要驗證這個插入操做是否爲一種特殊狀況。
 //這個特殊狀況就是咱們要插入的節點是樹的第一個節點 
 root = newNode; 
 } else { 
 insertNode(root,newNode); //將節點加在非根節點的其餘位置
 } 
};
複製代碼
// 將節點加在非根節點的其餘位置
var insertNode = function(node, newNode){ 
//  傳入樹的根節點和要插入的節點
 if (newNode.key < node.key){ //若是新節點的鍵小於當前節點的鍵 
 if (node.left === null){ //須要檢查當前節點的左側子節點
 // 若是它沒有左側子節點
 node.left = newNode; //就在那裏插入新的節點
 } else { 
 // 若是有左側子節點,須要經過遞歸調用insertNode方法
 insertNode(node.left, newNode); //繼續找到樹的下一層
 // 下次將要比較的節點將會是當前節點的左側子節點
 } 
 } else { 
 if (node.right === null){ 
 //若是節點的鍵比當前節點的鍵大,同時當前節點沒有右側子節點
 node.right = newNode; //就在那裏插入新的節點
 } else { 
 insertNode(node.right, newNode); 
 //若是有右側子節點,一樣須要遞歸調用insertNode方法,可是要用來和新節點比較的節點將會是右側子節點
 } 
 } 
};
複製代碼

示例:

var tree = new BinarySearchTree(); 
tree.insert(11);
複製代碼

樹的遍歷,遍歷一棵樹是指訪問樹的每一個節點並對它們進行某種操做的過程(中序遍歷的一種應用就是對樹進行排序操做)

訪問樹的全部節點有三種方式:中序、先序和後序

  • 中序遍歷是一種以上行順序訪問BST全部節點的遍歷方式(就是以從最小到最大的順序訪

問全部節點)

// 中序遍歷的一種應用就是對樹進行排序操做
this.inOrderTraverse = function(callback){ 
// 接收一個回調函數做爲參數
// 回調函數用來定義咱們對遍歷到的每一個節點進行的操做
 inOrderTraverseNode(root, callback); //接收一個節點和對應的回調函數做爲參數
};
複製代碼
var inOrderTraverseNode = function (node, callback) { 
 if (node !== null) {
 //要經過中序遍歷的方法遍歷一棵樹,首先要檢查以參數形式傳入的節點是否爲null
 inOrderTraverseNode(node.left, callback); //遞歸調用相同的函數來訪問左側子節點
 callback(node.key); //接着對這個節點進行一些操做
 inOrderTraverseNode(node.right, callback); //而後再訪問右側子節點
 } 
};
複製代碼
function printNode(value){ //須要建立一個回調函數 
 console.log(value); 
} 
tree.inOrderTraverse(printNode); 
//調用inOrderTraverse方法並將回調函數做爲參數傳入
複製代碼
  • 先序遍歷是以優先於後代節點的順序訪問每一個節點的。(先序遍歷的一種應用是打印一個結構化的文檔)

示例:

this.preOrderTraverse = function(callback){ 
 preOrderTraverseNode(root, callback); 
}; 

preOrderTraverseNode方法的實現以下:

var preOrderTraverseNode = function (node, callback) { 
 if (node !== null) { 
 callback(node.key); //先序遍歷和中序遍歷的不一樣點是,先序遍歷會先訪問節點自己 
 preOrderTraverseNode(node.left, callback); 
 //而後再訪問它的左側子節點 
 preOrderTraverseNode(node.right, callback); //最後是右側子節點 
 } 
};
複製代碼
  • 後序遍歷則是先訪問節點的後代節點,再訪問節點自己。(後序遍歷的一種應用是計算一個目錄和它的子目錄中全部文件所佔空間的大小。)

示例:

this.postOrderTraverse = function(callback){ 
 postOrderTraverseNode(root, callback); 
}; 

postOrderTraverseNode方法的實現以下:

var postOrderTraverseNode = function (node, callback) { 
 if (node !== null) { 
 postOrderTraverseNode(node.left, callback); //後序遍歷會先訪問左側子節點 
 postOrderTraverseNode(node.right, callback); //而後是右側子節點
 callback(node.key); //最後是父節點自己 
 } 
};
複製代碼

搜索樹中的值

有三種執行的搜索類型:搜索最小值,搜索最大值,搜索特定的值

示例:

// 尋找樹的最小鍵的方法
this.min = function() { 
 return minNode(root); //調用了minNode方法
 // 傳入樹的根節點
};
複製代碼
var minNode = function (node) { 
// 容許咱們從樹中任意一個節點開始尋找最小的鍵
 if (node){ 
 while (node && node.left !== null) { //遍歷樹的左邊
 node = node.left; //遍歷樹的左邊 
 } 
 return node.key; 
 } 
 return null; //直到找到樹的最下層
};
複製代碼
// 實現max方法
this.max = function() { 
 return maxNode(root); 
}; 

var maxNode = function (node) { 
 if (node){ 
 while (node && node.right !== null) { //沿着樹的右邊進行遍歷
 node = node.right; // 直到找到最右端的節點
 } 
 return node.key; 
 } 
 return null; 
};
複製代碼

搜索一個特定的值

// find、search或get方法來查找數據結構中的一個特定的值
this.search = function(key){ 
 return searchNode(root, key); 
 //searchNode方法能夠用來尋找一棵樹或它的任意子樹中的一個特定的值
}; 

var searchNode = function(node, key){ 
 if (node === null){ 
 //先要驗證做爲參數傳入的node是否合法(不是null)。
 //若是是null的話,說明要找的鍵沒有找到,返回false。 
 return false; 
 } 
 if (key < node.key){ //若是要找的鍵比當前的節點小
 return searchNode(node.left, key); 
 //那麼繼續在左側的子樹上搜索 
 
 } else if (key > node.key){ 
 //若是要找的鍵比當前的節點大,那麼就從右側子節點開始繼續搜索
 return searchNode(node.right, key); 
 } else { 
 return true; //不然就說明要找的鍵和當前節點的鍵相等
 // 就返回true來表示找到了這個鍵
 } 
};
複製代碼

移除一個節點

示例:

this.remove = function(key){ 
 root = removeNode(root, key); //傳入root和要移除的鍵做爲參數
 // root被賦值爲removeNode方法的返回值
};
複製代碼
// removeNode方法的實現

var removeNode = function(node, key){ 

 if (node === null){ 
 //若是正在檢測的節點是null,那麼說明鍵不存在於樹中,因此返回null
 return null; 
 } 
 
 if (key < node.key) { 
 //若是要找的鍵比當前節點的值小
  node.left = removeNode(node.left, key); 
  //就沿着樹的左邊找到下一個節點
  return node; //
 } else if (key > node.key){ 
 //若是要找的鍵比當前節點的值大 
  node.right = removeNode(node.right, key); 
  //那麼就沿着樹的右邊找到下一個節點
  return node; //
 } else { //鍵等於node.key 
  //第一種狀況——一個葉節點 移除一個葉節點
     if (node.left === null && node.right === null){ 
     //第一種狀況是該節點是一個沒有左側或右側子節點的葉節點
     // 給這個節點賦予null值來移除它
     // 還須要處理指針
       node = null; //  須要經過返回null來將對應的父節點指針賦予null值
       return node; //  值已是null了,父節點指向它的指針也會接收到這個值
       // 要在函數中返回節點的值的緣由
     } 
 
     //第二種狀況——一個只有一個子節點的節點
    // 移除有一個左側或右側子節點的節點
    
     if (node.left === null){ //若是這個節點沒有左側子節點
      node = node.right; //也就是說它有一個右側子節點
      // 把對它的引用改成對它右側子節點的引用
      return node; //並返回更新後的節點
     } else if (node.right === null){ 
     //若是這個節點沒有右側子節點 
      node = node.left; //把對它的引用改成對它左側子節點的引用 
      return node; //並返回更新後的值 
     } 
 
     //第三種狀況——一個有兩個子節點的節點
     // 移除有兩個子節點的節點
     var aux = findMinNode(node.right); //  

     node.key = aux.key; // 

     node.right = removeNode(node.right, aux.key); // 

     return node; // 
 } 
};
複製代碼

要移除有兩個子節點的節點,須要執行四個步驟

  • 當找到了須要移除的節點後,須要找到它右邊子樹中最小的節點
  • 而後,用它右側子樹中最小節點的鍵去更新這個節點的值
  • 繼續把右側子樹中的最小節點移除,畢竟它已經被移至要移除的節點的位置了
  • 向它的父節點返回更新後節點的引用
// 找到了要找的鍵(鍵和node.key相等)
var findMinNode = function(node){ 
 while (node && node.left !== null) { 
 node = node.left; 
 } 
 return node; // 在findMinNode中返回了節點
};
複製代碼
  • 二叉搜索樹

自平衡樹

BST存在一個問題:樹的一條分支會有不少層,而其餘的分支卻只有幾層的狀況。

  • Adelson-Velskii-Landi 樹(AVL 樹)
  • AVL樹是一種自平衡二叉搜索樹(任何一個節點左右兩側子樹的高度之差最多爲1)
  • AVL樹的不一樣之處在於咱們須要檢驗它的平衡因子,若是有須要,則將其邏輯應用於樹的自平衡
  • 計算平衡因子
  • 須要對每一個節點計算右子樹高度(hr)和左子樹高度(hl)的差值,該值(hr-hl)應爲0、1或1
  • 若是結果不是這三個值之一,則須要平衡該AVL樹-這就是平衡因子的概念。
// 計算節點高度
var heightNode = function(node) { 
 if (node === null) { 
 return -1;
 } else { 
 return Math.max(heightNode(node.left), 
 heightNode(node.right)) + 1; 
 } 
};

// 替換insertNode方法的行 
if ((heightNode(node.left) - heightNode(node.right)) > 1) { 
 // 旋轉 
} 

向右子樹插入新節點時,應用一樣的邏輯

// 替換insertNode方法的行 
if ((heightNode(node.right) - heightNode(node.left)) > 1) { 
 // 旋轉 
}
複製代碼
  • AVL旋轉:能夠執行單旋轉或雙旋轉兩種平衡操做
  1. 向左的單旋轉
  2. 向右的單旋轉
  3. 向右的雙旋轉
  4. 向左的雙旋轉

示例:

var insertNode = function(node, element) { 

 if (node === null) { 
  node = new Node(element); 
 } else if (element < node.key) { 
  node.left = insertNode(node.left, element); 
     if (node.left !== null) { 
      // 確認是否須要平衡
     } 
 } else if (element > node.key) { 
     node.right = insertNode(node.right, element); 
     if (node.right !== null) { 
      // 確認是否須要平衡
     } 
 } 
 
 return node; 
};
複製代碼

101. 對稱二叉樹

1、題目描述

給定一個二叉樹,檢查它是不是鏡像對稱的。

image.png

2、思路分析

  • 用遞歸:一棵樹是對稱的 等價於 它的左子樹和右子樹兩棵樹是對稱的,問題就轉變爲判斷兩棵樹是否對稱。

  • 遍歷每個節點的時候,若是我均可以經過某種方法知道它對應的對稱節點是誰,這樣的話我直接比較二者是否一致就好了。

  • 第一次遍歷的同時將遍歷結果存儲到哈希表中,而後第二次遍歷去哈希表取。

若是同時知足下面的條件,兩個樹互爲鏡像:

  • 它們的兩個根結點具備相同的值
  • 每一個樹的右子樹都與另外一個樹的左子樹鏡像對稱

咱們將根節點的左子樹記作 left,右子樹記作 right。比較 left 是否等於 right,不等的話直接返回就能夠了。

若是至關,比較 left 的左節點和 right 的右節點,再比較 left 的右節點和 right 的左節點。

終止條件:

  • left 和 right 不等,或者 left 和 right 都爲空
  • 遞歸的比較 left,leftright.right,遞歸比較 left,right 和 right.left

image.png

image.png

image.png

3、答案代碼

var isSymmetric = function(root) {
    if(!root) return true;
    var stack = [root.left,root.right];
    while(stack.length){
        var p = stack.pop();
        var q = stack.pop();
        if (p === q) continue;
        if (p && q && p.val === q.val) {
            stack.push(p.left, q.right, p.right, q.left);
        } else {
            return false;
        }
    }
    return true;

};
複製代碼

4、總結

對稱二叉樹,樹

回看筆者往期高贊文章,也許能收穫更多喔!

❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更好的文章

點贊、收藏和評論

我是Jeskson(達達前端),感謝各位人才的:點贊、收藏和評論,咱們下期見!(如本文內容有地方講解有誤,歡迎指出☞謝謝,一塊兒學習了)

咱們下期見!

文章持續更新,能夠微信搜一搜「 程序員哆啦A夢 」第一時間閱讀,回覆【資料】有我準備的一線大廠資料,本文 www.dadaqianduan.cn/#/ 已經收錄

github收錄,歡迎Stargithub.com/webVueBlog/…

本文正在參與「掘金 2021 春招闖關活動」, 點擊查看 活動詳情

相關文章
相關標籤/搜索