合并N个有序链表与FQ公平调度

下大雨了,于是就想表达一些只有下雨才能表达的东西。夜半酒酣惊觉起,使我流泪忆江南…前天晚上下班带着小小在暴雨中狂奔,非常舒服,其实也算是流言终结者吧。反驳一下几千年来在我国北方通过长辈代代相传的淋雨和感冒之间的因果关系。

??昨天早上很早起来,听雨作文,今天早上继续,文章不算太长。

合并两个有序链表

这是一道超级常见的课后作业题或者面试题,网上答案一搜一箩筐,我自己也写了一个不会编程版

有序链表合并C语言递归版–我稍微会一点编程https://blog.csdn.net/dog250/article/details/80154795

写是写出来了,但写得不好。不管怎么说也算是一个答案。

??但是,这个问题有什么实际意义呢?或者说它是不是仅仅就是一个单纯考查你对编程技法掌握程度的问题呢?类似我们大学教科书课后的习题?用“*”号打印个菱形?写个冒泡排序?

??其实并不是。在看一个实际问题之前,我们先暂时抛开代码实现,来看一下所谓的合并两个有序链表观感上到底需要做什么。这个问题相信一个完全不懂IT技术的人也能给出答案。

??如下图,两个有序链表,假设均为升序链表:

所谓的合并,其实就是做下面的事:

就是这么一个简单的过程,在出现大小交错的锚点处,断开旧链接,接入新链接,仅此而已,剩下的问题就是如何用Java,C/C++这种语言来表达了,事实证明,用编程语言表达它并不比用自然语言表达它更困难。

??好的,原理就这么简单,当你抛开代码的时候,才会知道它到底有什么用。现在来看一个实际问题

Linux FQ实现

Linux内核在3.12版本引入了FQ这个Schedule,它的实现参照我的这篇文章:

Linux FQ 队列实现原理浅析https://blog.csdn.net/dog250/article/details/80025939

其实,你有没有发现,FQ的dequeue例程要做的就是把N个流所代表的N个有序链表进行合并并输出的过程,只不过这个过程是动态的!我来解释一下。

??在FQ中,每一个socket代表一个流,系统为每一个流维护了一个发送队列,该队列可以视为一个skb作为元素,按照该socket指示的pacing确定的发送时间升序排列的有序链表,系统中有N个socket,就有N条该类型链表:

注意,每一个链表的时间序都是按照skb在该流的上的相对发送时间排序的,纵然软件层面可以维护很多的队列,网卡却只有一张,网线只有一条,每个时刻只能有一个skb可以被发送,所以,所有的队列中的所有的数据包还需要基于绝对发送时间进行排序,我们按照绝对发送时间展开上图,即将多个链表映射到同一个排序维度(简略版):

然后绝对发送序列就很明显了:

这其实就是一个典型的合并N个有序链表的实例!合并后的结果完全可以有足够的信息指示数据包调度系统什么时间做什么事

??Linux FQ的实现使用了红黑树来定义锚点,即所有的链表中下一个要check的节点都被维护在一棵红黑树中,这样做的目的是高效取出离当前节点最近的锚点进行check。

合并N个有序链表的基本思想

很简单:

  1. 尽量保持在当前链表向后遍历,直到当前链表下一个节点的key大于其它链表最小锚点的key;
  2. 将当前节点的下一个节点作为一个锚点插入红黑树,从该最小锚点开始继续遍历,重复第1步。

这是一个递归实现的好场所!

合并N个有序链表的C语言实现

不得不说这其实就是Linux FQ实现的抽象版本了,因此必然也是用红黑树来维护锚点咯…

??我并没有自己从零开始实现一版红黑树,估计我也写不出来…我直接用了github上别人的实现,我使用的是这一版:

https://github.com/manuscola/rbtree

值得注意的是,这一版红黑树实现是不允许插入键值相同的元素的,为此要稍微做一点点修改,使得重复键值元素可以插入,这样代码实现起来按照统一的方式处理会更加清爽简单些。

??代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include"rbtree.h"

int a[] = {1,3,7,8,10,11,12,15,19,21,22,24,25,26};
int b[] = {0,4,5,6,9,16,17,18,27,30,31,32};
int c[] = {13,14,20,23,28,29};
int d[] = {2,33,100};

struct list {
        struct list *next;
        struct rbtree_node *node;
        int v;
};

int compare(void* key_a,void* key_b)
{
        int v_a = *(int *) (key_a);
        int v_b = *(int *) (key_b);
        if (v_a > v_b) {
                return 1;
        } else if (v_a == v_b) {
                return 0;
        }

        return -1;
}

void merge(struct list *iter, struct rbtree* tree)
{
        struct list *last = iter, *base = NULL;
        struct rbtree_node *base_node = rbtree_min(tree);
    // 取出最小的锚点并从红黑树中将其删除
        if (base_node) {
                base = (struct list *)base_node->data;
                __rbtree_remove(base_node,tree);
        }

        if (!iter) {
                return;
        }
        iter = iter->next;
    // 一直遍历,直到next大于锚点
        while (iter && (!base || iter->v < base->v)) {
                last = iter;
                iter = iter->next;
        }
    // 将next作为新的锚点加入红黑树
        if (last->next) {
                rbtree_insert(tree, &last->next->v, last->next);
        }
    // 递归重复
        last->next = base;
        merge(base, tree);
}

int main(int argc, char **argv)
{
        int i = 0;
        struct list *al = (struct list*)calloc(14, sizeof(*al));
        struct list *bl = (struct list*)calloc(12, sizeof(*bl));
        struct list *cl = (struct list*)calloc(6, sizeof(*cl));
        struct list *dl = (struct list*)calloc(3, sizeof(*dl));
        struct list *itera;
        struct list *iterb;
        struct list *iterc;
        struct list *iterd;
        struct list *base;

        struct rbtree* tree = rbtree_init(compare);

        // create 3 ordered-lists
        {
                for (i = 0; i < 14; i++) {
                        itera = &al[i];
                        itera->v = a[i];
                        itera->next = &al[i+1];
                        if (i == 0) {
                                rbtree_insert(tree, &itera->v, itera);
                        }
                }
                itera->next = NULL;

                for (i = 0; i < 12; i++) {
                        iterb = &bl[i];
                        iterb->v = b[i];
                        iterb->next = &bl[i+1];
                        if (i == 0) {
                                rbtree_insert(tree, &iterb->v, iterb);
                        }
                }
                iterb->next = NULL;

                for (i = 0; i < 6; i++) {
                        iterc = &cl[i];
                        iterc->v = c[i];
                        iterc->next = &cl[i+1];
                        if (i == 0) {
                                rbtree_insert(tree, &iterc->v, iterc);
                        }
                }
                iterc->next = NULL;

                for (i = 0; i < 3; i++) {
                        iterd = &dl[i];
                        iterd->v = d[i];
                        iterd->next = &dl[i+1];
                        if (i == 0) {
                                rbtree_insert(tree, &iterd->v, iterd);
                        }
                }
                iterd->next = NULL;
        }

        // merge n sorted-lists
        {
                struct rbtree_node *node;

                node = rbtree_min(tree);
                __rbtree_remove(node,tree);

                base = (struct list *)node->data;
                merge(base, tree);
        }

        // print the result
        {// 注意,从base开始遍历
                for (; base; base=base->next) {
                        printf("%d %p\n", base->v, base);
                }
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133


注意,之所以选择红黑树而不是最小堆,是因为红黑树的cache亲和性要比最小堆好!在红黑树的旋转过程中,最大限度地在树的平衡性以及节点的局部性之间取得了比较好的折中,这点是我看中的。此外,Linux FQ以及CFS等大量使用了红黑树,见贤思齐,所以我也就用了。

总结

本文用Linux FQ包调度机制解释了合并N个有序链表到底有什么用,在该例子中,其实实施的就是将N个相对时间序列整合到一个绝对时间序列的过程,这是一个典型的案例。

??除此之外,链表合并在批处理任务调度系统中也有非常多的应用,和Linux FQ一样,其实也是一个N到1整合的过程。链表合并并不单单是一道面试题,一道课后作业,它确确实实是一把真刀一杆真枪啊!

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

原文地址:https://www.cnblogs.com/ksiwnhiwhs/p/10390188.html

时间: 2024-08-28 20:59:01

合并N个有序链表与FQ公平调度的相关文章

链表(14)----合并两个有序链表

1.链表定义 typedef struct ListElement_t_ { void *data; struct ListElement_t_ *next; } ListElement_t; typedef struct List_t_{ int size; int capacity; ListElement_t *head; ListElement_t *tail; } List_t; 2.合并两个有序链表 ListElement_t * MergeList( ListElement_t *

23. Merge k Sorted Lists 合并K个有序链表

这道题是21题合并2个有序链表的升级版本,看了许多解题思路: A:直接暴力解锁,全部放进一个堆,然后依次吐出来: B:利用21题的算法,循环一次做两两合并,这样就得到结果:但是时间复杂度有点差: C:利用归并排序思想,进行分治:其实就是利用递归,牺牲空间,提升时间效率: 存在的问题是:看过了许多解答后发现,大家基于的给定数据类型是 List<ListNode>/ArrayList<ListNode>,然后,现在系统更新了,给的数据类型是 ListNode[] lists,所以,我现

合并两个有序链表

题目描述: 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则. (hint: 请务必使用链表.) 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第一行为两个整数n和m(0<=n<=1000, 0<=m<=1000):n代表将要输入的第一个链表的元素的个数,m代表将要输入的第二个链表的元素的个数. 下面一行包括n个数t(1<=t<=1000000):代表链表一中的元素.接下来一行包含m个元素,s(1

每天一个小算法(2)----合并两个有序链表

每天一个小算法还是有点没时间,尽量抽出时间写一写. 今天是合并有序的链表,对单链表有点忘了,尤其是指针指来指去的,有点晕,幸好基础还算好,想了想还是能想回来. 代码使用随机数函数生成一个链表,然后对链表排序,最后合并链表并打印,删除链表的函数于算法无关紧要,所以未实现^_^. 在Linux/g++下编译运行成功. 合并思路:和合并数组有些类同,比较两个节点的元素大小然后将小的摘下来尾插到链表bList中,然后指针指向下一个节点,最后直接把非空的链表合并到bList的末尾. 1 #include

经典算法——合并K个有序链表

一.题目要求: 将K个有序链表合并为一个有序链表 二.实现方法: 方法一:利用最小堆方法 用一个大小为K的最小堆(用优先队列+自定义降序实现)(优先队列就是大顶堆,队头元素最大,自定义为降序后,就变成小顶堆,队头元素最小),先把K个链表的头结点放入堆中,每次取堆顶元素,然后将堆顶元素所在链表的下一个结点加入堆中. 整体测试代码: #include <vector> #include <iostream> #include<queue> #include<set&g

[LeetCode] 23. Merge k Sorted Lists 合并k个有序链表

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 与21. Merge Two Sorted Lists的拓展,这道题要合并k个有序链表,还是可以两两合并. 类似题目: [LeetCode] 21. Merge Two Sorted Lists 合并有序链表 原文地址:https://www.cnblogs.com/lightwindy/p/8512

合并k个有序链表

题目: 合并k个有序链表,并将结果用一个有序链表输出 例如:输入: [   1->4->5,   1->3->4,   2->6 ] 输出: 1->1->2->3->4->4->5->6 思路: 假设k个链表的总元素数目为n.首先想到两两合并列表,在序列1和2合并,3和4合并,依次类推.直到合并的只剩一个链表.这种操作的时间复杂度为O(nlog(k)),空间复杂度为O(1).python代码如下: class Solution(obj

[leetcode] 21. 合并两个有序链表

21. 合并两个有序链表 两个有序链表合并为一个新的有序链表 class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode ans = new ListNode(Integer.MAX_VALUE); ListNode p = ans; while (l1 != null && l2 != null) { if (l1.val < l2.val) { p.next = l1; l

leetcode python 012 hard 合并k个有序链表

#[LeetCode] Merge k Sorted Lists 合并k个有序链表(升序) import numpy as npimport time class Node(object):    def __init__(self,n,next_node=None):        self.data=n        self.next=next_node    class linklist(object):    def __init__(self):        self.head=N