java数据结构和算法10(堆)

  这篇我们说说堆这种数据结构,其实到这里就暂时把java的数据结构告一段落,感觉说的也差不多了,各种常见的数据结构都说到了,其实还有一种数据结构是“图”,然而暂时对图没啥兴趣,等有兴趣的再说;还有排序算法,emmm....有时间再看看吧!

  其实从写数据结构开始到现在让我最大的感触就是:新手刚开始还是不要看数据结构为好,太无聊太枯燥了,很容易让人放弃;可以等用的各种框架用得差不多了之后,再回头静下心来搞搞数据结构还是挺有趣的;废话不多说,开始今天的内容:

1.二叉树分类

  树分为二叉树和多叉树,其实吧有个很有趣的现象,如果树为一叉也就是一个链表(当然没有一叉树这个说法啊,这里是为了好理解);如果为二叉就是二叉树(包括二叉搜索树,红黑树等);如果为二叉以上就是为多叉树(包括2-3树,2-3-4树,B树等)

  我们再来简单看看二叉树,通过前面的学习我们知道了二叉树是什么鬼,一句话概括就是:除了叶节点外其他的节点最多只有两个子节点;这里我们还可以继续对二叉树进行分类,可以简单的分为完美二叉树,完全二叉树,满二叉树,我们就简单说说这三种分类;

  完美二叉树:跟名字差不多,很完美;除了叶节点之外,任意节点都有两个子节点,整棵树可以构成一个大三角形,下图所示:

  完全二叉树:没有上面的那么完美,只需要从根节点到倒数第二层满足完美二叉树,而最后一层不需要都填满,填满一部分即可,但是最后一层有个要求:从最左边的节点到最右边的节点中间不能有空位置,例如下图:

  

  满二叉树:比完全二叉树要求更低,只需要保证除叶节点外,其他节点都有两个子节点,下面两个图都是满二叉树:

     

2.堆的简介

  首先我们要明白,此堆非彼堆,我们这里的堆是一种数据结构而不是jvm中的堆内存空间;深入一点来说,堆是一种完全二叉树,通常用数组实现,而且堆中任意一个节点数据都要大于两个子节点的数据,下图所示:

  注意,只要子节点两个子节点的数据都比父节点小即可,两个子节点谁大谁小无所谓;

  由于堆是由数组实现的,那么数组到底是怎么保存堆中的数据的呢?看下图,其实就是先将第一层数据放入数组,然后将第二层数据从左到右放入数组,然后第三层数据从左到右放入数组....

  这里补充一个小知识点,后面写java代码实现的时候会用到:在这里我们可以知道堆中的每一个节点都对应于数组中的一个位置,,所以可以根据任意一个节点的位置得出来父节点的位置和两个子节点的位置;举个例子,50这个节点的数组下标是5,那么父节点下标为(5-1)/2向下取整等于2,左子节点下标为2*5+1 = 11;右子节点下标为2*5+2 = 12,我们可以简单归纳一下:

  假如一个节点对应的数组下标为a,那么父节点为:(a-1)/2向下取整,左子节点:2a+1,右子节点:2a+2

3.堆的操作

  从上面这个图可以看出来堆中的数据放在数组中是没有强制性的顺序的(这里叫做弱序),只能知道数组第一个数最大,最小的数不知道在哪里,而且父节点肯定要比子节点大,除此之外我们就什么也不知道了;

  对于数据结构的操作而言无非就是增删改查,我们可以知道在堆中是没有办法进行查询的,因为左右节点没有规定必须哪个大那个小,所以查找的时候不知道应该往哪个子节点去比较,一般而言修改操作是建立在查询的基础上的,所以堆也不支持修改,还有还不支持遍历操作,这样算下来,堆就支持增加和删除操作了,那么下面我们就看看这两个操作;

  3.1添加节点

  添加节点分为两种情况,第一种,添加的节点数据非常小,直接放在堆的最后一个位置即可(换句话说直接添加到数组有效长度的后面一个空位置即可),这种情况很容易就不多介绍了;第二种,添加节点的数据稍微比较大,比如下面这样的:

  

  此时堆的结构就被破坏了,我们需要向上调整,那么应该怎么调整呢?很容易,直接和父节点交换位置即可,直到满足堆的这个条件:任意一个父节点都要大于子节点数据

  3.2.删除节点

  这里的删除指的是删除堆中最大的节点,换句话说就是每次删除都是删除根节点(对应于数组的第一个元素)

  但是删除过后堆的结构就会被破坏,于是要进行调整来平衡堆的结构,看看下图:

  我们的做法就是将堆中最后一个节点放在根节点那里(对应于数组就是将数组有效长度的最后元素放在第一个位置那里),然后判断新的根节点是不是比两个子节点中最大的那个还要大?是,不需要调整;否,则将此时新的根节点与比较大的子节点交换位置,然后无限重复这个交换步骤,直到该节点的数据大于两个子节点即可;

  3.3.换位

  上面说的换位是什么意思呢?我们知道在java中要交换两个数据要有一个中间变量。如下伪代码:

int a = 3;
int b = 10;

//交换a和b的数据
int temp;
temp = a;//第一步
a = b;//第二步
b = temp;//第三步

  可以看到这样的一次简单换位要进行三步复制操作,如果数组中对象很多,都要进行这种换位,那么效率有点低,看看下面这个图;

  上图进行三次这样的交换就要经过3x3 = 9次复制操作,那有没有方法可以优化一下呢?

  下图所示,用一个临时节点存储A节点,然后D、C、B依次复制到前面的位置,最后就将临时节点放到原来D的位置,总共只需要进行5次复制,这样减少了4次复制,在交换的次数很多的时候这种方式效率可还行。。。

 4.java代码

  根据上面说的我们用java代码来实现一下堆的添加和删除操作,而且效率都是logN:

package com.wyq.test;

public class MyHeap {
    //堆中的数组要存节点,于是就是一个Node数组
    private Node[] heapArray;
    //数组的最大容量
    private int maxSize;
    //数组中实际节点的数量
    private int currentSize;

    public MyHeap(int size){
        this.maxSize = size;
        this.currentSize = 0;
        this.heapArray = new Node[maxSize];
    }

    //这里为了方便使用,节点类就为一个静态内部类,其中就存了一个int类型的数据,然后get/set方法
    public static class Node{
        private int data;
        public Node(int data){
            this.data = data;
        }
        public int getData() {
            return data;
        }
        public void setData(int data) {
            this.data = data;
        }
    }
    //向堆中插入数据,这里有几点需要注意一下;首先,如果数组最大的容量已经存满了,那么插入失败,直接返回false;
    //然后,数组没有满的话就将新插入的节点放在数组实际存放数据的后一个位置
    //最后就是向上调整,将新插入的节点和父节点交换,重复这个操作,直到放在合适的位置,调整完毕,数组实际存放节点数量加一
    //下面我们就好好看看向上调整的方法
    public boolean insert(int value){
        if (maxSize == currentSize) {
            return false;
        }
        Node newNode = new Node(value);
        heapArray[currentSize] = newNode;
        changeUp(currentSize);
        currentSize++;
        return true;

    }
    //向上调增
    private void changeUp(int current) {
        int parent = (current-1)/2;//得到父节点的数组下标
        Node temp = heapArray[current];//将我们新插入的节点暂时保存起来,这在前面交换那里说过这种做法的好处
        //如果父节点数据小于插入节点的数据
        while(current>0 && heapArray[parent].getData()<temp.getData()){
            heapArray[current] = heapArray[parent];//这里相当于把父节点复制到当前新插入节点的这个位置
            current = parent;//当前指针指向父节点位置
            parent = (parent-1)/2;//继续获取父节点的数组下标,然后又会继续比较新插入节点数据和父节点数据,无限重复这个步骤
        }
        //到达这里,说明了该交换的节点已经交换完毕,换句哈来说就是已经把很多个节点向下移动了一个位置,留下了一个空位置,那就把暂时保存的节点放进去就ok了
        heapArray[current] = temp;
    }

    //删除节点,逻辑比较容易,始终都是删除根节点,然后将最后面一个节点放到根节点位置,然后向下调整就好了;最后就是将实际容量减一就可以了,重点看看向下调整
    public Node delete(){
        Node root = heapArray[0];
        heapArray[0] = heapArray[currentSize-1];
        currentSize--;
        changeDown(0);
        return root;
    }
    //向下调整,大概理一下思路,我们首先将新的根节点临时保存起来,然后要找两个子节点中比较大的那个节点,最后就是比较临时节点和比较大的节点的大小,
    //如果小,那么把那个比较大的节点往上移动到父节点位置,继续重复这个步骤将比较大的子节点往上移动,最后会留下一个空位置,就把临时节点放进去就好
    private void changeDown(int current) {
        Node largeChild;
        Node temp = heapArray[0];//临时节点为新的根节点
        //注意这个while循环的条件,current<currentSize/2可以保证当前节点至少有一个子节点
        while (current<currentSize/2) {
            int leftIndex = 2*current+1;//左子节点数组下标
            int rightIndex = 2*current+2;//右子节点数组下标
            int largeIndex;//比较大的子节点数组下标

            Node leftChild = heapArray[leftIndex];//左子节点
            Node rightChild = heapArray[rightIndex];//右子节点
            if (rightIndex<currentSize && leftChild.getData()<rightChild.getData()) {
                largeChild = rightChild;
                largeIndex = rightIndex;
            }else {
                largeChild = leftChild;
                largeIndex = leftIndex;
            }
            //如果临时节点(即新的根节点)比大的那个子节点还大,那么就直接跳出循环
            if (temp.getData() >= largeChild.getData()) {
                break;
            }
            heapArray[current] = largeChild;
            current = largeIndex;//当前节点的执着呢指向比较大的子节点

        }
        heapArray[current] = temp;//临时节点插入到堆数组中
    }
    //展示堆中的数据
    public void display(){
        System.out.print("堆中的数据为:");
        for (int i = 0; i < currentSize; i++) {
            System.out.print(heapArray[i].getData()+"  ");
        }
    }

    public static void main(String[] args) {
        MyHeap heap = new MyHeap(10);
        heap.insert(3);
        heap.insert(5);
        heap.insert(1);
        heap.insert(10);
        heap.insert(6);
        heap.insert(7);
        heap.display();

        System.out.println();
        System.out.print("删除操作之后");
        heap.delete();
        heap.display();
    }

}

  我们插入的节点如下图所示:

  

  代码测试结果为如下所示,成功;

5.总结

  到这里堆就差不多说完了,其实还有个堆排序,其实最简单的就是向堆中添加很多节点,然后不断的删除节点,就会以从大到小的顺序返回了,比较容易吧!当然还可以进行改进不过也很简单,有兴趣的可以自己去看看堆排序。

  java数据结构到这里差不多了,就随意列举一下我们用过的数据结构的时间复杂度:

原文地址:https://www.cnblogs.com/wyq1995/p/10903057.html

时间: 2024-10-11 04:04:04

java数据结构和算法10(堆)的相关文章

Java数据结构和算法(二)——数组

数组的用处是什么呢?--当你需要将30个数进行大小排列的时候,用数组这样的数据结构存储是个很好的选择,当你是一个班的班主任的时候,每次要记录那些学生的缺勤次数的时候,数组也是很有用.数组可以进行插入,删除,查找等. 1)创建和内存分配 Java中有两种数据类型,基本类型和对象类型,也有人称为引用类型,Java中把数组当成对象,创建数组时使用new操作符. int array[] = new int[10]; 既然是对象,那么array便是数组的一个引用,根据Java编程思想(一) -- 一切都是

Java数据结构和算法之链表

三.链表 链结点 在链表中,每个数据项都被包含在'点"中,一个点是某个类的对象,这个类可认叫做LINK.因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点.每个LINK对象中都包含一个对下一个点引用的字段(通常叫做next)但是本身的对象中有一个字段指向对第一个链结点的引用. 单链表 用一组地址任意的存储单元存放线性表中的数据元素. 以元素(数据元素的映象)  + 指针(指示后继元素存储位置)  = 结点(表示数据元素 或 数据元素的映象) 以"结点的序列&q

java数据结构与算法之(Queue)队列设计与实现

[版权申明]转载请注明出处(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/53375004 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) java数据结构与算法之栈设计与实现 java数据结构

《Java数据结构和算法》- 数组

Q: 数组的创建? A: Java中有两种数据类型,基本类型和对象类型,在许多编程语言中(甚至面向对象语言C++),数组也是基本类型.但在Java中把数组当做对象来看.因此在创建数组时,必须使用new操作符: int [] objArray = null; // defines a reference to an array objArray = new int[100]; // creates the array, and sets objArray to refer to it 或使用等价的

java数据结构与算法之顺序表与链表深入分析

转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 ??数据结构与算法这门学科虽然在大学期间就已学习过了,但是到现在确实也忘了不少,因此最近又重新看了本书-<数据结构与算法分析>加上之前看的<java数据结构>也算是对数据结构的进一步深入学习了,于是也就打算

Java数据结构和算法(一)——开篇

这篇文章里面不讲技术,抽空讲讲技术和通俗之间有一种奇特的关系,还有驱动力学习的东西. 1)技术与通俗 大学里面那本严蔚敏的数据结构不厚,内容丰富,但是复杂问题的讲解方面篇幅这样就少了,比较难理解,c也不是很擅长,但是基本的思路还是有的. 简单的链表,数组,堆栈,队列,图,几个排序算法. 后面看到知乎涛吴的回答,当时很震撼,这里引用一下他的回答: 如果说 Java 是自动档轿车,C 就是手动档吉普.数据结构呢?是变速箱的工作原理.你完全可以不知道变速箱怎样工作,就把自动档的车子从 A 开到 B,而

java数据结构与算法之平衡二叉树(AVL树)的设计与实现

[版权申明]未经博主同意,不允许转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/53892797 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) java数据结构与算法之栈(Stack)设

java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制)

转载请注明出处(请尊重原创!谢谢~): http://blog.csdn.net/javazejian/article/details/53073995 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) ??这篇是数据结构与算法的第3篇,通过前两篇的介绍,对应顺序表和链表已有

java数据结构与算法之双链表设计与实现

转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/53047590 出自[zejian的博客] 关联文章: 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) ??上一篇文章分析顺序表和单链表,本篇就接着上篇继续聊链表,在单链表