十四、堆

优先级队列是一个抽象数据类型(ADT),它提供了删除最大(或最小)关键字值的数据项的方法,插入数据项的方法,有时还有一些其他操作的方法。配合不同的ADT,优先级队列可以用不同的内部结构来实现。优先级队列可以用有序数组来实现,但是这种作法的问题是,尽管删除最大数据项的时间复杂度为O(1),但是插入还是需要比较长的O(N)时间。这是因为必须移动数组中平均一半的数据项以插入新数据项,并在完成插入后,数组依然有序。实现优先级队列的另一种结构:堆。堆是一种树,由它实现的优先级队列的插入和删除的时间复杂度都是(O(logN))。尽管这样删除的时间变慢了一些,但是插入的时间快得多了。当速度非常重要,且有很多插入操作时,可以选择堆来实现优先级队列。

 

堆的特点:

它是完全二叉树。除了树的最后一层节点不需要是满的,其他的每一层从左到右都完全是满的。

它常常用一个数组实现。

堆中的每一个节点都满足堆的条件,也就是说每一个节点的关键字都大于(或等于)这个节点的子节点的关键字。

// to run this program: C>java HeapApp
import java.io.*;
class Node
{
   private int iData;             // data item (key)

   public Node(int key)           // constructor
   { iData = key; }

   public int getKey()
   { return iData; }

   public void setKey(int id)
   { iData = id; }

} 

class Heap
{
   private Node[] heapArray;
   private int maxSize;           // size of array
   private int currentSize;       // number of nodes in array

   public Heap(int mx)            // constructor
   {
      maxSize = mx;
      currentSize = 0;
      heapArray = new Node[maxSize];  // create array
   }

   public boolean isEmpty()
   { return currentSize==0; }

   public boolean insert(int key)
   {
      if(currentSize==maxSize)
         return false;
      Node newNode = new Node(key);
      heapArray[currentSize] = newNode;
      trickleUp(currentSize++);
      return true;
   } 

   public void trickleUp(int index)
   {
      int parent = (index-1) / 2;
      Node bottom = heapArray[index];

      while( index > 0 &&
             heapArray[parent].getKey() < bottom.getKey() )
      {
         heapArray[index] = heapArray[parent];  // move it down
         index = parent;
         parent = (parent-1) / 2;
      }
      heapArray[index] = bottom;
   } 

   public Node remove()           // delete item with max key
   {                           // (assumes non-empty list)
      Node root = heapArray[0];//移走根
      heapArray[0] = heapArray[--currentSize];//把最后一个节点移动到根的位置
      trickleDown(0);
      return root;
   } 

   public void trickleDown(int index)
   {
      int largerChild;
      Node top = heapArray[index];       // save root
      while(index < currentSize/2)       // while node has at
      {                               //    least one child,
         int leftChild = 2*index+1;
         int rightChild = leftChild+1;
                                         // find larger child
         if(rightChild < currentSize &&  // (rightChild exists?)
                             heapArray[leftChild].getKey() <
                             heapArray[rightChild].getKey())
            largerChild = rightChild;
         else
            largerChild = leftChild;
                                         // top >= largerChild?
         if( top.getKey() >= heapArray[largerChild].getKey() )
            break;
                                         // shift child up
         heapArray[index] = heapArray[largerChild];
         index = largerChild;            // go down
      } 
      heapArray[index] = top;            // root to index
   } 

   public boolean change(int index, int newValue)//关键字的更改
   {
      if(index<0 || index>=currentSize)
         return false;
      int oldValue = heapArray[index].getKey(); // remember old
      heapArray[index].setKey(newValue);  // change to new

      if(oldValue < newValue)             // if raised,
         trickleUp(index);                // trickle it up
      else                                // if lowered,
         trickleDown(index);              // trickle it down
      return true;
   } 

   public void displayHeap()
   {
      System.out.print("heapArray: ");    // array format
      for(int m=0; m<currentSize; m++)
         if(heapArray[m] != null)
            System.out.print( heapArray[m].getKey() + " ");
         else
            System.out.print( "-- ");
      System.out.println();
                                          // heap format
      int nBlanks = 32;
      int itemsPerRow = 1;
      int column = 0;
      int j = 0;                          // current item
      String dots = "...............................";
      System.out.println(dots+dots);      // dotted top line

      while(currentSize > 0)              // for each heap item
      {
         if(column == 0)                  // first item in row?
            for(int k=0; k<nBlanks; k++)  // preceding blanks
               System.out.print(' ');
                                          // display item
         System.out.print(heapArray[j].getKey());

         if(++j == currentSize)           // done?
            break;

         if(++column==itemsPerRow)        // end of row?
         {
            nBlanks /= 2;                 // half the blanks
            itemsPerRow *= 2;             // twice the items
            column = 0;                   // start over on
            System.out.println();         //    new row
         }
         else                             // next item on row
            for(int k=0; k<nBlanks*2-2; k++)
               System.out.print(' ');     // interim blanks
      } 
      System.out.println("
"+dots+dots); // dotted bottom line
   }  
} 

class HeapApp
{
   public static void main(String[] args) throws IOException
   {
      int value, value2;
      Heap theHeap = new Heap(31);  // make a Heap; max size 31
      boolean success;

      theHeap.insert(70);           // insert 10 items
      theHeap.insert(40);
      theHeap.insert(50);
      theHeap.insert(20);
      theHeap.insert(60);
      theHeap.insert(100);
      theHeap.insert(80);
      theHeap.insert(30);
      theHeap.insert(10);
      theHeap.insert(90);

      while(true)                   // until [Ctrl]-[C]
      {
         System.out.print("Enter first letter of ");
         System.out.print("show, insert, remove, change: ");
         int choice = getChar();
         switch(choice)
         {
            case 's':                        // show
               theHeap.displayHeap();
               break;
            case 'i':                        // insert
               System.out.print("Enter value to insert: ");
               value = getInt();
               success = theHeap.insert(value);
               if( !success )
                  System.out.println("Can't insert; heap full");
               break;
            case 'r':                        // remove
               if( !theHeap.isEmpty() )
                  theHeap.remove();
               else
                  System.out.println("Can't remove; heap empty");
               break;
            case 'c':                        // change
               System.out.print("Enter current index of item: ");
               value = getInt();
               System.out.print("Enter new key: ");
               value2 = getInt();
               success = theHeap.change(value, value2);
               if( !success )
                  System.out.println("Invalid index");
               break;
            default:
               System.out.println("Invalid entry
");
         } 
      }
   }  

   public static String getString() throws IOException
   {
      InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader br = new BufferedReader(isr);
      String s = br.readLine();
      return s;
   }

   public static char getChar() throws IOException
   {
      String s = getString();
      return s.charAt(0);
   }

   public static int getInt() throws IOException
   {
      String s = getString();
      return Integer.parseInt(s);
   }
} 

堆数组的扩展

在程序运行的过程中,如果插入太多的数据项,超出了堆数组的容量,可以创建一个新的数组,把数据从旧数组中复制到新的数组中。(和哈希表中的情况不同,改变堆的大小不需要重新排列数据。)执行复制操作的时间是线性的,但是增大数组容量的操作并不会经常发生,特别是当每次扩展数组容量时,数组的容量都充分地增大了。

 

堆排序

堆排序的基本思想是使用普通的insert()例程在堆中插入全部无序的数据项,然后重复用remove()例程,就可以按序移除所有数据项。因为insert()和remove()方法操作的时间复杂度都是O(logN),并且每个方法必须都要执行N次,所以整个排序操作需要O(N*logN)时间,这和快速排序一样。但是,它不如快速排序快,部分原因是trickleDown()里while循环中的操作比快速排序里循环中的操作要多。有几个技巧可以是堆排序更有效。其一是节省时间,其二是节省内存。

1、如果在堆中插入N个新数据项,则需要应用trickleUp()方法N次。但是如果现将无序的数据项依次插入数组中,再对节点N/2-1至节点0(排除最后一层的叶子节点)应用trickleUp(),那么只需要N/2次。

2、使用同一个数组。原始代码片段显示了数组中的无序数据。然后把数据插入到堆中,最后从堆中移除它并把它有序地写会到数组中。这个过程需要两个大小为N的数组:初始数组和用于堆的数组。事实上,堆和初始数据可以使用同一个数组。这样堆排序所需的存储空间减少了一半;除了初始数组之外不需要额外的存储空间。每从堆顶移除一个数据项,堆数组的末端单元就变为空的,堆减少一个节点。可以把最近一次移除的节点放到这个新空出的单元中。移除的节点越来越多,堆数据就越来越小,但是有序数据的数组却越来越大。因此,有序数组和堆数组就可以共同使用一块存储空间。

// to run this program: C>java HeapSortApp
import java.io.*;
class Node
{
   private int iData;             // data item (key)

   public Node(int key)           // constructor
   { iData = key; }

   public int getKey()
   { return iData; }
} 

class Heap
{
   private Node[] heapArray;
   private int maxSize;           // size of array
   private int currentSize;       // number of items in array

   public Heap(int mx)            // constructor
   {
      maxSize = mx;
      currentSize = 0;
      heapArray = new Node[maxSize];
   }

   public Node remove()           // delete item with max key
   {                           // (assumes non-empty list)
      Node root = heapArray[0];
      heapArray[0] = heapArray[--currentSize];
      trickleDown(0);
      return root;
   } 

   public void trickleDown(int index)
   {
      int largerChild;
      Node top = heapArray[index];        // save root
      while(index < currentSize/2)        // not on bottom row
      {
         int leftChild = 2*index+1;
         int rightChild = leftChild+1;
                                          // find larger child
         if(rightChild < currentSize &&   // right ch exists?
                             heapArray[leftChild].getKey() <
                             heapArray[rightChild].getKey())
            largerChild = rightChild;
         else
            largerChild = leftChild;
                                          // top >= largerChild?
         if(top.getKey() >= heapArray[largerChild].getKey())
            break;
                                          // shift child up
         heapArray[index] = heapArray[largerChild];
         index = largerChild;             // go down
      } 
      heapArray[index] = top;             // root to index
   }  

   public void displayHeap()
   {
      int nBlanks = 32;
      int itemsPerRow = 1;
      int column = 0;
      int j = 0;                          // current item
      String dots = "...............................";
      System.out.println(dots+dots);      // dotted top line

      while(currentSize > 0)              // for each heap item
      {
         if(column == 0)                  // first item in row?
            for(int k=0; k<nBlanks; k++)  // preceding blanks
               System.out.print(' ');
                                          // display item
         System.out.print(heapArray[j].getKey());

         if(++j == currentSize)           // done?
            break;

         if(++column==itemsPerRow)        // end of row?
         {
            nBlanks /= 2;                 // half the blanks
            itemsPerRow *= 2;             // twice the items
            column = 0;                   // start over on
            System.out.println();         //    new row
         }
         else                             // next item on row
            for(int k=0; k<nBlanks*2-2; k++)
               System.out.print(' ');     // interim blanks
      }  
      System.out.println("
"+dots+dots); // dotted bottom line
   } 

   public void displayArray()
   {
      for(int j=0; j<maxSize; j++)
         System.out.print(heapArray[j].getKey() + " ");
      System.out.println("");
   }

   public void insertAt(int index, Node newNode)
   { heapArray[index] = newNode; }

   public void incrementSize()
   { currentSize++; }

   public void heapify(int index)
   {
     if(index>currentSize/2-1)
    return;
     heapify(index*2+2);
     heapify(index*2+1);
     trickleDown(index);
   }
} 

class HeapSortApp
{
   public static void main(String[] args) throws IOException
   {
      int size, j;

      System.out.print("Enter number of items: ");
      size = getInt();
      Heap theHeap = new Heap(size);

      for(j=0; j<size; j++)       // fill array with
      {                        //    random nodes
         int random = (int)(java.lang.Math.random()*100);
         Node newNode = new Node(random);
         theHeap.insertAt(j, newNode);
         theHeap.incrementSize();
      }

      System.out.print("Random: ");
         theHeap.displayArray();  // display random array

     /* for(j=size/2-1; j>=0; j--)  // 从非最后一层节点开始make random array into heap
         theHeap.trickleDown(j);*/
      theHeap.heapify(0); //使用递归将数组生成堆  

      System.out.print("Heap:   ");
      theHeap.displayArray();     // dislay heap array
      theHeap.displayHeap();      // display heap

      for(j=size-1; j>=0; j--)    // remove from heap and
      {                        //    store at array end
         Node biggestNode = theHeap.remove();
         theHeap.insertAt(j, biggestNode);
      }   

      System.out.print("Sorted: ");
      theHeap.displayArray();     // display sorted array
   }  

   public static String getString() throws IOException
   {
      InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader br = new BufferedReader(isr);
      String s = br.readLine();
      return s;
   }

   public static int getInt() throws IOException
   {
      String s = getString();
      return Integer.parseInt(s);
   }
}

堆排序的效率:堆排序运行的时间复杂度为O(N*logN)。尽管它比快速排序略慢,但它比快速排序优越的一点是它对初始数据的分布不敏感。在关键字值按某种排列顺序的情况下,快速排序运行的时间复杂度可以降到O(N2)级,然而堆排序对任意排列的数据,其排序的时间复杂度都是O(N*logN)。

 

小结:

优先级队列是提供了数据插入和移除最大(或者最小)数据项方法的抽象数据类型(ADT)。

堆是优先级队列ADT的有效的实现形式。

堆提供移除最大数据项和插入的方法,时间复杂度都为O(logN)。

最大数据项总是在根的位置上。

堆不能有序地遍历所有的数据,不能找到特定关键字数据项的位置,也不能移除特定关键字的数据项。

堆通常用数组来实现,表现为一颗完全二叉树。根节点的下标为0,最后一个节点的下标为N-1。

每个节点的关键字都小于它的父节点,大于它的子节点。

要插入的数据项总是先被存放到数组第一个空的单元中,然后再向上筛选它至适当的位置。

当从根移除一个数据项时,用数据中最后一个数据项取代它的位置,然后再向下筛选这个节点至适当的位置。

向上筛选和向下筛选算法可以被看作是一系列的交换,但更有效的作法是进行一系列复制。

可以更改任一个数据项的优先级。首先,更改它的关键字。如果关键字增加了,数据项就向上筛选,而如果关键字减小了,数据项就向下筛选。

在概念上堆排序的过程包括现在堆中插入N次,然后再作N次移除。

通过对无序数组中的N/2个数据项施用向下筛选算法,而不作N次插入,可以使堆排序的运行速度更快。

可以使用同一个数组来存放初始无序的数据,堆以及最后有序的数据。因此,堆排序不需要额外的存储空间。

原文地址:https://www.cnblogs.com/xxlong/p/5016831.html