硬核总结!真二叉树、满二叉树、完全二叉树的性质与概念_IT界的泥石流的博客-CSDN博客_真二叉树转载一篇博客来记录一些二叉树的基本概念等,下面主要说明二叉树的实现。
深度:表示从根结点到目标结点的路径的长度
高度:等于最深的结点的深度+1
层数:等于深度
叶节点:没有非空子树的结点
分支结点:至少有一个非空子树的结点(内部节点)
定理1:满二叉树定理:非空满二叉树的叶结点数等于其分支结点数+1
定理2:一棵非空二叉树空子树的数目等于其结点数目+1(大约一半的指针浪费在存储null值上)
结构性开销:指为了实现数据结构所花费的空间
满二叉树空间约有2/3被结构性开销占据,去掉叶节点中的指针会省下大量空间p/(p+d)
构建、前中后序遍历、删除
遍历:
前:根左右
中:左根右
后:左右根
删除思路:1.因为二叉树是单向的,所以判断当前节点的子节点是否为需要删除的节点而无法判断当前节点是不是需要删除的节点
2.如果当前节点的左子节点不为空,并且左子节点为需要删除的节点,则this.left=null,并返回结束递归;
3.如果当前节点的右子节点不为空且右子节点就是需要删除的节点,同上
4.如果前两步没有完成删除,则先左后右对子树递归删除。 */
class BinaryTree{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
public void delNode(int no){
if(root!=null){
//如果只有root一个节点,就立即判断此节点是否是需要删除的节点
if(root.getNo()==no){
root=null;
}else {
root.delNode(no);
}
}
else {
System.out.println("空树,无法进行删除操作");
}
}
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}
else {
System.out.println("二叉树为空无法遍历");
}
}
public void infixOrder(){
if (this.root!=null){
this.root.infixOrder();
}
else {
System.out.println("二叉树为空无法遍历");
}
}
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}
else {
System.out.println("二叉树为空无法遍历");
}
}
}
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no,String name){
this.no=no;
this.name=name;
}
public void setNo(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getLeft() {
return left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(){
return "HeroNode[no="+no+" name="+name+"]";
}
//递归删除节点:如果删除的节点是叶子节点,则删除该节点;如果删除的节点是非叶子节点,则删除该子树
public void delNode(int no){
/* 思路:1.因为二叉树是单向的,所以判断当前节点的子节点是否为需要删除的节点而无法判断当前节点是不是需要删除的节点
2.如果当前节点的左子节点不为空,并且左子节点为需要删除的节点,则this.left=null,并返回结束递归;
3.如果当前节点的右子节点不为空且右子节点就是需要删除的节点,同上
4.如果前两步没有完成删除,则先左后右对子树递归删除。 */
if(this.left!=null&&this.left.no==no){
this.left=null;
return;
}
if (this.right!=null&&this.right.no==no){
this.right=null;
return;
}
if (this.left!=null){
this.left.delNode(no);
}
if (this.right!=null){
this.right.delNode(no);
}
}
public void preOrder(){
System.out.println(this);
if (this.left!=null ){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
}
public void infixOrder(){
if (this.left!=null ){
this.left.preOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.preOrder();
}
}
public void postOrder(){
if (this.left!=null ){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
System.out.println(this);
}
}
顺序存储二叉树:
数组实现完全二叉树如上
左子节点=n-1
右子节点:n+1
应用实例:堆排序
前序遍历:依次输出该下标对应的左(2n+1)与右(2n+2)递归。
class ArrBinaryTree{
private int[] arr;
public ArrBinaryTree(int[]arr){
this.arr=arr;
}
//前序遍历 index表示数组的下标
public void preOrder(int index){
if(arr==null||arr.length==0){
System.out.println("数组为空,无法遍历");
}
System.out.println(arr[index]);
//向左递归遍历
if ((index*2+1)<arr.length){
preOrder(2*index+1);
}
//向右递归遍历
if ((index*2+1)<arr.length){
preOrder(2*index+2);
}
}
}
线索化二叉树
package dsanda.threadedbinarytree;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
//测试中序线索二叉树
HeroNode root=new HeroNode(1,"tom");
HeroNode node2=new HeroNode(3,"tom3");
HeroNode node3=new HeroNode(6,"tom6");
HeroNode node4=new HeroNode(8,"tom8");
HeroNode node5=new HeroNode(10,"tom10");
HeroNode node6=new HeroNode(14,"tom14");
//二叉树后面递归创建,此次手动
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试中序线索化
ThreadedBinaryTree threadedBinaryTree=new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
threadedBinaryTree.threadedNodes();
//以十号节点测试(node5)
HeroNode leftNode =node5.getLeft();
HeroNode rightNode=node5.getRight();
System.out.println("10的前驱节点是"+leftNode);
System.out.println("10的后继节点为"+rightNode);
}
}
//定义ThreadeedBinaryTree实现线索化功能的二叉树
class ThreadedBinaryTree{
private HeroNode root;
private HeroNode pre = null;
public void setRoot(HeroNode root) {
this.root = root;
}
public void threadedNodes(){
this.threadedNodes(root);
}
//编写对二叉树进行 中序 线索化的方法
public void threadedNodes(HeroNode node){
if (node==null){
return;
}
//先线索化左子树
threadedNodes(node.getLeft());
//线索化当前节点
//处理当前节点的前驱节点
if (node.getLeft()==null){
//指向前驱节点
node.setLeft(pre);
node.setLeftType(1);
}
//处理后继节点
if (pre!=null&&pre.getRight()==null){
//让前驱节点的右指针指向当前节点
pre.setRight(node);
pre.setRightType(1);
}
//每处理一个节点后,让当前节点成为下一个节点的前驱节点
pre=node;
//线索化右子树
threadedNodes(node.getRight());
}
public void delNode(int no){
if(root!=null){
//如果只有root一个节点,就立即判断此节点是否是需要删除的节点
if(root.getNo()==no){
root=null;
}else {
root.delNode(no);
}
}
else {
System.out.println("空树,无法进行删除操作");
}
}
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}
else {
System.out.println("二叉树为空无法遍历");
}
}
public void infixOrder(){
if (this.root!=null){
this.root.infixOrder();
}
else {
System.out.println("二叉树为空无法遍历");
}
}
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}
else {
System.out.println("二叉树为空无法遍历");
}
}
}
class HeroNode {
private int no;
private String name;
private HeroNode left;
private HeroNode right;
//如果lefttype==0表示指向左子树,如果是1则表示指向前驱节点。righttype规则相同。
private int leftType;
private int rightType;
public HeroNode(int no, String name){
this.no=no;
this.name=name;
}
public void setNo(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getLeft() {
return left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
@Override
public String toString(){
return "HeroNode[no="+no+" name="+name+"]";
}
//递归删除节点:如果删除的节点是叶子节点,则删除该节点;如果删除的节点是非叶子节点,则删除该子树
public void delNode(int no){
/* 思路:1.因为二叉树是单向的,所以判断当前节点的子节点是否为需要删除的节点而无法判断当前节点是不是需要删除的节点
2.如果当前节点的左子节点不为空,并且左子节点为需要删除的节点,则this.left=null,并返回结束递归;
3.如果当前节点的右子节点不为空且右子节点就是需要删除的节点,同上
4.如果前两步没有完成删除,则先左后右对子树递归删除。 */
if(this.left!=null&&this.left.no==no){
this.left=null;
return;
}
if (this.right!=null&&this.right.no==no){
this.right=null;
return;
}
if (this.left!=null){
this.left.delNode(no);
}
if (this.right!=null){
this.right.delNode(no);
}
}
public void preOrder(){
System.out.println(this);
if (this.left!=null ){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
}
public void infixOrder(){
if (this.left!=null ){
this.left.infixOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.infixOrder();
}
}
public void postOrder(){
if (this.left!=null ){
this.left.postOrder();
}
if (this.right!=null){
this.right.postOrder();
}
System.out.println(this);
}
}
遍历线索化二叉树
二叉树线索化后各个节点可以通过线性的方式遍历而无需递归,给出中序遍历的代码。
public void threadedList(){
//定义一个变量存储当前便利的节点。从root开始
HeroNode node=root;
while (node!=null){
//循环的找到leftTYPE=1的节点,后面会随遍历而变化,lefttype=1时
//说明该节点时线索化处理后的有效节点
while (node.getLeftType()==0){
node=node.getLeft();
}
System.out.println(node);
//如果当前节点的右指针指向后继节点,就一直输出
while (node.getRightType()==1){
//获取到当前节点得到后继节点
node=node.getRight();
System.out.println(node);
}
node=node.getRight();
}
}
BST二叉排序树/二叉搜索树
BST中任何一个结点,设其值为K,则该结点左子树中任意一个结点的值都小于K,该结点右子树中任意一个结点的值都大于或等于K。按照中序遍历,则将获得从小到大的排序。
效率体现:检索只需检索两个子树之一(完成所需值与根节点的大小判断)
构建与遍历:
package dsanda.BinarySortTree;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr={7,3,10,12,5,1,9};
BinarySortTree binarySortTree =new BinarySortTree();
for (int i=0;i< arr.length;i++){
binarySortTree.add(new Node(arr[i]));
}
System.out.println("中序遍历二叉树");
binarySortTree.infixOrder();
}
}
class BinarySortTree{
private Node root;
public void add(Node node){
if (root==null){
root =node;
}
else {
root.add(node);
}
}
public void infixOrder(){
if (root!=null){
root.infixOrder();
}
else {
System.out.println("为空");
}
}
}
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value=value;
}
@Override
public String toString(){
return "Node [value="+value+"]";
}
//add node (递归) 满足二叉排序树的要求
public void add(Node node){
if (node==null){
return;
}
if (node.value<this.value){
if (this.left==null){
this.left=node;
}
else {
this.left.add(node);
}
}
else {
if (this.right==null){
this.right=node;
}
else {
this.right.add(node);
}
}
}
public void infixOrder(){
if (this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.infixOrder();
}
}
}
删除操作有多种情况
(1)删除叶子结点(2.5.9.12)
①找到需要删除的结点
②找到要删除的结点的父节点
③判断是左子节点还是右子节点然后置为null
(2)删除只有一颗子树的结点(1)
①找到需要删除的结点
②找到要删除的结点的父节点
③确定target的子节点是左子结点还是右子节点
④ 确定target是parent的左子节点还是右子节点,然后将target的左/右子结点作为parent的左/
右子节点
(3)删除有两棵树的结点(7,3,10)
①找到需要删除的结点
②找到要删除的结点的父节点
③从targetNode的右子树找到最小的结点
④用一个临时变量将最小结点保存为temp
⑤删除改最小结点
⑥targetNode.value =temp
public int delRightTreeMin(Node node){
Node target=node;
//循环查找左节点 找到最小的值
while (target.left!=null){
target=target.left;
}//使target指向最小节点并删除
delNode(target.value);
return target.value;
}
public void delNode(int value){
if (root==null){
return;
}
else {
//招目标节点
Node targetNode =search(value);
if (targetNode==null){
return;//not found
}
if (root.right==null&&root.left==null){
root=null;// the targetNode is the root
return;
}
//find the parentNode of targetNode
Node parent =searchParent(value);
//一 叶子节点
if (targetNode.left==null&&targetNode.right==null){
//判断tagetnode是左还是右结点
if(parent.left!=null&&parent.left.value==value){
parent.left=null;
}else if (parent.right!=null&&parent.right.value==value){
parent.right=null;
}
}else if (targetNode.left!=null&&targetNode.right!=null){
int minVal =delRightTreeMin(targetNode.right);
targetNode.value=minVal;
}//两棵子树
else {//一棵子树
//如果要删除的结点有左子节点
if (targetNode.left != null) {
if (parent!=null){
if (parent.left.value == value) {
parent.left = targetNode.left;
} else {
parent.right = targetNode.left;
}
}else {root =targetNode.left;}
} else {
if (parent!=null) {
if (parent.left.value == value) {
parent.left = targetNode.right;
} else {
parent.right = targetNode.right;
}
}else {
root=targetNode.right;
}
}
}
}
}
堆
堆使具有以下性质的完全二叉树:
每个结点的值都大于或等于其左右孩子结点的值的称大值堆
每个结点的值都小于等于其左右孩子节点的值称小值堆
堆排序:利用堆数据结构设计的排序算法,是一种选择排序
最好最坏平均时间复杂度均为O(nlogn),是不稳定的排序
小值堆:
堆排序的基本思想:
1)将待排序的序列构造为一个大值堆 此时序列的最大值即为堆顶的根节点
2)将根节点与末尾元素交换,此时末尾为最大值
3)将剩余n-1个元素重新构造为一个堆,重复上面的操作
从第一个非叶子节点开始(下标为 arr.length/2-1)
以上图源于:尚硅谷数据结构与算法java
package dsanda;
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int arr[]={4,6,8,5,9};
heapSort(arr);
}
//编写一个堆排序的方法
public static void heapSort(int arr[]){
int temp=0;
System.out.println("使用堆排序");
// adjustHeap(arr,1,arr.length);
// System.out.println(Arrays.toString(arr));
// adjustHeap(arr,0,arr.length);
// System.out.println(Arrays.toString(arr));
for (int i=arr.length/2-1;i>=0;i--){
adjustHeap(arr,i, arr.length);
}
//将堆顶元素与末尾元素交换,将最大元素沉到数组末端 重新调整结构,使其满足堆定义,
// 然后继续交换反复执行
for (int j=arr.length-1;j>0;j--){
temp=arr[j];
arr[j]=arr[0];
arr[0]=temp;
adjustHeap(arr,0,j);
}
System.out.println(Arrays.toString(arr));
}
//将一个数组(二叉树)调整为一个大值堆 arr为待调整数组
// i是表示非叶子结点在数组中的索引 length表示对多少个元素调整(length在逐渐减小)
public static void adjustHeap(int arr[],int i,int length){
//功能:完成 将 i对应的非叶子节点的树调整为大值堆
int temp=arr[i];//取出当前元素值保存
//说明 k是i的左子节点
for (int k=i*2+1;k<length;k=k*2+1){
if (k+1<length&&arr[k]<arr[k+1]){//说明左子节点小于右子节点
k++;//K指向右子节点
}
if (arr[k]>temp){
arr[i] = arr[k];
i =k;//如果子节点大于父节点 把较大的值赋给当前结点 i指向k继续循环比较
}else {
break;//
}
}//当for循环结束后,我们已将以i为结点树中的最大值放在了最顶上(局部调整为大值堆)
arr[i]=temp;
}
}