數據結構之 List
寫在前面
和vector/array一樣,鏈表/列表(list)也是線性表中的一種,即每個元素的直接前驅和直接後繼都唯一的線性邏輯結構。
鏈表/列表List
- 這裏依然是放在一起講,鏈表是通常的數據結構課上常見的一種結構,而列表是STL中已經封裝好的一個模板類。所以我們在學習鏈表的時候,完全可以看看STL中的源碼,對比學習,借鑑一些比較好的思想。值得注意的是,本文中的列表只按照STL的思想來,但代碼是自己實現的,和源代碼並不一致。
- 與vector一樣,列表也是由集合S中的元素依次排列的一個序列,這些元素分散地存儲在存儲空間中,因此難以直接根據地址直接訪問到每一個元素。爲此,每個元素存儲時需攜帶一些額外的信息:它的前驅、後繼等。
- 同樣地,參考vector,可以定義列表的前驅、後繼、直接前驅、直接後繼、前綴、後綴等。
- 根據所包含額外信息的不同,鏈表可分爲
- 單鏈表:只包含指向後繼結點的指針
- 雙鏈表:包含指向前驅結點和後繼節點的指針
- 單循環鏈表:最後一個結點的後繼指針指向頭結點
- 雙循環鏈表:最後一個結點的後繼指針指向頭結點,第一個結點的前驅指針指向最後一個結點
- 在已經封裝好的列表中,是按照雙鏈表來設置結點對象的,用的時候按需取用就好。
list的實現
結點的圖形化表示
結點類
#ifndef LISTNODE_H
#define LISTNODE_H
typedef int Rank;
#define ListNodePosi(T) ListNode<T>*
template <typename T>
struct ListNode {
// member variable
T data;
ListNodePosi(T) pred; //前驅
ListNodePosi(T) succ; //後繼
// member function
ListNode() {}
ListNode(T e, ListNodePosi(T) p = NULL, ListNodePosi(T) s = NULL)
: data(e), pred(p), succ(s) {}
ListNodePosi(T) insertAsPred(T const& e);
ListNodePosi(T) insertAsSuss(T const& e);
};
template <typename T>
ListNodePosi(T) ListNode<T>::insertAsPred(T const& e) {
ListNodePosi(T) node = new ListNode<T>(e);
node->succ = pred->succ;
node->pred = pred;
pred->succ = node;
pred = node;
}
template <typename T>
ListNodePosi(T) ListNode<T>::insertAsSuss(T const& e) {
ListNodePosi(T) node = new ListNode<T>(e);
node->pred = succ->pred;
node->succ = succ;
succ->pred = node;
succ = node;
}
#endif
列表的圖形化表示
- 這裏需要說明的是,鏈表分爲帶表頭和不帶表頭兩種,如上圖所示。在具體操作時兩種形式有所區別,一般來說,帶表頭的操作方便一些,列表中就是帶表頭的。
列表類
#ifndef LIST_H
#define LIST_H
// #include "ListNode.cpp"
#include "ListNode.h"
template <typename T>
class List {
private:
int _size;
ListNodePosi(T) header;
ListNodePosi(T) tailer;
protected:
void init(); // initial after creating
int clear(); // clear all nodes
void copyNodes(ListNodePosi(T), int); // copy n nodes from p
void merge(ListNodePosi(T) &,
int,
List<T>&,
ListNodePosi(T),
int); // merge two sorted lists
//有序列表的歸併:當前列表中自p起的n個元素,與列表L中自q起的m個元素歸併
void mergeSort(ListNodePosi(T) &, int); //對從p開始的n個節點歸併排序
void selectionSort(ListNodePosi(T), int); //對從p開始的n個節點選擇排序
void insertionSort(ListNodePosi(T), int); //對從p開始的n個節點插入排序
public:
List() { init(); }
List(List<T> const& L); // copy whole List
List(List<T> const& L, Rank r, Rank n); // copy n elements in L from r
List(ListNodePosi(T) p, int n);
~List();
Rank size() const { return _size; }
bool empty() const { return _size <= 0; }
T& operator[](int r) const;
ListNodePosi(T) fisrt() const { return header->succ; }
ListNodePosi(T) last() const { return tailer->pred; }
bool valid(ListNodePosi(T) p) {
return p && (header != p) && (tailer != p);
}
int disordered() const;
ListNodePosi(T) find(T const& e) const { find(e, _size, tailer); }
ListNodePosi(T)
find(T const& e,
int n,
ListNodePosi(T) p); // find e in range n before p from p
ListNodePosi(T) search(T const& e) const { search(e, _size, tailer); }
ListNodePosi(T) search(T const& e, int n, ListNodePosi(T) p);
ListNodePosi(T)
selextMax(ListNodePosi(T) p, int n); // select max node from p to p-n
ListNodePosi(T) selextMax() { return selextMax(header->succ, _size); }
ListNodePosi(T) insertAsFirst(T const& e);
ListNodePosi(T) insertAsFirst(T const& e);
ListNodePosi(T) insertBefore(ListNodePosi(T) p, T const& e);
ListNodePosi(T) insertAfter(ListNodePosi(T) p, T const& e);
T remove(ListNodePosi(T) p);
void merge(List<T>& L) {
merge(fisrt(), _size, L, L.fisrt(), L.size());
} // merge the whole list L
void sort(ListNodePosi(T) p, int n); // sort the part of list
void sort() { sort(first(), _size); }
int deduplicate(); // deduplicate a unordered list
int uniquify(); // uniquify an ordered list
void reverse(); // reverse the list
void traverse(void (*)(T&));
template <typename VST> // operator
void traverse(VST&);
};
#endif
與Vector的比較
- 循秩訪問與循位置訪問
- vector最大的特點就是循秩訪問,也就是可以根據元素的物理地址直接訪問該元素。
- 由於不能直接給出元素所在的物理地址,因此當需要訪問某個元素時,只能從頭結點依次向後查詢,直到找到該元素或者訪問到最後一個結點爲止,後一種情況時該元素不存在與列表中。
- 帶來的利弊
- vector可以隨機訪問,因此當有很多的讀取等靜態操作時,使用vector效率較高
- 但對於insert和remove等動態操作,vector的實現時間複雜度爲O(n),而list只需新開闢一塊地址空間存入新的元素並修改相鄰元素的指針或者修改完指針再回收地址空間即可實現,其時間複雜度不過O(1)
- 綜上所述,當需求中靜態訪問操作較多時,使用vector效率高;當需求中增刪操作較多時,使用list效率更高。