[轉(zhuǎn)]一文圖解二叉樹面試題

原文:泥瓦匠-一文圖解二叉樹面試題

二叉樹彪见,搜索二叉樹,是算法面試的必面題娱挨。聊聊面試點(diǎn):

一余指、樹 & 二叉樹

樹是由節(jié)點(diǎn)和邊構(gòu)成,儲存元素的集合跷坝。節(jié)點(diǎn)分根節(jié)點(diǎn)酵镜、父節(jié)點(diǎn)和子節(jié)點(diǎn)的概念。
如圖:樹深=4; 5是根節(jié)點(diǎn)柴钻;同樣8與3的關(guān)系是父子節(jié)點(diǎn)關(guān)系淮韭。

二叉樹binary tree,則加了“二叉”(binary)贴届,意思是在樹中作區(qū)分靠粪。每個節(jié)點(diǎn)至多有兩個子(child),left child & right child。二叉樹在很多例子中使用毫蚓,比如二叉樹表示算術(shù)表達(dá)式占键。
如圖:1/8是左節(jié)點(diǎn);2/3是右節(jié)點(diǎn)绍些;

二捞慌、二叉搜索樹 BST

顧名思義,二叉樹上又加了個搜索的限制柬批。其要求:每個節(jié)點(diǎn)比其左子樹元素大啸澡,比其右子樹元素小袖订。
如圖:每個節(jié)點(diǎn)比它左子樹的任意節(jié)點(diǎn)大,而且比它右子樹的任意節(jié)點(diǎn)小

Java 實現(xiàn)代碼如下:

public class BinarySearchTree {
    /**
     * 根節(jié)點(diǎn)
     */
    public static TreeNode root;

    public BinarySearchTree() {
        this.root = null;
    }

    /**
     * 查找
     *      樹深(N) O(lgN)
     *      1\. 從root節(jié)點(diǎn)開始
     *      2\. 比當(dāng)前節(jié)點(diǎn)值小,則找其左節(jié)點(diǎn)
     *      3\. 比當(dāng)前節(jié)點(diǎn)值大,則找其右節(jié)點(diǎn)
     *      4\. 與當(dāng)前節(jié)點(diǎn)值相等,查找到返回TRUE
     *      5\. 查找完畢未找到,
     * @param key
     * @return
     */
    public TreeNode search (int key) {
        TreeNode current = root;
        while (current != null
                && key != current.value) {
            if (key < current.value )
                current = current.left;
            else
                current = current.right;
        }
        return current;
    }

    /**
     * 插入
     *      1\. 從root節(jié)點(diǎn)開始
     *      2\. 如果root為空,root為插入值
     *      循環(huán):
     *      3\. 如果當(dāng)前節(jié)點(diǎn)值大于插入值,找左節(jié)點(diǎn)
     *      4\. 如果當(dāng)前節(jié)點(diǎn)值小于插入值,找右節(jié)點(diǎn)
     * @param key
     * @return
     */
    public TreeNode insert (int key) {
        // 新增節(jié)點(diǎn)
        TreeNode newNode = new TreeNode(key);
        // 當(dāng)前節(jié)點(diǎn)
        TreeNode current = root;
        // 上個節(jié)點(diǎn)
        TreeNode parent  = null;
        // 如果根節(jié)點(diǎn)為空
        if (current == null) {
            root = newNode;
            return newNode;
        }
        while (true) {
            parent = current;
            if (key < current.value) {
                current = current.left;
                if (current == null) {
                    parent.left = newNode;
                    return newNode;
                }
            } else {
                current = current.right;
                if (current == null) {
                    parent.right = newNode;
                    return newNode;
                }
            }
        }
    }

    /**
     * 刪除節(jié)點(diǎn)
     *      1.找到刪除節(jié)點(diǎn)
     *      2.如果刪除節(jié)點(diǎn)左節(jié)點(diǎn)為空 , 右節(jié)點(diǎn)也為空;
     *      3.如果刪除節(jié)點(diǎn)只有一個子節(jié)點(diǎn) 右節(jié)點(diǎn) 或者 左節(jié)點(diǎn)
     *      4.如果刪除節(jié)點(diǎn)左右子節(jié)點(diǎn)都不為空
     * @param key
     * @return
     */
    public TreeNode delete (int key) {
        TreeNode parent  = root;
        TreeNode current = root;
        boolean isLeftChild = false;
        // 找到刪除節(jié)點(diǎn) 及 是否在左子樹
        while (current.value != key) {
            parent = current;
            if (current.value > key) {
                isLeftChild = true;
                current = current.left;
            } else {
                isLeftChild = false;
                current = current.right;
            }

            if (current == null) {
                return current;
            }
        }

        // 如果刪除節(jié)點(diǎn)左節(jié)點(diǎn)為空 , 右節(jié)點(diǎn)也為空
        if (current.left == null && current.right == null) {
            if (current == root) {
                root = null;
            }
            // 在左子樹
            if (isLeftChild == true) {
                parent.left = null;
            } else {
                parent.right = null;
            }
        }
        // 如果刪除節(jié)點(diǎn)只有一個子節(jié)點(diǎn) 右節(jié)點(diǎn) 或者 左節(jié)點(diǎn)
        else if (current.right == null) {
            if (current == root) {
                root = current.left;
            } else if (isLeftChild) {
                parent.left = current.left;
            } else {
                parent.right = current.left;
            }

        }
        else if (current.left == null) {
            if (current == root) {
                root = current.right;
            } else if (isLeftChild) {
                parent.left = current.right;
            } else {
                parent.right = current.right;
            }
        }
        // 如果刪除節(jié)點(diǎn)左右子節(jié)點(diǎn)都不為空
        else if (current.left != null && current.right != null) {
            // 找到刪除節(jié)點(diǎn)的后繼者
            TreeNode successor = getDeleteSuccessor(current);
            if (current == root) {
                root = successor;
            } else if (isLeftChild) {
                parent.left = successor;
            } else {
                parent.right = successor;
            }
            successor.left = current.left;
        }
        return current;
    }

    /**
     * 獲取刪除節(jié)點(diǎn)的后繼者
     *      刪除節(jié)點(diǎn)的后繼者是在其右節(jié)點(diǎn)樹種最小的節(jié)點(diǎn)
     * @param deleteNode
     * @return
     */
    public TreeNode getDeleteSuccessor(TreeNode deleteNode) {
        // 后繼者
        TreeNode successor = null;
        TreeNode successorParent = null;
        TreeNode current = deleteNode.right;

        while (current != null) {
            successorParent = successor;
            successor = current;
            current = current.left;
        }

        // 檢查后繼者(不可能有左節(jié)點(diǎn)樹)是否有右節(jié)點(diǎn)樹
        // 如果它有右節(jié)點(diǎn)樹,則替換后繼者位置,加到后繼者父親節(jié)點(diǎn)的左節(jié)點(diǎn).
        if (successor != deleteNode.right) {
            successorParent.left = successor.right;
            successor.right = deleteNode.right;
        }

        return successor;
    }

    public void toString(TreeNode root) {
        if (root != null) {
            toString(root.left);
            System.out.print("value = " + root.value + " -> ");
            toString(root.right);
        }
    }
}

/**
 * 節(jié)點(diǎn)
 */
class TreeNode {

    /**
     * 節(jié)點(diǎn)值
     */
    int value;

    /**
     * 左節(jié)點(diǎn)
     */
    TreeNode left;

    /**
     * 右節(jié)點(diǎn)
     */
    TreeNode right;

    public TreeNode(int value) {
        this.value = value;
        left  = null;
        right = null;
    }
}

面試點(diǎn)一:理解 TreeNode 數(shù)據(jù)結(jié)構(gòu)

節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)嗅虏,即節(jié)點(diǎn)分左節(jié)點(diǎn)和右節(jié)點(diǎn)及本身節(jié)點(diǎn)值洛姑。如圖

面試點(diǎn)二:如何確定二叉樹的最大深度或者最小深度

答案:簡單的遞歸實現(xiàn)即可,代碼如下:

int maxDeath(TreeNode node){
    if(node==null){
        return 0;
    }
    int left = maxDeath(node.left);
    int right = maxDeath(node.right);
    return Math.max(left,right) + 1;
}

    int getMinDepth(TreeNode root){
        if(root == null){
            return 0;
        }
        return getMin(root);
    }
    int getMin(TreeNode root){
        if(root == null){
            return Integer.MAX_VALUE;
        }
        if(root.left == null&&root.right == null){
            return 1;
        }
        return Math.min(getMin(root.left),getMin(root.right)) + 1;
    }

面試點(diǎn)三:如何確定二叉樹是否是平衡二叉樹

答案:簡單的遞歸實現(xiàn)即可皮服,代碼如下:

    boolean isBalanced(TreeNode node){
        return maxDeath2(node)!=-1;
    }
    int maxDeath2(TreeNode node){
        if(node == null){
            return 0;
        }
        int left = maxDeath2(node.left);
        int right = maxDeath2(node.right);
        if(left==-1||right==-1||Math.abs(left-right)>1){
            return -1;
        }
        return Math.max(left, right) + 1;
    }

前面面試點(diǎn)是 二叉樹 的楞艾,后面面試點(diǎn)是 搜索二叉樹 的。先運(yùn)行搜搜二叉樹代碼:

public class BinarySearchTreeTest {

    public static void main(String[] args) {
        BinarySearchTree b = new BinarySearchTree();
        b.insert(3);b.insert(8);b.insert(1);b.insert(4);b.insert(6);
        b.insert(2);b.insert(10);b.insert(9);b.insert(20);b.insert(25);

        // 打印二叉樹
        b.toString(b.root);
        System.out.println();

        // 是否存在節(jié)點(diǎn)值10
        TreeNode node01 = b.search(10);
        System.out.println("是否存在節(jié)點(diǎn)值為10 => " + node01.value);
        // 是否存在節(jié)點(diǎn)值11
        TreeNode node02 = b.search(11);
        System.out.println("是否存在節(jié)點(diǎn)值為11 => " + node02);

        // 刪除節(jié)點(diǎn)8
        TreeNode node03 = b.delete(8);
        System.out.println("刪除節(jié)點(diǎn)8 => " + node03.value);
        b.toString(b.root);

    }
}

結(jié)果如下:

value = 1 -> value = 2 -> value = 3 -> value = 4 -> value = 6 -> value = 8 -> value = 9 -> value = 10 -> value = 20 -> value = 25 -> 
是否存在節(jié)點(diǎn)值為10 => 10
是否存在節(jié)點(diǎn)值為11 => null
刪除節(jié)點(diǎn)8 => 8
value = 1 -> value = 2 -> value = 3 -> value = 4 -> value = 6 -> value = 9 -> value = 10 -> value = 20 -> value = 25 ->

面試點(diǎn)四:搜索二叉樹如何實現(xiàn)插入

插入龄广,和刪除一樣會引起二叉搜索樹的動態(tài)變化硫眯。插入相對刪處理邏輯相對簡單些。如圖插入的邏輯:

    1. 從root節(jié)點(diǎn)開始
    1. 如果root為空,root為插入值
    1. 循環(huán):
    1. 如果當(dāng)前節(jié)點(diǎn)值大于插入值,找左節(jié)點(diǎn)
    1. 如果當(dāng)前節(jié)點(diǎn)值小于插入值,找右節(jié)點(diǎn)

面試點(diǎn)五:搜索二叉樹如何實現(xiàn)查找

其算法復(fù)雜度 : O(lgN),樹深(N)择同。如圖查找邏輯:

image
  1. 從root節(jié)點(diǎn)開始
  2. 比當(dāng)前節(jié)點(diǎn)值小,則找其左節(jié)點(diǎn)
  3. 比當(dāng)前節(jié)點(diǎn)值大,則找其右節(jié)點(diǎn)
  4. 與當(dāng)前節(jié)點(diǎn)值相等,查找到返回TRUE
  5. 查找完畢未找到

面試點(diǎn)五:搜索二叉樹如何實現(xiàn)刪除

比較復(fù)雜了两入。首先找到刪除節(jié)點(diǎn),其尋找方法:刪除節(jié)點(diǎn)的后繼者是在其右節(jié)點(diǎn)樹種最小的節(jié)點(diǎn)敲才。如圖刪除對應(yīng)邏輯:

結(jié)果為:

    1. 找到刪除節(jié)點(diǎn)
    1. 如果刪除節(jié)點(diǎn)左節(jié)點(diǎn)為空 , 右節(jié)點(diǎn)也為空
    1. 如果刪除節(jié)點(diǎn)只有一個子節(jié)點(diǎn) 右節(jié)點(diǎn) 或者 左節(jié)點(diǎn)
    1. 如果刪除節(jié)點(diǎn)左右子節(jié)點(diǎn)都不為空

三裹纳、小結(jié)

就像碼出高效面試的程序媛偶爾吃一碗“老壇酸菜牛肉面”一樣的味道,品味一個算法紧武,比如 BST 的時候剃氧,總是那種說不出的味道。

面試必備小結(jié):

  • 樹阻星,二叉樹的概念
  • BST 算法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朋鞍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子妥箕,更是在濱河造成了極大的恐慌番舆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矾踱,死亡現(xiàn)場離奇詭異恨狈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)呛讲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門禾怠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贝搁,你說我怎么就攤上這事吗氏。” “怎么了雷逆?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵弦讽,是天一觀的道長。 經(jīng)常有香客問我,道長往产,這世上最難降的妖魔是什么被碗? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮仿村,結(jié)果婚禮上锐朴,老公的妹妹穿的比我還像新娘。我一直安慰自己蔼囊,他們只是感情好焚志,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畏鼓,像睡著了一般酱酬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上云矫,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天岳悟,我揣著相機(jī)與錄音,去河邊找鬼泼差。 笑死,一個胖子當(dāng)著我的面吹牛呵俏,可吹牛的內(nèi)容都是我干的堆缘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼普碎,長吁一口氣:“原來是場噩夢啊……” “哼吼肥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起麻车,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缀皱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后动猬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啤斗,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年赁咙,在試婚紗的時候發(fā)現(xiàn)自己被綠了钮莲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡彼水,死狀恐怖崔拥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凤覆,我是刑警寧澤链瓦,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站盯桦,受9級特大地震影響慈俯,放射性物質(zhì)發(fā)生泄漏渤刃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一肥卡、第九天 我趴在偏房一處隱蔽的房頂上張望溪掀。 院中可真熱鬧,春花似錦步鉴、人聲如沸揪胃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喊递。三九已至,卻和暖如春阳似,著一層夾襖步出監(jiān)牢的瞬間骚勘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工撮奏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俏讹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓畜吊,卻偏偏與公主長得像泽疆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子玲献,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354