一道经典C++编程题,排序k个排好序的std::list

1. 问题:

有k个已经排好序的std::list, 把它们合并成一个大的排好序的std::list.

例如有以下3个列表:

1->3->5

4->6

2->7

输出:1->2->3->4->5->6->7

2. 思路

本文尽量说明思考的过程,而不是直接给出结果。争取达到“授人以渔”的效果。

下面分析能想到的算法, 顺带分析时间复杂度,假设有k个列表,每个list长度为n:

1). 前两个直接使用std::list.merge合并,结果和第三个merge,再和第四个merge,直到最后一个。

Complexity

At most std::distance(begin(), end()) + std::distance(other.begin(), other.end()) - 1 comparisons.

所以一次合并的时间复杂度等于两个list的长度之和,故以上算法的时间复杂度为

 2n+3n+4n+...+kn = O(nk^2)

2). 可以看到上述算法时间复杂度与k的平方成正比,随着k的增长时间复杂度增长很快!k一旦上来算法的效率就很低了。想想k/2个list复杂度是多少?O(n(k/2)^2)=O(nk^2/4), 和另外一半k/2合并总时间复杂度O(nk^2/2)+O(nk/2+nk/2)=O(nk^2/2+nk),  当k稍大时+nk可以忽略几乎是原来的一半!只要nk^2/2>nk=>k>2分两组策略就更优。

      k/2还可以继续分成两个k/4, 根据以上公式效率也更高。所以最优策略是两个一组,两个一组合并成2n的list,再两个2n的一组合并成4n,...

       第一层:两个一组合并成2n   时间复杂度=(n+n)* k/2 = kn

       第二层: list长度为2n, 共合并k/4次   时间复杂度= (2n+2n)* k/4 = kn

       ....

        共log(k)层,所以此算法的总时间复杂度= O(nklog(k))

3). 还可以设置k个指针(迭代器)分别指向k个list的第一个元素,找出它们中最小的元素放入结果列表,把这个指针++指向下一个;再找出k个指针中最小的元素,放入结果列表,...,  直到k个指针都指向了各自的end。此算法每次只找出一个元素放入结果列表,需要kn次查找;总的效率完全取决于内层查找最小元素的效率,如何组织这k个指针是关键。

  • 直接用list,按原始k个list的顺序。查找最小元素需要k-1次比较,O(k).
  • 使用sorted list, 最小元素是第一个,插入新值最少1次比较,最差n-1次比较。平均O(k/2)
  • 使用min-heap(priority queue - pq). pop最小元素后需要立即组织heap,耗时O(log(k)), 插入新值也耗时O(log(k)). 平均O(2log(k)), 其实说大O时2可以省略。

故使用min-heap组织这k个指针比较好,总时间复杂度= O(nk*log(k)).

4). 对3也分组,效仿2。比如m个一组。时间复杂度推导直接上手稿吧,不爱画图。

3. 最优算法

综上所述,算法2)3)4)时间复杂度相当,但考虑到std::list.merge仅仅移动指针不拷贝数据,且它可以直接访问list的内部成员,效率应该更好一些。所以2)具有最优效率, 时间复杂度为O(nk*log(k))。考虑到算法3)中min-heap法的真正复杂度是kn*2log(k), 所以3)4)应该是2)的两倍左右耗时。

4. 实现(基于C++11)

/* zhaimin.cn@gmail.com  20220426*/
template<typename T>
list<T> sort_klist4(list<list<T>>& lists){
	auto ll = lists;
	typename list<T>::size_type s = ll.size();
	if (s == 0) return list<T>();

	while (s > 1) {
		s = (s + 1) / 2;
		typename list<list<T>>::iterator begin = ll.begin();
		typename list<list<T>>::iterator half = begin;
		advance(half, s);
		typename list<list<T>>::iterator half_copy = half;//half half_copy都指向后半段
		for (auto it = half; it != ll.end(); it++,begin++) {
			begin->merge(*it);
		}
		ll.erase(half_copy, ll.end());//删除后半段
	}
	return ll.front();
    /* zhaimin.cn@gmail.com  20220426*/
    template <typename T>
        list<T> mergeSortedList(list<list<T>> &lists)  //算法3).pq
    {
        typedef typename list<T>::iterator list_iter;
        typedef pair<list_iter, list_iter> ptrs_value_type;

        auto cmp = [](const ptrs_value_type &p1, const ptrs_value_type &p2) -> bool
        { return *p1.first > *p2.first; };
        priority_queue<pair<list_iter, list_iter>, vector<ptrs_value_type>, decltype(cmp)> ptrs(cmp);

        for (auto &l : lists)
        {
            if (!l.empty())
            {
                ptrs.emplace(l.begin(), l.end());
            }
        }

        list<T> res;

        while (!ptrs.empty())
        {
            // get the minimal iterator that ptrs points to
            auto cur_end = ptrs.top();
            ptrs.pop();
            res.push_back(*(cur_end.first));
            cur_end.first++;
            if (cur_end.first != cur_end.second)
            {
                ptrs.push(cur_end);
            }
        }
        return res;
    }

5. 测试验证

1)4)速度较慢,没测,主要比较了2)3)。

---crowd vs sparse-----
Test performance, (max,k,listsize)=(100,100,100):
                  solution3-pq:        753
          solution2_list.merge:        332
Test performance, (max,k,listsize)=(1000000,100,100):
                  solution3-pq:        780
          solution2_list.merge:        355
---crowd. k vs listsize
Test performance, (max,k,listsize)=(100,100,500):
                  solution3-pq:       3863
          solution2_list.merge:       1858
Test performance, (max,k,listsize)=(100,100,1000):
                  solution3-pq:       7792
          solution2_list.merge:       4027
Test performance, (max,k,listsize)=(100,500,100):
                  solution3-pq:       4697
          solution2_list.merge:       2268
Test performance, (max,k,listsize)=(100,1000,100):
                  solution3-pq:      10062
          solution2_list.merge:       5370
---sparse. k vs listsize
Test performance, (max,k,listsize)=(1000000,100,500):
                  solution3-pq:       3855
          solution2_list.merge:       2017
Test performance, (max,k,listsize)=(1000000,100,1000):
                  solution3-pq:       7790
          solution2_list.merge:       4638
Test performance, (max,k,listsize)=(1000000,500,100):
                  solution3-pq:       4620
          solution2_list.merge:       2451
Test performance, (max,k,listsize)=(1000000,1000,100):
                  solution3-pq:      10101
          solution2_list.merge:       5680

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深山老宅

鸡蛋不错的话,要不要激励下母鸡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值