这篇咱们说说堆这种数据结构,其实到这里就暂时把java的数据结构告一段落,感受说的也差很少了,各类常见的数据结构都说到了,其实还有一种数据结构是“图”,然而暂时对图没啥兴趣,等有兴趣的再说;还有排序算法,emmm....有时间再看看吧!java
其实从写数据结构开始到如今让我最大的感触就是:新手刚开始仍是不要看数据结构为好,太无聊太枯燥了,很容易让人放弃;能够等用的各类框架用得差很少了以后,再回头静下心来搞搞数据结构仍是挺有趣的;废话很少说,开始今天的内容:算法
1.二叉树分类数组
树分为二叉树和多叉树,其实吧有个颇有趣的现象,若是树为一叉也就是一个链表(固然没有一叉树这个说法啊,这里是为了好理解);若是为二叉就是二叉树(包括二叉搜索树,红黑树等);若是为二叉以上就是为多叉树(包括2-3树,2-3-4树,B树等)数据结构
咱们再来简单看看二叉树,经过前面的学习咱们知道了二叉树是什么鬼,一句话归纳就是:除了叶节点外其余的节点最多只有两个子节点;这里咱们还能够继续对二叉树进行分类,能够简单的分为完美二叉树,彻底二叉树,满二叉树,咱们就简单说说这三种分类;框架
完美二叉树:跟名字差很少,很完美;除了叶节点以外,任意节点都有两个子节点,整棵树能够构成一个大三角形,下图所示:jvm
彻底二叉树:没有上面的那么完美,只须要从根节点到倒数第二层知足完美二叉树,而最后一层不须要都填满,填满一部分便可,可是最后一层有个要求:从最左边的节点到最右边的节点中间不能有空位置,例以下图:学习
满二叉树:比彻底二叉树要求更低,只须要保证除叶节点外,其余节点都有两个子节点,下面两个图都是满二叉树:测试
2.堆的简介优化
首先咱们要明白,此堆非彼堆,咱们这里的堆是一种数据结构而不是jvm中的堆内存空间;深刻一点来讲,堆是一种彻底二叉树,一般用数组实现,并且堆中任意一个节点数据都要大于两个子节点的数据,下图所示:this
注意,只要子节点两个子节点的数据都比父节点小便可,两个子节点谁大谁小无所谓;
因为堆是由数组实现的,那么数组究竟是怎么保存堆中的数据的呢?看下图,其实就是先将第一层数据放入数组,而后将第二层数据从左到右放入数组,而后第三层数据从左到右放入数组....
这里补充一个小知识点,后面写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数据结构到这里差很少了,就随意列举一下咱们用过的数据结构的时间复杂度: