【面試】基於二叉樹層次遍歷相關問題的求解

leesf發表於2016-02-23

一、問題描述

  1.遍歷二叉樹指定層次的所有結點

  2.統計指定層的搜尋結點個數

  3.對二叉樹進行層次遍歷

  4.找到二叉樹中每一層的第一個結點或最後一個結點

二、問題分析

  對樹結構的問題我們首先會想到使用遞迴來解決,因為樹結構完美適合遞迴,樹的前序、中序、後序遍歷使用遞迴很容易就可以解決,並且很容易理解。對於樹結構的層次遍歷則會稍微麻煩一點(當然,也很簡單),基於層次遍歷的問題也有很多,現在我們就著重來分析前面提到的3個問題。

  什麼是層次遍歷 -- 即按照節點在每一層的順序從上至下,由左到右進行遍歷。

  所有的結果都是基於如下的樹結構:

  

  樹的資料結構的具體程式碼如下:

class Node {
    private String data;
    private Node lChild;
    private Node rChild;
        
    public Node(String data) {
        this.data = data;
    }
    public Node(String data, Node lChild, Node rChild) {
        this.data = data;
        this.lChild = lChild;
        this.rChild = rChild;
    }
        
    public Node getLChild() {
        return lChild;
    }
        
    public Node getRChild() {
        return rChild;
    }
        
    public void setRChild(Node rChild) {
        this.rChild = rChild;
    }
        
    public void setLChild(Node lChild) {
        this.lChild = lChild;
    }
        
    @Override
    public String toString() {
        return data;
    }    
}
View Code

  問題1分析 -- 如何遍歷二叉樹指定層次的所有結點?首先,我們考慮能不能使用遞迴進行遍歷,因為樹問題太適合用遞迴解決了,假定我們的函式名為traverseLevel,我們至少能夠確定函式的引數應該會有樹的根節點root,然後,僅僅只有這個引數還不足以解決問題,再新增一個int型的level引數,通過這兩個引數能否解決問題呢?我們發現確實可以解決問題的。具體程式碼如下(其中樹的資料結構需要自己建立,只給出了遞迴的核心程式碼):

public void traverseLevel(Node root, int level) {
    if(null == root)
        return;
    if (1 == level) { // 表示到達指定層
        System.out.println(root.data);
    } else {
        traverseLevel(root.lChild, level - 1);
        traverseLevel(root.rChild, level - 1);
    }
}
View Code

  問題2分析 -- 基於問題1,我們已經可以遍歷樹的指定層,現在我們要統計指定層的所有結點個數,只需要簡單的修改問題1中的程式碼即可,具體的程式碼如下:

public int levelNodeCount(Node root, int level) {
    if(null == root)
        return 0;
    if (1 == level) { // 表示到達指定層
        System.out.println(root.data);
        return 1;
    } else {
        return levelNodeCount(root.lChild, level - 1)
                + levelNodeCount(root.rChild, level - 1);            
    }            
}      
View Code

  問題3分析 -- 基於問題1的分析,我們可以進一步求解問題3,既然我們可以遍歷指定層的所有結點,那麼遍歷按照層次結構遍歷整棵樹就只需要依次遍歷第一層,第二層...最後一層(n)。
這時,我們只需要求出樹有多少層,即n的大小(樹的高度)即可解決整個問題。求解樹的高度我們也可以採擁遞迴的方法來進行,具體程式碼如下:

public int treeHeight(Node root) {
    if (null == root)
        return 0;
    int lHeight = treeHeight(root.lChild);
    int rHeight = treeHeight(root.rChild);
    int max = -1;
    if (lHeight > rHeight)
        max = lHeight;
    else 
        max = rHeight;
    return max + 1;
}
View Code

既然求得了樹的高度,那麼問題2也就迎刃而解了,具體程式碼如下:

for (int i = 1; i <= treeHeight(root); i++) {
    traverseLevel(root, i);
}
View Code

  繼續問題3分析 -- 如果我們不採擁遞迴的方法來解決,而使用佇列的方式進行求解,可以得到如下的程式碼:

public static void levelOrder(Node root) {
    Queue<Node> queue = new LinkedList<Node>();
    if (null != root) {
        if (null != root.lChild)
            queue.offer(root.lChild);
        if (null != root.rChild) {
            queue.offer(root.rChild);
        }
        System.out.println(root.data);
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            System.out.println(node.data);
            if (null != node.lChild) {
                queue.offer(node.lChild);
            } 
            if (null != node.rChild) {
                queue.offer(node.rChild);
            }
        }
    }    
}
View Code

  問題4分析 -- 有了解決問題1、問題2和問題3的基礎,解決問題3也會容易很多,首先我們需要知道每一層的節點個數,然後使用一個計數器遞增,當達到每一層的第一個結點時,進行遍歷操作即可,具體程式碼如下:

public static void printFirstNodeOfEachLevel(Node root) {
    Queue<Node> queue = new LinkedList<Node>();
    int preCount = 0;
    int levelNodeCount = 0;
    if (null != root) {
        System.out.println(root.data);
        if (null != root.lChild) {
            queue.offer(root.lChild);
            levelNodeCount++;
        }
        if (null != root.rChild) {
            queue.offer(root.rChild);
            levelNodeCount++;
        }
        int count = 0;
        preCount = levelNodeCount;
        levelNodeCount = 0;
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            count++;
            if (count == 1) {
                System.out.println(node.data);
            }
        
            if (null != node.lChild) {
                queue.offer(node.lChild);
                levelNodeCount++;
            } 
            if (null != node.rChild) {
                queue.offer(node.rChild);
                levelNodeCount++;
            }
                    
            if (count == preCount) { // 已經到了本層的最後一個結點            
                count = 0;
                preCount = levelNodeCount;
                levelNodeCount = 0;
            }
        }
    }    
}
View Code

  同理,若理解了遍歷每層的第一個結點的思路,那麼遍歷每層的最後一個結點就很簡單了。只需要令count == preCount即可。

三、問題總結

  對於樹結構的相關問題我們首先應該想到的是使用遞迴來解決,然後思考能不能使用其他方法來解決。感謝各位園友觀看,謝謝~

 

相關文章