堆排序程序中的小于等于号问题

做堆排序问题时遇到一个bug,调试了很久才发现原因,是一个小于号和小于等于号的问题,在递归时的边界没考虑周全。代码用java写的,拿出来分析下,首先是网上比较多的使用大于号控制边界的程序:

import java.util.*;
public class MaxHeap {
    public static int[] heapSort(int[] A, int n) {
        // write code here
        MakeMaxHeap(A,n-1);
        System.out.println("build complete " );
        for(int i=n-1;i>=0;i--){ // 不断减小堆的大小
            swap(A, 0, i);
            for(int ii=0;ii<7;ii++){
                System.out.print(A[ii]+", " );
            }
            System.out.println("swap  "+"A[0] "+" <->"+" A[i]: "+i );   
            MaxHeapFixUp(A, 0, i);
        }
        return A;
    }
    public static void MakeMaxHeap(int[] a, int heapsize)  {  
        for (int i = (heapsize-1) / 2 ; i >= 0; i--){
            MaxHeapFixUp(a, i, heapsize);     
        }
    }
    //  从i节点开始调整,heapsize为堆的长度 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
    public static void MaxHeapFixUp(int[] a, int i, int heapsize){
        int lchild = 2*i+1;
        int rchild = 2*i+2;
        int largest=-1;
        if(
lchild<heapsize
 && a[lchild]>a[i]){ //lchild<=heapsize 为何出错?
            largest = lchild;
        }else{
            largest = i;
        }
        if(rchild<heapsize && a[rchild]>a[largest]){
            largest = rchild;
        }
        if(largest != i){
            swap(a, largest, i);
            for(int ii=0;ii<7;ii++){
                System.out.print(a[ii]+", " );
            }
            System.out.println("swap  "+"largest: "+ largest+" <->"+" i: "+i );
            MaxHeapFixUp(a,largest,heapsize);//堆发生变化,要递归调用来调整堆
        }
        for(int ii=0;ii<7;ii++){
            System.out.print(a[ii]+", " );
        }
        System.out.println("heapsize: "+ heapsize );
    }
    public static void swap(int[] a, int i, int j){
        int temp;
        temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    public static void main(String[] args){
        int[] A = {6,5,4,3,2,1,0};//{0,1,2,3,4,5,6};
        int[] a=heapSort(A,7);
        System.out.println("堆排序结果:" );
        for(int i=0;i<7;i++)
        System.out.print(a[i]+", " );
        System.out.println("  " );
    }    
}
堆排序结果:
0, 1, 2, 3, 4, 5, 6 是正确的
(输出的一些中间结果就不给了)
我的代码是参考《算法导论》中的伪代码,在MaxHeapFixUp函数中红色字体的部分使用了小于等于号
if(lchild<=heapsize && a[lchild]>a[i]){ //lchild<=heapsize 为何出错?
            largest = lchild;
        }else{
            largest = i;
        }
 if(rchild<=heapsize && a[rchild]>a[largest]){
            largest = rchild;
        }

结果就出错了:5, 2, 3, 0, 4, 1, 6,

下面是算法导论中伪代码(第一版,P75),可以看到这里也是用的小于等于号

Snap1

当时我是按照《算法导论》中思路写的,所以并不知道是小于等于号出的问题,只能把中间步骤输出来找原因。由于输入的数组已经是一个大根堆,所以建堆过程没有问题。建堆之后,要把堆顶元素跟数组末尾元素交换,同时使堆长度减一,再对剩余的堆进行调整。下面的输出就反映了堆调整的过程:

build complete
0, 5, 4, 3, 2, 1, 6, swap A[0] <-> A[i]: 6
5, 0, 4, 3, 2, 1, 6, swap largest: 1 <-> i: 0
5, 3, 4, 0, 2, 1, 6, swap largest: 3 <-> i: 1
5, 3, 4, 0, 2, 1, 6, heapsize: 6
5, 3, 4, 0, 2, 1, 6, heapsize: 6
5, 3, 4, 0, 2, 1, 6, heapsize: 6
1, 3, 4, 0, 2, 5, 6, swap A[0] <-> A[i]: 5
4, 3, 1, 0, 2, 5, 6, swap largest: 2 <-> i: 0
4, 3, 5, 0, 2, 1, 6, swap largest: 5 <-> i: 2
4, 3, 5, 0, 2, 1, 6, heapsize: 5
4, 3, 5, 0, 2, 1, 6, heapsize: 5
4, 3, 5, 0, 2, 1, 6, heapsize: 5

黄色部分的第一行表示执行了heapSort函数中的swap语句,将大根堆堆顶的元素5与数组末尾元素1交换(此时6已经不再堆中,1为最后一个数)

第二行表示执行MaxHeapFixUp(int[] a, int i, int heapsize)函数后的结果,首先对i=0节点进行进行调整,在0号节点和它的子节点1和2中找到最大值并放到根节点0处。显然这里A[2]=4是A[0],A[1],A[2]中最大的,所以把A[0]跟A[2]交换。数组由1, 3, 4, 0, 2, 5, 6变为4, 3, 1, 0, 2, 5, 6 ,这一步是没有问题的。

第三行是继续执行MaxHeapFixUp(int[] a, int i, int heapsize)函数后的结果,因为A[2]是一个根节点,在上一步中它的值发生了变化,所以需要重新调整以A[2]为根的堆。A[2]的孩子分别是A[5]和A[6],貌似我们要继续比较A[2],A[5],A[6]的值找到最大值并将其跟A[2]交换。但是问题来了,在第一行的输出中,我们将A[5]=5放到了数组的最后,也就是将它排出了堆,也就是说此后在堆的操作中不能再涉及到A[5]了。

然而,很不幸,在代码中,我写到

if(lchild<=heapsize && a[lchild]>a[i])

在这步中lchild=5,heapsize=5,就把A[5]的值和A[2]交换了,得到4, 3, 5, 0, 2, 1, 6

所以改成小于等于号后扩大了堆的范围,将刚被移出堆的数(移出的数已就完成了部分排序)又拉进来了,这样刚排好的序又给打乱了,结果就出现错误了。

解决方法:

1、如果把不等号改成小于号,改成

if(lchild<heapsize && a[lchild]>a[i])

if(rchild<heapsize && a[rchild]>a[largest])

的话就不会出现这个问题。

2、为了保持跟《算法导论》一致,而且方便理解,可以保留小于等于号,但是需要修改heapsize的值,在heapSort函数中将MaxHeapFixUp(A, 0, i); 改为 MaxHeapFixUp(A, 0, i-1);

public static int[] heapSort(int[] A, int n) {
        // write code here
        MakeMaxHeap(A,n-1);
        System.out.println("build complete " );
        for(int i=n-1;i>=0;i--){ // 不断减小堆的大小
            swap(A, 0, i);
            for(int ii=0;ii<7;ii++){
                System.out.print(A[ii]+", " );
            }
            System.out.println("swap  "+"A[0] "+" <->"+" A[i]: "+i );   
            MaxHeapFixUp(A, 0, i-1);
        }
        return A;
    }

这样其实更加合理,因为在heapSort函数中第一次执行swap(A,0,i)时,已经将A[0]=6和A[n-1]=1互换,也就是把堆顶元素A[0]=6移出了堆,此时堆的大小应该是6,在数组中下标是1到5,i-1=5

《算法导论》中伪代码认为数组下标从1开始,而实际的程序中下标是从0开始的,所以这里数组长度虽然为7,但是heapsize的值却是从6开始递减,因此这里需要用i-1而不是i。

最后把改完的代码整体发上来,注意标红部分为本文刚开始贴的代码的区别。

import java.util.*;
public class MaxHeap {
    public static int[] heapSort(int[] A, int n) {
        // write code here
        MakeMaxHeap(A,n-1);
        System.out.println("build complete " );
        for(int i=n-1;i>=0;i--){ // 不断减小堆的大小
            swap(A, 0, i);
            for(int ii=0;ii<7;ii++){
                System.out.print(A[ii]+", " );
            }
            System.out.println("swap  "+"A[0] "+" <->"+" A[i]: "+i );   
            MaxHeapFixUp(A, 0, i-1);
        }
        return A;
    }
    public static void MakeMaxHeap(int[] a, int heapsize)  {  
        for (int i = (heapsize-1) / 2 ; i >= 0; i--){
            MaxHeapFixUp(a, i, heapsize);     
        }
    }
    //  从i节点开始调整,heapsize为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
    public static void MaxHeapFixUp(int[] a, int i, int heapsize){
        int lchild = 2*i+1;
        int rchild = 2*i+2;
        int largest=-1;
        if(
lchild<=heapsize
 && a[lchild]>a[i]){ //lchild<=heapsize 为何出错?
            largest = lchild;
        }else{
            largest = i;
        }
        if(rchild<=heapsize && a[rchild]>a[largest]){
            largest = rchild;
        }
        if(largest != i){
            swap(a, largest, i);
            for(int ii=0;ii<7;ii++){
                System.out.print(a[ii]+", " );
            }
            System.out.println("swap  "+"largest: "+ largest+" <->"+" i: "+i );
            MaxHeapFixUp(a,largest,heapsize);//堆发生变化,要递归调用来调整堆
        }
        for(int ii=0;ii<7;ii++){
            System.out.print(a[ii]+", " );
        }
        System.out.println("heapsize: "+ heapsize );
    }
    public static void swap(int[] a, int i, int j){
        int temp;
        temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    public static void main(String[] args){
        int[] A = {6,5,4,3,2,1,0};//{0,1,2,3,4,5,6};
        int[] a=heapSort(A,7);
        System.out.println("堆排序结果:" );
        for(int i=0;i<7;i++)
        System.out.print(a[i]+", " );
        System.out.println("  " );
    }    
}
原文地址:https://www.cnblogs.com/oucsheep/p/5047532.html