單鏈表的單向性:只能從頭結點開始高效訪問鍊錶中的資料元素。
單鏈表還存在另乙個缺陷:逆序訪問時候的效率極低。
如下:
linklistlist;
for(int i = 0; i < 5; i++)
for(int i = list.length() - 1; i >= 0; i--)
根據大o推算法可以得出乙個for迴圈的時間複雜度為o(n),get(i)的時間複雜度也是o(n),所以乙個逆序訪問資料的時間複雜度為o(n^2),所以效率是極低的。
所以新的需求來了,我們需要一種線性表能夠高效逆序訪問資料。在單鏈表的結點基礎上增加乙個指標域pre,它指向上乙個結點,即前驅結點。
我們稱之為雙向鍊錶。
那麼雙向鍊錶的繼承層次結構是什麼?由於它和單鏈表的資料結點的結構不同,所以它的繼承層次為繼承自list類。
通過類模板實現雙向鍊錶。
模板實現宣告如下:
template class duallinklist : public list;
//mutable node m_header;//分析下面的匿名結構的作用:本質上就是防止呼叫建立t value物件時呼叫建構函式,下面的匿名結構在記憶體布局上和node m_header布局相同
mutable struct : public object//如果未繼承自object可能導致記憶體布局和node m_header記憶體布局不同。
m_header;
int m_length;
int m_step;//儲存游標移動的次數
node* m_current;//游標
node* position(int i)const//用於定位 ,優化insert、remove、get、set函式用,但是本檔案未優化,便於複習使用,這裡只是說明可以優化
return current;
}virtual node* create()
virtual void destroy(node* pn)
public:
duallinklist();
bool insert(int i,const t& e);
bool insert(const t& e);
bool remove(int i);
bool set(int i,const t& e);
bool get(int i,t& e )const;
virtual t get(int i)const;
int find(const t& e )const;//返回的是查詢到的結點的位置
int length()const;
void clear();
virtual bool move(int i, int step = 1);
virtual bool end();
virtual t current();
virtual bool next();
virtual bool pre();
~duallinklist();
};
在模板類中只需要實現一些關鍵操作,如insert、remove、clear、pre操作,其餘的都和linklist的實現完全相同。
一、插入
插入操作的原理本質上和單鏈表是一樣的,只是多了連線前驅指標的步驟,具體步驟如下圖:
實現**如下:
bool insert(int i,const t& e)
node* next = current->next;
node->value = e;
//連線next域
//第一二步
node->next = next;
current->next = node;
//連線pre域
//第三四步
if(current != reinterpret_cast(&m_header))
else
if(next != null)
m_length++;
}else
}return ret;
}bool insert(const t& e)
實現步驟就是如圖步驟所示,需要注意的是插入的位置為首結點時pre域應該為null,當next結點不指向null時pre域才有效。
最後實現了插入過載函式,每次插入末尾位置。
二、刪除
刪除操作也需要對pre域進行連線,先將鍊錶連好,最後銷毀需要刪除的結點。
如圖:
實現如下:
bool remove(int i)
node* todel = current->next;
node* next = todel->next;
if(m_current == todel)//作用:在遍歷中執行remove操作時刪除結點後會導致m_current指向不變,從而使m_current->value為隨機值,所以當需要刪除將m_current指向下乙個結點
//第一步
current->next = next;
//第二步
if(next != null)
m_length--;//保證異常安全,因為當銷毀資料時丟擲異常(結點是類型別,並且在析構函式中丟擲異常)先長度減一再銷毀結點
destroy( todel );
}return ret;
}
具體實現在圖中已有說明,程式是按照圖中步驟進行的。同樣需要注意的是next不為null時pre域才有效。
三、清空
實現原理是每次刪除首結點,直到鍊錶長度為0。
四、前移
單鏈表中實現了向後移動的操作,在雙向鍊錶中也新增相似的功能函式,實現如下:
virtual bool pre()
return (i == m_step);
}
基本操作就這些,在宣告中還有一些函式並沒有實現,因為它們和單鏈表中的實現完全一樣,具體實現請參考前面的文章「單鏈表實現」。且建構函式和析構函式的函式體幾乎一樣,只不過建構函式中需要初始化pre指標。
小結:雙向鍊錶是為了彌補單鏈表缺陷而設計的。
在概念上,雙向鍊錶不是單鏈表,所以沒有直接繼承關係。
雙向鍊錶中的游標能夠直接訪問當前節點的前驅和後繼。
雙向鍊錶是線性表概念的最終實現(更貼近理論上的線性表)。
資料結構 鍊錶 雙向鍊錶
注意typedef的定義結構,以及dinklist的資料型別 typedef struct dnode dnode,dinklist 注意插入第乙個結點時,prior指標的空指向問題 if l next null 若l後繼結點為空 則省略該步驟 l next prior p 基本 頭插法建立雙向鍊錶...
資料結構 雙向鍊錶
前幾天寫了乙個單向鍊錶,今天參考自己單向鍊錶改寫了乙個雙向非迴圈鍊錶,下面只討論雙向非迴圈鍊錶。雙向非迴圈鍊錶有如下特點 一 雙向鍊錶每個結點都有乙個前驅指標和後驅指標 當然頭結點和尾結點除外 二 雙向鍊錶中的任意乙個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。三 頭結點只有後驅指標沒有前驅...
資料結構 雙向鍊錶
帶頭節點的雙向鍊錶 dlinklist.h pragma once include include include typedef int dlinktype typedef struct dlinknode dlinknode void dlinklistinit dlinknode head 初...