AOI主要有九宮格、燈塔和十字鏈表的算法實現。本文闡述十字鏈表的實現和嘗試。
1. 基本原理
根據二維地圖,將其分成x軸和y軸兩個鏈表。如果是三維地圖,則還需要維護多一個z軸的鏈表。將對象的座標值按照大小相應的排列在相應的座標軸上面。
2. 基本接口
對對象的操作主要有以下三個接口:
- add:對象進入地圖;
- leave:對象離開地圖;
- move:對象在地圖內移動。
2. 算法實現
既然是鏈表,很自然地想到用線性表來實現。因爲存在向前和向後找的情況,所以使用雙鏈表實現。其實實現也是非常簡單,就是兩個雙鏈表(這裏以二維地圖舉例)。那麼我們的節點需要四個指針,分佈爲x軸的前後指針,y軸的前後指針。
// 雙鏈表(對象)
class DoubleNode
{
public:
DoubleNode(string key, int x, int y)
{
this->key = key;
this->x = x;
this->y = y;
xPrev = xNext = NULL;
};
DoubleNode * xPrev;
DoubleNode * xNext;
DoubleNode * yPrev;
DoubleNode * yNext;
string key; // 只是一個關鍵字
int x; // 位置(x座標)
int y; // 位置(y座標)
private:
};
下面是地圖場景信息和接口。這裏的實現比較粗略,是帶頭尾的的雙鏈表,暫時不考慮空間佔用的問題。類Scene
有分別有一個頭尾指針,初始化的時候會爲其賦值,主要用DoubleNode
類的指針來存儲x軸和y軸的頭尾。初始化的時候,將_head
的next指針指向尾_tail
;將_tail
的prev指針指向_head
。
// 地圖/場景
class Scene
{
public:
Scene()
{
this->_head = new DoubleNode("[head]", 0, 0); // 帶頭尾的雙鏈表(可優化去掉頭尾)
this->_tail = new DoubleNode("[tail]", 0, 0);
_head->xNext = _tail;
_head->yNext = _tail;
_tail->xPrev = _head;
_tail->yPrev = _head;
};
// 對象加入(新增)
DoubleNode * Add(string name, int x, int y);
// 對象離開(刪除)
void Leave(DoubleNode * node);
// 對象移動
void Move(DoubleNode * node, int x, int y);
// 獲取範圍內的AOI (參數爲查找範圍)
void PrintAOI(DoubleNode * node, int xAreaLen, int yAreaLen);
private:
DoubleNode * _head;
DoubleNode * _tail;
};
2.1. add(進入地圖)
將DoubleNode
對象插入到十字鏈表中。x軸和y軸分別處理,處理方法基本一致。其實就是雙鏈表的數據插入操作,需要從頭開始遍歷線性表,對比相應軸上的值的大小,插入到合適的位置。
void _add(DoubleNode * node)
{
// x軸處理
DoubleNode * cur = _head->xNext;
while(cur != NULL)
{
if((cur->x > node->x) || cur==_tail) // 插入數據
{
node->xNext = cur;
node->xPrev = cur->xPrev;
cur->xPrev->xNext = node;
cur->xPrev = node;
break;
}
cur = cur->xNext;
}
// y軸處理
cur = _head->yNext;
while(cur != NULL)
{
if((cur->y > node->y) || cur==_tail) // 插入數據
{
node->yNext = cur;
node->yPrev = cur->yPrev;
cur->yPrev->yNext = node;
cur->yPrev = node;
break;
}
cur = cur->yNext;
}
}
假設可視範圍爲x軸2以內,y軸2以內,則運行:
- 分別插入以下數據a(1,5)、f(6,6)、c(3,1)、b(2,2)、e(5,3),然後插入d(3,3),按照x軸和y軸打印其雙鏈表結果;
- 插入d(3,3)數據,求其可視AOI範圍(如圖,除了f(6,6),其它對象都在d的可視範圍內)。
控制檯結果(前8行):
步驟1結果圖示:
步驟2結果圖示:
2.2. leave(離開地圖)和move(移動)
其實都是雙鏈表的基本操作,斷掉其相應的指針就好了。按理,是需要
move和leave操作如圖,move是將d(3,3)移動到(4,4),然後再打印其AOI範圍。
控制檯結果:
移動後AOI範圍圖示:
3. 完整代碼實例
#include "stdafx.h"
#include "stdio.h"
#include <iostream>
#include <string>
using namespace std;
// 雙鏈表(對象)
class DoubleNode
{
public:
DoubleNode(string key, int x, int y)
{
this->key = key;
this->x = x;
this->y = y;
xPrev = xNext = NULL;
};
DoubleNode * xPrev;
DoubleNode * xNext;
DoubleNode * yPrev;
DoubleNode * yNext;
string key;
int x; // 位置(x座標)
int y; // 位置(y座標)
private:
};
// 地圖/場景
class Scene
{
public:
Scene()
{
this->_head = new DoubleNode("[head]", 0, 0); // 帶頭尾的雙鏈表(可優化去掉頭尾)
this->_tail = new DoubleNode("[tail]", 0, 0);
_head->xNext = _tail;
_head->yNext = _tail;
_tail->xPrev = _head;
_tail->yPrev = _head;
};
// 對象加入(新增)
DoubleNode * Add(string name, int x, int y)
{
DoubleNode * node = new DoubleNode(name, x, y);
_add(node);
return node;
};
// 對象離開(刪除)
void Leave(DoubleNode * node)
{
node->xPrev->xNext = node->xNext;
node->xNext->xPrev = node->xPrev;
node->yPrev->yNext = node->yNext;
node->yNext->yPrev = node->yPrev;
node->xPrev = NULL;
node->xNext = NULL;
node->yPrev = NULL;
node->yNext = NULL;
};
// 對象移動
void Move(DoubleNode * node, int x, int y)
{
Leave(node);
node->x = x;
node->y = y;
_add(node);
};
// 獲取範圍內的AOI (參數爲查找範圍)
void PrintAOI(DoubleNode * node, int xAreaLen, int yAreaLen)
{
cout << "Cur is: " << node->key << "(" << node ->x << "," << node ->y << ")" << endl;
cout << "Print AOI:" << endl;
// 往後找
DoubleNode * cur = node->xNext;
while(cur!=_tail)
{
if((cur->x - node->x) > xAreaLen)
{
break;
}
else
{
int inteval = 0;
inteval = node->y - cur->y;
if(inteval >= -yAreaLen && inteval <= yAreaLen)
{
cout << "\t" << cur->key << "(" << cur ->x << "," << cur ->y << ")" << endl;
}
}
cur = cur->xNext;
}
// 往前找
cur = node->xPrev;
while(cur!=_head)
{
if((node->x - cur->x) > xAreaLen)
{
break;
}
else
{
int inteval = 0;
inteval = node->y - cur->y;
if(inteval >= -yAreaLen && inteval <= yAreaLen)
{
cout << "\t" << cur->key << "(" << cur ->x << "," << cur ->y << ")" << endl;
}
}
cur = cur->xPrev;
}
};
// 調試代碼
void PrintLink() // 打印鏈表(從頭開始)
{
// 打印x軸鏈表
DoubleNode * cur = _head->xNext;
while (cur != _tail)
{
cout << (cur->key) << "(" << (cur->x) <<"," << (cur->y) << ") -> " ;
cur = cur->xNext;
}
cout << "end" << endl;
// 打印y軸鏈表
cur = _head->yNext;
while (cur != _tail)
{
cout << (cur->key) << "(" << (cur->x) <<"," << (cur->y) << ") -> " ;
cur = cur->yNext;
}
cout << "end" << endl;
};
private:
DoubleNode * _head;
DoubleNode * _tail;
void _add(DoubleNode * node)
{
// x軸處理
DoubleNode * cur = _head->xNext;
while(cur != NULL)
{
if((cur->x > node->x) || cur==_tail) // 插入數據
{
node->xNext = cur;
node->xPrev = cur->xPrev;
cur->xPrev->xNext = node;
cur->xPrev = node;
break;
}
cur = cur->xNext;
}
// y軸處理
cur = _head->yNext;
while(cur != NULL)
{
if((cur->y > node->y) || cur==_tail) // 插入數據
{
node->yNext = cur;
node->yPrev = cur->yPrev;
cur->yPrev->yNext = node;
cur->yPrev = node;
break;
}
cur = cur->yNext;
}
}
};
// --------------------------------------------
void main()
{
Scene scene = Scene();
// 增加
scene.Add("a", 1, 5);
scene.Add("f", 6, 6);
scene.Add("c", 3, 1);
scene.Add("b", 2, 2);
scene.Add("e", 5, 3);
DoubleNode * node = scene.Add("d", 3, 3);
scene.PrintLink();
scene.PrintAOI(node, 2, 2);
// 移動
cout << endl << "[MOVE]" << endl;
scene.Move(node, 4, 4);
scene.PrintLink();
scene.PrintAOI(node, 2, 2);
// 刪除
cout << endl << "[LEAVE]" << endl;
scene.Leave(node);
scene.PrintLink();
}
作者:Ron Ngai
出處:http://rondsny.github.io
關於作者:斷碼碼農一枚。
歡迎轉載,但未經作者同意須在文章頁面明顯位置給出原文連接
如有問題,可以通過rondsny#gmail.com 聯繫我,非常感謝。