list
list是一種基於雙向連結串列的資料結構,適用於需要在序列中執行頻繁插入和刪除操作的場景
特性
- 本質上是一個雙向連結串列,允許在序列的兩端和中間執行插入和刪除操作
- 只能透過迭代器訪問元素,即訪問元素的時間複雜度為\(O(n)\)
- 動態記憶體管理,內部透過類似指標的節點實現元素儲存,一個節點儲存了當前元素和前後節點的指標
- 迭代器在增加和刪除之後仍然有效
基礎成員變數
頭指標,尾指標以及List大小三個變數
對於list內部每個元素即一個節點,其具有當前元素值,前指標(第一個元素指向最後一個元素),後指標(最後一個元素指向第一個元素)
//整個成員變數
//節點結構體
struct Node
{
T data;
Node* prev;
Node* next;
Node(const T& value,Node* prevnode = nullptr,Node* nextnode = nullptr):data(value),prev(prevdata),next(nextdata){}
};
Node* head;
Node* tail;
size_t size;
相關操作的實現
<<運算子過載
template <typename T>
friend std::ostream& operator<<(std::ostream& os,const List<T>& pt)
{
for(typename List<T>::Node *cur = pt.head,cur;cur = cur->next)
{
os << " " << cur->data;
}
os << std::endl
return os;
}
壓入頭部和尾部
需要考慮原始list為空的情況
//在末尾新增元素,變換tail節點
void push_back(const T& value)
{
//建立新的節點
Node* newnode = new Node(value,nullptr,tail);
//如果tail指標指向的不為空
if(tail)
{
tail->next = newnode;
}
//如果tail為空,說明連結串列為空
else
{
head = newnode;
}
tail = newnode;
size++;
}
//在頭部新增元素,變換head節點
void push_front(const T& value)
{
Node* newnode = new Node(value,head,nullptr);
if(head)
{
head->prev = newnode;
}
else
{
tail = newnode;
}
head = newnode;
size++;
}
從頭部和尾部彈出元素
只需要考慮size >0 的情況,利用delete刪除節點
void pop_back()
{
if(size > 0)
{
Node* temp = tail->prev;
delete tail;
tail = temp;
if(tail)
{
tail->next = nullptr;
}
else
{
head = nullptr;
}
size--;
}
}
//刪除頭部元素,改變head節點
void pop_front()
{
if(size > 0)
{
Node* temp = head->next;
delete head;
head = temp;
if(head)
{
head->prev = nullptr;
}
else
{
tail = nullptr;
}
size--;
}
}
根據值刪除節點
需要考慮當前list為空,或者當前節點為頭節點尾節點的情況
void remove(const T& value)
{
Node* node = getNode(value);
if(!node) return ;
else if(node!=head && node != tail)
{
Node* prevnode = node->prev;
Node* nextnode = node->next;
prevnode->next = nextnode;
nextnode->prev = prevnode;
}
else if(node == head && node == tail)
{
head = nullptr;
tail = nullptr;
}
else if(node == head)
{
head = node->next;
head->prev = nullptr;
}
else
{
tail = node->prev;
tail->next = nullptr;
}
size--;
}
總結
刪除list中的特定元素
list中元素不是連續儲存的,而且插入和刪除不會導致迭代器失效
透過遍歷,利用erase實現
template <typename T>
void removeins(std::list<T>& lst, const T& value)
{
for(auto it = lst.begin();it!=lst.end();)
{
if(*it == value)
{
it = lst.erase(it);
}
else
{
it++;
}
}
}
list迭代器失效的情況
在插入操作時不會導致任何迭代器失效,而刪除操作會導致指向被刪除元素的迭代器失效,而其他迭代器不會失效
與vector的關係
內部實現
- list是雙向連結串列,不支援隨機訪問
- vector是一個動態陣列,支援隨機訪問
效能特點
- list 常數時間內進行插入和刪除操作,但訪問為\(O(n)\),每個元素存放在單獨的記憶體塊中,用指標連線,不需要重新分配整個容器的記憶體空間
- vector 常數時間內進行訪問,但插入和刪除為\(O(n)\),在記憶體中連續,超出容量,需要進行擴容操作
記憶體開銷
- list 每個元素都多儲存了指向前面和後面的節點指標
- vector 較小的記憶體開銷