数据结构-概述

概述

数据结构在学什么

  • 如何用程序代码把现实世界的问题信息化
  • 如何用计算机高效的处理这些信息从而创造价值

数据结构的基本概念

Col1Col2
数据数据是信息的载体,是描述客观事务属性的数、字符及所有能输入到计算机中并被计算机识别和处理的符号的集合,数据是计算机程序加工的原料
数据元素数据元素是数据的基本单位,通常作为一个整体进行考虑和处理
数据项一个数据元素可由若干个数据项组成,数据项是构成数据元素的不可分割的最小单元
数据结构是相互之间存在一种或几种特定的关系的数据元素的集合
数据对象是具有相同性质的数据元素集合,是数据的一个子集
数据类型是一个值的集合和定义在此集合上的一组操作,分为①原子类型 ②结构类型
抽象数据类型是抽象数据组织及与之相关的操作,是用数学化的语言定义数据的结构,定义运算,与具体实现无关
  • 三要素
    • 逻辑结构
      • 集合
      • 线性结构
      • 树形结构
      • 图状结构
    • 物理结构
    • 数据的运算

算法

程序 = 数据结构 + 算法。数据结构是要处理的信息,算法是处理信息的步骤。

特征
  • 又穷性、确定性、可行性、输入、输出

时间复杂度

计算方式
  • ①找到一个基本操作
  • ②分析这个基本操作的执行次数x与问题规模n的关系x=f(n)
  • ③x的数量级O(x)就是算法的时间复杂度T(n)
类型

O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!)

三种复杂度
  • 最坏时间复杂度
  • 平均时间复杂度
  • 最好时间复杂度

空间复杂度

程序运行时占用的内存空间大小

线性表

顺序结构

链式结构

单链
双链
循环链
静态链

队列

单向队列
双端队列

字符串

二叉树

二叉树是具有两个子节点的树形结构。

二叉搜索树

二叉搜索树的特点是元素具有可比较性,并且左子节点小于根节点小于又子节点.

节点结构
 private static class Node<E extends Comparable<E>>{ 
     E data;
     Node<E> left;
     Node<E> right;
 }
操作类型
  • 初始化
  • 添加
  • 删除
  • 查找
  • 遍历
    • 前序遍历
    • 中序遍历
    • 后续遍历
    • 层序遍历
初始化
public class BSTree<E extends Comparable<E>> { 
    private Node<E> root;
    private size;
    
    //①初始化
    public BSTree() {
        this.root = null;
        this.size = 0;
    }
}
添加
public void add(E e) {
    root = add(e, root);
}

private Node<E> add(E e, Node<E> node) {
    //遍历到底了
    if(node == null) {
        size ++;
        return new Node(e);
    }
    
    if (e.compareTo(node.data) < 0) {
        node.left = add(e, node.left);
    } else if (e.compareTo(node.data) > 0) {
        node.right = add(e, node.right);
    }
    //如果相等,不处理,直接返回
    return node;
}
删除

删除操作相对来说比较负责,主要分为以下几种情况:

  • 没有子节点:直接删除
  • 有右子树没有左子树:把右子树直接返回
  • 有左子树没有右子树:把左子树直接返回
  • 即有左子树又有右子树:可以将左子树的最大节点或右子树的最小节点提升带删除节点的位置,然后再将子树中该节点删除
    • 以用右子树中最小节点替换待删除节点为例
      • ①从右子树中找到最小的节点node;
      • ②node.left = cur.left; node.right = cur.right;
      • ③删除cur.right的最小值;
      • ④返回node;
//查找最小节点
private Node<E> minNode(Node<E> node) {
    if (node.left == null) {
        return node;
    }
    return minNode(node.left);
}
//查找最大节点
private Node<E> maxNode(Node<E> node) {
    if (node.right = null) {
        return node;
    }
    return maxNode(node.right);
}
//删除最小节点
private Node<E> removeMin(Node<E> node) {
    if (node.left == null) {
        Node<E> right = node.right;
        node.right = null;
        size --;
        return right;
    }
    node.left = removeMin(node.left);
    return node;
}
//删除最大节点
private Node<E> removeMax(Node<E> node) {
    if (node.right == null) {
        Node<E> left = node.left;
        node.left = null;
        size --;
        return left;
    }
    node.right = removeMax(node.right);
    return node;
}

//从node树中删除指定值的节点
private Node<E> remove(E e, Node<E> node) {
    if (node == null) {
        return null;
    }
    
    if (e.compareTo(node.data) < 0) {
        node.left = remove(e, node.left);
        return node;
    } else if (e.compareTo(node.data) > 0) {
        node.right = remove(e, node.right);
        return node;
    } else {  //找到了需要删除的节点
    
        //先处理没有左子节点 或 没有右子节点的情况
        if (node.left == null) {
            Node<E> right = node.right;
            node.right = null;
            size --;
            return right;
        }
        
        if (node.right == null) {
            Node<E> left = node.left;
            node.left = null;
            size --;
            return left;
        }
        
        /**
           如果即有左子树,又有右子树:
           先找到当前节点中 右子树中的最小节点
           在调整指针,将其替换掉到待删除的节点的位置
           再从右子树中把那个最小的值的节点删除
           返回新的根节点
           
           这样可以保证删除节点后的节点值仍然满足二叉搜索树的性质
        */
        Node<E> minNode = minNode(node.right);
        minNode.left = node.left;
        minNode.right = removeMin(node.right);
        
        node.left = null;
        node.right = null;
        return minNode;
    }
}

查找
public boolean contains(E e) {
    return contains(e, root);
}

private boolean contains(E e, Node<E> node) {
    if (node == null) {
        return false;
    }
    if (e.compareTo(node.data) < 0) {
        return contains(e, node.left);
    } else if (e.compareTo(node.data) > 0) {
        return contains(e, node.right);
    }
    return true;
}
遍历
//前序遍历
public void preOrder(Node<E> node) {
    if (node == null) {
        return ;
    }  
    System.out.println(node.data);
    preOrder(node.left);
    preOrder(node.right);
}

//中序遍历
public void inOrder(Node<E> node) {
    if (node == null) {
        return;
    }
    inOrder(node.left);
    System.out.println(node.data);
    inOrder(node.right);
}

//后续遍历
public void postOrder(Node<E> node) {
    if (node == null) {
        return;
    }
    postOrder(node.left);
    postOrder(node.right);
    System.out.println(node.data);
}

//层序遍历
public void levelOrder(Node<E> node) {
    Queue<E> queue = new LinkedList<>();
    queue.add(root);
    while(!stack.isEmpty()) {
        Node<E> cur = queue.remove();
        System.out.println(cur.data);
        if (cur.left != null) {
            queue.add(cur.left);
        }
        if (cur.right != null) {
            queue.add(cur.right);
        }
    }
}
AVL树(平衡二叉树)

在平衡二叉树中涉及到两个概念:树的高度和平衡因子。树的高度就是树的最大层数,由于树具有递归性,某个节点的高度就是以该节点为根节点的树的高度,第一层节点的高度为1,向上一次递增。平衡因子指的是一个节点的左子树和右子树的高度差,如果以某个节点为根的树的该节点的平衡因子>1,这个树就处于不平衡状态,就需要通过旋转将其调整为平衡树。

          ⑧
       ⑥    ⑨
    ③ 
  ①  ②  
  
①②的高度为1, ③的高度为2, ③的平衡因子为0, ⑥的高度为3,平衡因子为2,
以⑥为根的这棵子树就处于不平衡状态

当向AVL树中添加和删除节点时,会影响树的平衡性,需要对树的平衡性进行调整。

节点结构
public static class Node<E extends Comparebale<E>> {
    E data;
    Node<E> left;
    Node<E> right;
    
    //当前节点的高度
    int height ;
}


辅助操作
//获取指定节点的高度
private int getHeight(Node<E> node) {
    if (node == null) {
        return 0;
    }
    return node.height;
}

//获取指定节点的平衡因子
private int getBalanceFactor(Node<E> node) {
    if (node == null) {
        return 0;
    }
    return getHeight(node.left) - getHeight(node.right);
}
添加节点
public void add(E e) {
    root = add(e, root);
}

private void add(E e, Node<E> node) {
    if (node == null) {
        return node;
    }
    
    if (e.compareTo(node.data) < 0 ) {
        node.left = add(e, node.left);
    } else (e.compareTo(node.data) > 0 ) {
        node.right = add(e, node.right);
    } else {
        //如果相等就不用处理了
    }
    
    //更新当前节点的高度
    //当前节点的高度 = 左子树的高度 > 右子树的高度 ? 
        //  左子树的高度 + 1 : 右子树的高度 + 1
    node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));    
    //计算当前节点的平衡因子
    int balanceFactor = getBalanceFactor(node);
    
    //如果平衡因子 > 1了,就说明树发生倾斜了,就需要进行调整
    if (Math.abs(balanceFactor > 1)) {
        /**
          倾斜情况主要分为以下四种:
          LL:是由于左子节点的左子节点导致的倾斜
          RR:是由右子节点的右子节点导致的倾斜
          LR:是由左子节点的右子节点导致的倾斜
          RL:是由右子节点的左子节点导致的倾斜
        */
        
         /*
                  y
                x   t4
              z  t3
            t1 t2

            顺序是 t1 < z  < t2 < x < t3 < y < t4
            由于在递归添加的时候,是从下到上回溯,此时发现y的平衡因子为2,也就是向左倾斜(左高-右高)
            保持二叉树的顺序不变,围绕x进行旋转
                     x
                  z      y
                t1  t2  t3 t4
             旋转后的顺序让人是  t1 z t2 x t3 y t4
             即:
                 x.right = y
                 y.left = t3

             * */        
        
        */
        if (balanceFactor > 1 && getBalanceFactor(node.left) > 0) {
            //进行右旋转
            node = rightRotate(node);
        }
        
        /**
        同理,左旋转,处理向右倾斜的情况
              y
            t4   x
               t3  z
                 t2 t1

          >>>>>>

                  x
              y       z
            t4  t3  t2  t1

            x.left = y
            y.right = t3

         * */        
        if (balanceFactor < -1 && getBalanceFactor(node.right) < 0) {
            //左旋转
            node = leftRotate(node);
        }
         /**

             在左侧的右侧添加 或 在右侧的左侧添加 导致的不平衡就不能直接进行右旋转 或 左旋转了
             因为这会破坏二叉树的结构,比如
                12
             8
               10
             如果进行又旋转,变成
                8
             10  12
             这就不是一颗二叉搜索树了,所以,需要先对 8 和 10进行左旋转,然后再对10进行右旋转
             同理右侧相反

          * */
        //LR
        if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
            //先左旋再右旋
            node.left = leftRotate(node.left);
            node = rightRotate(node);
        }
       
        //RL
        if (balanceFactor < -1 && getBalanceFactor(ndoe.right) > 0) {
            //先右旋,再左旋
            node.right = rightRotate(node.right);
            node = leftRotate(node);
        }
    }
    return node;
}


//右旋转
private Node<E> rightRotate(Node<E> node) {
    Node<E> left = node.left;
    Node<E> leftRight = left.right;
    
    left.right = node;
    node.left = leftRight;
    
    //更新left和node的高度
    left.height = 1 + Math.asb(getHeight(left.left), getHeight(left.right));
    node.height = 1 + Math.abs(getHeight(node.left), getHeight(node.right));
    
    return left;
}

//左旋转
private Node<E> leftRight(Node<E> node) {
    Node<E> right = node.right;
    Node<E> rightLeft = right.left;
    
    right.left = node;
    node.right = rightLeft;
    
    right.height = Math.asb(getHeight(right.left), getHeight(right.right));
    node.height = 1 + Math.abs(getHeight(node.left), getHeight(node.right));
    return right;
}

删除节点

删除节点时,会导致从删除的节点的位置开始往上的节点 平衡因子发生变化,
因此需要重新计算节点的高度和平和公因子,然后进行调整,使之平衡

//从node为根的树中删除data=e的节点
public Node<E> remove(E e, Node<E> node) {
    if (node == null) {
        //没找到待删除的节点
        return null;
    }
    Node<E> retNode = null;
    if (e.compareTo(node.data) < 0) {
        node.left = remove(e, node.left);
        retNode = node;
    } else if (e.compareTo(node.data) > 0) {
        node.right = remove(e, node.right);
        retNode = node;
    } else {
        //找到了
        if (node.left == null) {
            Node<E> right = node.right;
            node.right = null;
            size --;
            retNode = node;
        } else if (node.right = null) {
            Node<E> left = node.left;
            node.left = null;
            size --;
            retNode = node;
        } else {
            //即有左子节点又有右子节点
            Node<E> rightMinNode = minNode(node.right);
            rightMinNode.left = node.left;
            rightMinNode.right = removeMin(node.right);
            
            node.left = null;
            node.right = null;
            retNode = rightMinNode;
        }
    }
    
    if (retNode == null) {
        return null;
    }
    
    retNode.hegith = Math.asb(getHeight(retNode.left.height), getHeight(retNode.right.height));
    //计算当前节点的平衡因子,看是否需要旋转
    int balanceFactor = getBalanceFactor(retNode);
    if (Math.abs(balanceFactor) > 1) {  //左右高度差 > 1,就需要进行旋转
        //LL
        if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) {
            //右旋转后,返回新的根
            return rightRotate(retNode);
        }
        //RR
        if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) {
            //向右倾斜,进行左旋转
            return leftRotate(retNode);
        }
        //LR
        if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
            //左旋转
            retNode.left = leftRotate(retNode.left);
            //右旋转
            return rightRotate(retNode);
        }
        //RL
        if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
            //右旋转
            retNode.right = rightRotate(retNode.right);
            //左旋转
            return leftRotate(retNode);
        }
    }
    return retNode;
}

线索二叉树

线索二叉树是利用空出来的指针域来存储 某个遍历序列 下的前驱和后继,左边的空节点指针指向前驱节点,右边的空节点指针指向后继节点。

由于线索二叉树是基于某种遍历序列的,所以就需要在对二分搜索树进行某种遍历的时候,构造线索,主要分为:

  • 前序序列线索树
  • 中序序列线索树
  • 后续序列线索树
用土办法找树中某节点的中序遍历前驱节点
Node<E> pre = null;
boolean findFlag = false;
Node<E> target = null;

private Node<E> findNode(E e) {
    return findNode(e, root);
}

private Node<E> findNode(E e, Node<E> node) {
    if (node == null) {
        return null;
    }
    if (e.compareTo(node.data) < 0) {
        return findNode(e, node.left);
    } else if (e.compareTo(node.data) > 0) {
        return findNode(e, node.right);
    }
    return node;
}

//中序遍历
public E findInOrderPre(E e) { 
    //找到目标节点
    target = findNode(e);
    
    return pre.data;
}

//按照前序的顺序遍历整棵树
private void inOrderForFindPre(Node<E> node) {
    if (node == null) {
        return;
    }
    inOrderForFindPre(node.left);
    //在这里调整pre指针
    visit(node);
    inOrderForFindPre(node.right);
}

private void visit(Node<E> node) {
    //如果还没有找到
    if (!findFlag) {
        //如果当前节点就是目标节点
        if (node.data.compareTo(q.data) == 0) {
            findFlag = true;
            //输出pre,即前序遍历中 目标节点的前驱节点
            System.out.println("目标节点的前驱节点为:" + pre);
        } else {
            //否则,将pre指向当前节点,继续向后遍历
            pre = node;
        }
    }
}
构造前序线索树
//临时前序指针
    private Node<E> preOrderThreadPrev;
    public void preThreadTree() {
        preThreadTree(root);
        if (preOrderThreadPrev != null) {
            preOrderThreadPrev.rtag = 1;
        }
    }
    private void preThreadTree(Node<E> node) {
        if (node == null) {
            return;
        }
        preThreadTreeVisit(node);
        if (node.ltag == 0) {
            preThreadTree(node.left);
        }
        if (node.rtag == 0) {
            preThreadTree(node.right);
        }
    }

    private void preThreadTreeVisit(Node<E> node) {
        if (node.left == null) {
            node.left = preOrderThreadPrev;
            node.ltag = 1;
        }

        //如果前驱的right是空,就让他指向node节点
        if (preOrderThreadPrev != null &&
                preOrderThreadPrev.right == null) {
            preOrderThreadPrev.right = node;
            preOrderThreadPrev.rtag = 1;
        }
        preOrderThreadPrev = node;
    }
遍历前序线索树
/**
     * ③由于先序遍历线索树中的节点无法找到它的前驱节点(因为前驱是向前指,而节点的当前两个子节点都是向后只的)
     *   所以只能从前往后遍历
     * */
    private Node<E> preOrderSequenceFirstNode(Node<E> node) {
        //以node为根节点的子树的先序遍历的第一个节点就是当前节点
        return node;
    }
    private Node<E> preOrderSequenceNextNode(Node<E> node) {
        //如果right被线索化了,就直接返回right
        if (node.rtag == 1) {
            return node.right;
        }
        //如果没有,先序遍历的后继节点 只可能是它的左 / 右子节点
        if (node.left != null) {
            return node.left;
        } else if (node.right != null) {
            return node.right;
        }
        return null;
    }
    public void preSequenceOrder() {
        for (Node<E> cur = preOrderSequenceFirstNode(root); cur != null; cur = preOrderSequenceNextNode(cur)) {
            System.out.print(cur.data + " ");
        }
        System.out.println();
    }
构造中序线索树
private Node<E> inOrderThreadPrev;
    //②中序序列线索化
    public void inThreadTree() {
        inThreadTree(root);
        if (inOrderThreadPrev != null) {
            inOrderThreadPrev.rtag = 1;
        }
    }
    private void inThreadTree(Node<E> node) {
        if (node == null) {
            return;
        }
        inThreadTree(node.left);
        inThreadTreeVisit(node);
        if (node.rtag == 0) {
            inThreadTree(node.right);
        }
    }

    private void inThreadTreeVisit(Node<E> node) {
        if (node.left == null) {
            node.left = inOrderThreadPrev;
            node.ltag = 1;
        }

        if (inOrderThreadPrev != null &&
                inOrderThreadPrev.right == null) {
            inOrderThreadPrev.right = node;
            inOrderThreadPrev.rtag = 1;
        }
        inOrderThreadPrev = node;
    }
遍历中序线索树(正向&逆向)
//③中序线索化二叉是正向遍历
    //中序遍历序列中的第一个节点
    private Node<E> inOrderSequenceFirstNode(Node<E> node) {
        Node<E> cur = node;
        while (cur.ltag == 0) {
            cur = cur.left;
        }
        return cur;
    }
    //中序遍历序列中的下一个节点
    private Node<E> inOrderSequenceNextNode(Node<E> node) {
        if (node.rtag == 1) {
            return node.right;
        }

        return inOrderSequenceFirstNode(node.right);
    }

    public void inSequenceOrder() {
        for (Node<E> cur = inOrderSequenceFirstNode(root); cur != null; cur = inOrderSequenceNextNode(cur)) {
            System.out.print(cur.data + " ");
        }
        System.out.println();
    }

    /**
     * 中序遍历序列的逆序遍历
     * */
    private Node<E> inOrderSequenceLastNode(Node<E> node) {
        Node<E> cur = node;
        while (cur.rtag == 0) {
            cur = cur.right;
        }
        return cur;
    }
    private Node<E> inOrderSequencePrevNode(Node<E> node) {
        if (node.ltag == 1) {
            return node.left;
        }
        return inOrderSequenceLastNode(node.left);
    }
    public void inSequenceOrderReverse(){
        for (Node<E> cur = inOrderSequenceLastNode(root); cur != null; cur = inOrderSequencePrevNode(cur)) {
            System.out.print(cur.data + " ");
        }
        System.out.println();
    }
构造后续线索树
private Node<E> postOrderThreadPrev;
    public void postThreadTree() {
        postThreadTree(root);

        //注意,由于后续遍历的最后一个节点不是叶子节点,所以不能同先序遍历和
        //中序遍历那样,存在下面的逻辑
        /*if (postOrderThreadPrev != null) {
            postOrderThreadPrev.rtag = 1;
        }*/
    }
    private void postThreadTree(Node<E> node) {
        if (node == null) {
            return;
        }
        //if (node.ltag == 0) {
            postThreadTree(node.left);
        //}
        //if (node.rtag == 0) {
            postThreadTree(node.right);
        //}
        postThreadTreeVisit(node);

    }
    private void postThreadTreeVisit(Node<E> node) {
        if (node.left == null) {
            node.left = postOrderThreadPrev;
            node.ltag = 1;
        }

        if (postOrderThreadPrev != null
                && postOrderThreadPrev.right == null) {
            postOrderThreadPrev.right = node;
            postOrderThreadPrev.rtag = 1;
        }
        postOrderThreadPrev = node;
    }
遍历后续线索树
/**
     * ③遍历
     * 由于后序线索二叉树找不到后继节点,所以只能从后往前遍历
     * */
    private Node<E> postOrderSequenceLastNode(Node<E> node) {
        return node;
    }

    private Node<E> postOrderSequencePreNode(Node<E> node) {
        if (node.ltag == 1) {
            return node.left;
        }

        if (node.right != null && node.rtag != 1) {
            return node.right;
        }
        return node.left;
    }

    public void postSequenceOrderReverse() {
        for (Node<E> cur = postOrderSequenceLastNode(root); cur != null; cur = postOrderSequencePreNode(cur)) {
            System.out.print(cur.data + " ");
        }
        System.out.println();
    }

哈夫曼树
概念
  • 路径和路径长度:从树中一个节点到另一个节点之间的分支构成两个节点直接的路径,路径上的分支数目称作路径长度
  • 树的路径长度:树的路径长度就是从树根到每一个节点的路径长度之和
  • 节点的权及带权路径长度:若给树中的节点赋予具有某种含义的数值,则这个数值就成为该节点的权,节点的带权路径长度为从根节点到该节点之间的路径长度与该节点的权的乘机
  • 树的带权路径长度:即所有叶子节点的带权路径长度之和,记作WPL,权值越大的节点离根节点越近的二叉树才是最有二叉树,WPL最小的二叉树就是哈夫曼树
创建思路
  • 从小到大对给定的节点进行排序;
  • 取出根节点权值最小的两棵二叉树组成一个新的二叉树;
  • 新的二叉树的根节点的权值是左右子节点的权值之和;
  • 让新的二叉树的根节点的权值参与剩余节点的排序,重复上面的步骤直到把所有的节点都处理完;
代码实现
/**
 * 哈夫曼树.
 * */
public class HuffmanTree {

    //前序遍历哈夫曼树
    public static void preOrder(Node node) {
        if (node == null) {
            return;
        }
        System.out.print(node.value + " ");
        preOrder(node.left);
        preOrder(node.right);
    }

    public static void inOrder(Node node) {
        if (node == null) {
            return;
        }
        inOrder(node.left);
        System.out.print(node.value + " ");
        inOrder(node.right);
    }


    public static Node buildHuffmanTree(int[] arr) {
        List<Node> list = new ArrayList(arr.length);
        for (int i = 0;i < arr.length;i ++) {
            list.add(new Node(arr[i]));
        }

        while (list.size() > 1) {
            //从小到大进行排序
            Collections.sort(list);
            //从排序后的list中取出前两个,构造出一棵新树
            Node left = list.get(0);
            Node right =list.get(1);

            Node parent = new Node(left.value + right.value);
            parent.left = left;
            parent.right = right;

            //把已经参加过构造的节点移除
            list.remove(left);
            list.remove(right);
            //把新构造出来的节点加入到排序序列中
            list.add(parent);
        }
        //剩余的最后一个就是哈夫曼树的根节点
        return list.get(0);
    }

    private static class Node implements Comparable<Node>{
        int value;
        Node left;
        Node right;
        public Node(int value) {
            this(value, null, null);
        }
        public Node(int value, Node left, Node right) {
            this.value = value;
            this.left = left;
            this.right = right;
        }

        @Override
        public int compareTo(Node o) {
            return value - o.value;
        }
    }

    public static void main(String[] args) {
        int[] arr = {1,2,4,7,8};
        Node root = buildHuffmanTree(arr);
        preOrder(root);  //22 8 14 7 7 3 1 2 4
        System.out.println();
        inOrder(root); //8 22 7 14 1 3 2 7 4

        /**

                22
             8      14
                 7      7
                     3     4
                  1     2

         * */
    }
}

性质
  • 堆是一颗完全二叉树(所以才可以用数组来存储);
  • 任意一个节点的值都大于等于左右子节点的值;
代码实现
/**
 * 大顶堆.
 * 堆的性质:完全二叉树,子节点比父节点小/大
 *
 * */
public class MaxHeap<E extends Comparable<E>> {

    private List<E> data;
    //private int size;

    public MaxHeap(int capacity) {
        //底层用一个动态数组来存储数据
        data = new ArrayList<>(capacity);
    }

    public void add(E e){
        //添加到末尾
        data.add(e);
        //执行上浮操作
        siftUp(data.size() - 1);
    }

    public E findMax() {
        if (isEmpty()) {
            throw new InternalException("heap is empty.");
        }
        //返回堆顶数据
        return data.get(0);
    }

    public E extractMax() {
        E ret = findMax();
        E last = data.get(getSize() - 1);
        data.set(0, last);
        data.remove(getSize() - 1);
        siftDown(0);
        return ret;
    }

    //初始化时,给定数据,自动构建一个大顶堆
    public MaxHeap(E[] dataArray) {
        data = new ArrayList<E>(Arrays.asList(dataArray));
        if (dataArray.length > 1) {
            //从最后一个父节点开始,指向shiftDown操作
            for (int i = parent(data.size() - 1);i >= 0;i --) {
                siftDown(i);
            }
        }
    }

    /**
     * 上浮:添加数据时,先将数据添加到最后一个节点,然后在执行上浮操作
     *      直到整体满足堆的性质
     *  过程:如果父节点的值比当前节点小,就讲父节点和当前节点调换位置,直到根节点为止
     * */
    private void siftUp(int idx) {
        while (idx > 0 && data.get(parent(idx)).compareTo(data.get(idx)) < 0) {
            //交换父子节点的位置
            E temp = data.get(parent(idx));
            data.set(parent(idx), data.get(idx));
            data.set(idx, temp);

            //向上移动指针
            idx = parent(idx);
        }
    }

    /**
     * 下沉:移除头节点时,将最后一个节点移动到头节点,然后执行下沉操作,
     *      直到整体满足堆的性质
     *
     * */
    private void siftDown(int idx) {
        //卡一下左节点的边界
        while (left(idx) < data.size()) {
            int j = left(idx);
            //如果存在右节点,并且右节点的值比左节点的值大,就将j指向右节点,
            //即找到左右子节点中较大的那个
            if (j + 1 < data.size()
                    && data.get(j + 1).compareTo(data.get(j)) > 0) {
                j ++;
            }

            //如果idx处的值比子节点中最大的值还大,就可以终止了
            if (data.get(idx).compareTo(data.get(j)) > 0) {
                break;
            }

            //否则,就交换位置
            E temp = data.get(idx);
            data.set(idx, data.get(j));
            data.set(j, temp);

            //相下一层推进指针
            idx = j;
        }
    }

    //定义计算索引的公式
    private int parent(int i) {
        return (i - 1) / 2;
    }
    private int left(int i) {
        return 2 * i + 1;
    }
    private int right(int i) {
        return 2 * i + 2;
    }

    public int getSize() {
        return data.size();
    }
    public boolean isEmpty() {
        return data.isEmpty();
    }

    public static void main(String[] args) {
        MaxHeap<Integer> heap = new MaxHeap<>(10);
        heap.add(4);
        heap.add(100);
        heap.add(45);
        heap.add(34);
        heap.add(888);
        System.out.println(heap.findMax());
        System.out.println("----");
        for (int i = 0;i < 5;i ++) {
            System.out.println(heap.extractMax());
        }
    }
}

字段树

线段树

红黑树

B/B+树

算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

echo20222022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值