树
二叉树
- 二叉树的第 (i) 层结点数不多于 (2^{i-1})
- 深度为 (k) 的二叉树结点数不多于 (2^k-1)
满二叉树 :指的是深度为 (k) 且含有 (2^k - 1) 个结点的二叉树。
完全二叉树 :树中所含的 (n) 个结点和满二叉树中编号为 1 至 n 的结点一一对应。
public class BinaryTree<T> {
private Node root;
class Node {
T val;
Node left;
Node right;
Node(T val) {
this.val = val;
}
}
}
遍历
1. 前序遍历
根 - 左 -右
// 非递归
public List<T> preorder() {
List<T> res = new LinkedList<>();
Stack<Node> stack = new Stack<>();
if (root != null)
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
res.add(node.val);
if (node.right != null) // 栈 先保存 后访问
stack.push(node.right);
if (node.left != null)
stack.push(node.left);
}
return res;
}
// 递归
public void preorder(Node node) {
if (node == null)
return;
System.out.print(node.val + " ");
preorder(node.left);
preorder(node.right);
}
2. 中序遍历
左 - 根 - 右
// 非递归
public List<T> inorder() {
List<T> res = new LinkedList<>();
Stack<Node> stack = new Stack<>();
Node node = root;
while (!stack.isEmpty() || node != null) {
while (node != null) {
stack.add(node);
node = node.left;
}
if (!stack.isEmpty()) {
node = stack.pop();
res.add(node.val);
node = node.right;
}
}
return res;
}
// 递归
public void inorder(Node node) {
if (node == null)
return;
inorder(node.left);
System.out.print(node.val + " ");
inorder(node.right);
}
3. 后序遍历
左 - 右 - 根
// 非递归
// 后序遍历等价于镜像树前序遍历的逆序
public List<T> postorder() {
List<T> res = new LinkedList<>();
Stack<Node> stack = new Stack<>();
if (root != null)
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
res.add(node.val);
if (node.left != null)
stack.push(node.left);
if (node.right != null)
stack.push(node.right);
}
Collections.reverse(res);
return res;
}
// 递归
public void postorder(Node node) {
if (node == null)
return;
postorder(node.left);
postorder(node.right);
System.out.print(node.val + " ");
}
4. 层次遍历
public List<T> level() {
List<T> res = new LinkedList<>();
Queue<Node> queue = new LinkedList<>();
if (root != null)
queue.offer(root);
while (!queue.isEmpty()) {
int num = queue.size();
for (int i = 0; i < num; i++) {
Node node = queue.poll();
res.add(node.val);
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
}
return res;
}
树高
public int height(Node node) {
if (node == null)
return 0;
return Math.max(height(node.left), height(node.right)) + 1;
}
直径
即树中任两结点间的最长路径。
一棵二叉树的直径长度是任意两个结点路径长度中的最大值。
这条路径可能穿过也可能不穿过根结点。
private int res = 0;
public int diameter(Node root) {
height(root);
return res;
}
private int height(Node node) {
if (node == null)
return 0;
int l = height(node.left);
int r = height(node.right);
res = Math.max(res, l + r);
return Math.max(l, r) + 1;
}
翻转
public Node invert(Node node) {
if (node == null)
return null;
Node tmp = invert(node.left);
node.left = invert(node.right);
node.right = tmp;
return node;
}
对称
public boolean isSymmetric() {
return isSymmetric(root, root);
}
private boolean isSymmetric(Node n1, Node n2) {
if (n1 == null && n2 == null)
return true;
if (n1 == null || n2 == null)
return false;
return n1.val == n2.val && isSymmetric(n1.left, n2.right) && isSymmetric(n1.right, n2.left);
}
森林和二叉树
将森林中的每一棵树采用二叉链表(孩子-兄弟)法表示,根结点的右子树为空,再将所有树的根结点依次连接起来。
孩子-兄弟:二叉树结点的左孩子为该结点第一个孩子,右孩子为兄弟结点。
每棵树的后序遍历等价于二叉树的中序遍历。
字典树
Trie,字典树,或称前缀树,用于存储字符串,判断字符串前缀,检索字符串集中的键。如:
- 自动补全
- 拼写检查
- IP路由 最长前缀匹配
- 九宫格打字预测
- 单词游戏
public class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); i++) {
char currentChar = word.charAt(i);
if (!node.containsKey(currentChar)) {
node.put(currentChar, new TrieNode());
}
node = node.get(currentChar);
}
node.setEnd();
}
private TrieNode searchPrefix(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); i++) {
char curLetter = word.charAt(i);
if (node.containsKey(curLetter)) {
node = node.get(curLetter);
} else {
return null;
}
}
return node;
}
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isEnd();
}
public boolean startsWith(String prefix) {
TrieNode node = searchPrefix(prefix);
return node != null;
}
class TrieNode {
// TrieNode 中并不需要保存字符信息
// 该结点储存的字符记录在它的父结点中
private TrieNode[] links;
private final int R = 26; // 26 个小写字母
private boolean isEnd; // 是否为一个单词的结尾
public TrieNode() {
links = new TrieNode[R];
}
public boolean containsKey(char ch) {
return links[ch - 'a'] != null;
}
public TrieNode get(char ch) {
return links[ch - 'a'];
}
public void put(char ch, TrieNode node) {
links[ch - 'a'] = node;
}
public void setEnd() {
isEnd = true;
}
public boolean isEnd() {
return isEnd;
}
}
}
哈夫曼树
哈夫曼编码:根据数据出现的频率对数据进行编码,从而压缩原始数据。
思路: 贪心策略
每次选取频率最小的两个结点,生成一个新结点,其频率为这两个结点的和,作为他们的父结点。
(n) 个结点进行 (n-1) 次选择,生成一棵 (2n-1) 个结点的树。树的叶子结点为数据。
编码:每个结点的左侧路径标记为 0,右侧路径标记为 1。从根结点出发,到叶结点结束,经过的路径即为该叶子结点的编码。