堆与优先级队列研究(C++)

 堆,是一个很有意思的数据结构。逻辑结构是树,一般为二叉树,每个节点的值都大于(小于)其子树中的任意节点。也就是说,使用堆结构的数组中,元素 是部分有序的。而正是这个特点,使得在堆上,得到最大值(最小值)的时间复杂度为O(1),移除最大值(最小值)、插入元素、改变元素值(或者是删除位置 已知的元素)的时间复杂度为O(lgn)。另外,用堆结构的排序是一种原地的、时间复杂度为O(nlgn)的排序算法。

在优先级队列前先说一下堆排序。

堆排序和归并排序都是时间复杂度为O(nlgn)的排序算法,不同的是,归并排序不是原地排序,它需要额外的n的空间;而堆排序是在原数组中进行的。

堆排序的过程,首先需要在给定的数组上进行建堆,然后在此基础上一个一个的取根节点放到数组的后部,从后向前排。

建堆的过程BuildHeap 代码很简单,从下到上对每个节点依次处理,处理中调用了另一个方法:Heapify ,这个才是核心(具体参见完整代码中的私有BuildHeap(void) 方法与Heapify(ItemType items[], int index, int count, bool isMaxHeap) 方法);Heapify 方法对指定节点的子树进行堆化,具体地说,当除了根节点以外,其子树中的所有节点都已经符合堆的要求时,Heapify 方 法会把指定的节点的子树调整为一个堆,也就是把指定节点调整到子树中合适的位置上,其时间复杂度为子树的深度,O(lgn)。所以,可以算出建堆的时间复 杂度为O(n),是线性的。这个时间复杂度怎么得到的?n长度的树,有lgn层,第h层(从0开始)至多有2^h的节点,进行一下求和就行了。

堆建好了,下来就是排序(具体可以见完整代码中私有的SortTo(ItemType destitionArray[]) 方法)。因为堆的根节点是最值,所以只需要依次把根节点和堆的最后一个元素交换位置,堆大小-1,再对根节点调用Heapify 就可以了。时间复杂度也就是n个Heapify ,O(nlgn)。

这样从而得到总的排序时间复杂度O(nlgn)。

优先级队列,目的是从队列中取出的一个元素总是当前队列中的最大值(最小值)。而堆刚好具有这个特点。当然,有序的ArrayList或者LinkedList也是可以的,但是慢,这个后面分析。

看一下优先级队列都需要哪些操作。第一,其根本目的是能取出一个最值元素,这个就不用说了;第二,要能往队列中插入元素。这是两个核心的功能,另带一些其他的辅助功能,如改变其中的某个元素的值,删除某个元素,得到其中的所有元素,得到其中元素个数等等。

给出优先级队列的类的接口:

  1. template   
  2. <typename  ItemType>  
  3. class  PriorityQueue {  
  4.     PriorityQueue(bool  isMaxHeap =  true );  
  5.     PriorityQueue(int  size,  bool  isMaxHeap =  true );  
  6.     PriorityQueue(ItemType items[], int  count,  bool  isMaxHeap =  true );  
  7.     int  Count( void );  
  8.     ItemType Get();  
  9.     ItemType Remove();  
  10.     void  Insert(ItemType value);  
  11.     void  Change( int  index, ItemType value);  
  12.     ItemType Remove(int  index);  
  13.     bool  ToArray(ItemType destinationArray[],  int  destinationSize);  
  14.     ~PriorityQueue(void );  

Remove() 方法是得到并移除第一个元素,Get() 仅仅是返回第一个元素。这个优先级队列用堆实现,从而可以得到主要方法Get() (O(1))、Remove() (O(lgn))、Insert() (O(lgn))较好的性能。具体实现参见完整代码。

Heapify 方法书上给的是个递归的,我给改成迭代的了,避免函数调用带来的些许开销,从而可以提高一点内隐的时间与空间的性能。

那么为什么用堆实现优先级队列比较合适呢?有序的线性表呢?

这个不难想,对于线性表,基于数组实现:首先,如果无序,要Get() 、要Remove() ,肯定要找到个最值,如果每次都去重新搜索,就很慢了,而且数组牵扯到数据的整体移动(O(n));而保持有序,虽然Get()Remove() 快,但是Insert(ItemType value) 费的功夫有些多,因为堆中只是部分有序,而数组需要保持整体有序,又是数据整体移动(O(n))。

基于链表的呢?链表的修改是很快,但链表如何快速搜索与定位?建索引?-_-!

扯回来,说些关于具体实现时的问题。

当然,Change(int index, ItemType value)Remove(int index) 两个方法在实际中是不实用的,或者是不现实的。因为通常情况下,不知道某个元素的index 是多少。通常要么就不实现,要么需要搜索,或者有类似索引的机制。

还有,实际使用中队列中存放的元素很可能是个什么东西的指针,而不是一个没什么用的具体数值,这样就还需要进行改进。

一种方式是原有的类不动,用一个实现了比较运算符的类或结构体的包装类包装一个有实际意义的东西,用这个类型当作PriorityQueue 的模板参数。

或者是改造PriorityQueue ,把模板参数ItemType 改成一个结构体或类,思想和上面的相似,不过这样做功能会强大许多。比如添加一个这样的内部类来替代模板参数ItemType

  1. class  QueueItem {  
  2. public :  
  3.     QueueItem(ItemType theValue, int  thePriority)  
  4.             : priority(thePriority), value(theValue) {  
  5.     }  
  6.           
  7.     // Getters and Setters   
  8.   
  9. private :  
  10.     friend PriorityQueue;  
  11.     inline void  SetOnwer(PriorityQueue* queue) {  
  12.         onwer = queue;  
  13.     }  
  14.     inline void  SetIndex( int  index) {  
  15.         this ->index = index;  
  16.     }  
  17.     inline int  GetIndex() {  
  18.         return  index;  
  19.     }  
  20. private :  
  21.     PriorityQueue* onwer;  
  22.     int  index;  
  23.     int  priority;  
  24.     ItemType value;       
  25. }; 

然后把原先代码中,items 改为QueueItem 类型的数组;类内部的的ItemType 换成QueueItem 的指针。QueueItem 含有所在PriorityQueueindex ,所有的地方注意改动这个;Insert 方法中new 一个QueueItem 来包装ItemType ,并返回此类的指针供外部更改使用(构造器就没办法了,可能无法保留带有元素的构造方法);移除的方法中注意回收QueueItem 的空间;涉及到index 的接口可以改为传入QueueItem 的指针,PriorityQueue 可直接根据QueueItem 指针指向的对象得到index;…… 细节比较琐碎,不再描述,这个已经超出了本文的范围。

完整的类代码如下:

 

  1. #include <exception>   
  2. #pragma once   
  3.   
  4. using  std::exception;  
  5.   
  6. template   
  7. <typename  ItemType>  
  8. class  PriorityQueue {  
  9. private :  
  10.     static   const   int  DefaultSize = 10;  
  11.   
  12. public :  
  13.     PriorityQueue(bool  isMaxHeap =  true ) {  
  14.         Initialize(nullptr, 0, DefaultSize, isMaxHeap);  
  15.     }  
  16.   
  17.     PriorityQueue(int  size,  bool  isMaxHeap =  true ) {  
  18.         Initialize(nullptr, 0, size, isMaxHeap);  
  19.     }  
  20.   
  21.     PriorityQueue(ItemType items[], int  count,  bool  isMaxHeap =  true ) {  
  22.         Initialize(items, count, count, isMaxHeap);  
  23.     }  
  24.   
  25.     // Item count in queue.   
  26.     inline   int  Count( void ) {  
  27.         return  count;  
  28.     }  
  29.   
  30.     // Only get first item.   
  31.     inline  ItemType Get() {  
  32.         if  (count == 0) {  
  33.             // empty queue.   
  34.             // do something here.   
  35.             throw  exception();  
  36.         } else  {  
  37.             return  *items;  
  38.         }  
  39.     }  
  40.   
  41.     // Get and remove first item.   
  42.     ItemType Remove() {  
  43.         if  (count == 0) {  
  44.             // empty queue.   
  45.             // do something here.   
  46.             throw  exception();  
  47.         }  
  48.         return  Remove(0);  
  49.     }  
  50.   
  51.     // Add a new item into queue.   
  52.     void  Insert(ItemType value) {  
  53.         TryResize(true );  
  54.         items[count - 1] = value;  
  55.         TryMoveUp(count - 1, value);  
  56.     }  
  57.   
  58.     // Change value at index.   
  59.     void  Change( int  index, ItemType value) {  
  60.         if  (index >= count) {  
  61.             // out of range.   
  62.             // throw exception or do nothing.   
  63.             throw  exception();  
  64.         }  
  65.   
  66.         if  (isMaxHeap ? value < items[index] : value > items[index]) {  
  67.             TryMoveDown(index, value);  
  68.         } else   if  (isMaxHeap ? value > items[index] : value < items[index]) {  
  69.             TryMoveUp(index, value);  
  70.         } else  {  
  71.             // do nothing.   
  72.         }  
  73.     }  
  74.   
  75.     ItemType Remove(int  index) {  
  76.         if  (index >= count) {  
  77.             // out of range.   
  78.             // throw exception or do nothing.   
  79.             throw  exception();  
  80.         }  
  81.   
  82.         ItemType result = items[index];  
  83.         items[index] = items[count - 1];  
  84.         TryResize(false );  
  85.         if  (index != count - 1) {  
  86.             Heapify(items, index, count, isMaxHeap);  
  87.         }  
  88.         return  result;  
  89.     }  
  90.   
  91.     // Sort all items in queue to destinationArray, destinationSize for safe.   
  92.     bool  ToArray(ItemType destinationArray[],  int  destinationSize) {  
  93.         if  (destinationSize < count) {  
  94.             // can not copy all items into destination array.   
  95.             throw  exception();  
  96.         }  
  97.           
  98.         SortTo(destinationArray);  
  99.         return   true ;  
  100.     }  
  101.   
  102.     ~PriorityQueue(void ) {  
  103.         delete [] items;  
  104.     }  
  105.   
  106. private :  
  107.     void  Initialize(ItemType items[],  int  count,  int  size,  bool  isMaxHeap) {  
  108.         if  (size < count) {  
  109.             // throw exception or set a new size.   
  110.             size = count + DefaultSize;  
  111.         }  
  112.   
  113.         this ->maxSize = size;  
  114.         this ->count = count;  
  115.         this ->isMaxHeap = isMaxHeap;  
  116.         this ->items =  new  ItemType[size];  
  117.   
  118.         if  (items != nullptr) {  
  119.             for  ( int  i = 0; i < count; ++i) {  
  120.                 this ->items[i] = items[i];  
  121.             }  
  122.             BuildHeap();  
  123.         }  
  124.     }  
  125.   
  126.     void  BuildHeap( void ) {  
  127.         for  ( int  i = ParentIndex(count - 1); i >= 0; --i) {  
  128.             Heapify(items, i, count, isMaxHeap);  
  129.         }  
  130.     }  
  131.   
  132.     // move items[index] down to the right position in its subtree.   
  133.     void  Heapify(ItemType items[],  int  index,  int  count,  bool  isMaxHeap) {  
  134.   
  135.         int  currentIndex = index;  
  136.         int  targetIndex = index;  
  137.   
  138.         if  (isMaxHeap) {  // ... how to do this better?   
  139.             while  ( true ) {  
  140.                 int  leftIndex = LeftChildIndex(currentIndex);  
  141.                 int  rightIndex = RightChildIndex(currentIndex);  
  142.   
  143.                 if  (leftIndex < count && items[leftIndex] > items[targetIndex]) {  
  144.                     targetIndex = leftIndex;  
  145.                 }  
  146.                 if  (rightIndex < count && items[rightIndex] > items[targetIndex]) {  
  147.                     targetIndex = rightIndex;  
  148.                 }  
  149.                 if  (targetIndex != currentIndex) {  
  150.                     Swap(items + targetIndex, items + currentIndex);  
  151.                     currentIndex = targetIndex;  
  152.                 } else  {  
  153.                     break ;  
  154.                 }  
  155.             }  
  156.         } else  {  
  157.             while  ( true ) {  
  158.                 int  leftIndex = LeftChildIndex(currentIndex);  
  159.                 int  rightIndex = RightChildIndex(currentIndex);  
  160.   
  161.                 if  (leftIndex < count && items[leftIndex] < items[targetIndex]) {  
  162.                     targetIndex = leftIndex;  
  163.                 }  
  164.                 if  (rightIndex < count && items[rightIndex] < items[targetIndex]) {  
  165.                     targetIndex = rightIndex;  
  166.                 }  
  167.                 if  (targetIndex != currentIndex) {  
  168.                     Swap(items + targetIndex, items + currentIndex);  
  169.                     currentIndex = targetIndex;  
  170.                 } else  {  
  171.                     break ;  
  172.                 }  
  173.             }         
  174.         }  
  175.     }  
  176.   
  177.     inline   int  LeftChildIndex( int  parentIndex) {  
  178.         return  RightChildIndex(parentIndex) - 1;  
  179.     }  
  180.     inline   int  RightChildIndex( int  parentIndex) {  
  181.         return  ++parentIndex << 1;  
  182.     }  
  183.     inline   int  ParentIndex( int  index) {  
  184.         return  (++index >> 1) - 1;  
  185.     }  
  186.   
  187.     void  SortTo(ItemType destitionArray[]) {  
  188.         // copy items.   
  189.         for  ( int  i = 0; i < count; ++i) {  
  190.             destitionArray[i] = items[i];  
  191.         }  
  192.   
  193.         // only for exercises... do heap sort in destitionArray.   
  194.         for  ( int  i = count, tempCount = count; i > 0; --i) {  
  195.             Swap(destitionArray, destitionArray + tempCount - 1);  
  196.             --tempCount;  
  197.             Heapify(destitionArray, 0, tempCount, isMaxHeap);  
  198.         }  
  199.   
  200.     }  
  201.   
  202.     void  Swap(ItemType* a, ItemType* b) {  
  203.         ItemType temp = *a;  
  204.         *a = *b;  
  205.         *b = temp;  
  206.     }  
  207.   
  208.     void  TryMoveUp( int  index, ItemType value) {  
  209.         if  (isMaxHeap ? value < items[index] : value > items[index]) {  
  210.             // do something about value error.   
  211.             throw  exception();  
  212.         }  
  213.         items[index] = value;  
  214.         int  currentIndex = index;  
  215.         int  parentIndex = ParentIndex(index);  
  216.         while  (index > 0 && (isMaxHeap ? items[parentIndex] < items[index] : items[parentIndex] > items[index])) {  
  217.             Swap(items + index, items + parentIndex);  
  218.             index = parentIndex;  
  219.             parentIndex = ParentIndex(parentIndex);  
  220.         }  
  221.     }  
  222.   
  223.     void  TryMoveDown( int  index, ItemType value) {  
  224.         if  (isMaxHeap ? value > items[index] : value < items[index]) {  
  225.             // do something about value error.   
  226.             throw  exception();  
  227.         }  
  228.         items[index] = value;  
  229.         Heapify(items, index, count, isMaxHeap);  
  230.     }  
  231.   
  232.     void  TryResize( bool  isIncrease) {  
  233.         if  (isIncrease) {  
  234.             if  (count >= maxSize) {  
  235.                 maxSize = (maxSize << 1) + 10;  
  236.                 NewItems(maxSize);  
  237.             }  
  238.             ++count;  
  239.         } else  {  
  240.             if  (count < maxSize >> 1) {  
  241.                 maxSize = (maxSize >> 1) + 5;  
  242.                 NewItems(maxSize);  
  243.             }  
  244.             --count;  
  245.         }  
  246.     }  
  247.   
  248.     void  NewItems( int  size) {  
  249.         ItemType* newItems = new  ItemType[size];  
  250.         // copy items.   
  251.         for  ( int  i = 0; i < count; ++i) {  
  252.             newItems[i] = items[i];  
  253.         }  
  254.         delete [] items;  
  255.         items = newItems;  
  256.     }  
  257.   
  258. private :  
  259.     ItemType* items;  
  260.     int  count;  
  261.     int  maxSize;  
  262.     bool  isMaxHeap;  
  263. };
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值