zoukankan      html  css  js  c++  java
  • 十四、堆

    优先级队列是一个抽象数据类型(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次插入,可以使堆排序的运行速度更快。

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

  • 相关阅读:
    构造函数+this关键字+super关键字
    封装
    数组+方法
    流程控制语句
    java运算符+引用数据类型
    java的基础安装
    mysql数据库连接
    mysql数据库约束
    mysql数据库
    练习010:按奇偶排序数组
  • 原文地址:https://www.cnblogs.com/xxlong/p/5016831.html
Copyright © 2011-2022 走看看