二叉树与优先级队列

1.树

树是由n个数据构成的非线性结构,它是根朝上,叶朝下。

注意:树形结构之中,子树之间不能连接,不然就不构成树形结构

1.子树之间没有交集

2.除了根节点以外,每一个节点有且只有一个父亲节点

3.一个n个节点的树,有n-1条边

1.1树的一些概念

结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为6

树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为6

叶子结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等节点为叶结点 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点

根结点:一棵树中,没有双亲结点的结点;如上图:A 

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4 树的以下概念只需了解,

非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等节点为分支结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

森林:由m(m>=0)棵互不相交的树组成的集合称为森林

2.二叉树

2.1概念

一棵二叉树的节点是有限集合

对于任意的二叉树,都是由下列情况组合而成

2.2两种特殊的二叉树

1.满二叉树:一棵二叉树树,每一层的节点数都到达了最大值,那么这就是满二叉树。也就是说如果一棵二叉树是k层,那么这棵树的节点树,且结点总数是2^{k}-1,则它就是满二叉树。

2.完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.3二叉树的性质

1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有(i>0)个结点

2. 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^{k}-1(k>=0)

3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

4. 具有n个结点的完全二叉树的深度k为\log_{2}n+1上取整

5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,对于序号为i 的结点有:

                若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点

              若2i+1<n  左孩子序号:2i+1,否则没有左孩子

              若2i+2<n  右孩子序号:2i+1,否则没有右孩子

2.4二叉树的存储

二叉树的存储结构分为:顺序存储和类似于链表的链式存储。

二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下

//孩子表示法
class Node {
     int val;  //用于储存数据
     Node left; //表示左孩子,通常为以左子树为根节点的二叉树
     Node right;  //表示右孩子,通常为右子树为根节点的二叉树
}
// 孩子双亲表示法
class Node {
  int val;        // 数据域
  Node left;      // 左孩子的引用,常常代表左孩子为根的整棵左子树
  Node right;     // 右孩子的引用,常常代表右孩子为根的整棵右子树
  Node parent;    // 当前节点的根节点
}

2.2二叉树的基本操作

存储方式是孩子表示方法

BinaryTree类

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class BinaryTree {
    public static class TreeNode {
        public int val;
        public TreeNode left;//储存左子树
        public TreeNode rigth;//储存右子树

        public TreeNode(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return "TreeNode{" +
                    "val=" + val +
                    '}';
        }
    }

    //创建一个二叉树
    public TreeNode createbinarytree() {
        TreeNode A = new TreeNode(1);
        TreeNode B = new TreeNode(2);
        TreeNode C = new TreeNode(3);
        TreeNode D = new TreeNode(4);
        TreeNode E = new TreeNode(5);
        TreeNode F = new TreeNode(6);
        TreeNode G = new TreeNode(7);
        TreeNode H = new TreeNode(8);

        //连接数的结构
        A.left = B;
        A.rigth = C;
        B.left = D;
        B.rigth = E;
        C.left = F;
        C.rigth = G;
        E.rigth = H;
        return A;
    }

    //先实现三种遍历方法
    // 前序遍历
    public void preOrder(TreeNode root) {
        //NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点--->根的左子树--->根的右子树。
        //递归的结束条件
        if (root == null) {
            return;
        }
        System.out.print(" " + root.val);
        preOrder(root.left);
        preOrder(root.rigth);
        //走到结尾这个循环结束
    }
    //不使用递归的的方法实现前序遍历
    public void preOrder1(TreeNode root){
        //借用栈来实现
        if(root==null){
            return ;
        }
        Stack<TreeNode> stack=new Stack<>();
        TreeNode cur=root;
        while(cur!=null||!stack.isEmpty()){
            while(cur!=null){
                stack.push(cur);
                System.out.print(cur.val+" ");
                cur=cur.left;
            }
            TreeNode top=stack.pop();

            cur=top.rigth;
        }
        }


    // 中序遍历
    public void inOrder(TreeNode root) {
        //LNR:中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。
        if (root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(" " + root.val);
        inOrder(root.rigth);

    }
    //不使用递归的的方法实现中序遍历
    public void inOrder1(TreeNode root){
        //借用栈来实现i
        if(root==null){
            return ;
        }
        Stack<TreeNode> stack=new Stack<>();
        TreeNode cur=root;
        while(cur!=null||!stack.isEmpty()){
            while(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            TreeNode top=stack.pop();
            System.out.print(top.val+" ");
            cur=top.rigth;
        }
    }

    // 后序遍历
    public void postOrder(TreeNode root) {
        //LRN:后序遍历(Postorder Traversal)——根的左子树--->根的右子树--->根节点。
        if (root == null) {
            return;
        }
        postOrder(root.left);
        postOrder(root.rigth);
        System.out.print(" " + root.val);
    }
    //使用不递归的方法使用后序遍历
    public void postOrder1(TreeNode root){
        //借用栈来实现
        if(root==null){
            return ;
        }
        Stack<TreeNode> stack=new Stack<>();
        TreeNode cur=root;
        TreeNode prve=null;
        while(cur!=null||!stack.isEmpty()){
            while(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            TreeNode top=stack.peek();
            if(top==null||top.rigth==prve){
                System.out.println(top.val+" ");
                prve= stack.pop();
            }else{
                cur=top.rigth;
            }

        }
    }

    //获取数中节点个数
    public int nodesize;

    //第一种遍历式获取
    public void Nodesize1(TreeNode root) {
        //采用前序遍历
        if (root == null) {
            return;
        }
        //遇见节点将打印改成nodesize++
        nodesize++;
        Nodesize1(root.left);
        Nodesize1(root.rigth);
    }
    //第二种获取节点个数方法
    //左子树节点个数+右子树节点个数+根节点个数
    public int Nodesize2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Nodesize2(root.left) + Nodesize2(root.rigth) + 1;
    }

    // 获取叶子节点的个数
    //依旧两种方法
    //第一遍种历二叉树
    public int getLeafNodeCount;

    public void getLeafNodeCount1(TreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.rigth == null) {
            getLeafNodeCount++;
        }
        getLeafNodeCount1(root.left);
        getLeafNodeCount1(root.rigth);
    }

    //第二种获取叶子节点个数
    //左子树叶子+右子树叶子节点个数
    public int getGetLeafNodeCount2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.rigth == null) {
            return 1;
        }
        return getGetLeafNodeCount2(root.left) + getGetLeafNodeCount2(root.rigth);
    }

    // 获取第K层节点的个数
    public int getKLevelNodeCount(TreeNode root, int k) {
        //统计k层的节点个数,先找到第k-1层
        if (k == 1) {
            if (root == null) {
                return 0;
            } else {
                return 1;
            }
        }
        return getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.rigth, k - 1);

    }
    // 获取二叉树的高度
    public int getHeight(TreeNode root) {
        //二叉树的高度等于左子树高度和右子树高度的最大值
        if(root==null) {
            return 0;
        }
        return getHeight(root.left)>getHeight(root.rigth)?getHeight(root.left)+1:getHeight(root.rigth)+1;
    }
    // 检测值为value的元素是否存在
    public TreeNode find(TreeNode root, int val){
        if(root.val==val){
            return root;
        }
        if(root.left!=null){
            TreeNode leftT=find(root.left,val);
            if(leftT!=null){
                return leftT;
            }
        }
      if(root.rigth!=null){
          TreeNode rigthT =find(root.rigth,val);
          if(rigthT!=null){
              return rigthT;
          }
      }
        return null;
    }
    //层序遍历
    public void levelOrder(TreeNode root){
        if(root==null){
            return;
        }
        //创建一个队列
        Queue<TreeNode> queue=new LinkedList<>();
        queue.offer(root);
        TreeNode cur=root;
        while(!queue.isEmpty()){
            //先将队列元素弹出
            cur=queue.poll();
            System.out.print(cur.val+" ");
            if(cur.left!=null){
                queue.offer(cur.left);
            }
            if(cur.rigth!=null){
                queue.offer(cur.rigth);
            }
        }
        System.out.println();
    }

    // 判断一棵树是不是完全二叉树
   public  boolean isCompleteTree(TreeNode root){
       //完全二叉树从左到右依次填满
       Queue<TreeNode> queue=new LinkedList<>();
       TreeNode cur=root;
       queue.offer(cur);
       while(queue.peek()!=null){
           cur=queue.poll();
           queue.offer(cur.left);
           queue.offer(cur.rigth);
       }
       while(!queue.isEmpty()){
           if(queue.poll()!=null){
               return false;
           }
       }
       return true;
    }

    public static void main(String[] args) {

    }
}

Test类

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Test {
    public static void main(String[] args) {
        //调用创建二叉树
        BinaryTree binaryTree=new BinaryTree();
        //调用类中2的成员变量
      BinaryTree.TreeNode root=binaryTree.createbinarytree();
      //测试前序遍历打印
        binaryTree.preOrder(root);
      System.out.println();
        //  //测试前序遍历打印1
      binaryTree.preOrder1(root);
      System.out.println();
      System.out.println("==============================");
        System.out.println();
        //测试中序遍历
        binaryTree.inOrder(root);
      System.out.println();
      //测试中序遍历的非递归方法
      binaryTree.inOrder1(root);
      System.out.println("===============================");
        System.out.println();
        //测试后序遍历
        binaryTree.postOrder(root);
      System.out.println();
      //测试后续遍历的非递归方法
      binaryTree.postOrder1(root);
      System.out.println("=========================");
        System.out.println();
        //打印节点个数

        binaryTree.Nodesize1(root);
        System.out.println("二叉树结点个数:"+binaryTree.nodesize);
        //测试第二种打印节点个数方法
        System.out.println("二叉树结点个数:"+binaryTree.Nodesize2(root));
        //测试第一种叶子节点个数
        binaryTree.getLeafNodeCount1(root);
        System.out.println("二叉树叶子节点个数:"+binaryTree.getLeafNodeCount);
        //测试第二种叶子节点个数
        System.out.println("二叉树叶子节点个数:"+binaryTree.getGetLeafNodeCount2(root));
        //测试统计第k层的节点个数
        System.out.println("第1层的节点个数为"+binaryTree.getKLevelNodeCount(root, 1));
        System.out.println("第2层的节点个数为"+binaryTree.getKLevelNodeCount(root, 2));
        System.out.println("第3层的节点个数为"+binaryTree.getKLevelNodeCount(root, 3));
        System.out.println("第4层的节点个数为"+binaryTree.getKLevelNodeCount(root, 4));
        //测试计算二叉树的高度
        System.out.println("二叉树的高度为:"+binaryTree.getHeight(root));
        //测试二叉树的是否存在的元素
      List<List<Integer>> llist=new ArrayList<List<Integer>>();
      List<Integer> list1=new ArrayList<>();
      llist.add(list1);
      Queue<Integer> queue=new LinkedList<>();
      queue.offer(1);
      System.out.println(queue.poll());
      System.out.println("元素1存在节点"+binaryTree.find(root, 1)+"中");
      System.out.println("元素4存在节点"+binaryTree.find(root, 4)+"中");
      System.out.println("元素8存在节点"+binaryTree.find(root, 8)+"中");
      System.out.println("元素7存在节点"+binaryTree.find(root, 7)+"中");
      //测试二叉树的层序遍历
      binaryTree.levelOrder(root);
      //判断二叉树是否是完全二叉树

      System.out.println("判断这棵树是否是完全二叉树 "+binaryTree.isCompleteTree(root));

    }
}

3.优先队列(PriorityQueue)

3.1堆的性质

堆逻辑上是一颗完全二叉树,物理上是存储在数组中

总结:一颗完全二叉树以层序遍历方式放入数组中存储,这种方式的主要用法就是堆的表示。

  关于堆下标位置的计算

如果已知父亲(parent) 的下标,

则: 左孩子(left) 下标 = 2 * parent + 1;

       右孩子(right) 下标 = 2 * parent + 2;

已知孩子(不区分左右)(child)下标,

则:    双亲(parent) 下标 = (child - 1) / 2;

3.2堆的分类

大堆:根节点大于左右两个子节点的完全二叉树 (父亲节点大于其子节点),叫做大堆,或者大根堆,或者最大堆 。

小堆:根节点小于左右两个子节点的完全二叉树叫 小堆(父亲节点小于其子节点),或者小根堆,或者最小堆。

3.3堆的底层代码实现

创建小根堆

创建大根堆

向上调整

现在有一个堆,我们需要在堆的末尾插入数据,再对其进行调整,使其仍然保持堆的结构,这就是向上调整。

以大堆为例:

向下调整

堆的删除和添加

PriorityQueue类

import java.lang.reflect.Array;
import java.util.Arrays;

public class PriorityQueue {
    public int[] elem;
    public int usedsize;

    public PriorityQueue() {
        this.elem=new int[10];
    }
    public void initelem(int [] array){
        for(int i=0;i<array.length;i++){
            this.elem[i]=array[i];
            usedsize++;
        }
    }
    public void elempeek(){
        for(int i=0;i<this.usedsize;i++){
            System.out.print(elem[i]+" ");
        }
    }
    //创建小根堆
    public void Smallrootpiles(){
        //小根堆的节点数要大于子树
        //第一步先确定有多少个树,利用最后一个节点来找到父亲节点
        for(int parent=(usedsize-1-1)/2;parent>=0;parent--){
            //也是一样采用向下调整的方法
            siftDown2(parent,usedsize);
        }
    }
    public void siftDown2(int parent,int usedsize){
        //parent:代表调整子树的起始位置
        //usedsize:判断子树是否还需要调整
        //1.根据父亲节点,找到左子树孩子节点
        int child=parent*2+1;
        //2.找到孩子之间的最小值
        while(child<usedsize){
            if(child+1<usedsize&&elem[child]>elem[child+1]){
                child++;
            }
            if(elem[child]<elem[parent]){
                int num=elem[parent];
                elem[parent]=elem[child];
                elem[child]=num;
                parent=child;
                child=child*2+1;
            }else{
                break;
            }
        }
    }

    //创建大根堆
    public void  createhead(){
        //大根堆的节点数要大于子树
        //第一步先确定有多少个树,利用最后一个节点来找到父亲节点
        for(int parent=(usedsize-1-1)/2;parent>=0;parent--){
            //采用向下调整
            siftDown1(parent,usedsize);
        }
    }
    //parent:代表调整子树的起始位置
    //usedsize:判断子树是否还需要调整
    public void siftDown1(int parent,int usedsize){
        //根据父亲节点找到左子树节点
        int child=parent*2+1;
        while(child<usedsize){
            if(child+1<usedsize&&elem[child]<elem[child+1]){
                child++;
            }
            if(elem[child]>elem[parent]){
                int num=elem[parent];
                elem[parent]=elem[child];
                elem[child]=num;
//                elem[child]=elem[child]^elem[parent];
//                elem[parent]=elem[child]^elem[parent];
//                elem[child]=elem[child]^elem[child];
                parent=child;
                child=child*2+1;
            }else{
                break;
            }
        }
    }
    //向堆中增加元素
    public void push(int val) {
        //先进行判满处理
        if (isFull()) {
            //进行扩容
            elem = Arrays.copyOf(elem, elem.length * 2);
        }
        elem[usedsize] = val;
        usedsize++;
        siftUp(usedsize - 1);
    }
    //向上调整
    public void siftUp(int child) {
        //孩子节点找到父亲节点
        int parent=(child-1)/2;
        //向上调整
        while(parent>=0){
            if(elem[child]>elem[parent]){
                int num=elem[parent];
                elem[parent]=elem[child];
                elem[child]=num;
                child=parent;
                parent=(parent-1)/2;
            }else{
                break;
            }
        }
    }
    public boolean isFull(){
        if(usedsize==this.elem.length){
            return true;
        }
        return false;
    }
    //删除堆的元素
    public int poll(){
        if(isEmpty()){
            return -1;
        }
        int val=elem[0];
        //交换0位置和最后一个节点位置
        elem[0]=elem[usedsize-1];
        elem[usedsize-1]=val;
        //对0节点位置进行向下调整
        siftDown1(0,usedsize-1);
        usedsize--;
        return val;
    }
    //判段是否为空
    public boolean isEmpty(){
        if (usedsize==0){
            return true;
        }
        return false;
    }
    //对节点进行排从小到大排序(再节点本身上排序)
    public void headsort(){
        //先对数据进行建大堆
        this.createhead();
        int end=usedsize-1;
        while(end>0){
            int num=elem[0];
            elem[0]=elem[end];
            elem[end]=num;
            siftDown1(0,end);
            end--;
        }


    }
    public static void main(String[] args) {

    }
}

Test类

public class Test {
    public static void main(String[] args) {
        //对队进行初始化
        PriorityQueue priorityQueue=new PriorityQueue();
        int[] array={ 27,15,19,18,28,34,65,49,25,37 };

        priorityQueue.initelem(array);
        //测试建立小根堆
        priorityQueue.Smallrootpiles();
        priorityQueue.elempeek();
        System.out.println();
        //测试大根堆的实现
        priorityQueue.createhead();
        priorityQueue.elempeek();
        //测试插入一个元素
        priorityQueue.push(80);
        System.out.println();
        priorityQueue.elempeek();
        //测试删除一个元素
        priorityQueue.poll();
        System.out.println();
        priorityQueue.elempeek();
        System.out.println();
        //测试堆排序
        priorityQueue.headsort();
        priorityQueue.elempeek();
        //
    }
}

3.4堆的问题

3.4.1topk问题

给你6个数据,求前3个最大数据。这时候我们用堆怎么做的?

解题思路:

1、如果求前K个最大的元素,要建一个小根堆。

2、如果求前K个最小的元素,要建一个大根堆。

3、第K大的元素。建一个小堆,堆顶元素就是第K大的元素。

4、第K小的元素。建一个大堆,堆顶元素就是第K大的元素。

3.4.2数组的排序

再者说如果要对一个数组进行从小到大排序,要借助大根堆还是小根堆呢?

---->大根堆

3.5PriorityQueue

使用注意:

1. 使用时必须导入PriorityQueue所在的包,即:

import java.util.PriorityQueue; 

2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常

3. 不能插入null对象,否则会抛出NullPointerException

4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

5. 插入和删除元素的时间复杂度为

6. PriorityQueue底层使用了堆数据结构

7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

1.优先级队列的构造


 
static void TestPriorityQueue(){
        // 1.创建一个空的优先级队列,底层默认容量是11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
 
        // 2.创建一个空的优先级队列,底层的容量为initialCapacity
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
 
        ArrayList<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(3);
        list.add(2);
        list.add(1);
 
        // 用ArrayList对象来构造一个优先级队列的对象
        // q3中已经包含了三个元素
        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());
    }

//注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器


 // 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
 }
 
public class TestPriorityQueue {
    public static void main(String[] args) {
        PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
        p.offer(4);
        p.offer(3);
        p.offer(2);
        p.offer(1);
        p.offer(5)

自定义排序规则

1.该改变升序降序

2.根据HashMap的value值对HashMap的键值对排序

方法摘要

 
        System.out.println(p.peek());
    }
 }
 static void TestPriorityQueue2(){
    int[] arr = {4,1,9,2,8,0,7,3,6,5};
 
    // 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
    // 否则在插入时需要不多的扩容
    // 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
    PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
    for (int e: arr) {
        q.offer(e);
    }
 
    System.out.println(q.size());   // 打印优先级队列中有效元素个数
    System.out.println(q.peek());   // 获取优先级最高的元素
 
    // 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
    q.poll();
    q.poll();
    System.out.println(q.size());   // 打印优先级队列中有效元素个数
    System.out.println(q.peek());   // 获取优先级最高的元素
 
    q.offer(0);
    System.out.println(q.peek());   // 获取优先级最高的元素
 
    // 将优先级队列中的有效元素删除掉,检测其是否为空
    q.clear();
    if(q.isEmpty()){
        System.out.println("优先级队列已经为空!!!")
    }
    else{
        System.out.println("优先级队列不为空");
    }
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值