二叉樹節點結構
class Node<V>{
V value;
Node left;
Node right;
}
二叉樹的遍歷(遞迴)
先序遍歷
順序:根左右
public static void preOrderRecur(Node head) {
if (head == null) {
return;
}
System.out.print(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
中序遍歷
順序:左根右
public static void inOrderRecur(Node head) {
if (head == null) {
return;
}
inOrderRecur(head.left);
System.out.print(head.value + " ");
inOrderRecur(head.right);
}
後序遍歷
順序:左右根
public static void posOrderRecur(Node head) {
if (head == null) {
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.print(head.value + " ");
}
二叉樹的遍歷(非遞迴)
先序遍歷
順序:根左右
先把根節點壓入棧中,每次
-
從棧中彈出一個節點cur
-
處理節點cur
-
先壓入cur的右節點,再壓入cur的左節點(如果有的話)
只要棧不為空,周而復始
public static void preOrder(Node head) {
if (head != null) {
Stack<Node> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.value + " ");
if (head.right != null)
stack.push(head.right);
if (head.left != null)
stack.push(head.left);
}
System.out.println();
}
中序遍歷
順序:左根右
-
每棵子樹整棵樹左邊界進棧
-
依次彈出的過程中處理節點
-
對彈出節點右樹做同樣操作
周而復始
public static void inOrder(Node head) {
if (head != null) {
Stack<Node> stack = new Stack<>();
while (!stack.isEmpty() || head != null) { //剛開始stack為空,head不為null
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
System.out.print(head.value + " ");
head = head.right;
}
}
System.out.println();
}
}
後序遍歷
順序:左右根
反過來就是根右左,準備兩個棧。
先把根節點壓入棧1中,每次
-
從棧1中彈出一個節點cur
-
把節點cur壓入棧2
-
先在棧1中壓入cur的右節點,再壓入cur的左節點(如果有的話)
只要棧1不為空,周而復始
最後依次彈出棧2中的節點,其順序就是後序遍歷的順序
public static void postOrder(Node head) {
if (head != null) {
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
stack1.push(head);
while (!stack1.isEmpty()){
head = stack1.pop();
stack2.push(head);
if(head.left!=null){
stack1.push(head.left);
}
if(head.right!=null){
stack1.push(head.right);
}
}
while (!stack2.isEmpty()){
head = stack2.pop();
System.out.print(head.value+" ");
}
System.out.println();
}
}
二叉樹層序(寬度)遍歷
先把根節點放入佇列中,每次
-
彈出節點cur
-
處理節點cur
-
先把cur的左節點放入佇列,再把cur的右節點放入佇列(如果存在的話)
周而復始,直到佇列為空
public static void leverOrder(Node head) {
if (head != null) {
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
System.out.print(head.value + " ");
if (head.left != null) {
queue.add(head.left);
}
if (head.right != null) {
queue.add(head.right);
}
}
System.out.println();
}
}
public static void leverOrder(Node head) {
if (head == null) return;
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
int n = queue.size();
for (int i = 0; i < n; i++) { //一次處理一層節點
head = queue.poll();
System.out.printf(head.value + " ");
if (head.left != null) queue.add(head.left);
if (head.right != null) queue.add(head.right);
}
}
System.out.println();
}
二叉樹應用題
二叉樹深度
遞迴
class Solution {
public int maxDepth(TreeNode root) {
if(root==null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
層序遍歷bfs
public int maxDepth(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int ans = 0;
while(!queue.isEmpty()){
ans++; //每次處理一層,處理完ans加一
int n = queue.size();
for(int i = 0;i < n; i++){
root = queue.poll();
if(root.left!=null) queue.add(root.left);
if(root.right!=null) queue.add(root.right);
}
}
return ans;
}
求二叉樹最大寬度
層序遍歷的過程中使用HashMap記錄節點層數
public static void maxWidth(Node head) {
if (head != null) {
Queue<Node> queue = new LinkedList<>();
queue.add(head);
HashMap<Node, Integer> levelMap = new HashMap<>();
levelMap.put(head, 1);
int curLevel = 1; //當前層數
int curLevelNodes = 0; //當前層數的節點數
int max = Integer.MIN_VALUE;
while (!queue.isEmpty()) {
Node cur = queue.poll();
int curNodeLevel = levelMap.get(cur); //獲取當前節點層數
if (curNodeLevel == curLevel) { //當前節點層數等於當前層數,節點數加一
curLevelNodes++;
} else {
max = Math.max(max, curLevelNodes); //否則max取較大值
curLevel++; //當前層數加一
curLevelNodes = 1; //重置當前層數的節點數為1
}
if (cur.left != null) {
levelMap.put(cur.left, curNodeLevel + 1);
queue.add(cur.left);
}
if (cur.right != null) {
levelMap.put(cur.right, curNodeLevel + 1);
queue.add(cur.right);
}
}
System.out.println(max);
}
}
判斷是否為搜尋二叉樹
中序遍歷遞增就是搜尋二叉樹
遞迴方式
public static int preValue = Integer.MIN_VALUE;
public static boolean checkBST(Node head){
if(head!=null){
boolean isLeftBst = checkBST(head.left);
if(!isLeftBst){
return false;
}
if(head.value<=preValue){
return false;
}else{
preValue = head.value;
}
return checkBST(head.right);
}
return true;
}
非遞迴方式
public static boolean checkBST(Node head) {
if (head != null) {
Stack<Node> stack = new Stack<>();
while (!stack.isEmpty() || head != null) { //剛開始stack為空,head不為null
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
if(head.value<=preValue){
return false;
}else {
preValue = head.value;
}
head = head.right;
}
}
}
return true;
}
樹形DP處理
-
左子樹是搜尋二叉樹
-
右子樹是搜尋二叉樹
-
根節點大於左子樹最大值
-
根節點小於右子樹最小值
public static boolean isBST(Node head){
return process(head).isBST;
}
public static class ReturnType {
public boolean isBST;
public int max;
public int min;
public ReturnType(boolean isBST, int max, int min) {
this.isBST = isBST;
this.max = max;
this.min = min;
}
}
public static ReturnType process(Node x){
if(x==null) {
return null;
}
ReturnType leftData = process(x.left); //獲取左子樹處理資訊
ReturnType rightData = process(x.right); //獲取右子樹處理資訊
int min = x.value;
int max = x.value;
if(leftData!=null){ //獲取當前樹的最大值最小值
min = Math.min(min,leftData.min);
max = Math.max(max,leftData.max);
}
if(rightData!=null){
min = Math.min(min,rightData.min);
max = Math.max(max,rightData.max);
}
boolean isBST = true;
//左子樹存在並且(左子樹不是BST或者左子樹最大值大於x)
if(leftData!=null&&(!leftData.isBST||leftData.max>=x.value)){
isBST = false;
}
//右子樹存在並且(右子樹不是BST或者右子樹最小值小於x)
if(rightData!=null&&(!rightData.isBST||x.value>=rightData.min)){
isBST = false;
}
return new ReturnType(isBST,max,min);
}
判斷是否是完全二叉樹
-
層序遍歷
-
任何一個節點有右孩子沒左孩子,則不是完全二叉樹(1)
-
在(1)的前提下,遇到第一個左右不雙全節點,那其後面必須都是葉子節點,否則不是二叉樹
public static boolean checkCBT(Node head) {
if (head != null) {
boolean leaf = false; //是否遇到過左右不雙全節點
Node l = null;
Node r = null;
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
l = head.left;
r = head.right;
if ((leaf && !(l == null && r == null)) //遇到第一個左右不雙全節點,那麼以後的節點都必須是葉子節點
||
(l == null && r != null)) { //任何一個節點有右孩子沒左孩子
return false;
}
if (l != null) {
queue.add(l);
}
if (r != null) {
queue.add(r);
}
if (l == null || r == null) {
leaf = true;
}
}
}
return true;
}
判斷是否是平衡二叉樹
-
左子樹是平衡的
-
右子樹是平衡的
-
左右子樹高度差的絕對值小於2
獲取左樹資訊和右樹資訊後,結合處理。屬於樹形DP
public static boolean isBalancd(Node head) {
return process(head).isBalanced;
}
public static class ReturnType { //封裝了平衡狀態和高度
public boolean isBalanced;
public int height;
public ReturnType(boolean isB, int hei) {
isBalanced = isB;
height = hei;
}
}
public static ReturnType process(Node x) {
if (x == null) { //空樹是平衡的,高度為0
return new ReturnType(true, 0);
}
ReturnType leftData = process(x.left); //左樹
ReturnType rightData = process(x.right); //右樹
int height = Math.max(leftData.height, rightData.height) + 1; //獲取左子樹和右子樹的最高高度+1
boolean isBalanced = leftData.isBalanced && rightData.isBalanced && //如果左子樹平衡,右子樹平衡
Math.abs(leftData.height - rightData.height) < 2; //左右子樹的高度差的絕對值小於2
return new ReturnType(isBalanced, height); //返回新狀態
}
判斷是否是滿二叉樹
如果一個二叉樹的層數為K,且結點總數是 (2^k) -1 ,則它就是滿二叉樹。
樹形DP問題
public static boolean isFull(Node head) {
if (head == null) {
return true;
}
Info data = process(head);
return data.nodes == ((1 << data.height) - 1);//是否層數為K,且結點總數是 (2^k) -1
}
public static class Info { //封裝樹的高度和節點數
public int height;
public int nodes;
public Info(int h, int n) {
height = h;
nodes = n;
}
}
public static Info process(Node x) {
if (x == null) {
return new Info(0, 0);
}
Info leftData = process(x.left); //獲取左子樹資訊
Info rightData = process(x.right); //獲取右子樹資訊
int height = Math.max(leftData.height, rightData.height) + 1; //求新高度
int nodes = leftData.nodes + rightData.nodes + 1; //求總的節點數
return new Info(height, nodes);
}
在二叉樹中找到一個節點的後繼節點
現在有一種新的二叉樹節點型別如下:
public class Node {
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val) {
value = val;
}
}
該結構比普通二叉樹節點結構多了一個指向父節點的parent指標。
假設有一棵Node型別的節點組成的二叉樹,樹中每個節點的parent指標都正確地指向自己的父節點,頭節 點的parent指向null。 只給一個在二叉樹中的某個節點node,請實現返回node的後繼節點的函式。
在二叉樹的中序遍歷的序列中, node的下一個節點叫作node的後繼節點。
-
一個節點有右子樹,那麼它的下一個節點就是它的右子樹中的最左子節點。例如b的後繼節點是h。
-
一個節點沒有右子樹時分兩種情況:
- 當前節點是它父節點的左子節點,那麼它的下一個節點就是它的父節點。 例如節點f的後繼節點是c,節點d的後繼節點是b。
- 當前節點是它父節點的右子節點,此時沿著指向父節點的指標一直向上遍歷,直到找到一個是它父節點的左子節點的節點,如果這個節點存在,那麼這個節點的父節點就是我們要找的下一個節點。如下圖所示: f的下一個節點是a。
- 當前節點是它父節點的左子節點,那麼它的下一個節點就是它的父節點。 例如節點f的後繼節點是c,節點d的後繼節點是b。
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) {
if (node == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
摺紙問題
請把一段紙條豎著放在桌子上,然後從紙條的下邊向上方對摺1次,壓出摺痕後 展開。 此時摺痕是凹下去的,即摺痕突起的方向指向紙條的背面。 如果從紙條的下邊向上方連續對摺2次,壓出摺痕後展開,此時有三條摺痕,從 上到下依次是下摺痕、下摺痕和上摺痕。 給定一個輸入引數N,代表紙條都從下邊向上方連續對摺N次。 請從上到下列印所有摺痕的方向。
例如:
N=1時,列印: down
N=2時,列印: down down up
分析:
發現第n+1次的摺痕中凹摺痕一定在第n次摺痕的左邊,第n+1次摺痕中凸摺痕一定在第n次摺痕的右邊
形成一棵二叉樹
中序遍歷該二叉樹就可得到答案
public static void printAllFolds(int N) {
printProcess(1, N, true);
}
public static void printProcess(int i, int N, boolean down) {
if (i > N) {
return;
}
printProcess(i + 1, N, true);
System.out.println(down ? "down " : "up ");
printProcess(i + 1, N, false);
}
public static void main(String[] args) {
int N = 1;
printAllFolds(N);
}