数组作链表
一般傳統(tǒng)鏈表的物理結(jié)構(gòu),是由指針把一個一個的節(jié)點相互連接而成:
struct node
{
DataType data;
node* previous;
node* next;
}
其特點是按需分配節(jié)點,靈活動態(tài)增長。
但是此外,還有另外一種方式是使用數(shù)組實現(xiàn)鏈表,這里所有的node都在預(yù)先分配好的數(shù)組中,不使用指針,而是用數(shù)組下標來指向前一個、下一個元素:
struct node
{
DataType data;
int previous;
int next;
}
其特點是預(yù)先分配節(jié)點,并且如果需要鏈表長度隨需增加,需要reallocation ,和vector類似。
下面就我自己的一些了解,談一下其優(yōu)缺點與應(yīng)用。
數(shù)組作鏈表有哪些優(yōu)點
能要省些內(nèi)存嗎?不見得;速度要快嗎?沒看出來,那么為什么要使用這種不那么直觀的方式來實現(xiàn)鏈表呢?
- 數(shù)組的內(nèi)存布局比較緊湊,能占些局部性原理的光
- 在不提供指針的語言中實現(xiàn)鏈表結(jié)構(gòu),如vb等
- 進程間通信,使用index比使用指針是要靠譜 - 一個進程中的指針地址在另外一個進程中是沒有意義的
- 對一些內(nèi)存奇缺應(yīng)用,當其值類型為整型,且值域與數(shù)組index相符時,可以將next指針與data復(fù)用,從而節(jié)省一些內(nèi)存
- 整存零取,防止內(nèi)存碎片的產(chǎn)生(多謝Emacs補充)
實現(xiàn)與應(yīng)用
Id allocator
這里第一個例子針對上面第四點展開,其主要的應(yīng)用在于ID的分配與回收,比如數(shù)據(jù)庫表中的每條記錄都需要一個unique id,當你增增減減若干次之后,然后新建一個表項,你該分配給它哪個id呢?
- 維持一個id,每增加一行就加1,刪行不回收id --- 這樣id會無限增加,太浪費了
- 每次分配都遍歷一遍,找到最小的那個還沒被用過的id --- 這樣太浪費時間了
struct idnode
{
int availableID;
idnode* next;
}; 這里,因為鏈表的值的類型與值域都與數(shù)組的index相符,我們可以復(fù)用其值和next,從而只需一個int數(shù)組,而不是一個idnode數(shù)組,數(shù)組中某個元素的值代表的即是鏈表節(jié)點的值,也是鏈表的下一個節(jié)點下標。下面是一個idallocator的實現(xiàn),主要提供allocate和free兩個函數(shù)用來滿足上述要求: const static char LIST_END = -1;
template < typename integer>
class idallocator
{
public:
idallocator(int _num): num(_num)
{
array = new integer[num];
// Initialize the linked list. Initially, it is a full linked list with all elements linked one after another.
head = 0;
for (integer i = 0; i < num; i++)
{
array[i] = i + 1;
}
array[num-1] = LIST_END;
count = num;
}
~idallocator()
{
delete [] array;
}
integer allocate() // pop_front, remove a node from the front
{
int id = head;
if(id != LIST_END)
{
head = array[head];
count--;
}
return id;
}
// push_front, add a new node to the front
void free(const integer& id)
{
array[id] = head;
count++;
head = id;
}
// push_front, add a new node to the front
bool free_s(const integer& id)
{
// make sure this id is not in the list before we add it
if(exist(id)) return false;
free(id);
return true;
}
size_t size()
{
return count;
}
private:
bool exist(const integer& id)
{
int i = head;
while (i != LIST_END)
{
if(i == id) return true;
i = array[i];
}
return false;
}
private:
integer* array;
int num;// totall number of the array
int count;// number in the linked list
int head;// index of the head of the linked list
}; Double linked list
用數(shù)組實現(xiàn)鏈表,大多數(shù)情況下,是與傳統(tǒng)鏈表類似的,無非是在添加、刪除節(jié)點后,調(diào)整previous,next域的指向。但是有一點,當我在添加一個新的節(jié)點時,如何從數(shù)組中拿到一個還未被使用過的節(jié)點呢?這里有兩種方法:
- 如果你看懂了上面的id allocator,你也許已經(jīng)意識到,使用上面那個idallocator類就可以簡單的實現(xiàn)這個需求
- 另外一種方法,其實原理上也類似,就是在這個double linked list類中維護兩個鏈表,一個是已使用的,一個是未使用的
這里有個粗略的實現(xiàn):arraylist.?
參考
- 算法導(dǎo)論10.3
- 另類的鏈表數(shù)據(jù)結(jié)構(gòu)以及算法
- 鏈表結(jié)構(gòu)原理 與 數(shù)組模擬鏈表 的應(yīng)用
轉(zhuǎn)載于:https://www.cnblogs.com/baiyanhuang/archive/2010/08/22/1805644.html
總結(jié)