堆的插入与删除,上浮与下沉

2023-10-19 02:10
文章标签 删除 插入 上浮 下沉

本文主要是介绍堆的插入与删除,上浮与下沉,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这文章写的真棒!!!网上关于堆的文章里非常棒的一篇,比较全面,详细.在最大堆构造,堆排序,和最大堆维护的基础上,补充了堆的插入和删除,和其中用到的上浮,下沉等操作。

个人理解:最大堆维护是用元素A替换掉堆中某元素后通过最大堆维护操作使得该堆依然是最大堆.

而插入则是插入元素A后使得堆依然为最大堆的操作.

删除是删除堆中某元素后使得堆依然为最大堆的操作.

插入与删除的核心操作其实就是上浮与下沉,叶结点只能进行上浮,非叶结点(除去根结点)则既可以上浮,也可以下沉,注意这个区别,在插入与删除中有重要作用.

其实利用插入与删除操作与最大堆维护操作可以互相取代,都可以完成对方所能完成的任何操作.



此坑待埋。

点击打开漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析链接



白话经典算法系列之七 堆与堆排序



二叉排序树与二叉堆


堆排序(注:这篇文章说明了如何从一个数组构建一个最大堆,推荐看)


最大堆的插入/删除/调整/排序操作(图解+程序)(JAVA)


下面来说一说具体算法。


堆排序解释第一篇(描述不太清楚)

1.堆

  堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:

  Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]

  即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。

  堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。

2.堆排序的思想

   利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

    其基本思想为(大顶堆):

    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;

    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 

    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    操作过程如下:

     1)初始化堆:将R[1..n]构造为堆;

     2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

    因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

    下面举例说明:

     给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。

    首先根据该数组元素构建一个完全二叉树,得到

 然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:

20和16交换后导致16不满足堆的性质,因此需重新调整

这样就得到了初始堆。


即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。

此时3位于堆顶不满堆的性质,则需调整继续调整

这样整个区间便已经有序了。
从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

【ok,从一个原始数组调整成为一个堆,想必已经很清楚了,那么如何从堆里面获取数据变成已经排好序的数组呢?】
下面是一个例子:

    0.待排序序列:

         A[6]={3,5,8,9,1,2},  

        1.建堆后(建堆过程参见4.4):

        A[6]={9,3,8,5,1,2}

       2.9和2交换,然后把9从堆中去掉后:

         A[6]={2,3,8,5,1,9}

      3.筛选法调整堆A[5]={2,3,8,5,1}后(调整过程参见4.3):

       A[6]={8,3,2,5,1,9}

      4.堆顶记录与最后一个记录互换,重复第二步,但是堆顶记录和最后一个记录的值变了


【附上另一篇文章--- 最大堆的插入/删除/调整/排序操作(图解+程序)(JAVA)


最大堆的插入/删除/调整/排序操作(图解+程序)(JAVA)



 堆有最大堆和最小堆之分,最大堆就是每个节点的值都>=其左右孩子(如果有的话)值的完全二叉树。最小堆便是每个节点的值都<=其左右孩子值的完全二叉树。 

  设有n个元素的序列{k1,k2,...,kn},当且仅当满足下列关系时,称之为堆。 
 

堆的三种基本操作(以下以最大堆为例): 
⑴最大堆的插入    

    由于需要维持完全二叉树的形态,需要先将要插入的结点x放在最底层的最右边,插入后满 足完全二叉树的特点; 
  然后把x依次向上调整到合适位置满足堆的性质,例如下图中插入80,先将80放在最后,然后两次上浮到合适位置. 
  时间:O(logn)。  “结点上浮” 
 

程序实现: 
[java] view plain copy
print ?
  1. //向最大堆中插入元素, heap:存放堆元素的数组  
  2.    public static void insert(List<Integer> heap, int value) {   
  3.       //在数组的尾部添加  
  4.        if(heap.size()==0)  
  5.          heap.add(0);//数组下标为0的位置不放元素  
  6.        heap.add(value);   
  7.        //开始上升操作   
  8.       // heapUp2(heap, heap.size() - 1);   
  9.        heapUp(heap, heap.size() - 1);   
  10.   
  11.    }   
  12.   
  13.    //上升,让插入的数和父节点的数值比较,当大于父节点的时候就和父节点的值相交换   
  14.    public static void heapUp(List<Integer> heap, int index) {   
  15.   
  16.        //注意由于数值是从下标为1开始,当index = 1的时候,已经是根节点了   
  17.        if (index > 1) {   
  18.            //求出父亲的节点   
  19.            int parent = index / 2;   
  20.   
  21.            //获取相应位置的数值   
  22.            int parentValue = (Integer) heap.get(parent);   
  23.            int indexValue = (Integer) heap.get(index);   
  24.            //如果父亲节点比index的数值小,就交换二者的数值   
  25.            if (parentValue < indexValue) {   
  26.                //交换数值   
  27.                swap(heap, parent, index);   
  28.                //递归调用   
  29.                heapUp(heap, parent);   
  30.            }   
  31.   
  32.        }   
  33.    }   
 //向最大堆中插入元素, heap:存放堆元素的数组public static void insert(List<Integer> heap, int value) { //在数组的尾部添加if(heap.size()==0)heap.add(0);//数组下标为0的位置不放元素heap.add(value); //开始上升操作 // heapUp2(heap, heap.size() - 1); heapUp(heap, heap.size() - 1); } //上升,让插入的数和父节点的数值比较,当大于父节点的时候就和父节点的值相交换 public static void heapUp(List<Integer> heap, int index) { //注意由于数值是从下标为1开始,当index = 1的时候,已经是根节点了 if (index > 1) { //求出父亲的节点 int parent = index / 2; //获取相应位置的数值 int parentValue = (Integer) heap.get(parent); int indexValue = (Integer) heap.get(index); //如果父亲节点比index的数值小,就交换二者的数值 if (parentValue < indexValue) { //交换数值 swap(heap, parent, index); //递归调用 heapUp(heap, parent); } } } 


⑵删除  
   操作原理是:当删除节点的数值时,原来的位置就会出现一个孔,填充这个孔的方法就是, 
把最后的叶子的值赋给该孔并下调到合适位置,最后把该叶子删除。 
  
如图中要删除72,先用堆中最后一个元素来35替换72,再将35下沉到合适位置,最后将叶子节点删除。 
   “结点下沉” 
注释:我们对堆进行任何操作都要考虑到操作完成之后保证它依然是一个完全二叉树或者近似完全二叉树,
所以删除操作才需要先对调最后一个位置和待删除结点,这样清除之后依然是个近似完全二叉树.

 

【勘误】
大家看到上面的删除过程是不是觉得很容易明白?
我也如此认为,直到我写程序时候出现了问题才重新审视删除算法的正确性。
譬如说:现在有一个最小堆,如下图:


现在我选中了93,并且要删除它,接下来会发生什么事?
接下来就是这个算法的结果了:

对,当节点没有空间下沉的时候它就会无所事事,结果导致不对了。
这种情况下面我们可以借用插入过程的上浮调整方式,从最下面开始向上调整。

注释:下边这段代码没看出来有改进的意思...
照我的意思,另外设立一个调整函数,根据元素当前的状态,决定是上浮还是下沉.
比如:元素是叶结点,那只能上浮,元素是根结点,那只能下沉,其他结点比较其与子结点的
大小关系,能下沉的便一直下沉,不能下沉的便一直上浮.
这样插入,删除,替换都使用这一个调整函数就能完成,被统一起来了.
PS:删除并不需要从数组中删除掉某元素(当然删除了也可以),只需要把它与最后一个结点互换,然后把堆的大小-1即可.

[java] view plain copy
print ?
  1. 程序:  
  2.  /** 
  3.      * 删除堆中位置是index处的节点 
  4.      * 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔 
  5.      * 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除 
  6.      * @param heap  
  7.      */   
  8.     public static void delete(List<Integer> heap,int index) {   
  9.         //把最后的一个叶子的数值赋值给index位置   
  10.         heap.set(index, heap.get(heap.size() - 1));   
  11.         //下沉操作   
  12.         //heapDown2(heap, index);   
  13.         heapDown(heap, index);   
  14.         //把最后一个位置的数字删除   
  15.         heap.remove(heap.size() - 1);   
  16.     }   
  17.     /** 
  18.      * 递归实现 
  19.      * 删除堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变 
  20.      * @param heap 保持堆元素的数组 
  21.      * @param index 被删除的那个节点的位置 
  22.      */   
  23.     public static void heapDown(List<Integer> heap, int index) {   
  24.         //因为第一个位置存储的是空值,不在考虑之内   
  25.         int n = heap.size() - 2;   
  26.    
  27.         //记录最大的那个儿子节点的位置   
  28.         int child = -1;   
  29.    
  30.         //2*index>n说明该节点没有左右儿子节点了,那么就返回   
  31.         if (2 * index > n) {   
  32.             return;   
  33.         } //如果左右儿子都存在   
  34.         else if (2 * index < n) {   
  35.    
  36.             //定义左儿子节点   
  37.             child = 2 * index;   
  38.             //如果左儿子小于右儿子的数值,取右儿子的下标   
  39.             if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) {   
  40.                 child++;   
  41.             }   
  42.    
  43.         }//如果只有一个儿子(左儿子节点)   
  44.         else if (2 * index == n) {   
  45.             child = 2 * index;   
  46.         }   
  47.    
  48.         if ((Integer) heap.get(child) > (Integer) heap.get(index)) {   
  49.             //交换堆中的child,和index位置的值   
  50.             swap(heap, child, index);   
  51.    
  52.             //完成交换后递归调用,继续下降   
  53.             heapDown(heap, child);   
  54.         }   
  55.     }   
  56.    
程序:/*** 删除堆中位置是index处的节点* 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔* 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除* @param heap */ public static void delete(List<Integer> heap,int index) { //把最后的一个叶子的数值赋值给index位置 heap.set(index, heap.get(heap.size() - 1)); //下沉操作 //heapDown2(heap, index); heapDown(heap, index); //把最后一个位置的数字删除 heap.remove(heap.size() - 1); } /*** 递归实现* 删除堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变* @param heap 保持堆元素的数组* @param index 被删除的那个节点的位置*/ public static void heapDown(List<Integer> heap, int index) { //因为第一个位置存储的是空值,不在考虑之内 int n = heap.size() - 2; //记录最大的那个儿子节点的位置 int child = -1; //2*index>n说明该节点没有左右儿子节点了,那么就返回 if (2 * index > n) { return; } //如果左右儿子都存在 else if (2 * index < n) { //定义左儿子节点 child = 2 * index; //如果左儿子小于右儿子的数值,取右儿子的下标 if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } }//如果只有一个儿子(左儿子节点) else if (2 * index == n) { child = 2 * index; } if ((Integer) heap.get(child) > (Integer) heap.get(index)) { //交换堆中的child,和index位置的值 swap(heap, child, index); //完成交换后递归调用,继续下降 heapDown(heap, child); } } 


⑶初始化 
方法1:插入法: 
  从空堆开始,依次插入每一个结点,直到所有的结点全部插入到堆为止。 
注释:这里所谓的空堆,并不需要把数组给清空,只需要调整堆的大小就可以获得我们想要的堆,比如数组不变,设定堆的大小为1,
那么便只有第一个元素属于该堆了.
  时间:O(n*log(n)) 
  方法2:调整法: 
    序列对应一个完全二叉树;从最后一个分支结点(n div 2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),
以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。
注释:这里不应该叫"调整法",应该叫"下沉法",依据是若某结点的子结点都是最大/最小堆,那么下沉该结点便可保证以该结点为根结点的堆亦是最大/最小堆,为此,必须从下往上进行.实际上,上边的"插入法"可以看作是"上浮法",因为插入操作只需要上浮最后一个元素即可,当一个堆是最大/最小堆的时候,加入新元素在最后一个结点,上浮该元素即可保证该堆依然最大/最小堆的性质. 
  时间:O(n)    注释:此处存疑,我认为这个时间复杂度和上边应该是一样的. 
对如图的序列,要使其成为堆,我们从最后一个分支结点(10/2),其值为72开始,依次对每个分支节点53,18,36 45进行调整(下沉). 
 
 
 

【补充说明】
如何获取相应数组序列?
方法是依次将堆的根节点的小数记下,然后删除根节点,如此反复直到堆为空。上面提到了删除操作,每次删除之后都是要调整堆让堆的性质不变,即根节点必为最大值或最小值,明白了吗?
注释:上边这段说的是堆排序,注意这里的删除,并不需要从数组中删除某个元素,而是将该元素和最后一个结点互换,堆的大小-1即可.

[java] view plain copy
print ?
  1. 程序:  
  2.      /*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/    
  3.      public static void adjust(List<Integer> heap){  
  4.         for (int i = heap.size() / 2; i > 0; i--)    
  5.             adjust(heap,i, heap.size()-1);    
  6.             
  7.         System.out.println("=================================================");  
  8.         System.out.println("调整后的初始堆:");  
  9.           print(heap);  
  10.       }  
  11.     /**  
  12.      * 调整堆,使其满足堆得定义  
  13.      * @param i  
  14.      * @param n  
  15.      */    
  16.     public static void adjust(List<Integer> heap,int i, int n) {    
  17.          
  18.         int child;    
  19.         for (; i <= n / 2; ) {    
  20.             child = i * 2;    
  21.             if(child+1<=n&&heap.get(child)<heap.get(child+1))    
  22.                 child+=1;/*使child指向值较大的孩子*/    
  23.             if(heap.get(i)< heap.get(child)){    
  24.                 swap(heap,i, child);    
  25.                 /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/    
  26.                 i = child;    
  27.                  
  28.             }  else break;  
  29.         }    
  30.     }    
程序:/*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/  public static void adjust(List<Integer> heap){for (int i = heap.size() / 2; i > 0; i--)  adjust(heap,i, heap.size()-1);  System.out.println("=================================================");System.out.println("调整后的初始堆:");print(heap);}/** * 调整堆,使其满足堆得定义 * @param i * @param n */  public static void adjust(List<Integer> heap,int i, int n) {  int child;  for (; i <= n / 2; ) {  child = i * 2;  if(child+1<=n&&heap.get(child)<heap.get(child+1))  child+=1;/*使child指向值较大的孩子*/  if(heap.get(i)< heap.get(child)){  swap(heap,i, child);  /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/  i = child;  }  else break;}  }  


(4)最大堆排序   

[java] view plain copy
print ?
  1. //对一个最大堆heap排序  
  2.    public static void heapSort(List<Integer> heap) {    
  3.         
  4.        for (int i = heap.size()-1; i > 0; i--) {    
  5.         /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/    
  6.            swap(heap,1, i);    
  7.            adjust(heap,1, i - 1);    
  8.        }    
  9.    }    
 //对一个最大堆heap排序public static void heapSort(List<Integer> heap) {  for (int i = heap.size()-1; i > 0; i--) {  /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/  swap(heap,1, i);  adjust(heap,1, i - 1);  }  }  


(5)完整的代码 
[java] view plain copy
print ?
  1. import java.util.*;   
  2.    
  3. /** 
  4.  *实现的最大堆的插入和删除操作 
  5.  * @author Arthur 
  6.  */   
  7. public class Heap {   
  8.      /** 
  9.      * 删除堆中位置是index处的值 
  10.      * 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔 
  11.      * 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除 
  12.      * @param heap 一个最大堆 
  13.      */   
  14.     public static void delete(List<Integer> heap,int index) {   
  15.         //把最后的一个叶子的数值赋值给index位置   
  16.         heap.set(index, heap.get(heap.size() - 1));   
  17.         //下沉操作   
  18.         //heapDown2(heap, index);   
  19.         heapDown(heap, index); //节点下沉  
  20.         //把最后一个位置的数字删除   
  21.         heap.remove(heap.size() - 1);   
  22.     }   
  23.    
  24.    
  25.     /**  
  26.      * 节点下沉递归实现 
  27.      * 删除一个堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变 
  28.      * @param heap 保持最大堆元素的数组 
  29.      * @param index 被删除的那个节点的位置 
  30.      */   
  31.     public static void heapDown(List<Integer> heap, int index) {   
  32.         //因为第一个位置存储的是空值,不在考虑之内   
  33.         int n = heap.size() - 2;   
  34.    
  35.         //记录最大的那个儿子节点的位置   
  36.         int child = -1;   
  37.    
  38.         //2*index>n说明该节点没有左右儿子节点了,那么就返回   
  39.         if (2 * index > n) {   
  40.             return;   
  41.         } //如果左右儿子都存在   
  42.         else if (2 * index < n) {   
  43.    
  44.             //定义左儿子节点   
  45.             child = 2 * index;   
  46.             //如果左儿子小于右儿子的数值,取右儿子的下标   
  47.             if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) {   
  48.                 child++;   
  49.             }   
  50.    
  51.         }//如果只有一个儿子(左儿子节点)   
  52.         else if (2 * index == n) {   
  53.             child = 2 * index;   
  54.         }   
  55.    
  56.         if ((Integer) heap.get(child) > (Integer) heap.get(index)) {   
  57.             //交换堆中的child,和index位置的值   
  58.             swap(heap, child, index);   
  59.    
  60.             //完成交换后递归调用,继续下降   
  61.             heapDown(heap, child);   
  62.         }   
  63.     }   
  64.    
  65.     //非递归实现   
  66.     public static void heapDown2(List<Integer> heap, int index) {   
  67.         int child = 0;//存储左儿子的位置   
  68.    
  69.         int temp = (Integer) heap.get(index);   
  70.         int n = heap.size() - 2;   
  71.         //如果有儿子的话   
  72.         for (; 2 * index <= n; index = child) {   
  73.             //获取左儿子的位置   
  74.             child = 2 * index;   
  75.             //如果只有左儿子   
  76.             if (child == n) {   
  77.                 child = 2 * index;   
  78.             } //如果右儿子比左儿子的数值大   
  79.             else if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) {   
  80.                 child++;   
  81.             }   
  82.    
  83.             //如果数值最大的儿子比temp的值大   
  84.             if ((Integer) heap.get(child) >temp) {   
  85.                 //交换堆中的child,和index位置的值   
  86.                 swap(heap, child, index);   
  87.             } else {   
  88.                 break;   
  89.             }   
  90.         }   
  91.     }   
  92.    
  93.       
  94.      //打印链表   
  95.     public static void print(List<Integer> list) {   
  96.         for (int i = 1; i < list.size(); i++) {   
  97.             System.out.print(list.get(i) + " ");   
  98.         }   
  99.         System.out.println();  
  100.     }   
  101.    
  102.     //把堆中的a,b位置的值互换   
  103.     public static void swap(List<Integer> heap, int a, int b) {   
  104.         //临时存储child位置的值   
  105.         int temp = (Integer) heap.get(a);   
  106.    
  107.         //把index的值赋给child的位置   
  108.         heap.set(a, heap.get(b));   
  109.    
  110.         //把原来的child位置的数值赋值给index位置   
  111.         heap.set(b, temp);   
  112.     }   
  113.    
  114.     //向最大堆中插入元素   
  115.     public static void insert(List<Integer> heap, int value) {   
  116.            //在数组的尾部添加要插入的元素  
  117.         if(heap.size()==0)  
  118.           heap.add(0);//数组下标为0的位置不放元素  
  119.         heap.add(value);   
  120.         //开始上升操作   
  121.        // heapUp2(heap, heap.size() - 1);   
  122.         heapUp(heap, heap.size() - 1);   
  123.    
  124.     }   
  125.    
  126.     //节点上浮,让插入的数和父节点的数值比较,当大于父节点的时候就和节点的值相交换   
  127.     public static void heapUp(List<Integer> heap, int index) {   
  128.    
  129.         //注意由于数值是从小标为一开始,当index = 1的时候,已经是根节点了   
  130.         if (index > 1) {   
  131.             //保存父亲的节点   
  132.             int parent = index / 2;   
  133.    
  134.             //获取相应位置的数值   
  135.             int parentValue = (Integer) heap.get(parent);   
  136.             int indexValue = (Integer) heap.get(index);   
  137.             //如果父亲节点比index的数值小,就交换二者的数值   
  138.             if (parentValue < indexValue) {   
  139.                 //交换数值   
  140.                 swap(heap, parent, index);   
  141.                 //递归调用   
  142.                 heapUp(heap, parent);   
  143.             }   
  144.    
  145.         }   
  146.     }   
  147.    
  148.     //非递归实现   
  149.     public static void heapUp2(List<Integer> heap, int index) {   
  150.         int parent = 0;   
  151.         for (; index > 1; index /= 2) {   
  152.             //获取index的父节点的下标   
  153.             parent = index / 2;   
  154.    
  155.             //获得父节点的值   
  156.             int parentValue = (Integer) heap.get(parent);   
  157.             //获得index位置的值   
  158.             int indexValue = (Integer) heap.get(index);   
  159.                
  160.             //如果小于就交换   
  161.             if (parentValue < indexValue) {   
  162.                 swap(heap, parent, index);   
  163.             }   
  164.         }   
  165.     }   
  166.      /*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/    
  167.      public static void adjust(List<Integer> heap){  
  168.         for (int i = heap.size() / 2; i > 0; i--)    
  169.             adjust(heap,i, heap.size()-1);    
  170.             
  171.         System.out.println("=================================================");  
  172.         System.out.println("调整后的初始堆:");  
  173.           print(heap);  
  174.       }  
  175.     /**  
  176.      * 调整堆,使其满足堆得定义  
  177.      * @param i  
  178.      * @param n  
  179.      */    
  180.     public static void adjust(List<Integer> heap,int i, int n) {    
  181.          
  182.         int child;    
  183.         for (; i <= n / 2; ) {    
  184.             child = i * 2;    
  185.             if(child+1<=n&&heap.get(child)<heap.get(child+1))    
  186.                 child+=1;/*使child指向值较大的孩子*/    
  187.             if(heap.get(i)< heap.get(child)){    
  188.                 swap(heap,i, child);    
  189.                 /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/    
  190.                 i = child;    
  191.                  
  192.             }  else break;  
  193.         }    
  194.     }    
  195.     
  196.    //对一个最大堆heap排序  
  197.     public static void heapSort(List<Integer> heap) {    
  198.          
  199.         for (int i = heap.size()-1; i > 0; i--) {    
  200.         /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/    
  201.             swap(heap,1, i);    
  202.             adjust(heap,1, i - 1);    
  203.         }    
  204.     }    
  205.    public static void main(String args[]) {   
  206.         List<Integer> array = new ArrayList<Integer>(Arrays.asList(null,   
  207. 125103711151720915816));  
  208.         adjust(array);//调整使array成为最大堆  
  209.          
  210.         delete(array,8);//堆中删除下标是8的元素  
  211.         System.out.println("删除后");  
  212.         print(array);  
  213.         insert(array, 99);//堆中插入  
  214.         print(array);   
  215.         heapSort(array);//排序  
  216.         System.out.println("将堆排序后:");  
  217.         print(array);  
  218.         System.out.println("-------------------------");  
  219.         List<Integer> array1=new ArrayList<Integer>();  
  220.         insert(array1,0);  
  221.         insert(array1, 1);insert(array1, 2);insert(array1, 5);  
  222.         insert(array1, 10);insert(array1, 3);insert(array1, 7);  
  223.         insert(array1, 11);insert(array1, 15); insert(array1, 17);  
  224.         insert(array1, 20);insert(array1, 9);  
  225.         insert(array1, 15);insert(array1, 8);insert(array1, 16);  
  226.         print(array1);  
  227.           
  228.         System.out.println("==============================");  
  229.         array=new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));  
  230.         adjust(array);  
  231.           insert(array, 80);//堆中插入  
  232.           print(array);  
  233.          delete(array,2);//堆中删除80的元素  
  234.          print(array);  
  235.          delete(array,2);//堆中删除72的元素  
  236.          print(array);  
  237.                 
  238.     }   
  239. }   
import java.util.*; /***实现的最大堆的插入和删除操作* @author Arthur*/ 
public class Heap { /*** 删除堆中位置是index处的值* 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔* 填充这个孔的方法就是,把最后的叶子的值赋给该孔,最后把该叶子删除* @param heap 一个最大堆*/ public static void delete(List<Integer> heap,int index) { //把最后的一个叶子的数值赋值给index位置 heap.set(index, heap.get(heap.size() - 1)); //下沉操作 //heapDown2(heap, index); heapDown(heap, index); //节点下沉//把最后一个位置的数字删除 heap.remove(heap.size() - 1); } /** * 节点下沉递归实现* 删除一个堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变* @param heap 保持最大堆元素的数组* @param index 被删除的那个节点的位置*/ public static void heapDown(List<Integer> heap, int index) { //因为第一个位置存储的是空值,不在考虑之内 int n = heap.size() - 2; //记录最大的那个儿子节点的位置 int child = -1; //2*index>n说明该节点没有左右儿子节点了,那么就返回 if (2 * index > n) { return; } //如果左右儿子都存在 else if (2 * index < n) { //定义左儿子节点 child = 2 * index; //如果左儿子小于右儿子的数值,取右儿子的下标 if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } }//如果只有一个儿子(左儿子节点) else if (2 * index == n) { child = 2 * index; } if ((Integer) heap.get(child) > (Integer) heap.get(index)) { //交换堆中的child,和index位置的值 swap(heap, child, index); //完成交换后递归调用,继续下降 heapDown(heap, child); } } //非递归实现 public static void heapDown2(List<Integer> heap, int index) { int child = 0;//存储左儿子的位置 int temp = (Integer) heap.get(index); int n = heap.size() - 2; //如果有儿子的话 for (; 2 * index <= n; index = child) { //获取左儿子的位置 child = 2 * index; //如果只有左儿子 if (child == n) { child = 2 * index; } //如果右儿子比左儿子的数值大 else if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } //如果数值最大的儿子比temp的值大 if ((Integer) heap.get(child) >temp) { //交换堆中的child,和index位置的值 swap(heap, child, index); } else { break; } } } //打印链表 public static void print(List<Integer> list) { for (int i = 1; i < list.size(); i++) { System.out.print(list.get(i) + " "); } System.out.println();} //把堆中的a,b位置的值互换 public static void swap(List<Integer> heap, int a, int b) { //临时存储child位置的值 int temp = (Integer) heap.get(a); //把index的值赋给child的位置 heap.set(a, heap.get(b)); //把原来的child位置的数值赋值给index位置 heap.set(b, temp); } //向最大堆中插入元素 public static void insert(List<Integer> heap, int value) { //在数组的尾部添加要插入的元素if(heap.size()==0)heap.add(0);//数组下标为0的位置不放元素heap.add(value); //开始上升操作 // heapUp2(heap, heap.size() - 1); heapUp(heap, heap.size() - 1); } //节点上浮,让插入的数和父节点的数值比较,当大于父节点的时候就和节点的值相交换 public static void heapUp(List<Integer> heap, int index) { //注意由于数值是从小标为一开始,当index = 1的时候,已经是根节点了 if (index > 1) { //保存父亲的节点 int parent = index / 2; //获取相应位置的数值 int parentValue = (Integer) heap.get(parent); int indexValue = (Integer) heap.get(index); //如果父亲节点比index的数值小,就交换二者的数值 if (parentValue < indexValue) { //交换数值 swap(heap, parent, index); //递归调用 heapUp(heap, parent); } } } //非递归实现 public static void heapUp2(List<Integer> heap, int index) { int parent = 0; for (; index > 1; index /= 2) { //获取index的父节点的下标 parent = index / 2; //获得父节点的值 int parentValue = (Integer) heap.get(parent); //获得index位置的值 int indexValue = (Integer) heap.get(index); //如果小于就交换 if (parentValue < indexValue) { swap(heap, parent, index); } } } /*根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆*/  public static void adjust(List<Integer> heap){for (int i = heap.size() / 2; i > 0; i--)  adjust(heap,i, heap.size()-1);  System.out.println("=================================================");System.out.println("调整后的初始堆:");print(heap);}/** * 调整堆,使其满足堆得定义 * @param i * @param n */  public static void adjust(List<Integer> heap,int i, int n) {  int child;  for (; i <= n / 2; ) {  child = i * 2;  if(child+1<=n&&heap.get(child)<heap.get(child+1))  child+=1;/*使child指向值较大的孩子*/  if(heap.get(i)< heap.get(child)){  swap(heap,i, child);  /*交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整*/  i = child;  }  else break;}  }  //对一个最大堆heap排序public static void heapSort(List<Integer> heap) {  for (int i = heap.size()-1; i > 0; i--) {  /*把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序*/  swap(heap,1, i);  adjust(heap,1, i - 1);  }  }  public static void main(String args[]) { List<Integer> array = new ArrayList<Integer>(Arrays.asList(null, 
1, 2, 5, 10, 3, 7, 11, 15, 17, 20, 9, 15, 8, 16));adjust(array);//调整使array成为最大堆delete(array,8);//堆中删除下标是8的元素System.out.println("删除后");print(array);insert(array, 99);//堆中插入print(array); heapSort(array);//排序System.out.println("将堆排序后:");print(array);System.out.println("-------------------------");List<Integer> array1=new ArrayList<Integer>();insert(array1,0);insert(array1, 1);insert(array1, 2);insert(array1, 5);insert(array1, 10);insert(array1, 3);insert(array1, 7);insert(array1, 11);insert(array1, 15); insert(array1, 17);insert(array1, 20);insert(array1, 9);insert(array1, 15);insert(array1, 8);insert(array1, 16);print(array1);System.out.println("==============================");array=new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));adjust(array);insert(array, 80);//堆中插入print(array);delete(array,2);//堆中删除80的元素print(array);delete(array,2);//堆中删除72的元素print(array);} 
} 


程序运行: 
D:\java>java   Heap 
================================================= 
调整后的初始堆: 
20 17 16 15 9 15 11 1 10 3 2 7 8 5 
删除后 
20 17 16 15 9 15 11 5 10 3 2 7 8 
99 17 20 15 9 15 16 5 10 3 2 7 8 11 
将堆排序后: 
2 3 5 7 8 9 10 11 15 15 16 17 20 99 
------------------------- 
20 17 16 10 15 9 15 0 5 2 11 1 7 3 8 
============================== 
================================================= 
调整后的初始堆: 
93 72 48 53 45 30 18 36 15 35 
93 80 48 53 72 30 18 36 15 35 45 
93 72 48 53 45 30 18 36 15 35 
93 53 48 36 45 30 18 35 15 

好了,想必大家都明白了,下一篇文章将放出相关算法及结果。

这篇关于堆的插入与删除,上浮与下沉的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/236601

相关文章

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

如何恢复回收站中已删除/清空的文件

回收站清空后如何恢复已删除的文件?是否可以恢复永久删除的文件?或者最糟糕的是,如果文件直接被删除怎么办?本文将向您展示清空回收站后恢复已删除数据的最佳方法。 回收站清空后如何恢复已删除的文件? “回收站清空后我还能恢复已删除的文件吗?” 答案是肯定的,但是在这种情况下您将需要一个  回收站恢复工具 来从回收站中检索文件: 错误/永久删除回收站或任何数字存储设备中的文件 直接删除的文件/

matplotlib绘图中插入图片

在使用matplotlib下的pyplot绘图时,有时处于各种原因,需要采用类似贴图的方式,插入外部的图片,例如添加自己的logo,或者其他的图形水印等。 一开始,查找到的资料都是使用imshow,但是这会有带来几个问题,一个是图形的原点发生了变化,另外一个问题就是图形比例也产生了变化,当然最大的问题是图形占据了整个绘图区域,完全喧宾夺主了,与我们设想的只在绘图区域中占据很小的一块不相符。 经

Linux 删除 当前下的 mysql-8.0.31 空文件夹

在Linux中,如果你想要删除当前目录下的名为mysql-8.0.31的空文件夹(即该文件夹内没有任何文件或子文件夹),你可以使用rmdir命令。但是,如果mysql-8.0.31文件夹并非完全为空(即它包含文件或子文件夹),rmdir命令会失败。 如果你的目标是删除mysql-8.0.31文件夹及其内部的所有内容(无论是否为空),你应该使用rm命令结合-r(或-R,它们是等价的)选项来递归地删

如何删除不小心上传到git远程仓库中的.idea .iml文件

如果在开始的时候不配置,gitignore文件或者文件配置不正确,初始化上传的时候就会有一些不必要的信息上传上去 如果已经存在了一些文件在git远程仓库中,如。idea,.iml文件等。 首先在项目中定义一个  .gitignore文件,简单的实例如下也可以用idea中的gitignore插件 .DS_Storeclasses/*.settings/target/.classpath

Win8下如何快速查找和删除电脑中的病毒

Win8系统如何查找和删除病毒?检查你的电脑是否存在病毒的一种快速方法是使用 Windows Defender. 此恶意软件防护随 Windows 提供,可帮助识别和删除病毒、间谍软件和其他恶意软件。   注意:如果你使用的是 Windows RT,则 Windows Defender 会始终启用,并且不能关闭。   如果你使用的是 Windows 8,则可以根据自己的喜好运行由其他

【数据结构与算法 | 灵神题单 | 删除链表篇】力扣3217, 82, 237

总结,删除链表节点问题使用到列表,哈希表,递归比较容易超时,我觉得使用计数排序比较稳,处理起来也不是很难。 1. 力扣3217:从链表中移除在数组中的节点 1.1 题目: 给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后,返回修改后的链表的头节点。 示例 1: 输入: nums = [1,2,3], head = [1,2,3,

JAVA学习-练习试用Java实现“删除有序数组中的重复项”

问题: 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 说明: 为什么返回数值是整数,但输出的答案是数组呢? 请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 你可以想象内部操作如下