<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [數(shù)據(jù)結(jié)構(gòu)] 堆與堆排序

      這篇文章使用 JavaScript 語言進行相關(guān)代碼的編寫。

      數(shù)據(jù)結(jié)構(gòu)——堆 heap

      基本概念與性質(zhì)

      堆是一顆完全二叉樹,根據(jù)父子節(jié)點之間值的大小關(guān)系可以分為:

      • 大根堆:每一個節(jié)點的值 大于或等于 其子節(jié)點的值;
      • 小根堆:每一個節(jié)點的值 小于或等于 其子節(jié)點的值;

      image-20240727231821817

      堆數(shù)據(jù)結(jié)構(gòu)的底層通常使用順序表進行存儲。

      通過索引的計算可以快速得到子節(jié)點、父節(jié)點的值:

      以 0 作為根節(jié)點的索引:(下文使用這種標準)

      Parent(i) = (i-1)/2;
      LeftChild(i) = i*2+1;
      RightChild(i) = i*2+2;
      

      以 1 作為根節(jié)點的索引

      Parent(i) = i/2;
      LeftChild(i) = i*2;
      RightChild(i) = i*2+1;
      

      操作

      一開始堆是一個空數(shù)組,最主要的兩個API是插入和刪除,這兩個操作會在堆的頭部和尾部進行操作,而這些操作會導(dǎo)致堆的性質(zhì)丟失,因此每次進行插入和刪除操作之后都需要重新維護堆的結(jié)構(gòu)。

      插入

      插入操作將一個新的數(shù)據(jù)添加到堆的末尾,然后向上調(diào)整

      圖片來源:二叉堆 - OI Wiki (oi-wiki.org)

      刪除

      刪除操作將根節(jié)點摘除,然后將堆的末尾的節(jié)點移動到堆的頂部,最后向下調(diào)整

      向上調(diào)整

      對于某個節(jié)點(由一個明確的索引i指定),通過計算得到它的父節(jié)點的值,進行比較:

      • 如果二者大小不滿足堆性質(zhì),則交換(即上升),上升后繼續(xù)與新的父節(jié)點進行比較;
      • 如果二者大小滿足堆性質(zhì),則終止。

      時間復(fù)雜度與樹的高度有關(guān),這是完全二叉樹,即O(log N)

      function up(index){
        while(true){
          const parent = (index-1)>>1;
          if(this.arr[parent] < this.arr[index]){
            [this.arr[index], this.arr[parent]] = [this.arr[parent], this.arr[index]];
            index = parent;
          }else{
            break;
          }
        }
      }
      
      • (index-1)>>1是通過位運算快速實現(xiàn)向下取整的除以2,有時候懶得寫Math.floor...

      向下調(diào)整

      對于某個節(jié)點,通過索引計算得到左右兩個子節(jié)點,分別進行比較:(以小根堆為例)

      • 如果當(dāng)前節(jié)點的值均小于左右兩個子節(jié)點的值,則說明滿足堆結(jié)構(gòu),終止;
      • 如果左右兩個子節(jié)點存在小于當(dāng)前節(jié)點的值,那么找出最小值,最小的節(jié)點與當(dāng)前節(jié)點進行交換(即下沉),下沉后繼續(xù)與新的左右子節(jié)點進行比較。

      時間復(fù)雜度:O(log N)

      function down(index){
        while(true){
          let left = index*2+1;
          let right = index*2+2;
          let target = index;
          if(left<this.size() && this.arr[left]>this.arr[index]){
            target = left;
          }
          if(right<this.size() && this.arr[right]>this.arr[index]){
            target = right;
            if(left<this.size() && this.arr[left]>this.arr[right]){
              target = left;
            }
          }
          if(target!==index){
            [this.arr[index], this.arr[target]] = [this.arr[target], this.arr[index]];
            index = target;
          }else{
            break;
          }
        }
      }
      

      完整代碼

      class Heap {
        constructor(arr) {
          if(arr){
            this.arr = arr;
            for(let i=Math.floor((arr.length-1)/2); i>=0; i--){
              this._down(i);
            }
          }else{
            this.arr = [];
          }
        }
      
        size() {
          return this.arr.length;
        }
      
        push(val) {
          // 新的數(shù)據(jù)插入到尾部,然后向上調(diào)整
          this.arr.push(val);
          this._up(this.size() - 1);
        }
      
        top() {
          return this.arr[0];
        }
      
        pop() {
          // 沒有數(shù)據(jù)了,返回null
          if (this.size() == 0) return null;
          // 只剩下一個數(shù)據(jù)了,直接彈出返回
          if(this.size() == 1)return this.arr.pop();
          // 先記錄堆頂數(shù)據(jù)
          let top = this.top();
          // 將最后一個數(shù)據(jù)放到堆頂,然后向下調(diào)整
          let last = this.arr.pop();
          this.arr[0] = last;
          this._down(0);
          return top;
        }
      
        _up(index){
          while(true){
            const parent = (index-1)>>1;
            if(this.arr[parent] < this.arr[index]){
              [this.arr[index], this.arr[parent]] = [this.arr[parent], this.arr[index]];
              index = parent;
            }else{
              break;
            }
          }
        }
      
        _down(index){
          while(true){
            let left = index*2+1;
            let right = index*2+2;
            let target = index;
            if(left<this.size() && this.arr[left]>this.arr[index]){
              target = left;
            }
            if(right<this.size() && this.arr[right]>this.arr[index]){
              target = right;
              if(left<this.size() && this.arr[left]>this.arr[right]){
                target = left;
              }
            }
            if(target!==index){
              [this.arr[index], this.arr[target]] = [this.arr[target], this.arr[index]];
              index = target;
            }else{
              break;
            }
          }
        }
      }
      

      堆排序

      堆排序是建立在堆這種數(shù)據(jù)結(jié)構(gòu)上的選擇排序。

      首先建立大根堆,每次從根節(jié)點獲取最大值,將最大值移動到堆的尾部,然后對剩下的前n-1個節(jié)點進行維護大根堆的操作。

      即:每次選擇最大值移動至尾部,再繼續(xù)在剩下的數(shù)中繼續(xù)查找最大值。

      • 普通的選擇排序是在線性表上尋找最大值,時間復(fù)雜度為 \(O(n)\) ,要查找 \(n\) 個最大值,因此時間復(fù)雜度為 \(O(n^2)\).

      • 在大根堆上尋找最大值的時間復(fù)雜度是\(O(1)\),而獲取刪除最大值之后的向下調(diào)整操作時間復(fù)雜度為\(O(\log n)\),共尋找 \(n\) 個最大值,因此時間復(fù)雜度為 \(O(n\log n)\)

      考慮到排序算法的輸入是一個亂序的數(shù)組,我們首要的任務(wù)是將一個普通的數(shù)組轉(zhuǎn)換成一個大根堆

      方法:從 最后一個節(jié)點的父節(jié)點 ,逆序遍歷數(shù)組,依次向下調(diào)整。

      原理:自底向上,先保證子樹滿足堆性質(zhì),然后逐漸向上擴展直到整棵樹都滿足堆性質(zhì)。

      綜上,堆排序總共分為兩個步驟:

      1. 構(gòu)造大根堆
      2. 選擇排序 + 維護堆結(jié)構(gòu)

      代碼

      function heapSort(arr){
        const n = arr.length;
        // 從最后一個節(jié)點的父節(jié)點開始調(diào)整
        // 作用:構(gòu)造大根堆
        for(let i=Math.floor(n/2)-1; i>=0; i--){
          heapify(arr, i, n);
        } 
        // 將大根堆的根節(jié)點和最后一個節(jié)點交換,同時維護堆的性質(zhì)
        // 作用:排序
        for(let i=n-1; i>0; i--){
          [arr[0], arr[i]] = [arr[i], arr[0]];
          heapify(arr, 0, i);
        }
      }
      
      // 調(diào)整堆
      function heapify(arr, index, n){
        let left = 2*index+1;
        let right = 2*index+2;
        while(true){
          let largest = index;
          // 找到左右子樹中的最大值
          if(left<n && arr[left]>arr[largest]){
            largest = left;
          }
          if(right<n && arr[right]>arr[largest]){
            largest = right;
          }
          // 如果最大值不是根節(jié)點,則交換
          if(largest!==index){
            [arr[index], arr[largest]] = [arr[largest], arr[index]];
            // 更新下一輪的索引
            index = largest;
            left = 2*index+1;
            right = 2*index+2;
          }else{
            break;
          }
        }
      }
      

      堆的其它應(yīng)用

      優(yōu)先隊列

      優(yōu)先隊列是一種特殊的隊列,每個元素都有一個優(yōu)先級,按優(yōu)先級順序出隊。刷算法題的時候經(jīng)常會用到的輔助類PriorityQueue

      數(shù)組中的第 k 大(或小)元素

      可以使用最小堆來高效找到第 \(k\) 大的元素。首先將前 \(k\) 個元素插入堆中,然后遍歷數(shù)組的其余部分,更新堆,保持堆的大小為 \(k\)。最終堆頂元素就是第 \(k\) 大的元素。

      合并多個有序鏈表

      使用最小堆來維護每個有序列表的當(dāng)前最小元素。每次從堆中取出最小元素,并將該元素所在列表的下一個元素插入堆中。重復(fù)該過程直到所有列表都被合并。

      事件驅(qū)動模擬

      Node.js中,多個計時器回調(diào)就是使用最小堆記錄的,每次事件循環(huán)進行到timer階段,就檢查堆頂計時器的剩余時間是否已到達:

      • 如果未到達,則執(zhí)行下一個階段的回調(diào)任務(wù);
      • 如果已到達,則執(zhí)行本階段的所有已到達的定時器回調(diào)任務(wù);
        • 每個回調(diào)執(zhí)行完成之后,如果是setInterval,則重新將任務(wù)添加到堆中。
      posted @ 2024-07-28 00:55  feixianxing  閱讀(50)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产特级毛片AAAAAA视频 | 玖玖在线精品免费视频| 免费A级毛片无码A∨蜜芽试看| 91人妻熟妇在线视频| 亚洲精品久久无码av片软件| 午夜天堂一区人妻| 日韩在线观看 一区二区| 97人人添人人澡人人澡人人澡| 内射一区二区三区四区| 亚洲va久久久噜噜噜久久狠狠 | 翘臀少妇被扒开屁股日出水爆乳| 无码激情亚洲一区| 亚洲男人天堂一级黄色片| 国产黄色免费看| 亚洲国产精品va在线观看麻豆| 亚洲一区二区三区18禁| 国产日产精品系列| 国产精品不卡一二三区 | 东京热无码av男人的天堂| 国产a在视频线精品视频下载| 久久久久国产精品人妻 | 国产99视频精品免费专区| 欧美熟妇乱子伦XX视频| 99国精品午夜福利视频不卡99| 国产亚洲一区二区三区成人| 亚洲国产成人av国产自| 国产精品99久久免费| 亚洲精品视频一二三四区| 国产亚洲日韩av在线播放不卡| 免费人成视频网站在线观看18| 亚洲人妻精品中文字幕| 亚洲性无码av在线| 欧美人妻久久精品| 国产精品人人爽人人做我的可爱| 久久精品国产99国产精品澳门 | 中文字幕亚洲男人的天堂| 麻豆国产va免费精品高清在线| 202丰满熟女妇大| 国产精品亚洲二区在线看| 亚洲AV无码专区亚洲AV桃| 福利一区二区在线播放|