C++ STL -- list

XTG111發表於2024-04-20

list

list是一種基於雙向連結串列的資料結構,適用於需要在序列中執行頻繁插入和刪除操作的場景

特性

  1. 本質上是一個雙向連結串列,允許在序列的兩端和中間執行插入和刪除操作
  2. 只能透過迭代器訪問元素,即訪問元素的時間複雜度為\(O(n)\)
  3. 動態記憶體管理,內部透過類似指標的節點實現元素儲存,一個節點儲存了當前元素和前後節點的指標
  4. 迭代器在增加和刪除之後仍然有效

基礎成員變數

頭指標,尾指標以及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 較小的記憶體開銷

相關文章