树
实现二叉树的先序、中序、后序遍历,包括递归方式和非递归方式
- 递归方式:略
- 非递归方式
- 先序遍历:先打印头结点,若头结点的右孩子不为空则先将右孩子压栈,然后再看其左孩子是否为空,若不为空将左孩子压栈,然后再将栈顶弹出打印。让头结点等于弹出的栈顶节点,再重复上述过程,直到栈为空为止。
- 中序遍历:先将头结点压栈,若左孩子不为空,则将左孩子压栈,然后再看左孩子的左孩子是否为空,若不为空则将其压栈。一直找左孩子,将其压栈,直到没有左孩子为止。此时看栈顶元素的右孩子是否为空,若不为空,则将右孩子重复上述过程。若为空,则直接将其弹出打印,然后再弹出栈顶,重复栈顶元素找右孩子的过程。
- 后序遍历:后序的遍历顺序是左-右-中,先序的遍历顺序是中-左-右,所以只需将先序遍历过程中的先压右孩子,变成先压左孩子,得到的遍历顺序就是中-右-左,这个顺序刚好和后序遍历相反。所以此时将中-右-左的顺序逆序即可(栈结构来实现)
- 代码
import java.util.Stack; /** * 分别用递归和非递归的方式遍历二叉树 */ public class PreInPosTraversal { public static class Node { public int data; public Node left; public Node right; public Node(int data) { this.data = data; } } /** * 递归方式先序遍历 * @param head */ public static void preOrderRecur(Node head) { if (head == null) { return; } System.out.print(head.data+" "); preOrderRecur(head.left); preOrderRecur(head.right); } /** * 递归方式中序遍历 * @param head */ public static void inOrderRecur(Node head) { if (head == null) { return; } inOrderRecur(head.left); System.out.print(head.data+" "); inOrderRecur(head.right); } /** * 递归方式后序遍历 * @param head */ public static void posOrderRecur(Node head) { if (head == null) { return; } posOrderRecur(head.left); posOrderRecur(head.right); System.out.print(head.data+" "); } /** * 递归方式先序遍历 * @param head */ public static void preOrderUnRecur(Node head) { if (head != null) { Stack<Node> stack = new Stack<>(); stack.push(head); while (!stack.isEmpty()) { head=stack.pop(); System.out.print(head.data+" "); if (head.right != null) { stack.push(head.right); } if (head.left != null) { stack.push(head.left); } } } } /** * 递归方式中序遍历 */ public static void inOrderUnRecur(Node head) { if (head != null) { Stack<Node> stack = new Stack<>(); while (head != null || !stack.isEmpty()) { if (head != null) { stack.push(head); head = head.left; } else { head=stack.pop(); System.out.print(head.data+" "); head=head.right; } } } } /** * */ public static void posOrderUnRecur(Node head) { if (head != null) { Stack<Node> stack = new Stack<>(); Stack<Node> res = new Stack<>(); stack.push(head); while (!stack.isEmpty()) { head=stack.pop(); res.push(head); if (head.left != null) { stack.push(head.left); } if (head.right != null) { stack.push(head.right); } } while (!res.isEmpty()) { System.out.print(res.pop().data+" "); } } } public static void main(String[] args) { Node head = new Node(5); head.left = new Node(3); head.right = new Node(8); head.left.left = new Node(2); head.left.right = new Node(4); head.left.left.left = new Node(1); head.right.left = new Node(7); head.right.left.left = new Node(6); head.right.right = new Node(10); head.right.right.left = new Node(9); head.right.right.right = new Node(11); // recursive System.out.println("==============recursive=============="); System.out.print("pre-order: "); preOrderRecur(head); System.out.println(); System.out.print("in-order: "); inOrderRecur(head); System.out.println(); System.out.print("pos-order: "); posOrderRecur(head); System.out.println(); // unrecursive System.out.println("============unrecursive============="); preOrderUnRecur(head); System.out.println(); inOrderUnRecur(head); System.out.println(); posOrderUnRecur(head); } }
如何直观打印一颗二叉树
public class Code_02_PrintBinaryTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
printInOrder(head.right, height + 1, "v", len);
String val = to + head.value + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = getSpace(lenL) + val + getSpace(lenR);
System.out.println(getSpace(height * len) + val);
printInOrder(head.left, height + 1, "^", len);
}
public static String getSpace(int num) {
String space = " ";
StringBuffer buf = new StringBuffer("");
for (int i = 0; i < num; i++) {
buf.append(space);
}
return buf.toString();
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(-222222222);
head.right = new Node(3);
head.left.left = new Node(Integer.MIN_VALUE);
head.right.left = new Node(55555555);
head.right.right = new Node(66);
head.left.left.right = new Node(777);
printTree(head);
head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.right.left = new Node(5);
head.right.right = new Node(6);
head.left.left.right = new Node(7);
printTree(head);
head = new Node(1);
head.left = new Node(1);
head.right = new Node(1);
head.left.left = new Node(1);
head.right.left = new Node(1);
head.right.right = new Node(1);
head.left.left.right = new Node(1);
printTree(head);
}
}
在二叉树中找到一个节点的后继节点(前驱结点刚好和后继节点找法相反)
- 【题目】 现在有一种新的二叉树节点类型如下:
public class Node { public int value; public Node left;
public Node right; public Node parent;
public Node(int data) { this.value = data; }
}
该结构比普通二叉树节点结构多了一个指向父节点的parent指针。假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null。只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。在二叉树的中序遍历的序列中,node的下一个节点叫作node的后继节点。 - 思路:
- 如果node节点有右子树,则node节点的后继节点就是其右子树上最左的那个节点。
- 如果node节点无右子树,则找后继节点就是找,到底node是作为哪一个节点的左子树的最后一个节点。
- 代码
public class SuccessorNode { public static class Node { public int data; public Node parent; public Node left; public Node right; public Node(int data) { this.data = data; } } public static Node findSuccessorNode(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 = parent.parent; } return parent; } } private static Node getLeftMost(Node head) { if (head == null) { return head; } while (head.left != null) { head = head.left; } return head; } public static void main(String[] args) { Node head = new Node(6); head.parent = null; head.left = new Node(3); head.left.parent = head; head.left.left = new Node(1); head.left.left.parent = head.left; head.left.left.right = new Node(2); head.left.left.right.parent = head.left.left; head.left.right = new Node(4); head.left.right.parent = head.left; head.left.right.right = new Node(5); head.left.right.right.parent = head.left.right; head.right = new Node(9); head.right.parent = head; head.right.left = new Node(8); head.right.left.parent = head.right; head.right.left.left = new Node(7); head.right.left.left.parent = head.right.left; head.right.right = new Node(10); head.right.right.parent = head.right; Node test = head.left.left; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.left.left.right; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.left; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.left.right; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.left.right.right; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.right.left.left; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.right.left; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.right; System.out.println(test.data + " next: " + findSuccessorNode(test).data); test = head.right.right; // 10's next is null System.out.println(test.data + " next: " + findSuccessorNode(test)); } }
二叉树的序列化和反序列化
- 按先序的方式序列化和反序列化(中序和后序方式类似)
代码:
import java.util.LinkedList;
import java.util.Queue;
/**
* 序列化和反序列化二叉树
*/
public class SerializeAndReconstructTree {
public static class Node {
public int data;
public Node left;
public Node right;
public Node(int data) {
this.data = data;
}
}
public static String preSerializeTree(Node head) {
if (head == null) {
return null;
}
String str = head.data + "_";
if (head.left != null) {
str += preSerializeTree(head.left);
} else {
str += "#_";
}
if (head.right != null) {
str += preSerializeTree(head.right);
} else {
str += "#_";
}
return str;
}
public static Node preReconstructTree(String str) {
if (str == null) {
return null;
}
Queue<Node> queue = new LinkedList();
Node head = null;
Node res = null;
String nodes[] = str.split("_");
for (int i = 0; i < nodes.length; i++) {
if (!"#".equals(nodes[i])) {
queue.add(new Node(Integer.valueOf(nodes[i])));
} else {
queue.add(null);
}
}
return process(queue);
}
public static Node process(Queue<Node> queue) {
Node head = queue.poll();
if (head == null) {
return null;
}
head.left = process(queue);
head.right = process(queue);
return head;
}
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
printInOrder(head.right, height + 1, "v", len);
String val = to + head.data + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = getSpace(lenL) + val + getSpace(lenR);
System.out.println(getSpace(height * len) + val);
printInOrder(head.left, height + 1, "^", len);
}
public static String getSpace(int num) {
String space = " ";
StringBuffer buf = new StringBuffer("");
for (int i = 0; i < num; i++) {
buf.append(space);
}
return buf.toString();
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
String pre = preSerializeTree(head);
System.out.println("serialize tree by pre-order: " + pre);
Node head1 = preReconstructTree(pre);
printTree(head1);
}
}
判断一棵二叉树是否是平衡二叉树
- 平衡二叉树概念:二叉树中任何一个节点的左子树和右子树的高度差不超过1。
- 整体思路:一个节点的左子树若不平衡,那么整棵树就不平衡;如果左子树平衡,那么看右子树,若右子树不平衡则整棵树不平衡,否则右子树平衡。然后左子树高度和右子树高度差若大于1,则整棵树不平衡。否则平衡。对每个节点都用这种方式递归判断。
- 代码
/** * 判断一颗二叉树是否是平衡二叉树 */ public class IsBalancedTree { public static class Node{ public int data; public Node left; public Node right; public Node(int data) { this.data = data; } } public static class ReturnData{ public int height; public boolean isB; public ReturnData(int height, boolean isB) { this.height = height; this.isB = isB; } } public static boolean isB(Node head) { return process(head).isB; } public static ReturnData process(Node head) { if (head == null) { return new ReturnData(0, true); } ReturnData leftData=process(head.left); if (!leftData.isB) { return new ReturnData(0, false); } ReturnData rightData=process(head.right); if (!rightData.isB) { return new ReturnData(0, false); } if (Math.abs(leftData.height - rightData.height) > 1) { return new ReturnData(0, false); } return new ReturnData(Math.max(leftData.height , rightData.height)+1, true); } public static void main(String[] args) { Node head = new Node(1); head.left = new Node(2); head.right = new Node(3); head.left.left = new Node(4); head.left.right = new Node(5); head.right.left = new Node(6); head.right.right = new Node(7); System.out.println(isB(head)); } }
判断一棵树是否是搜索二叉树、判断一棵树是否是完全二叉树
- 相关概念:
- 搜索二叉树:它或者是一棵空树,或者是具有下列性质的二叉树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
-
判断一棵树是否是二叉搜索树的思路:若二叉树的中序遍历序列的值是依次升序的,则满足条件
代码:/** * 判断一棵树是否是搜索二叉树 */ public class IsBST { public static class Node{ public int data; public Node left; public Node right; public Node(int data) { this.data = data; } } public static boolean isBST(Node head) { int res=Integer.MIN_VALUE; if (head == null) { return true; } Stack<Node> stack = new Stack<>(); while (head != null || !stack.isEmpty()) { if (head != null) { stack.push(head); head=head.left; }else { head=stack.pop(); res = Math.max(res, head.data); if (res != head.data) { return false; } head=head.right; } } return true; } public static void main(String[] args) { Node head = new Node(5); head.left = new Node(3); head.right = new Node(8); head.left.left = new Node(2); head.left.right = new Node(4); head.left.left.left = new Node(1); head.right.left = new Node(7); head.right.left.left = new Node(6); head.right.right = new Node(10); head.right.right.left = new Node(12); head.right.right.right = new Node(11); System.out.println(isBST(head)); } }
-
判断一棵树是否是完全二叉树
思路:
- 若某个节点有右子树而没有左子树,则该树一定不是完全二叉树。
- 如果某个节点的两个孩子不全,则有两种情况,有左孩子没有右孩子和左右两个孩子都没有。若是这种情况,则该节点后面的所有节点都是叶节点,则该树为完全二叉树,否则为非完全二叉树。
代码:
import java.util.LinkedList;
import java.util.Queue;
public class IsCBT {
public static class Node{
public int data;
public Node left;
public Node right;
public Node(int data) {
this.data = data;
}
}
public static boolean isCBT(Node head) {
if (head == null) {
return true;
}
Queue<Node> queue=new LinkedList<>();
Node left=null;
Node right=null;
boolean leaf=false;
queue.add(head);
while (!queue.isEmpty()) {
head=queue.poll();
left=head.left;
right=head.right;
if (leaf && (left!=null || right!=null) || (right != null && left == null)) {
return false;
}
if (left != null) {
queue.add(left);
}
if (right != null) {
queue.add(right);
}
if (left == null || right == null) {
leaf=true;
}
}
return true;
}
public static void main(String[] args) {
Node head = new Node(4);
head.left = new Node(2);
head.right = new Node(6);
head.left.left = new Node(1);
head.left.right = new Node(3);
head.right.left = new Node(5);
System.out.println(isCBT(head));
}
}
##已知一棵完全二叉树,求其节点的个数(要求:时间复杂度低于O(N),N为这棵树的节点个数)
- 相关结论:若一颗满二叉树的高度为L,则这颗满二叉树的总节点数为2L-1;
- 思路:先求出整个二叉树的深度h,从头结点开始,若头结点的右子树的最大深度到达了h,则头结点的左子树是满二叉树,即可求出左子树的节点数,然后将右子树拿去递归。若头结点的右子树的最大深度小于h,则右子树是满二叉树,这时即可求出右子树的深度,然后再将左子树拿去递归。
- 代码
public class CompleteTreeNodeNumber { public static class Node{ public int data; public Node left; public Node right; public Node(int data) { this.data = data; } } public static int nodeNum(Node head) { if (head == null) { return 0; } return bs(head,1,mostLeftLevel(head,1)); } private static int bs(Node node, int level, int h) { if (level == h) { return 1; } if (h == mostLeftLevel(node.right, level + 1)) { return (1 << (h - level)) + bs(node.right, level + 1, h); }else { return (1 << (h - level-1)) + bs(node.left, level + 1, h); } } private static int mostLeftLevel(Node node, int level) { while (node != null) { level++; node=node.left; } return level-1; } public static void main(String[] args) { Node head = new Node(1); head.left = new Node(2); head.right = new Node(3); head.left.left = new Node(4); head.left.right = new Node(5); head.right.left = new Node(6); System.out.println(nodeNum(head)); } }
前缀树
- 介绍前缀树
前缀数是经常用到的一种数据结构。主要思路是用边来记录数据,用点来记录有多少字符串经过某条边以及把某条边作为结尾。主要用于查找一个结构中有多少以某个字符开头的结果,或者某一个字符出现了几次。 - 基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
- 代码
public class TrieTree { public static class TrieNode { public int path; public int end; public TrieNode[] nexts; public TrieNode() { this.path = 0; this.end = 0; this.nexts = new TrieNode[26]; } } public static class Trie { TrieNode root; public Trie() { root = new TrieNode(); } //向前缀树中添加一个字符串 public void insert(String word) { if (word == null) { return; } TrieNode node = root; char[] chs = word.toCharArray(); for (int i = 0; i < chs.length; i++) { int index = chs[i] - 'a'; if (node.nexts[index] == null) { node.nexts[index] = new TrieNode(); } node = node.nexts[index]; node.path++; } node.end++; } //在前缀树中查找字符串 public int search(String word) { if (word == null) { return 0; } TrieNode node = root; char[] chs = word.toCharArray(); for (int i = 0; i < chs.length; i++) { int index = chs[i] - 'a'; if (node.nexts[index] == null) { return 0; } node = node.nexts[index]; } return node.end; } //删除某个字符串 public void delete(String word) { if (search(word) == 0) { return; } TrieNode node = root; char[] chs = word.toCharArray(); for (int i = 0; i < chs.length; i++) { int index = chs[i] - 'a'; if (--node.nexts[index].path == 0) { node.nexts[index] = null; return; } node = node.nexts[index]; } node.end--; } //查找以某一个字符串序列开头的字符串个数 public int prefixNumber(String pre) { if (pre == null) { return 0; } TrieNode node = root; char[] chs = pre.toCharArray(); for (int i = 0; i < chs.length; i++) { int index = chs[i] - 'a'; if (node.nexts[index] == null) { return 0; } node = node.nexts[index]; } return node.path; } public static void main(String[] args) { Trie trie = new Trie(); System.out.println(trie.search("zuo")); trie.insert("zuo"); System.out.println(trie.search("zuo")); trie.delete("zuo"); System.out.println(trie.search("zuo")); trie.insert("zuo"); trie.insert("zuo"); trie.delete("zuo"); System.out.println(trie.search("zuo")); trie.delete("zuo"); System.out.println(trie.search("zuo")); trie.insert("zuoa"); trie.insert("zuoac"); trie.insert("zuoab"); trie.insert("zuoad"); trie.delete("zuoa"); System.out.println(trie.search("zuoa")); System.out.println(trie.prefixNumber("zuo")); } } }