sgi_stl slist::sort() 简要分析

5 篇文章 0 订阅
2 篇文章 0 订阅

说是这个暑假复习数据结构,其实进度有点慢。

我还在链表这里,大概是因为想造出更好的轮子,我阅读了 java.util.LinkedList.java 源码以及 sgi_stlstl_slist.h

互联网是个好东西,把我的标题放进搜索框,一下子就有几十条优质的相关内容,可是我为什么还要写呢。这个问题大概可以一句诗作答“纸上得来终觉浅,绝知此事要躬行”。写博客这件事,虽然有很多很多人比我写得好,而且可能我也没有什么有创意的自己的想法,但是只是阅读人家的文字,自己不去动手的话,那么将永远停留在“浅”的层次。

总之,“1”好过没有,即使它只是一个起点。

好了,不废话了。这篇博客基于博主lijun5635的专栏STL List::sort() 解析 ,感谢博主精彩的分析,带给我很多启示。


1、slist::sort() 源码

我研究的 slist 数据结构其实是 Singly Linked List , 而 stllist 实际上是一个 Doubly Linked List,但是我在寻找 slist::sort() 的解释时,意外发现 list::sort() 基本是相同的。下面是 slist::sort() 的源码。

template <class _Tp, class _Alloc>
void slist<_Tp,_Alloc>::sort()
{
  if (this->_M_head._M_next && this->_M_head._M_next->_M_next) {
    slist __carry;
    slist __counter[64];
    int __fill = 0;
    while (!empty()) {
      __slist_splice_after(&__carry._M_head,
                           &this->_M_head, this->_M_head._M_next);
      int __i = 0;
      while (__i < __fill && !__counter[__i].empty()) {
        __counter[__i].merge(__carry);
        __carry.swap(__counter[__i]);
        ++__i;
      }
      __carry.swap(__counter[__i]);
      if (__i == __fill)
        ++__fill;
    }

    for (int __i = 1; __i < __fill; ++__i)
      __counter[__i].merge(__counter[__i-1]);
    this->swap(__counter[__fill-1]);
  }
}

2、 用list 相关函数模拟 slist::sort()

看这类数据结构的算法时,我通常的做法是在纸上演算整个过程,这个通常都很有效,但是比较繁琐,也容易犯错。

在阅读 java.util.LinkedList.java 源码时,我发现一些我觉得很复杂的方法,源码却不是很长,因为基本操作封装得合理,复杂的方法只需要在较低层的方法上,进行严谨的逻辑判断和调用即可。

而在阅读 slist::sort() 时,我刚开始是没有意识到可以自己调用底层方法来实现与之相同的代码的。原因有两点:

  • slist 不是 stl 的标准里的,想要调用其方法的话,也许要自己造完整的轮子。

  • 我以前没有试过。

但是看了上面提到的博主的文章后,我受到了启示,并且因为 list 可以说是 slist 的超集,模拟当然是可以的。下面是我的模拟的代码:

  list<int> carry;
  list<int> counter[64];
  int fill = 0;

  while (!_MT_list.empty()) {
    carry.splice(carry.begin(), _MT_list, _MT_list.begin());

    int i = 0;
    while (i < fill && !counter[i].empty()) {
      counter[i].merge(carry); // after the merger, carray is empty.
      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]);

测试数据

{7, 11, 2, 1, 8, 20, 5, 99, 33}

插入输出语句后,得到运行过程:
simulate_sort.png

3、运行结果分析

(1)先取第一个元素:7 ,把它存入 counter[0]中;

(2)然后取第二个元素:11 , 将它与 7 合并后,存入了 counter[1] 中,此时 counter[0] 为空, 现在为止已经取了 2 个元素:{7, 11},而 fill = 1;

(3)取第三个元素: 2,把它存入 counter[0] 中,此时 fill = 2;

(4)取第四个元素:1,把它与 counter[0] 合并得到 {1, 2},然后再与 counter[1] 合并得到 counter[2] = {1, 2, 7, 11},然后 counter[0] 和 counter[1]由于合并操作的缘故全部变成空,此时 fill = 2,整个 counter 中含有的元素个数是4;

(5)取第五个元素,counter[0] 中存入 8,开始与之前的过程相似。此时 fill = 3,counter 中含有的元素个数为5,而 22<5<23

(6)取第六个元素之后,仍然 22<6<23

(7)取第七个元素后, 20+21+22=7

(8)取第八个元素后,由于 23=8 ,所以这次的合并成功将所有已经取到的元素合并到有序,且没有遗漏 counter[3] 之前的 counter[i] 存储的元素,因为它们都为空了。

(9)去第九个元素后, 20+23=9 ,已经取完了所有元素,然后从 1 到 fill - 1 依次合并相邻的 counter[i],得到最终结果。

来张表总结一下:


取第 i 个元素i 的值与2的幂的关系counter所含总元素的表示fill
11 = 20 counter[0]0
22 = 21 counter[1]1
33 = 20+21 counter[0]+ counter[1]2
44 = 22 counter[2].size()2
55 = 20+22 counter[0] + counter[2]3
66 = 21+22 counter[1] + counter[2]3
77 = 20+21+22 counter[0] + counter[1] + counter[2]3
88 = 23 counter[3]3
99 = 20+23 counter[0] + counter[3]4

可以看到不同 fill 代表了不同的层级, 假设 fill = level,则能表示的层级为

[2level1,2level]

当取得的元素个数从 2level1 逐渐增加到 2level 时,便完成了一次从低层向高层的“进位”—— 所有低层的元素全部合并到高层,低层被全部清空。

如果所有元素的个数 size() 刚好等于 2 的某个幂,那么 counter[fill - 1] 就是 sort 的最终结果,但是如果不是那么巧合,那么需要从低层向高层合并得到最终结果。

4、代码分析

从之前的分析可以看到这是一个归并排序:1 个元素加 1 个元素合并成 2 个元素; 2 个元素 加 2 个元素合并成 4 个元素;4 个元素加 4 个元素合并成 8 个元素……以此类推。

然而不是每一个元素都能经历从最底层到最高层的完整合并过程,因为元素个数不一定是 2 的幂,所以需要最后的 for 循环,将经过完整合并过程以及没有经历完整合并过程的元素进行最后的合并,以求得最终结果。

下面是注释形式的更加具体的代码分析。

template <class _Tp, class _Alloc>
void slist<_Tp,_Alloc>::sort()
{
  // 如果 this->size() < 2,返回。
  if (this->_M_head._M_next && this->_M_head._M_next->_M_next) {
    // carry,命名的含义大概是作为传输的媒介。
    slist __carry; 
    // counter,计数器,这里大概是广义的寄存器含义。
    slist __counter[64];
    // fill,“填满”,fill 指示 counter 中下一个将被填充的层次。
    int __fill = 0;

    // bool empty() const { return this->_M_head._M_next == 0; }
    while (!empty()) { 
      // 取链表中第一个元素,取出后,链表的第一个元素将变成原来首元素的下一个。
      __slist_splice_after(&__carry._M_head,
                           &this->_M_head, this->_M_head._M_next);

      int __i = 0;
      // 如果 __counter[__i] 为空,那么它是可以存放元素的层次。
      while (__i < __fill && !__counter[__i].empty()) {
        __counter[__i].merge(__carry); // merge 后, __carry 为空
        __carry.swap(__counter[__i]); // swap 后,__counter[__i] 为空
        ++__i;
      }
      // 将合并保存到被合并底层的高一层。
      __carry.swap(__counter[__i]); 

      // __i == __fill,表示第fill层以下已经全部合并,并被清空
      // __fill 需要增加,以进入下一个元素存储范围,需要合并的层次增加了一层
      if (__i == __fill)
        ++__fill;
    }

    // 如果 __counter[__fill-1] 没有包含 *this 的所有元素的话,
    // 说明元素个数在 (2^(fill-1), 2^fill) 之间,有部分元素滞留在底层
    // 需要进行最终的合并。
    for (int __i = 1; __i < __fill; ++__i)
      __counter[__i].merge(__counter[__i-1]);
    this->swap(__counter[__fill-1]);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值