算法--快速排序(链表)

快速排序

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;
}

 

你可能感兴趣的:(算法--快速排序(链表))