AStar 算法 2 (開放列表的維護)

AStar 在每次主循環中都要在 openList 中找到一個 F 值最小的節點作為當前節點。之前的 openList 使用簡單的數組來實現,當在其中搜尋最小節點時把整個 openList 遍歷一遍找到最小的節點。這是一個可以優化的點。

為 openList 維護一個有序表

因為要在 openList 中找到最小節點,一個比較容易想到的辦法是把 openList 排序,然后每次都取這個表的第一個(升序)或者最后一個(降序)節點作為當前節點。
但是,如果采用快速排序對 openList 排序的話,每次主循環內都要付出平均 O(n*log(n)) 的代價,而遍歷搜索的平均代價為 O(n) 。基于這個粗略的估計,維護一個有序表似乎對提高算法速度并沒有什么幫助。

使用二叉堆

二叉堆是一棵完全二叉樹,它的每一個節點都小于(小頂堆)或者大于(大頂堆)它的左右兒子。

因為二叉堆的性質,找到它最小或者最大的節點花費的時間是常量的,即 O(1)。

AStar 算法在找到當前節點后還要在 openList 中移除這個節點,移除這個節點后就需要重新調整二叉堆的結構一滿足它最小節點在樹根的性質。這里采用的方法是將樹的最后一個節點移動到樹根,然后不斷向分支尋找這個節點的位置,直到找到它的合適位置,這個過程的平均時間復雜度是 O(log(n))。這個過程稱為“下濾”。

二叉堆在插入新的節點之后也要調整結構以滿足性質。這里采用的方法是在完全二叉樹的最后一個節點之后插入一個新的節點,然后不斷向上調整這個節點,直到找到它的合適位置,這個過程的平均時間復雜度是 O(log(n))。這個過程稱為“上濾”。

綜上,在以正方形為基本節點的地圖中的 AStar 算法中使用二叉堆來實現 openList 的總的時間復雜度是:

O(1) + O(log(n)) + m * O(log(n))

其中 m 是每次檢測加入的新節點的個數。

下面是代碼:

接口:

template <typename BinaryHeapNode>
class BinaryHeap {
public:
  BinaryHeap();    //構造函數

  bool empty();    //二叉堆是為空
  BinaryHeapNode getMin(); //得到最小節點
  BinaryHeapNode isIn(const BinaryHeapNode &node); //判斷一個節點是否在二叉堆中
  void insert(const BinaryHeapNode &node); //插入一個節點
  void deleteMin(); //刪除最小節點

private:
  int _currentSize; //二叉堆的 size
  std::vector<BinaryHeapNode> _array; //節點采用 vector 儲存

  void _percolateUp(int hole); //上濾
  void _percolateDown(int hole); //下濾
};

實現:

template <typename BinaryHeapNode>
BinaryHeap<BinaryHeapNode>::BinaryHeap(){
//構造函數,初始化二叉堆的大小為 0,并為節點的儲存 vector 分配一個初始尺寸
  _currentSize = 0;
  _array.resize(100);
}

template <typename BinaryHeapNode>
bool BinaryHeap<BinaryHeapNode>::empty(){
//判空方法
  if (_currentSize == 0){
      return true;
   }
  return false;
}

template <typename BinaryHeapNode>
BinaryHeapNode BinaryHeap<BinaryHeapNode>::isIn(const BinaryHeapNode &node){
//判斷一個節點是否在這個二叉堆中,簡單的遍歷儲存節點的 vector 來判斷是否存在這個節點
//因為 vector 中儲存的是 AStarNode 的指針,如果想要調用重載的 == 運算符需要對這個節點解一次引用,下同
  for (int index = 1; index <= _currentSize; ++index){
      if ((*node) == _array[index]){
          return _array[index];
      }
  }

  return nullptr;
}

template <typename BinaryHeapNode>
BinaryHeapNode BinaryHeap<BinaryHeapNode>::getMin(){
//返回二叉堆的第一個節點,然后從二叉堆中刪除它
  BinaryHeapNode min = _array[1];
  deleteMin();
  return min;
}

template <typename BinaryHeapNode>
void BinaryHeap<BinaryHeapNode>::insert(const BinaryHeapNode &node){
//向二叉堆中插入一個節點
//首先判斷 vector 的空間是否已經用完,如果已經用完重新分配當前需要空間二倍的空間,避免頻繁的調用 resize
//然后將新元素插入二叉堆的最后一個元素的后面,接著執行“上濾”操作
  int hole = ++_currentSize;
  if (_array.size() - 1 <= _currentSize){
      _array.resize(_currentSize * 2);
  }
  _array[_currentSize] = node;
  _percolateUp(hole);
}

template <typename BinaryHeapNode>
void BinaryHeap<BinaryHeapNode>::deleteMin(){
//刪除二叉堆中最小的元素
//使用最后一個元素覆蓋第一個元素,然后對第一個元素執行“下濾”操作
    _array[1] = _array[_currentSize--];
    _percolateDown(1);
}

template <typename BinaryHeapNode>
void BinaryHeap<BinaryHeapNode>::_percolateDown(int hole){
//下濾
  int child;
  BinaryHeapNode temp = _array[hole]; //先找到將要下濾的元素備用

  for (; hole * 2 <= _currentSize; hole = child){ //如果這個元素不是葉節點進入循環
      child = hole * 2; //獲得這個元素的左兒子指針
      if (child != _currentSize && (*_array[child + 1]) < _array[child]){
          //如果這個元素的左兒子不是最后一個元素并且右兒子比左兒子還小,意味著如果這個元素要向下交換的話也要和右兒子交換,所以把指針移向右兒子
          ++child; 
      }
    
    //交換操作
      if ((*_array[child]) < temp){
          _array[hole] = _array[child];
      }else {
          break;
      }
  }

  _array[hole] = temp;
}

template <typename BinaryHeapNode>
void BinaryHeap<BinaryHeapNode>::_percolateUp(int hole){
//上濾
//不斷的和當前位置的父節點做比較判斷是否需要交換
  for (; hole > 1 && (*_array[hole]) < _array[hole / 2]; hole /= 2){
      BinaryHeapNode tempNode = _array[hole];
      _array[hole] = _array[hole / 2];
      _array[hole / 2] = tempNode;
  }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容