本文是Clifford A. Shaffer所著《數據結構與算法分析》(C++版)習題4.4的解答。
鏈表是常見的數據結構,鏈表中的結點通常定義如下。
template <typename E> class Link {
public:
E element;
Link<E> *next;
Link(const E& it, Link<E>* next) { }
};
結點是一個定義爲Link的模板類,其元素element的類型E可以代入不同的具體類型,指針成員nex指向下一個結點。只有一個指針的結點只能用於單鏈表,在使用時需要保存一個頭指針head,搜索鏈表的時候,每次都要從head開始,而且只能向後搜索,不能向前搜索。
雙鏈表中的結點含有兩個指針域,分別爲prev和next,如下所示。
template <typename E> class Link {
public:
E element;
Link<E> *prev, *next;
Link(const E& it, Link<E>* prev, Link<E>* next) { }
};
雙鏈表在使用時可以保存一個頭指針head,一個尾指針next。既可以從head出發向後搜索,也可以從tail出發向前搜索。如果再增加一個當前指針curr,那麼在單鏈表中,只能從curr出發向後搜索,而在雙鏈表中,可以從curr出發向後或者向前搜索。
基於結點類,可以設計鏈表類,下面的抽象數據類型給出了鏈表類所需要支持的基本操作。
template <typename E> class List { // List ADT
private:
void operator =(const List&) {}
List(const List&) {}
public:
List() {}
virtual ~List() {}
virtual void clear() = 0;
virtual void insert(const E& item) = 0;
virtual void append(const E& item) = 0;
virtual E remove() = 0;
virtual void moveToStart() = 0;
virtual void moveToEnd() = 0;
virtual void prev() = 0;
virtual void next() = 0;
virtual int length() const = 0;
virtual int currPos() = 0;
virtual void moveToPos(int pos) = 0;
virtual const E& getValue() const = 0;
};
基於帶有prev和next指針的結點類Link來說,通過在鏈表中增加head、tail或者curr成員變量,很容易寫出一個雙向鏈表的實現,支持上述抽象類List中的操作。但是,也可以在Link類中只使用一個指針pt,而實現雙向鏈表。主要思想是從這一個指針中,既可以獲取prev的內容,也可以獲取next的內容。其實現依賴於異或操作的一個重要性質:
(L^R)^R = L, (L^R)^L = R。
可以定義指針pt的值爲
pt = prev ^ next。
也就是說,pt是其後向指針next和前向指針prev的異或。那麼,當前結點的pt和指向其之前結點的指針在做異或操作,就可以得到指向當前結點之後的結點的指針。當前結點的pt和指向其之後結點的指針做異或操作,就可以得到指向當前結點之前的結點的指針。換言之,在這種形式的雙鏈表中,除了保存head,tail和curr指針指向鏈表中的不同結點之外,還需要使用兩個指針prevp和nextp,分別指向curr結點之前和之後的結點。從當前結點向後移動一步時,可以通過如下語句序列實現。
prevp = curr;
curr = nextp;
nextp = prevp ^ curr->pt;
從當前結點向前一步所對應的語句序列如下。
nextp = curr;
curr = prevp;
prevp = curr->pt ^ nextp;
當然,鏈表中多了兩個變量prevp和nextp,但是每個結點都節省了一個指針,所以總體而言,這種雙鏈表的實現能夠大幅度節省空間。當然這是以搜索時計算步驟增多、時間變長爲代價的。
雙向鏈表的具體實現代碼如下。
template <typename E> class LList: public List<E> {
private:
Link<E>* head, *tail, *curr;
Link<E> *prevp, *nextp;
int cnt;
void init() {
curr = head = new Link<E>;
head->pt = tail = new Link<E>(head, NULL);
prevp = NULL;
nextp = tail;
cnt = 0;
}
void removeall() {
prevp = NULL;
while(head != NULL) {
curr = head;
head = xor_op(prevp, head->pt);
prevp = curr;
delete curr;
}
}
Link<E>* xor_op(Link<E>* pt1, Link<E>* pt2)
{
unsigned long op1 = (unsigned long)pt1;
unsigned long op2 = (unsigned long)pt2;
return (Link<E>*)(op1 xor op2);
}
public:
LList(int size=defaultSize) { init(); }
~LList() { removeall(); }
void clear() { removeall(); init(); }
void moveToStart() // Place curr at list start
{
curr = head;
prevp = NULL;
nextp = head->pt;
}
void moveToEnd() // Place curr at list end
{
curr = tail->pt;
nextp = tail;
prevp = xor_op(tail, curr->pt);
}
void next()
{
if (curr != tail->pt)
{
prevp = curr;
curr = nextp;
nextp = xor_op(prevp, curr->pt);
}
}
int length() const { return cnt; }
// Return the position of the current element
int currPos() {
Link<E>* temp = head;
int i;
prevp = NULL;
nextp = head->pt;
for (i=0; curr != temp; i++){
prevp = temp;
temp = nextp;
nextp = xor_op(prevp, temp->pt);
}
return i;
}
// Move down list to "pos" position
void moveToPos(int pos) {
Assert ((pos>=0)&&(pos<=cnt), "Position out of range");
curr = head;
prevp = NULL;
nextp = head->pt;
for(int i=0; i<pos; i++)
{
prevp = curr;
curr = nextp;
nextp = xor_op(prevp, curr->pt);
}
}
const E& getValue() const { // Return current element
if(nextp == tail)
return NULL;
return nextp->element;
}
void insert(const E& it) {
Link<E> *tmp;
tmp = new Link<E>(it, curr, nextp);
curr->pt = xor_op(xor_op(curr->pt, nextp), tmp);
nextp->pt = xor_op(xor_op(nextp->pt, curr), tmp);
nextp = tmp;
cnt++;
}
void append(const E& it) {
Link<E> *tmp;
tmp = new Link<E>(it, tail->pt, tail);
tail->pt->pt = xor_op(xor_op(tail->pt->pt, tail), tmp);
tail->pt = tmp;
if(nextp == tail)
nextp = tmp;
cnt++;
}
E remove() {
if(nextp == tail)
return NULL;
Link<E> *new_next;
E it = nextp->element;
new_next = xor_op(nextp->pt, curr);
curr->pt = xor_op(prevp, new_next);
new_next->pt = xor_op(xor_op(nextp, new_next->pt), curr);
delete nextp;
nextp = new_next;
cnt--;
return it;
}
void prev()
{
if (curr != head)
{
nextp = curr;
curr = prevp;
prevp = xor_op(nextp, curr->pt);
}
}
};