二分木を知る
- preorder、inorder、postorder の再帰的な実装
class Node<V>{
V value;
Node left;
Node right;
}
再帰的な順序によると、
first order: 最初に到着したときに出力し、最初は何もしない
inorder: 2 回目のノードに到着したときに出力する postorder
: 3 回目のノードに到着したときに出力する時間
在这里插入代码片
- 非再帰的な実装
任意の再帰を非再帰
@@@Preorder トラバーサルに変更できます
。スタックを使用して、最初のステップはヘッド ノードをスタックにプッシュし、次に固定ステップを再生することです (
ノードがスタックにポップアップするたびに 1. 、それは
currentとして記録されます
。
これがポストオーダーのトラバーサルです (2 つのスタックを用意する必要があります。1 つのオリジナル スタックと 1 つのコレクション スタックです)。
1 ポップアップし、現在のノードを cur として記録する
2. cur をコレクション スタックに入れる
3. 最初に左に押し、次に右に押す
4. 何度も繰り返す
順序通りのトラバーサル (主に左側の境界を見つけるため)
サブツリーごとに、ツリー全体の左側の境界がスタックにプッシュされ、ノードを順番にポップアップし、印刷し、ポップされたノードの右側のツリーを繰り返します (左側の境界がスタックにプッシュされます)。自分の理解: 最初にツリー全体の左境界を入れて
スタックをプッシュし、1 つずつポップして出力し、ポップされたノードの右ツリーの左境界を (もしあれば) スタックにプッシュします
。
左のサブツリーは分解できるため (右のサブツリーでも分解できる)
バイナリ ツリーの幅優先トラバーサルを完了する方法(よくある質問: バイナリ ツリーの幅を見つける
)
ツリーの幅は最初にキュー (Queue q=new Linkedlist<>()) を使用してトラバースされ、最初にヘッド ノードをキューに入れ、ノードをポップアップして出力し、次にノードの左右の子をキューに入れます (存在する場合)。 ) )、コードは上記
123
方法 1 (ハッシュ テーブルを使用). テーブル (HashMap)を準備します.このテーブルは彼がどの層にいるかを知っています.次のレイヤー; int currentLevelNodes、現在のレイヤーのノード数; int max、currentLevelNodes と比較して、毎回、現在のレイヤーの最後のノードに到達したら、max を取得します
二分木の再帰ルーチン
二分木を検索: 任意のノードについて、その左の部分木は彼よりも小さく、その右の部分木は彼よりも大きい.
(1) isBST、最初の方法 - 順序トラバーサルを使用して値のサイズを比較します (動的検査はあまり理解できません。2 番目の方法 - 静的検査は理解しやすいです。リストを使用してノードを格納し、リストを取得します。昇順でトラバースすることは、二分木を検索することです)
public int preValue=Integer.MIN_VALUE;//遍历过程中上一次出现的
//用中序遍历动态检查是否是搜索二叉树(动态检查)---不理解
public boolean isBST(Node head) {
if(head==null) {
return true;
}
boolean isLeftBST=isBST(head.left);//左树是不是搜索二叉树
if(!isLeftBST) {
return false;
}
//当前节点是否比我上一次处理的结点大
//如果左树是搜索二叉树,head与左树最后一个打印的比较
if(head.value<=preValue) {
return false;
}else {
preValue=head.value;
}
System.out.print(head.value+" ");
return isBST(head.right);//如果右树是搜索二叉树,整棵树就是了
}
//用中序遍历检查是否是搜索二叉树(第二种方法静态检查)
public boolean isBST2(Node head) {
List<Node> inOrderList =new ArrayList<>();
//得到按照中序排列的list
process2(head,inOrderList);
for(int i=0;i<inOrderList.size()-1;i++) {
if(inOrderList.get(i).value>inOrderList.get(i+1).value) {
return false;
}
}
return true;
}
public void process2(Node head,List<Node> inOrderList) {
//这个方法是把一个个结点按照中序遍历的顺序放到List中
if(head==null) {
return ;
}
process2(head.left,inOrderList);
inOrderList.add(head);
process2(head.right,inOrderList);
}
3番目の方法と見なす必要があります-再帰ルーチンソリューション
//判断是否是搜索二叉树的第三种方法,(递归套路)
//本来只需要四个条件,就可以判断一棵树是否是搜索二叉树,1左树是否是搜索二叉树2左树最大值(要小于head)3右树是否是搜索二叉树4右树最小值(要大于head值)
//使用递归,要求左树右树返回值一样,所以要三个变量,是否是搜索二叉树,最大值,最小值
public class ReturnData {
public boolean isBST;
public int min;
public int max;
public ReturnData(boolean is,int mi,int ma) {
isBST=is;
min=mi;
max=ma;
}
}
public ReturnData process2(Node x) {
if(x==null) {
return null;
}
ReturnData leftData=process2(x.left);//左树给我三个信息
ReturnData rightData=process2(x.right);//右树给我三个信息
//我自己也要给出三个信息,递归才能连起来1.boolean isBST;2.int min;3.int max;
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;//先认为没有违规,返回true
if(leftData!=null&&(!leftData.isBST||leftData.max>=x.value)) {
//如果左树 有信息 并且左树已经不是搜索二叉树了违规;或者左树有信息 并且左树最大值大于等于x.value,就违规
isBST=false;
}
if(rightData!=null&&(!rightData.isBST||rightData.min<=x.value)) {
//如果右树 有信息 并且右树已经不是搜索二叉树了违规;或者右树有信息 并且左树最小值小于等于x.value,就违规
isBST=false;
}
/**或者使用三目运算,
* boolean isBST=false;//先认为是false,寻找成立条件
* if(
* (leftData!=null?(leftData.isBST&&leftData.max<x.value):true)
* &&
* (rightData!=null?(rightData.isBST&&rightData.min>x.value):true)
* ){
* isBST=true;
* }
* */
return new ReturnData(isBST,min,max);
}
(2) isCBT、a に右があるが左がない場合は false、b の場合、a が規則に違反していない場合、左と右の子が両方でないのは初めてで、次はすべて葉ノードでなければなりません
//当前代码是完全二叉树的判断
public boolean isCBT(Node head) {
//采用宽度优先遍历
if(head==null) {
return false;
}
LinkedList<Node> queue=new LinkedList<>();
boolean leaf=false;//是否遇到过左右孩子不双全的节点,一旦遇到,就一直是true了,因为将诶下来都是叶子结点
Node l=null;
Node r=null;
queue.add(head);
while(!queue.isEmpty()) {
head=queue.poll();
l=head.left;
r=head.right;
if(
(l==null&&r!=null)//第一种,有右无左
||//如果遇到了左右孩子不双全的结点之后,又发现当前结点居然有孩子,也可以这样写(leaf&&!(l==null||r==null))
(leaf&&(l!=null||r!=null))//遇到了左右孩子不双全的结点(这种结点至多只能有一个),该结点它还有孩子,则返回false
) {
return false;
}
if(l!=null) {
//l!=null,r!=null,l==null||r==null相当于宽度优先遍历
queue.add(l);
}
if(r!=null) {
queue.add(r);
}
if(l==null||r==null) {
leaf=true;
}
}
return true;
}
(3) 完全な二分木の判定 木
の最大深さ L、木のノード数 N を求め、2 の L 乗 -1=N を満たす場合、真であり、再帰ルーチンで解きます
。
//判断是否是满二叉树,需要的信息,树的高度h和结点个数n,
public class Info{
public int height;
public int nodes;
public Info(int h,int n) {
height=h;
nodes=n;
}
}
public boolean FullBT(Node head) {
if(head==null) {
return true;
}
Info data=process3(head);//收整棵树的两个信息
//判断,n是否等于2的L次方-1
boolean res=(data.nodes==1<<data.height-1)?true:false;
return res;
}
public Info process3(Node x) {
if(x==null) {
return new Info(0,0);
}
Info leftInfo=process3(x.left);//先向左树要信息
Info rightInfo=process3(x.right);//再向右树要
//加工自己的信息,树的高度及结点个数
int height=Math.max(leftInfo.height, rightInfo.height)+1;
int nodes=leftInfo.nodes+leftInfo.nodes;
return new Info(height,nodes);
}
(4) バランスのとれた二分木かどうかの判定、二分木の再帰ルーチン
//是否是平衡二叉树
public boolean isBalanced(Node head) {
return process(head).isBalanced;
}
public class ReturnType{
public boolean isBalanced;
public int height;
public ReturnType(boolean isB,int hei){
isBalanced=isB;
height=hei;
}
}
public ReturnType process(Node x) {
if(x==null) {
return new ReturnType(true,0);
}
ReturnType leftData=process(x.left);
ReturnType rightData=process(x.right);
int height=Math.max(leftData.height, rightData.height)+1;
boolean isBalanced=leftData.isBalanced&&rightData.isBalanced
&&Math.abs(leftData.height- rightData.height)<2;
return new ReturnType(isBalanced,height);
}
ルーチンは木型 DP を解くことができる (左の木から情報を求める必要があり、頭の情報を解くために右の木からも情報を求めたい) – 木型 DP トピックを検索する準備をする練習
トピック: 2 つのバイナリ ツリー ノード node1 と node2 が与えられた場合、それらの最も低い共通の祖先ノードを見つけます
。
//题目:给定两个二叉树的结点node1和node2,找到他们的最低公共祖先结点
public Node LowCommonAncestor(Node head,Node o1,Node o2) {
HashMap<Node,Node> fatherMap=new HashMap<>();
fatherMap.put(head, head);//大头结点的父是他自己
process4(head,fatherMap);//这样一来所有结点的父就都找到了
HashSet<Node> setO1=new HashSet<>(); //记录o1往上的链
Node cur=o1;
while(cur!=fatherMap.get(cur)) {
//只有头结点的父节点才是他自己,当跳到head,结束循环
setO1.add(cur);//把o1加入set
cur=fatherMap.get(cur);//o1往上窜
}
setO1.add(head);//最后把head加入set
Node cur2=o2;//让 o2一直网上窜去setO1里寻找o1,找到就返回
if(!setO1.contains(cur2)) {
cur2=fatherMap.get(cur2);
}
return cur2;
}
2番目の解決策は難しすぎて理解できません
トピック 4
シリアライゼーションとデシリアライゼーション
シリアライゼーション: メモリ内のツリーがハードディスク上の文字列になる
デシリアライゼーション: ハードディスク上の文字列がメモリ内のツリーになる
シリアライゼーション コード
//以head为头的树,使用先序遍历,序列化为字符串返回
public String serialByPre(Node head) {
if(head==null) {
return "#_";
}
String res=head.value+"_";
res+=serialByPre(head.left);
res+=serialByPre(head.right);
return res;
}
シリアル化テスト コード
public static void main(String[] args) {
XuLieHua xl=new XuLieHua();
Node node1=xl.new Node(1);
Node node2=xl.new Node(1);
Node node3=xl.new Node(1);
node1.right=node2;
node2.left=node3;
String xuliehuaRES=xl.serialByPre(node1);
System.out.println(xuliehuaRES);
}
テスト結果をシリアル化 – このような文字列に
逆シリアル化コード
//反序列化-由字符串建树
public Node reconByPreString(String preStr) {
String[] values=preStr.split("_");
Queue<String> queue=new LinkedList<>();
for(int i=0;i<values.length;i++) {
queue.add(values[i]);
}
return reconPreOrder(queue);
}
public Node reconPreOrder(Queue<String> queue) {
//不断消费一个队列
String value=queue.poll();
if(value.equals("#")) {
return null;
}
Node head=new Node(Integer.valueOf(value));//先建头
head.left=reconPreOrder(queue);//再建左子树
head.right=reconPreOrder(queue);//最后建右子树
return head;
}
逆シリアル化テスト コード
public static void main(String[] args) {
XuLieHua xl=new XuLieHua();
Node head=xl.reconByPreString("1_#_1_1_#_#_#_");
System.out.println(head.value);
}
テスト結果: 返されたヘッド ノードの値は 1 です