list::sort源码对比(侯捷老师源码剖析和c++11中当前使用的库中的实现)

17 篇文章 0 订阅
9 篇文章 0 订阅

首先看一下侯捷老师《STL源码剖析》中的源码

// list 不能使用STL 算法 sort(),必须使用自己的 sort() member function,
// 因为STL算法sort() 只接受RamdonAccessIterator.
template <class T, class Alloc>
void list<T, Alloc>::sort() {
    // 如果为空链表或只有一个元素的链表,则什么也不做
    if (node->next == node || link_type(node->next)->next == node) return;
    
    // 一些新的 lists,做为中介数据存放区
    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].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]);
}

在书中侯捷老师说是使用的快速排序,由于自己的水平有限,不妄作定义,看了分析过程,各位大佬可能会自有各自理解。

分析代码流程

先看一下代码中用到的函数:

  1. splice:主要拼接作用,有以下重载版本
	1. splice(itreator position, list &x)//将x拼接到指定Position位置之前,Splice为成员函数,所以默认对*this进行操作,该函数x必须不同于*this
	2. splice(iterator position, list &, iterator i)//将i所指元素接合于position位置之前。position和i可指向同一个list
	3. splice(iterator position, list &, iterator first, iterator last)//将[fisrt, last)范围内的元素接合于position位置之前
  1. merge
	merge(list<T,Alloc> &x)//将*this和x进行合并,两个list都必须确保进行了递增排序
  1. swap
	//交换两个对象的值
	template <class T>
	void swap(T &x, T &y){
		T temp = x;
		x = y;
		y = temp;
	}
通过一个例子来进行说明:
假设现在需要对list = [0, 2, 99, 3, 4]进行排序
第一步:fill = 0, carry链表通过splice后为{0}
	此时i = 0,对于while(i < fill && !counter[i].empty()), i == fill所以为假
	将carry和counter[i](即count[0])进行交换,所以此时counter[0]为{0} ,carry为null
	最后一个if (i == fill) ++fill;满足条件,所以fill变为1,进行下一轮循环
第二步:fill = 1, carry通过splice后为{2}
	此时i = 0, while(i < fill && !counter[i].empty()), (i = 0) < (fill = 1),并且由第一步知counert[i](即counter[0])不为空,条件为真,进入内部while循环
	将carry合并到counter[0]上后,carry为空,counter0]为{0,2}
	再通过swap将carry和counter0]进行交换,所以此时carry为{0,2},counter[0]为空,并且伴随将i值变为1
	此时内层的while循环条件不满足,跳出循环,执行carry和counter[1]的交换,所以此时
	carry为空,counter[0]为空,counter[1]为{0,2}.
	此时(i = 1) == (fill = 1),所以fill变为2.
	到这一步,counter数组的样子如下:
	counter[0]:  {}
	counter[1]:  {0,2}
	counter[2]:  {}
	....
	counter[64]: {}
第三步:fill  = 2,carry = {99},i = 0
	while(i < fill && !counter[i].empty())为假 因为counter[0]为空
	carry和counter[0]交换,carry为空,counert[0]:{99}
第四步:fill = 2, carry = {3}, i = 0
	while(i < fill && !counter[i].empty())为真
		counter0]为{3,99}, carry为空;
		counter[0]为空, carry为{3,99}, i = 1
		counter[1]为{0,2,3,99}, carry为空
		counter[1]为空, carry为{0,2,3,99},i = 2
	counter[2] = {0,2,3,99},carry为空, (i = 2) == (fill = 2),所以fill = 3
	到此:
		counter[0]:{}
		counter[1]:{}
		counter[2]:{0,2,3,99}
		....
		counter[64]:{}
第五步:fill = 3, carry :{4},i = 0
	while(i < fill && !counter[i].empty())为假,counter[0-1]均为空
	counter[0] = {4}, carry:空
	此时进行下一轮循环判断while (!empty())为假,跳出循环,此时counter和carry的情况为

carry : {}
counter[0]:{4}
counter[1]:{}
counter[2]:{0,2,3,99}
counter[3]:{}
...
counter[64]:{}
fill = 3
随后执行
for (int i = 1; i < fill; ++i)
   	 counter[i].merge(counter[i-1]);
最后合并后counter[2]:{0,2,3,4,99}
最后经过交换,*this即为{0,2,3,4,99}完成排序

上述即为一个简单的排序过程,描述有点口水,从该过程看着不像是快排,有点归并的意思,各位大佬觉得呢?
最后再来看看c++11后list类库中的sort代码:

/**
 * Sorts the elements of this list in NlogN time
 * c++11的版本
 */
template<typename _Tp, typename _Alloc>
void list<_Tp, _Alloc>::sort() {
      // Do nothing if the list has length 0 or 1.
      if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node
	        && this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node) {
            list __carry;
            list __tmp[64];
            list * __fill = __tmp;
            list * __counter;
        __try {
            do {
                __carry.splice(__carry.begin(), *this, begin());

                for(__counter = __tmp;
                    __counter != __fill && !__counter->empty();
                    ++__counter) {
                    __counter->merge(__carry);
                    __carry.swap(*__counter);
                }
                __carry.swap(*__counter);
                if (__counter == __fill) ++__fill;

            }while ( !empty() );

            for (__counter = __tmp + 1; __counter != __fill; ++__counter)
                __counter->merge(*(__counter - 1));
            swap( *(__fill - 1) );

        }__catch(...){
            this->splice(this->end(), __carry);
            for (int __i = 0; __i < sizeof(__tmp)/sizeof(__tmp[0]); ++__i)
            this->splice(this->end(), __tmp[__i]);
            __throw_exception_again;    //一个宏定义相当于throw抛出异常
        }
    }
}

整体思想没有改变,只是代码结构方面进行重新设计!
最后感谢各位,时间紧迫,写的急,如有错误,还请指出!

参考资料:侯捷《STL源码剖析》

使用在leetcode 148. 排序链表 https://leetcode-cn.com/problems/sort-list/
代码示例如下:

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head; //空或者有一个节点
        ListNode* temp = head;
        ListNode *carry = new ListNode(0); //哑节点
        ListNode* counter[64];
        for(int i = 0; i < 64; ++i)
            counter[i] = new ListNode(0); //哑节点
        int fill = 0;
        while(temp != nullptr){
            carry->next = temp;
            temp = temp->next;
            carry->next->next = nullptr;
            int i = 0;
            while(i < fill && counter[i]->next != nullptr){
                myMerge(counter[i], carry);//把carry合并到counter[i]上
                mySwap(carry, counter[i]);
                ++i;
            }
            mySwap(carry, counter[i]);
            if(i == fill) ++fill;
        }
        for (int i = 1; i < fill; ++i)
            myMerge(counter[i], counter[i-1]);
        return counter[fill-1]->next;
    }

    void mySwap(ListNode* A, ListNode* B){//交换链表
        ListNode* temp = A->next;
        A->next = B->next;
        B->next = temp;
    }

    void myMerge(ListNode* A, ListNode* B){ //有序合并链表
        ListNode* newHead = new ListNode(0);//哑节点
        ListNode* temp = newHead, *temp1 = A->next, *temp2 = B->next;
        A->next = B->next = nullptr;
        while(temp1 && temp2){
            if(temp1->val < temp2->val){
                temp->next = temp1;
                temp1 = temp1->next;
            }else{
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if(temp1){
            temp->next = temp1;
        }else{
            temp->next = temp2;
        }
        A->next = newHead->next;
    }
};

当然,实际情况下,使用仿照list.sort源码进行编写的链表排序自己编写的代码量比较多,需要写合并算法,交换算法,当然简单的单链表排序可以借助快慢指针的归并排序来实现,如下:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 the head node
     * @return ListNode类
     */
    ListNode* sortInList(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;
        ListNode *fast = head->next, *slow = head; // 使用快慢指针分割原链表
        while(fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* temp = slow->next;
        slow->next = nullptr; // 进行断开,得到两个链表
        // 递归两边进行排序
        ListNode* left = sortInList(head);
        ListNode* right = sortInList(temp);
        
        // 创建新链表,并进行真正的归并排序
        ListNode *h = new ListNode(-1);
        ListNode* ans = h;
        while(left && right){
            if(left->val > right->val){
                h->next = right;
                right = right->next;
            }else{
                h->next = left;
                left = left->next;
            }
            h = h->next;
        }
        h->next = left ? left : right;
        h = ans->next;
        delete ans;
        return h;
    }
};

实际笔试时,使用第二份代码比较容易通过,自己写的代码量也相对较少。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值