1、二叉树的遍历
1.1 前序遍历
中 -> 左 -> 右
public static void preOrderRecur(Node head){
if(head == null){
return;
}
//打印放在最开始的地方就是先序遍历,打印放在中间位置,则是中序遍历,打印放在最后则是后序遍历。
System.out.print(head.value+" ");
preOrderRecur(head.left);
preOderRecur(head.right);
}
1.2 中序遍历
左 -> 中 -> 右
public static void inOrderRecur(Node head){
if(head == null){
return;
}
inOrderRecur(head.left);
//打印放在最开始的地方就是先序遍历,打印放在中间位置,则是中序遍历,打印放在最后则是后序遍历。
System.out.print(head.value+" ");
inOderRecur(head.right);
}
1.3 后序遍历
左 -> 右 -> 中
public static void posOrderRecur(Node head){
if(head == null){
return;
}
posOrderRecur(head.left);
posOderRecur(head.right);
//打印放在最开始的地方就是先序遍历,打印放在中间位置,则是中序遍历,打印放在最后则是后序遍历。
System.out.print(head.value+" ");
}
2、判断一颗二叉树是否为平衡二叉树(AVL)
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
思路1:从根节点开始,求出根的左右子树的高度,如果根的左右子树的高度差大于1,返回FALSE,否则递归的判断根的左子树和右子树是否满足条件。
public boolean IsBalanced_Solution(TreeNode root) {
if(root==null)
return true;
//如果树为 null 返回 TRUE。否则判断根的左右子树的高度差的绝对值是否大于1,若大于1 则返回false。
// 否则判断树的左右孩子是否是平衡二叉树,当两者都是平衡二叉树时返回TRUE,否则返回false.
else if(Math.abs(TreeDepth(root.left)-TreeDepth(root.right))>1)
return false;
else return IsBalanced_Solution(root.left)&&IsBalanced_Solution(root.right);
}
//求树的深度。
public int TreeDepth(TreeNode root)
{
if(root==null)
return 0;
//如果树为 null 返回0 否则返回左右孩子的最大值+1。
return Math.max(TreeDepth(root.left), TreeDepth(root.right))+1;
思路2: 上面的方法中,重复的计算子树的高度。可以用后序遍历,从下到上遍历如果子树中任一不满足条件返回 false,否则返回 true 这样每个节点的高度只会算一次。
public class IsBalancedTree {
boolean isBalance=true;
public boolean IsBalanced_Solution(TreeNode root) {
TreeDepth1(root);
return isBalance;
//isBalance 会在 TreeDepth1(root)中赋值。
}
public int TreeDepth1(TreeNode root)
{
if(root==null)
return 0;
int left=TreeDepth1(root.left);
//左子树高度
int right=TreeDepth1(root.right);
//右子树高度
if(Math.abs(left-right)>1)
{
isBalance=false;
//只要有一个子树的左右子树的高度绝对值大于 1 isBalance=false
}
return Math.max(left, right)+1;
3、判断一棵树是否为搜索二叉树
对于任何节点而言,左子树都比他小,右子树都比他大。
**思路:**搜索二叉树在中序遍历(左中右)中是按照从小到大的顺序。
我们可以利用一个pre变量来保存前驱结点,并初始化pre为尽可能小的值。
反过来讲,如果存在一个当前结点小于其前驱结点,那么这棵树就不是二叉搜索树。
//判断给定的二叉树是否为BST树的改进算法
boolean isBSTOfBetter(BinaryTreeNode<T> root,int pre) {
if(root == null)
return true;
//如果有一棵左子树不是BST树则返回false
if(!isBSTOfBetter(root.getLeft(), pre))
return false;
//如果当前结点小于前驱结点则返回false
if((int)root.getData() < pre)
return false;
//更新前驱结点
pre = (int) root.getData();
//如果有一棵右子树不是BST树则返回false
if(!isBSTOfBetter(root.getRight(), pre))
return false;
return true;
}
4、判断一棵树是否为完全二叉树
完全二叉树:约定从根起,自上而下,自左而右,给满二叉树中的每个结点从1到n连续编号。
**思路:**根据完全二叉树的定义,对完全二叉树按照从上到下、从左到右的层次遍历,应该满足一下两条要求:
●某节点没有左孩子,则一定无右孩子
●若某节点缺左或右孩子,则其所有后继一定无孩子
若不满足上述任何一条,均不为完全二叉树。
public static boolean isCBT(Node head){
if(head == null){
return true;
}
Queue<Node> queue = new LinkedList<Node>();
//判断叶节点阶段何时开启
boolean leaf = false;
Node l = null;
Node r = null;
queue.offer(head);
while(!queue.isEmpty()){
head = queue.poll();
l = head.left;
r = head.right;
//左边为空右边不为空或者开启了叶节点的阶段,则左右两个节点仍有子节点,则为false
if((leaf &&(l != null || r != null)) || (l == null && r!= null)){
return false;
}
if(l != null){
queue.offer(l);
}
if(r != null){
queue.offer(r);
}else{
leaf = true;
}
}
return true;
}
5、trie树(前缀树/字典树)
基本性质
1,根节点不包含字符,除根节点意外每个节点只包含一个字符。
2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
3,每个节点的所有子节点包含的字符串不相同。
(1) 字符串检索
事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。
(2)文本预测、自动完成,see also,拼写检查
(3)词频统计
例子:
一个字符串类型的数组arr1,另一个字符串类型的数组arr2。arr2中有哪些字符,是arr1中出现的?请打印。arr2中有哪些字符,是作为arr1中某个字符串前缀出现?请打印。arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印arr2中出现次数最多的前缀。
//前缀树节点
public static class TrieNode{
public int pass; //经过节点的次数
public int end; //作为字符串尾节点的次数
public TriNode[] nexts; //节点next节点们的集合(字符种类多时,用HashMap<char, Node> nexts;
public TrieNode(){
pass = 0;
end = 0;
nexts = new TrieNode[26];
//每个节点后都有26条路,连向26个字母,一开始全为null,next[0]代表a字母,next[1]代表b字母....
//nexts[0] == null 没有走向‘a’的路
//next[1] != null 有走向‘a’的路
}
//建立前缀树
public static class Trie{
//根节点
private TrieNode root;
public Trie(){
root = new TrieNode();
}
//将word单词插进前缀树中
public void insert(String word){
if(word == null){
return;
}
//分割字符串
char[] chs = word.toCharArray();
//标志节点指向根节点
TrieNode node = root;
//根节点的pass++
node.pass++;
//index代表nexts[index]中的下标
int index = 0;
for(int i = 0; i < chs.length; i++){
//用ASCII来计算下一条路的下标,如‘a’-‘a’=0,则选next[0]的路
index = chs[i] - 'a';
//如果当前节点没有去nexts[index]的路,新建一条
if(node.nexts[index] == null){
node.nexts[index] = new TrieNode();
}
//然后node;来到下一个节点,即nexts[index]
node = node.nexts[index];
//pass++,继续循环遍历
node.pass++;
}
//遍历完字符串则end++
node.end++;
}
//查询word这个单词加入过前缀树几次
public int search(String word){
if(word == null){
return 0;
}
//分割字符串
char[] chs = word.toCharArray();
//从根节点开始遍历
TrieNode node = root;
int index = 0;
for(int i = 0, i < chs.length, i++){
index = chs[i] - 'a';
//如果出现有一个字符串没有路,则没有出现过这个word,直接返回0
if(node.nexts[index] == null){
return 0;
}
//node不断指向下一条路
node = node.nexts[index];
}
//遍历完字符串后返回的end节点就是出现的次数
return node.end;
}
//求所有加入的字符串中,有几个是以pre这个字符串作为前缀出现的
public int prefixNumber(String pre){
if(pre == null){
return 0;
}
char[] chs = pre.toCharArray();
TrieNode node = root;
int index = 0;
for(int i = 0, i < chs.length, i++){
index = chs[i] - 'a';
if(node.nexts[index] == null){
return 0;
}
node = node.nexts[index];
}
return node.pass;
}
//删除word字符串
public void delete(String word){
if(search(word) != 0){
char[] chs = word.toCharArray();
TrieNode node = root;
node.pass--;
int index = 0;
for(int i = 0, i < chs.length, i++){
index = chs[i] - 'a';
if(--node.nexts[index].pass == 0){
//只有java能这样 c++要析构来释放内存
//遇到pass--后变0的节点,直接标空,后面的节点自动释放
node.nexts[index] = null;
return;
}
//没遇到就继续遍历,继续--node.pass
node = node.nexts[index];
}
//删除完整个字符串后end要--
node.end--;
}
}
}