zoukankan      html  css  js  c++  java
  • 算法--快速排序(链表)

    快速排序

    http://m.blog.csdn.net/blog/u013071074/36867589

    快速排序是由C. A. R. Hoare所发展的一种排序算法。其基本思想是基本思想是,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

    快速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。
    步骤为:
    1、从数列中挑出一个元素,称为 "基准"(pivot),
    2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
    3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
    最差时间复杂度:O(n^2)
    最优时间复杂度:O(n log n)
    平均时间复杂度:O(n log n)
    最差空间复杂度:根据实现的方式不同而不同

    算法理解

    此排序算法, 思想是分治和筛选:

    选择一个元素作为分割点, 将链表中分割成两个部分, 第一部分(比分割点小的部分), 第二部分(比分割点大的部分)

    所以整个链表分为三个部分:  分割点、 第一部分、第二部分。

    然后对于第一部分 和 第二部分, 分别执行此筛选方法(递归执行),  直到递归到 链表中只有一个元素。

    形象点比喻, 有一筐柿子, 选取一个中等个头的柿子, 以此为标准, 将这堆柿子筛进去两个筐, 第一个筐中柿子的个头都比标准小, 第二个筐中柿子的个头都比标准大,

    然后对分出来的两个筐, 分别执行相同的筛选, 到最后会形成, 一个筐中装一个柿子的情况, 这种情况也是递归的终止条件, 不需要继续筛选了。

    算法的精髓是, 按照标准筛选:

    1、 确定链表头一个元素作为标准值, 从表尾开始向前找到第一个一个小于此标准值的元素A, 与表头元素交换, (这样能够保证, A元素之后的元素都是大于标准值的)。

    2、 第一步执行完毕后, 则标准元素的位置 变为 原来元素A的位置(即, 表尾第一个小于标准值的元素位置), 则从第二个元素 到 标准元素的位置 之前, 可能还是有 大于标准值的元素, 我们需要找出来, 让此元素换到标准元素的右边, 即执行:  从第二个元素开始找到第一个大于标准值的元素B, 换到标准元素位置(这样能够保证, B元素之前的元素都是小于标准值的)。

    3、 对于 中间部分未经筛选过的元素链表 (此时, 其第一个元素为 标准元素), 同样执行 步骤 1 和 2。可以循环执行、或者递归执行, 终止条件都是 未筛选部分链表长度为 1, 即 只有一个元素。

    总体思路是, 将右边的比标准值小的 元素 换到  左边,  将左边的比标准值大的元素 换到  右边。 目的是, 将链表分成两个部分, 左边小(比标准), 右边大(比标准)。

    事实上可以这么理解, 从第一个元素开始向右边找到第一个比标准值大的元素, 然后从最后一个元素开始向左边找到第一个小于标准值的元素, 然后交换两者,

      然后对未筛选的区间, 递归执行此帅选, 直到此区间长度为1。

    C代码实现

    完整代码如下URL

    https://github.com/fanqingsong/code-snippet/blob/master/C/QuickSort/quicksort.c

    下面给出核心code

    移植list api后, 基于list实现的链表排序核心代码:

    /***********************************************************************
                                   QuickSort  function
    ************************************************************************/
    
    // list quick sort core function
    void _List_QuickSort(PT_LIST_LINKNODE ptLinkFirst, PT_LIST_LINKNODE ptLinkLast)
    {
        // center node that will partion list into two part, 
        // left nodes all are less than it, 
        // right nodes all are greater than it
        PT_LIST_LINKNODE ptLinkPivot = NULL; 
        char* szPivot = NULL;
    
        // the left and right cursor used in one comparing procedure
        PT_LIST_LINKNODE ptLinkLeft = NULL;
        PT_LIST_LINKNODE ptLinkRight = NULL;
    
        PT_NODE ptNodePivot = NULL;
        PT_NODE ptNodeLeft = NULL;
        PT_NODE ptNodeRight = NULL;
    
        // recurse to the ceasing condtion, 
        // one node is the list, list is ordered.
        if ( ptLinkFirst == ptLinkLast )
        {
            return ;
        }
    
        // cursor initialization
        ptLinkLeft = ptLinkFirst;
        ptLinkRight = ptLinkLast;
    
        // select first node as the pivot ( center pointer )
        ptLinkPivot = ptLinkLeft;
        ptNodePivot = list_entry(ptLinkPivot, T_NODE, tLinkNode);
        szPivot = ptNodePivot->str;
    
        while( ptLinkLeft!= ptLinkRight )
        {
            // search first node less than pivot from right end
            while( ptLinkLeft!= ptLinkRight )
            {
                ptNodeRight = list_entry(ptLinkRight, T_NODE, tLinkNode);
    
                // find it
                if ( strcmp(ptNodeRight->str, szPivot) < 0 )
                {
                    // save the string to pivot pointer node
                    // note, this time pivot pointer is ptLinkLeft
                    ptNodeLeft = list_entry(ptLinkLeft, T_NODE, tLinkNode);
                    ptNodeLeft->str = ptNodeRight->str;
    
                    // now pivot node is less than szPivot,  so ptLinkLeft node is not pivot any more, should set ptLinkLeft to its next node
                    ptLinkLeft = ptLinkLeft->next;
    
                    // right searching over
                    break;
                }
                // not found yet
                else
                {
                    //set right node to its previous node, for next comparing
                    ptLinkRight = ptLinkRight->prev;
                }
            }
    
            // search first node greater than pivot from left end
            while( ptLinkLeft!= ptLinkRight )
            {
                ptNodeLeft = list_entry(ptLinkLeft, T_NODE, tLinkNode);
    
                // find it
                if ( strcmp(ptNodeLeft->str, szPivot) > 0 )
                {
                    // save the string to pivot pointer node, 
                    // note after first while, ptNodePivot is ptLinkRight
                    ptNodeRight= list_entry(ptLinkRight, T_NODE, tLinkNode);
                    ptNodeRight->str = ptNodeLeft->str;
    
                    // now pivot node is greater than szPivot,  so ptLinkRight node is not pivot any more, should set ptLinkRight to its previous node
                    ptLinkRight = ptLinkRight->prev;
    
                    // left searching over
                    break;
                }
                // not found yet
                else
                {
                    //set right node to its next node, for next comparing
                    ptLinkLeft = ptLinkLeft->next;
                }
            }
        }
    
        //now center pointer node(pivot) is ptLinkLeft, which is equal to ptLinkRight
        //save pivot node string
        ptLinkPivot = ptLinkLeft;
        ptNodePivot = list_entry(ptLinkPivot, T_NODE, tLinkNode);
        ptNodePivot->str = szPivot;
    
        //now recursively, quick sort pivot left list
        if ( ptLinkPivot != ptLinkFirst )
        {
            _List_QuickSort( ptLinkFirst, ptLinkPivot->prev );
        }
    
        //now recursively,  quick sort pivot right list
        if ( ptLinkPivot != ptLinkLast )
        {
            _List_QuickSort( ptLinkPivot->next, ptLinkLast );
        }
    }
    
    void QuickSortList(PT_LIST_LINKNODE ptListHead)
    {
        PT_LIST_LINKNODE ptLinkFirst = NULL;
        PT_LIST_LINKNODE ptLinkLast = NULL;
    
        if ( IsListEmpty(ptListHead) )
        {
            return ;
        }
    
        ptLinkFirst = ptListHead->next;
        ptLinkLast = ptListHead->prev;
        
        _List_QuickSort(ptLinkFirst, ptLinkLast);
    }

    一处宏函数定义与指针关系的经验总结

    调试过程发现 递归出现死循环, 最终查证为定义的链表宏(list_add_head 在表头插入元素)有问题:

    定义的  在链表任意两个相邻位置的节点 间插入 一个新节点 宏如下:

    if 分支处理 链表为空的情况, prev next 都为链表头

    else 分支处理, 链表不为空的情况。

    // insert new link node between previous link node and next link node
    // note: if clause is must item to add node to empty list, otherwise list_add_tail error
    #define _list_add(newLink, prevLink, nextLink) do{
                            (newLink)->next = (nextLink);
                            (newLink)->prev = (prevLink);
                            if ( (prevLink) == (nextLink) ){
                                (prevLink)->next = (newLink);
                                (prevLink)->prev = (newLink);
                            }else{
                                (prevLink)->next = (newLink);
                                (nextLink)->prev = (newLink);
                            }
                            } while(0)

    有问题的 list_add_head 宏如下:

    // add new list node to  list head
    #define list_add_head(ptListHead, ptListNewLink) do {
                            _list_add((ptListNewLink), (ptListHead), (ptListHead)->next);
                            } while(0)

    链表为空没有问题, 当链表不为空, 则这种写法, 被 _list_add替换后, 其中else分支别替换为

    则可以看出 (ptListHead)->next 本来是指代  表头的下一个节点, 但是被替换后由于其上的依据, 导致了 (ptListHead)->next 值被修改为 新插入的 节点,

    则产生语义错误!!

                            }else{
                                (ptListHead)->next = (newLink);
                                ((ptListHead)->next)->prev = (newLink);
                            }

    修正方法, 在宏行数的入参中 不要放置 指针表达式, 取而代之的为指针变量, 即让入参指针直接指代目标节点, 避免依赖其他节点的指针值:

    这样被替换后, 就没有你问题了, 因为入参就是直接代表 对应的节点。

    // add new list node to  list head
    #define list_add_head(ptListHead, ptListNewLink) do {
                            PT_LIST_LINKNODE ptPrevLink = (ptListHead);
                            PT_LIST_LINKNODE ptNextLink = (ptListHead)->next;
                            _list_add((ptListNewLink), (ptPrevLink), (ptNextLink));
                            } while(0)

    查看linux内核实现的链表添加为函数形式, 其采用内联函数, 可以避免宏函数的参数(指针表达式)被替换后语义混乱的情况, 即避免宏的副作用:

    http://blog.csdn.net/sunweizhong1024/article/details/7586383

    /**
     * list_add_tail - add a new entry
     * @new: new entry to be added
     * @head: list head to add it before
     *
     * Insert a new entry before the specified head.
     * This is useful for implementing queues.
     */
    static __inline__ void list_add_tail(struct list_head *_new, struct list_head *head)
    {
        __list_add(_new, head->prev, head);
    }
    
    /*
     * Insert a new entry between two known consecutive entries.
     *
     * This is only for internal list manipulation where we know
     * the prev/next entries already!
     */
    static __inline__ void __list_add(struct list_head * _new,
                      struct list_head * prev,
                      struct list_head * next)
    {
        next->prev = _new;
        _new->next = next;
        _new->prev = prev;
        prev->next = _new;
    }
  • 相关阅读:
    牛客小白月赛2 D 虚虚实实 【欧拉图】【连通图】
    牛客小白月赛2 A 数字方阵【随机】【找规律】
    牛客小白月赛1 J おみやげをまらいました 【MAP】
    牛客小白月赛1 I あなたの蛙が帰っています 【卡特兰数】
    欧拉函数
    乘法逆元
    扩展欧几里得
    快速乘法
    JPEG图像压缩出现资源不足问题的解决
    如何避免关键程序被意外关闭?
  • 原文地址:https://www.cnblogs.com/lightsong/p/4865385.html
Copyright © 2011-2022 走看看