從零開始寫STL-容器-list

從零開始寫STL-容器-list

  • List 是STL 中的鏈表容器,今天咱們將經過閱讀和實現list源碼來解決一下問題:
  • List內部的內存結構是如何實現的?
  • 爲何List的插入複雜度爲O(1)?
  • 爲何List的size()函數複雜度爲O(n)?

list 容器的幕後英雄 - list 結點

做爲一個鏈表首先要維護數據(模板元素的實例內容),指向前一個節點的指針 和 指向後一個節點的指針。List 的 結點做爲list 容器中 元素內容 和 容器組織邏輯的一箇中間層。node

  • 爲何不能搞成單向鏈表? 若是單向鏈表的話,請您試想一下要刪除迭代器pos指向的元素應該怎麼操做?咱們必須知道前驅後繼才能正確進行刪除
for(auto it = begin(); it != end(); it++)
{
	if(it->next == pos)
	{
		//...邏輯代碼
	}
}

這樣的單向鏈表每次尋找一個節點的前驅 後繼 都要通過最壞複雜度爲O(n)的查詢,全部應該實現爲雙向鏈表。c++

list 容器的 迭代器

  • 維護一個指向list_node 的指針
template<class T>
	struct list_iterator : public bidirectional_iterator<T>
	{}//繼承bidirectional_iterator類 便於類型萃取 和 特定算法應用

list 容器的 邏輯結構

  • 環狀鏈表 >node 表示尾部的一個空白結點,其next 指向list的頭節點, pre指向list的尾節點

爲何這樣設計? 首先list做爲雙向鏈表須要提供向前和向後的迭代能力,這樣設計能夠在O1的時間得到首尾元素,並且能夠避免鏈表爲空時的邊界檢查(只須要看一下node->pre == node,則爲空)算法

node_ptr get_node()
		{
			return data_allocator.allocate(1);
		}
		node_ptr new_node(const T& x)
		{
			node_ptr p = get_node();
			construct(&p->data, x);
			p->next = p->pre = nullptr;
			return p;
		}

構造函數

list()
		{
			//注意 typedef  std::allocator<list_node<T>> Data_allocator;
			node.ptr = data_allocator.allocate(1);
			node.ptr->next = node.ptr->pre = node.ptr;
		}
		list(const self& rhs) :list(rhs.begin(), rhs.end())
		{
	
		}
		list(std::initializer_list<T> li):list(li.begin(),li.end())
		{

		}
		template<class c>
		list(const c l,const c r)
		{
			node.ptr = data_allocator.allocate(1);
			node.ptr->next = node.ptr->pre = node.ptr;
			for (auto it = l; it != r; it++)
			{
				push_back((*it));//逐個插入到list的末端
			}
		}
		list(size_type n, const T& val)
		{
			node.ptr = data_allocator.allocate(1);//初始化node
			node.ptr->next = node.ptr->pre = node.ptr;
			while (n--)
				push_back(val);
		}

析構函數

遍歷列表 銷燬數據實例函數

~list()
		{
			if (!empty())
			{
				for (iterator it = begin(); it != end(); )
				{
					data_allocator.destroy(&it.ptr->data);
					it++;
				}
			}
		}

Modify

  • 注意指針操做的前後順序
void push_front(const T& x)
		{
			node_ptr p = new_node(x);
			node.ptr->next->pre = p;
			p->next = node.ptr->next;
			node.ptr->next = p;
			p->pre = node.ptr;
		}
		void push_back(const T& x)
		{
			node_ptr p = new_node(x);
			node.ptr->pre->next = p;
			p->pre = node.ptr->pre;
			p->next = node.ptr;
			node.ptr->pre = p;
		}
		void pop_front()
		{
			node_ptr tmp = node.ptr->next;
			node.ptr->next = node.ptr->next->next;
			node.ptr->next->pre = node.ptr;
			data_allocator.deallocate(tmp,sizeof(list_node));
		}
		void pop_back()
		{
			node_ptr tmp = node.ptr->pre;
			node.ptr->pre = tmp->pre;
			tmp->pre->next = node.ptr;
			data_allocator.deallocate(tmp, sizeof(list_node));
		}
		iterator erase(iterator pos)
		{
			pos.ptr->pre->next = pos.ptr->next;
			pos.ptr->next->pre = pos.ptr->pre;// 前驅 後繼 結點的指針操做
			node_ptr tmp = pos.ptr->next;
			destroy(&pos.ptr->data);//銷燬
			data_allocator.deallocate(pos.ptr,sizeof(list_node));// 回收內存
			return iterator(tmp);
		}
		iterator erase(iterator first, iterator last)
		{
			first.ptr->pre->next = last.ptr;
			last.ptr->pre = first.ptr->pre;
			for (auto it = first; it != last; it++)
				destroy(&it.ptr->data);
			return first;
		}
		//The list container is extended by inserting new elements before the element at position.
		iterator insert(iterator pos, const T& x)
		{
			node_ptr p = new_node(x);
			pos.ptr->pre->next = p;
			p->pre = pos.ptr->pre;
			p->next = pos.ptr;
			pos.ptr->pre = p;
			return pos;
		}

		void insert(iterator pos, size_type sz, const T& x)
		{
			while (sz--)
				insert(pos, x);
		}

Splice 函數

/* 將 first到last的元素移動到 pos 以前 */
		void transfer(iterator pos, iterator first, iterator last)
		{
			if (pos != last)
			{
				last.ptr->pre->next = pos.ptr;
				first.ptr->pre->next = last.ptr;
				pos.ptr->pre->next = first.ptr;
				auto tmp = pos.ptr->pre;
				pos.ptr->pre = last.ptr->pre;
				last.ptr->pre = first.ptr->pre;
				first.ptr->pre = tmp;
			}
		}
		void splice(iterator pos, list<T>& rhs)
		{
			if (*this != rhs)
				transfer(pos, rhs.begin(), rhs.end());
		}
		void splice(iterator pos, list<T>& rhs, iterator first, iterator last)
		{
			if (*this != rhs)
			{
				transfer(pos, first, last);
			}
		}
		void splice(iterator pos, list<T>& rhs, iterator it)
		{
			it.ptr->pre->next = it.ptr->next;
			it.ptr->next->pre = it.ptr->pre;
			pos.ptr->pre->next = it.ptr;
			it.ptr->pre = pos.ptr->pre;
			pos.ptr->pre = it.ptr;
			it.ptr->next = pos.ptr;
		}

merge 函數

注意鏈表的merge函數 和算法庫中含義有所不一樣,在這裏會將參數鏈表所有合併進來(也就是說在調用完這個函數以後,other 參數應該是空)this

void merge(list<T> &other)
		{
			auto p1 = begin(), p2 = other.begin();
			while (p1 != end() && p2 != other.end())
			{
				if (*p1 < *p2)
					p1++;
				else if (*p1 >= *p2)
				{
					auto tmp = p2;// 注意保存迭代器下一個位置,若是不保存,直接再調用splice以後p2++會怎樣?首先p2!=other.end()這個條件永遠不會觸發,由於p2已經到了this的鏈表中
					tmp++;
					splice(p1, other, p2);
					p2 = tmp;
				}
			}
			if (!other.empty()) {
				splice(end(), other);
			}
		}

list 歸併排序的非遞歸形式(難點)

非遞歸形式的 歸併排序,由於List並不支持隨機存取迭代器,sort不能用於排序list。 歸併的思路以下:先創建64個桶,而後從前向後遍歷,每次從要排序的list頭取出一個元素插入桶中,第一個桶要存的元素最多爲1個,第二個桶要存的最多爲2個,第K個桶最多爲2^K個元素。在插入元素的時候從前向後插入,若是到達當前桶的上限就向後歸併。 具體的算法實現極爲巧妙! 我的感受參考了位運算的進位性質spa

void sort()
		{
			if (size() <= 1)
			{
				return;
			}
			self carry;
			self counter[64];
			int fill = 0;
			while (!empty())
			{
				carry.splice(carry.begin(), *this, begin());
				int i = 0;
				while (i < fill && !counter[i].empty())
				{
 					counter[i].merge(carry);
					carry.swap(counter[i++]);
				}
				carry.swap(counter[i]);
				if (i == fill)
				{
					++fill;
				}
			}
			for (int i = 1; i < fill; i++)
			{
				counter[i].merge(counter[i - 1]);
			}
			swap(counter[fill - 1]);
		}
相關文章
相關標籤/搜索