STL中关于list容器的sort函数详解

写在前面

因为在stl中stl_algo中提供有sort函数,他的函数原型:

template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
inline void sort(RandomAccessIterator first, RandomAccessIterator last,
                 Compare comp);

我们看到关于迭代器都是 RandomAccessIterator类型的,就是最高层次的指针,但是我们list中迭代器类型为:

typedef bidirectional_iterator_tag iterator_category;

由于bidirectional_iterator_tag是random_access_iterator_tag的子类,不会传递调用random_access_iterator_tag版本的排序函数,那么关于list中的sort函数的实现就放在了stl_list中来进行实现了

struct random_access_iterator_tag : public bidirectional_iterator_tag {};

前提了解

splice函数:在sort函数中使用到了这种重载类型

//将list &中i位置上的节点,插入到this的position位置之前,list &中i位置上的节点消失
void splice(iterator position, list&, iterator i) {           //将i所指元素结合与position所指位置之前,position和i可指向同一个list
    iterator j = i;
    ++j;
    if (position == i || position == j) return;                 //位置发生重合或者要移动的位置已经在哦position前面
    transfer(position, i, j);
  }

swap函数,我们这里使用一个栗子来看一下swap函数,因为我最后没有找到swap的原函数在哪里,函数可以实现将两个list容器里的所有内容互换(感觉只交换一下最后尾部的节点就好)

#include <iostream>
#include <list>

using namespace std;

int main()
{
	list<int> first(3, 100);  
	list<int> second(5, 200);

	first.swap(second);

	list<int>::iterator iter = first.begin();
	while (iter != first.end())
		cout << *iter++ << " ";
	cout << endl;

	list<int>::iterator iter_ = second.begin();
	while (iter_ != second.end())
		cout << *iter_++ << " ";
	cout << endl;
	system("pause");
}

merge函数:

//将list x的内容全部添加到当前操作的list中,并且按照顺序进行插入
//前提是两个list都是递增排序的
template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x) {
  iterator first1 = begin();
  iterator last1 = end();
  iterator first2 = x.begin();
  iterator last2 = x.end();
  while (first1 != last1 && first2 != last2)                           //只有两个都不为空的情况下
    if (*first2 < *first1) {
      iterator next = first2;
      transfer(first1, first2, ++next);                              
      first2 = next;
    }
    else
      ++first1;
  if (first2 != last2) transfer(last1, first2, last2);
}

这里要着重说一下,因为在list中进行转移操作,都是指针的移动,而不知重新建造空间,使用构造函数,所以在merge函数执行完之后,list x中的内容全都会消失,只剩最后的指向自身的节点。

这里实现的sort函数有点类似于归并排序,但是很神奇的是,表面上看不出什么限制,但是他的list数组counter,每个里面的元素最多都是2^i个,这里我觉得也是理解的难点。

sort函数程序

template <class T, class Alloc>  
void list<T, Alloc>::sort() {
  if (node->next == node || link_type(node->next)->next == node) return;
  list<T, Alloc> carry;
  list<T, Alloc> counter[64];
  int fill = 0;
  while (!empty()) {
    carry.splice(carry.begin(), *this, begin());
    int i = 0;
    while(i < fill && !counter[i].empty()) {                         //这里的!counter[i].empty(),是用来对第i列最多2^i个元素控制的
      counter[i].merge(carry);                                       //清空carry,将它合并到counter[i]中
      carry.swap(counter[i++]);                                      //因为可能还会向下传递,要将内容在此放到carry中,清空counter[i],因为此时carry内容为空
    }
    carry.swap(counter[i]);                                          //carry不为空的话,那么就将所有内容放到后面一个counter中
    if (i == fill) ++fill;                                           //说明前面的所有counter都进行了更新,那么之前第fill中存在的元素个数为2^(fill+1),所以上面一句话中开一个新的counter来储存,这里来更新循环次数
  } 

  for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);     //最后合并所有的counter
  swap(counter[fill-1]);
}

为什么counter[i]最多储存2^i个内容?

        我认为这里关键的位置就是!counter[i].empty()以及if(i==fill) ++fill,这里因为每次carry都是携带一个节点来,第一次循环不再陈述,假设为第二次循环,首先和counter[0]合并,然后counter[0]中内容为2个,执行一次swap,那么carry中内容为2个,counter[0]为空,但是上次counter[0]的大小为1,那么第一次循环的时候会满足i==fill条件,fill会变为1,但是counter[1] 为空,所以后面会将carry中的两个元素放入counter[1]中,依次这样向上推进,向counter[2]中存放数据的时候一定是counter[0]含有一个元素,counter[1]中含有两个元素,当carry再次携带一个元素来的时候,会将所有元素放入counter[2]中,counter[2]中含有4个元素

因为函数merge执行完后,元素都是有序的,那么在最后进行合并的时候会非常省时,基本上就是归并排序的思想。

我们可以来模拟一下这个过程,使用链表:8 ,2 ,5, 1, 9,  7

第一次:carry = 8,但是counter[0]为空,那么直接将carry放入到counter[0]中,i == fill,此时fill++

第二次:carry = 2,首先与counter[0]合并,counter[0] = {2,8},carry = NULL,此时counter[1] = fill,直接将所有元素放入counter[1]中,counter[0] = NULL,counter[1] = {2,8}

第三次:carry = 5,直接将5放入counter[0]中,因为counter[0],为空

第四次:carry = 1,与counter[0]合并,生成序列{1,5},再与counter[1]合并,生成序列{1,2,5,8},将序列放入counter[2]中,前面全部为空

第五次:carry = 9,直接放入到counter[0]中

第六次:carry = 7,与counter[0]合并,且因为counter[1] = NULL,所以将合并的序列放入counter[1]中,形成{7,9}

最后将counter[0]-counter[3]合并到counter[3]中,得到正确的序列

使用数组简化的归并排序

参考博客

https://blog.csdn.net/return_cc/article/details/79604923#commentsedit

《STL源码分析》侯捷著

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/83216113