list概述


1、list概述

list 是一種雙向鏈表。list 的設計更加復雜一點,好處是每次插入或刪除一個元素,就配置或釋放一個元素,list 對於空間的運用有絕對的精准,一點也不浪費。而且對於任何位置的元素插入或刪除,list 永遠是常數空間。注意:list 源碼里其實分了兩個部分,一個部分是 list 結構,另一部分是 list 節點的結構。也就是說指針變量和數據變量分開定義,目的是是為了給迭代器做鋪墊,因為迭代器遍歷的時候不需要數據成員的,只需要前后指針就可以遍歷該 list。list 的節點結構如下圖所示:

2、list 數據結構-節點

__list_node 用來實現節點,數據結構中就儲存前后指針和屬性。

template <class T> struct __list_node {
    // 前后指針
   typedef void* void_pointer;
   void_pointer next;
   void_pointer prev;
    // 屬性
   T data;
};

基本結構如下圖所示:

基本類型

template<class T, class Ref, class Ptr> struct __list_iterator {
   typedef __list_iterator<T, T&, T*>     iterator; // 迭代器
   typedef __list_iterator<T, const T&, const T*> const_iterator;
   typedef __list_iterator<T, Ref, Ptr>    self;  
 
    // 迭代器是bidirectional_iterator_tag類型
   typedef bidirectional_iterator_tag iterator_category;
   typedef T value_type;
   typedef Ptr pointer;
   typedef Ref reference;
   typedef size_t size_type;
   typedef ptrdiff_t difference_type;
    ... 
};

構造函數

template<class T, class Ref, class Ptr> struct __list_iterator {
    ...
    // 定義節點指針
   typedef __list_node<T>* link_type;
   link_type node;
 // 構造函數
   __list_iterator(link_type x) : node(x) {}
   __list_iterator() {}
   __list_iterator(const iterator& x) : node(x.node) {}
   ... 
};

重載

template<class T, class Ref, class Ptr> struct __list_iterator  {
    ...
 // 重載
   bool operator==(const self& x) const { return node == x.node; }
   bool operator!=(const self& x) const { return node != x.node; }
    ...

    // ++和--是直接操作的指針指向next還是prev, 因為list是一個雙向鏈表
   self& operator++() { 
     node = (link_type)((*node).next);
     return *this;
   }
   self operator++(int) { 
     self tmp = *this;
     ++*this;
     return tmp;
   }
   self& operator--() { 
     node = (link_type)((*node).prev);
     return *this;
   }
   self operator--(int)  { 
     self tmp = *this;
     --*this;
     return tmp;
   }
};

3、list 結構

list 自己定義了嵌套類型滿足 traits 編程, list 迭代器是 bidirectional_iterator_tag 類型,並不是一個普通指針。

list 在定義 node 節點時, 定義的不是一個指針:

template <class T, class Alloc = alloc>
class list {
protected:
    typedef void* void_pointer;
    typedef __list_node<T> list_node; // 節點 就是前面分析過的
    typedef simple_alloc<list_node, Alloc> list_node_allocator; // 空間配置器
public:      
    // 定義嵌套類型
    typedef T value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef list_node* link_type;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    
protected:
    // 定義一個節點, 這里節點並不是一個指針.
    link_type node;
    
public:
    // 定義迭代器
    typedef __list_iterator<T, T&, T*>             iterator;
    typedef __list_iterator<T, const T&, const T*> const_iterator;
 ...
};

 4、list 構造和析構函數實現

每個構造函數都會創造一個空的 node 節點,為了保證我們在執行任何操作都不會修改迭代器。list 默認使用 alloc 作為空間配置器,並根據這個另外定義了一個 list_node_allocator,目的是更加方便以節點大小來配置單元。

template <class T, class Alloc = alloc>
class list {
protected:
    typedef void* void_pointer;
    typedef __list_node<T> list_node; // 節點
    typedef simple_alloc<list_node, Alloc> list_node_allocator; // 空間配置器

其中,list_node_allocator(n) 表示配置 n 個節點空間。以下四個函數,分別用來配置,釋放,構造,銷毀一個節點。

class list {
protected:
 // 配置一個節點並返回
  link_type get_node() { return list_node_allocator::allocate(); }
  // 釋放一個節點
  void put_node(link_type p) { list_node_allocator::deallocate(p); }
 // 產生(配置並構造)一個節點帶有元素初始值
  link_type create_node(const T& x) {
      link_type p = get_node();
      __STL_TRY {
        construct(&p->data, x);
      }
      __STL_UNWIND(put_node(p));
      return p;
  }
//銷毀(析構並釋放)一個節點
  void destroy_node(link_type p) {
    destroy(&p->data);
    put_node(p);
  }
  // 對節點初始化
  void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
  }  
};

5、基本屬性獲取

template <class T, class Alloc = alloc>
class list {
    ...
public: 
    iterator begin() { return (link_type)((*node).next); } // 返回指向頭的指針
    const_iterator begin() const { return (link_type)((*node).next); }
    iterator end() { return node; } // 返回最后一個元素的后一個的地址
    const_iterator end() const { return node; }
    
    // 這里是為旋轉做准備, rbegin返回最后一個地址, rend返回第一個地址. 我們放在配接器里面分析
    reverse_iterator rbegin() { return reverse_iterator(end()); }
    const_reverse_iterator rbegin() const { 
      return const_reverse_iterator(end()); 
    }
    reverse_iterator rend() { return reverse_iterator(begin()); }
    const_reverse_iterator rend() const { 
      return const_reverse_iterator(begin());
    } 
    
    // 判斷是否為空鏈表, 這是判斷只有一個空node來表示鏈表為空.
    bool empty() const { return node->next == node; }
    // 因為這個鏈表, 地址並不連續, 所以要自己迭代計算鏈表的長度.
    size_type size() const {
      size_type result = 0;
      distance(begin(), end(), result);
      return result;
    }
    size_type max_size() const { return size_type(-1); }
    // 返回第一個元素的值
    reference front() { return *begin(); }
    const_reference front() const { return *begin(); }
    // 返回最后一個元素的值
    reference back() { return *(--end()); }
    const_reference back() const { return *(--end()); }
    
    // 交換
    void swap(list<T, Alloc>& x) { __STD::swap(node, x.node); }
    ...
};
template <class T, class Alloc>
inline void swap(list<T, Alloc>& x, list<T, Alloc>& y) {
   x.swap(y);
}

6、list 的頭插和尾插

因為 list 是一個循環的雙鏈表, 所以 push 和 pop 就必須實現是在頭插入,刪除還是在尾插入和刪除。在 list 中,push 操作都調用 insert 函數, pop 操作都調用 erase 函數。

template <class T, class Alloc = alloc>
class list {
    ...
    // 直接在頭部或尾部插入
    void push_front(const T& x) { insert(begin(), x); } 
    void push_back(const T& x) { insert(end(), x); }
    // 直接在頭部或尾部刪除
    void pop_front() { erase(begin()); } 
    void pop_back() { 
      iterator tmp = end();
      erase(--tmp);
    }
    ...
};

上面的兩個插入函數內部調用的 insert 函數。

class list {
    ...
public:
  // 最基本的insert操作, 之插入一個元素
  iterator insert(iterator position, const T& x) {
      // 將元素插入指定位置的前一個地址
    link_type tmp = create_node(x);
    tmp->next = position.node;
    tmp->prev = position.node->prev;
    (link_type(position.node->prev))->next = tmp;
    position.node->prev = tmp;
    return tmp;
  }

注意:

  • 節點實際是以 node 空節點開始的。
  • 插入操作是將元素插入到指定位置的前一個地址進行插入的。

7、刪除操作

刪除元素的操作大都是由 erase 函數來實現的,其他的所有函數都是直接或間接調用 erase。

list 是鏈表,所以鏈表怎么實現刪除元素, list 就在怎么操作:很簡單,先保留前驅和后繼節點, 再調整指針位置即可。

由於它是雙向環狀鏈表,只要把邊界條件處理好,那么在頭部或者尾部插入元素操作幾乎是一樣的,同樣的道理,在頭部或者尾部刪除元素也是一樣的。

template <class T, class Alloc = alloc>
class list {
    ...
 iterator erase(iterator first, iterator last);
    void clear();   
    // 參數是一個迭代器 修改該元素的前后指針指向再單獨釋放節點就行了
 iterator erase(iterator position) {
      link_type next_node = link_type(position.node->next);
      link_type prev_node = link_type(position.node->prev);
      prev_node->next = next_node;
      next_node->prev = prev_node;
      destroy_node(position.node);
      return iterator(next_node);
    }
    ...
};
...
}

list 內部提供一種所謂的遷移操作(transfer):將某連續范圍的元素遷移到某個特定位置之前,技術上實現其實不難,就是節點之間的指針移動,只要明白了這個函數的原理,后面的 splice,sort,merge 函數也就一一知曉了,我們來看一下 transfer 的源碼:

template <class T, class Alloc = alloc>
class list {
    ...
protected:
    void transfer(iterator position, iterator first, iterator last) {
      if (position != last) {
        (*(link_type((*last.node).prev))).next = position.node;
        (*(link_type((*first.node).prev))).next = last.node;
        (*(link_type((*position.node).prev))).next = first.node;  
        link_type tmp = link_type((*position.node).prev);
        (*position.node).prev = (*last.node).prev;
        (*last.node).prev = (*first.node).prev; 
        (*first.node).prev = tmp;
      }
    }
    ...
};

上面代碼的七行分別對應下圖的七個步驟:

splice函數: 將兩個鏈表進行合並:內部就是調用的 transfer 函數。

merge 函數: 將傳入的 list 鏈表 x 與原鏈表按從小到大合並到原鏈表中(前提是兩個鏈表都是已經從小到大排序了). 這里 merge 的核心就是 transfer 函數。

reverse 函數: 實現將鏈表翻轉的功能:主要是 list 的迭代器基本不會改變的特點, 將每一個元素一個個插入到 begin 之前。

sort 函數: list 這個容器居然還自己實現一個排序,看一眼源碼就發現其實內部調用的 merge 函數,用了一個數組鏈表用來存儲 2^i 個元素, 當上一個元素存儲滿了之后繼續往下一個鏈表存儲, 最后將所有的鏈表進行 merge歸並(合並), 從而實現了鏈表的排序。

賦值操作: 需要考慮兩個鏈表的實際大小不一樣時的操作:如果原鏈表大 : 復制完后要刪除掉原鏈表多余的元素;如果原鏈表小 : 復制完后要還要將x鏈表的剩余元素以插入的方式插入到原鏈表中。

resize 操作: 重新修改 list 的大小,傳入一個 new_size,如果鏈表舊長度大於 new_size 的大小, 那就刪除后面多余的節點。

clear 操作: 清除所有節點:遍歷每一個節點,銷毀(析構並釋放)一個節點。

remove 操作: 清除指定值的元素:遍歷每一個節點,找到就移除。

unique 操作: 清除數值相同的連續元素,注意只有“連續而相同的元素”,才會被移除剩一個:遍歷每一個節點,如果在此區間段有相同的元素就移除之。

8、list 總結

list 是一種雙向鏈表。每個結點都包含一個數據域、一個前驅指針 prev 和一個后驅指針 next。

由於其鏈表特性,實現同樣的操作,相對於 STL 中的通用算法, list 的成員函數通常有更高的效率,內部僅需做一些指針的操作,因此盡可能選擇 list 成員函數。

優點

  • 在內部方便進行插入刪除操作。
  • 可在兩端進行push和pop操作。

缺點

  • 不支持隨機訪問,即下標操作和.at()。
  • 相對於 vector 占用內存多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM