算法--快速排序(链表)
快速排序
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; }