隨筆-13  評論-22  文章-0  trackbacks-0


          PriorityQueue

          本文github地址

          總體介紹

          前面以Java ArrayDeque為例講解了StackQueue,其實還有一種特殊的隊列叫做PriorityQueue,即優(yōu)先隊列。優(yōu)先隊列的作用是能保證每次取出的元素都是隊列中權(quán)值最小的(Java的優(yōu)先隊列每次取最小元素,C++的優(yōu)先隊列每次取最大元素)。這里牽涉到了大小關(guān)系,元素大小的評判可以通過元素本身的自然順序(natural ordering),也可以通過構(gòu)造時傳入的比較器Comparator,類似于C++的仿函數(shù))。

          Java中PriorityQueue實現(xiàn)了Queue接口,不允許放入null元素;其通過堆實現(xiàn),具體說是通過完全二叉樹(complete binary tree)實現(xiàn)的小頂堆(任意一個非葉子節(jié)點的權(quán)值,都不大于其左右子節(jié)點的權(quán)值),也就意味著可以通過數(shù)組來作為PriorityQueue的底層實現(xiàn)。

          PriorityQueue_base.png

          上圖中我們給每個元素按照層序遍歷的方式進行了編號,如果你足夠細(xì)心,會發(fā)現(xiàn)父節(jié)點和子節(jié)點的編號是有聯(lián)系的,更確切的說父子節(jié)點的編號之間有如下關(guān)系:

          leftNo = parentNo*2+1

          rightNo = parentNo*2+2

          parentNo = (nodeNo-1)/2

          通過上述三個公式,可以輕易計算出某個節(jié)點的父節(jié)點以及子節(jié)點的下標(biāo)。這也就是為什么可以直接用數(shù)組來存儲堆的原因。

          PriorityQueuepeek()element操作是常數(shù)時間,add(), offer(), 無參數(shù)的remove()以及poll()方法的時間復(fù)雜度都是log(N)。

          方法剖析

          add()和offer()

          add(E e)offer(E e)的語義相同,都是向優(yōu)先隊列中插入元素,只是Queue接口規(guī)定二者對插入失敗時的處理不同,前者在插入失敗時拋出異常,后則則會返回false。對于PriorityQueue這兩個方法其實沒什么差別。

          PriorityQueue_offer.png

          新加入的元素可能會破壞小頂堆的性質(zhì),因此需要進行必要的調(diào)整。

          //offer(E e)
          public boolean offer(E e) {
              if (e == null)//不允許放入null元素
                  throw new NullPointerException();
              modCount++;
              int i = size;
              if (i >= queue.length)
                  grow(i + 1);//自動擴容
              size = i + 1;
              if (i == 0)//隊列原來為空,這是插入的第一個元素
                  queue[0] = e;
              else
                  siftUp(i, e);//調(diào)整
              return true;
          }
          上述代碼中,擴容函數(shù)grow()類似于ArrayList里的grow()函數(shù),就是再申請一個更大的數(shù)組,并將原數(shù)組的元素復(fù)制過去,這里不再贅述。需要注意的是siftUp(int k, E x)方法,該方法用于插入元素x并維持堆的特性。
          //siftUp()
          private void siftUp(int k, E x) {
              while (k > 0) {
                  int parent = (k - 1) >>> 1;//parentNo = (nodeNo-1)/2
                  Object e = queue[parent];
                  if (comparator.compare(x, (E) e) >= 0)//調(diào)用比較器的比較方法
                      break;
                  queue[k] = e;
                  k = parent;
              }
              queue[k] = x;
          }
          新加入的元素x可能會破壞小頂堆的性質(zhì),因此需要進行調(diào)整。調(diào)整的過程為:k指定的位置開始,將x逐層與當(dāng)前點的parent進行比較并交換,直到滿足x >= queue[parent]為止。注意這里的比較可以是元素的自然順序,也可以是依靠比較器的順序。

          element()和peek()

          element()peek()的語義完全相同,都是獲取但不刪除隊首元素,也就是隊列中權(quán)值最小的那個元素,二者唯一的區(qū)別是當(dāng)方法失敗時前者拋出異常,后者返回null。根據(jù)小頂堆的性質(zhì),堆頂那個元素就是全局最小的那個;由于堆用數(shù)組表示,根據(jù)下標(biāo)關(guān)系,0下標(biāo)處的那個元素既是堆頂元素。所以直接返回數(shù)組0下標(biāo)處的那個元素即可。

          PriorityQueue_peek.png

          代碼也就非常簡潔:

          //peek()
          public E peek() {
              if (size == 0)
                  return null;
              return (E) queue[0];//0下標(biāo)處的那個元素就是最小的那個
          }

          remove()和poll()

          remove()poll()方法的語義也完全相同,都是獲取并刪除隊首元素,區(qū)別是當(dāng)方法失敗時前者拋出異常,后者返回null。由于刪除操作會改變隊列的結(jié)構(gòu),為維護小頂堆的性質(zhì),需要進行必要的調(diào)整。

          PriorityQueue_poll.png
          代碼如下:

          public E poll() {
              if (size == 0)
                  return null;
              int s = --size;
              modCount++;
              E result = (E) queue[0];//0下標(biāo)處的那個元素就是最小的那個
              E x = (E) queue[s];
              queue[s] = null;
              if (s != 0)
                  siftDown(0, x);//調(diào)整
              return result;
          }
          上述代碼首先記錄0下標(biāo)處的元素,并用最后一個元素替換0下標(biāo)位置的元素,之后調(diào)用siftDown()方法對堆進行調(diào)整,最后返回原來0下標(biāo)處的那個元素(也就是最小的那個元素)。重點是siftDown(int k, E x)方法,該方法的作用是k指定的位置開始,將x逐層向下與當(dāng)前點的左右孩子中較小的那個交換,直到x小于或等于左右孩子中的任何一個為止。
          //siftDown()
          private void siftDown(int k, E x) {
              int half = size >>> 1;
              while (k < half) {
                  //首先找到左右孩子中較小的那個,記錄到c里,并用child記錄其下標(biāo)
                  int child = (k << 1) + 1;//leftNo = parentNo*2+1
                  Object c = queue[child];
                  int right = child + 1;
                  if (right < size &&
                      comparator.compare((E) c, (E) queue[right]) > 0)
                      c = queue[child = right];
                  if (comparator.compare(x, (E) c) <= 0)
                      break;
                  queue[k] = c;//然后用c取代原來的值
                  k = child;
              }
              queue[k] = x;
          }

          remove(Object o)

          remove(Object o)方法用于刪除隊列中跟o相等的某一個元素(如果有多個相等,只刪除一個),該方法不是Queue接口內(nèi)的方法,而是Collection接口的方法。由于刪除操作會改變隊列結(jié)構(gòu),所以要進行調(diào)整;又由于刪除元素的位置可能是任意的,所以調(diào)整過程比其它函數(shù)稍加繁瑣。具體來說,remove(Object o)可以分為2種情況:1. 刪除的是最后一個元素。直接刪除即可,不需要調(diào)整。2. 刪除的不是最后一個元素。此處不再贅述。

          PriorityQueue_remove2.png

          具體代碼如下:

          //remove(Object o)
          public boolean remove(Object o) {
              //通過遍歷數(shù)組的方式找到第一個滿足o.equals(queue[i])元素的下標(biāo)
              int i = indexOf(o);
              if (i == -1)
                  return false;
              int s = --size;
              if (s == i) //情況1
                  queue[i] = null;
              else {
                  E moved = (E) queue[s];
                  queue[s] = null;
                  siftDown(i, moved);//情況2
                  
              }
              return true;
          }

          posted on 2016-05-12 21:22 CarpenterLee 閱讀(1452) 評論(2)  編輯  收藏

          評論:
          # re: Java PriorityQueue源碼剖析 2016-05-15 18:36 | 有機綠茶
          剖析得很詳細(xì),尤其是那些圖片,很形象,學(xué)習(xí)啦!  回復(fù)  更多評論
            
          # re: Java PriorityQueue源碼剖析 2016-05-16 07:27 | CarpenterLee
          @有機綠茶
          只有代碼太枯燥了,附圖更生動些。  回復(fù)  更多評論
            

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 赤峰市| 日照市| 尉犁县| 会东县| 丘北县| 和龙市| 汉沽区| 岳西县| 马山县| 麻江县| 天全县| 河间市| 绥芬河市| 平昌县| 会理县| 合山市| 江门市| 邵东县| 连平县| 儋州市| 韶关市| 那坡县| 宁阳县| 鹤壁市| 常山县| 饶河县| 南涧| 阿坝县| 东港市| 玉溪市| 开化县| 阳山县| 健康| 夏津县| 五家渠市| 闽清县| 中江县| 荣昌县| 潼关县| 巧家县| 宜宾县|