堆 --- 永不过时的数据结构

目录

堆的定义:

堆的实现(以小根堆为例):

建堆的时间复杂度:

堆的插入:

堆的删除:

完整代码:

堆排序:


堆的定义:


        现在我们有一颗完全二叉树,它一共有n颗节点,这颗二叉树的左右节点都比它的父节点小(大)。现在我们将这颗二叉树用数组表示出来,下标为0的节点为根节点,下标为1的节点为根节点的左节点,下标为2的节点为根节点的右节点,下标为3的节点为下标为2的节点的左节点......

        这样逻辑上为一颗完全二叉树,并且左右节点比父节点小(大),以数组表示出来的存储结构称为堆。

        父节点比左右节点小的(左右节点比父节点大的)称为小根堆。

        

        父节点比左右节点大的(左右节点比父节点小的)称为大根堆。

堆的实现(以小根堆为例):

public class Heap {
    private ArrayList<Integer> heap;

    public Heap(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        heap = new ArrayList<>();
        for (int j = 0; j < arr.length; j++) {
            heap.add(arr[j]);
        }
        for (int i = (arr.length - 1 - 1) / 2 ; i >= 0; i--) {
            //(arr.length - 1 - 1) / 2  是最后一个非叶子节点
            //arr.length - 1 是最后一个元素的下标,再减1是因为我们的堆是由0下标开始放元素的
            //所以想要求最后一个非叶子节点就要再减一次 1 再除以2
            adjustDown(i);
        }
    }


    public void adjustDown(int parent) {//小根堆:父节点比左右节点小
        int child = parent * 2 + 1;//child开始是parent的左孩子
        while (child < heap.size()) {
            if (child + 1 < heap.size() && heap.get(child + 1) < heap.get(child)) {
                child = child + 1;//如果右孩子存在且右孩子小于左孩子,child变成右孩子
                //因为这个child需要时两个孩子中最小的
            }

            if (heap.get(child) < heap.get(parent)) {
                swap(parent, child);//如果孩子比父亲节点小,则进行交换
            } else {
                break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
            }

            parent = child;
            child = parent * 2 + 1;
            //继续向下交换
        }
    }


    public void swap(int a, int b) {
        int tmp = heap.get(a);
        heap.set(a, heap.get(b));
        heap.set(b,tmp);
    }


    public void showHeap() {
        for (int i = 0; i < heap.size(); i++) {
            System.out.print(heap.get(i) + " ");
        }
        System.out.println();
    }
}
public class Main {
    public static void main(String[] args) {
        int[] arr = {5,6,3,2,1,4,7,8,9,10,12};
        Heap heap = new Heap(arr);
        heap.showHeap();
    }
}

运行结果:

建堆的时间复杂度:

 时间复杂度:O(n)


堆的插入:


要实现堆的插入,我们首先要实现向上调整:

    public void adjustUp(int child) {//小根堆:父节点比左右节点小
        int parent = (child - 1) / 2;//求父节点下标公式
        while (parent >= 0) {
            if (heap.get(child) < heap.get(parent)) {
                swap(parent, child);//如果孩子比父亲节点小,则进行交换
            } else {
                break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
            }

            child = parent;
            parent = (child - 1) / 2;
            //继续向上交换
        }
    }

然后我们在每次插入的时候将这个元素向上调整一下就可以了~~

    public void heapPush(int val) {
        heap.add(val);
        adjustUp(heap.size() - 1);
    }

堆的删除:


删除堆的数据就是删除堆的堆顶元素,我们将堆顶元素与最后一个元素调换,然后进行向下调整。

    public void heapPop() {//删除堆顶元素
        swap(0, heap.size() - 1);
        heap.remove(heap.size() - 1);
        adjustDown(0);
    }

完整代码:

import javafx.scene.Parent;

import java.util.ArrayList;
import java.util.Arrays;

public class Heap {
    private ArrayList<Integer> heap;

    public Heap(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        heap = new ArrayList<>();
        for (int j = 0; j < arr.length; j++) {
            heap.add(arr[j]);
        }
        for (int i = (arr.length - 1 - 1) / 2 ; i >= 0; i--) {
            //(arr.length - 1 - 1) / 2  是最后一个非叶子节点
            //arr.length - 1 是最后一个元素的下标,再减1是因为我们的堆是由0下标开始放元素的
            //所以想要求最后一个非叶子节点就要再减一次 1 再除以2
            adjustDown(i, arr.length);
        }
    }


    public void adjustDown(int parent/*终止位置*/) {//小根堆:父节点比左右节点小
        int child = parent * 2 + 1;//child开始是parent的左孩子
        while (child < heap.size()) {
            if (child + 1 < heap.size() && heap.get(child + 1) < heap.get(child)) {
                child = child + 1;//如果右孩子存在且右孩子小于左孩子,child变成右孩子
                //因为这个child需要时两个孩子中最小的
            }

            if (heap.get(child) < heap.get(parent)) {
                swap(parent, child);//如果孩子比父亲节点小,则进行交换
            } else {
                break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
            }

            parent = child;
            child = parent * 2 + 1;
            //继续向下交换
        }
    }

    public void adjustUp(int child) {//小根堆:父节点比左右节点小
        int parent = (child - 1) / 2;//求父节点下标公式
        while (parent >= 0) {
            if (heap.get(child) < heap.get(parent)) {
                swap(parent, child);//如果孩子比父亲节点小,则进行交换
            } else {
                break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
            }

            child = parent;
            parent = (child - 1) / 2;
            //继续向上交换
        }
    }

    public void swap(int a, int b) {
        int tmp = heap.get(a);
        heap.set(a, heap.get(b));
        heap.set(b,tmp);
    }

    public void heapPush(int val) {
        heap.add(val);
        adjustUp(heap.size() - 1);
    }

    public void heapPop() {//删除堆顶元素
        swap(0, heap.size() - 1);
        heap.remove(heap.size() - 1);
        adjustDown(0);
    }

    public void showHeap() {
        for (int i = 0; i < heap.size(); i++) {
            System.out.print(heap.get(i) + " ");
        }
        System.out.println();
    }

    public void heapSort(int[] arr) {

    }
}


堆排序:


如果我们升序就用大根堆,降序就用小根堆。
以升序为例:
1.先从堆顶向下调整建堆。
2.建完堆之后堆顶存放的是最大的元素。
3.我们将这个最大的元素与最后一个元素调换,然后可以调整的元素个数-1,也就是说这个最大的元素固定在最后了,后面要操作的话只能修改这个元素之前的元素了。
4.元素调换之后,再继续从堆顶向下调整。
5.调整完之后最大的元素在堆顶,然后将这个元素与可调整的元素的最后一个调换。
6.然后可调整的元素个数-1。
7.重复4、5、6步。

    public static void adjustDown(int[] arr, int parent, int end) {
        int child = parent * 2 + 1;
        while (child < end) {
            if (child + 1 < end && arr[child + 1] > arr[child]) {
                child = child + 1;
            }

            if (arr[child] > arr[parent]) {
                Swap(arr, child, parent);
            } else {
                break;
            }
            parent = child;
            child = parent * 2 + 1;
        }
    }


    public static void heapSort(int[] arr) {
        for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
            adjustDown(arr, i, arr.length - 1);
        }//建堆

        for (int end = arr.length - 1; end > 0; end--) {
            Swap(arr, 0, end);
            adjustDown(arr, 0, end);
        }
    }


    public static void Swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

时间复杂度:O(nlog_{2}n)

好啦,以上就是全部内容啦,如果有不懂的地方就多敲几次代码,书读百遍,其意自见~~

灿烂的阳光播洒着温暖的希望!_哔哩哔哩_bilibili

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值