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

快速排序

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;
}
时间: 2024-10-12 16:48:36

算法--快速排序(链表)的相关文章

图解堆算法、链表、栈与队列(Mark)

原文地址: 图解堆算法.链表.栈与队列(多图预警) 堆(heap),是一类特殊的数据结构的统称.它通常被看作一棵树的数组对象.在队列中,调度程序反复提取队列中的第一个作业并运行,因为实际情况中某些时间较短的任务却可能需要等待很长时间才能开始执行,或者某些不短小.但很重要的作业,同样应当拥有优先权.而堆就是为了解决此类问题而设计的数据结构.--

算法学习 - 链表的游标实现~ C++

链表的游标实现,就是用另外一种方法来访问链表,模拟游标. 在我学习的理解中,就是创建一个节点数组,模拟内存的排列,然后从其中来申请内存和释放内存.但是实际的内存没有被释放~ 下面直接贴代码了: // // main.cpp // CursorList // // Created by Alps on 14-7-27. // Copyright (c) 2014年 chen. All rights reserved. // #include <iostream> #define CursorSp

Java数据结构和算法之链表

三.链表 链结点 在链表中,每个数据项都被包含在'点"中,一个点是某个类的对象,这个类可认叫做LINK.因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点.每个LINK对象中都包含一个对下一个点引用的字段(通常叫做next)但是本身的对象中有一个字段指向对第一个链结点的引用. 单链表 用一组地址任意的存储单元存放线性表中的数据元素. 以元素(数据元素的映象)  + 指针(指示后继元素存储位置)  = 结点(表示数据元素 或 数据元素的映象) 以"结点的序列&q

编程算法 - 快速排序算法 代码(C)

快速排序算法 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 经典的快速排序算法, 作为一个编程者, 任何时候都要完整的手写. 代码: /* * main.cpp * * Created on: 2014.6.12 * Author: Spike */ /*eclipse cdt, gcc 4.8.1*/ #include <stdio.h> #include <stdlib.h> int RandomInRange(int min,

Java算法快速排序

快速排序的原理:每次将序列以一个值为界限分成两组,在将两个序列分别以一个界限分成两组这样一直分下去. int[] a = {11,222,44,63,84,11,24,53,123,25,98,76,34}; 第一步:以34将数组a分成两组  11, 25, 24, 11              34,  63, 44, 53, 123, 222, 98, 76, 84 第二步:以11将11, 25, 24, 11分为两组  11, 11,     24, 25.以84将34,  63, 44

重点算法--快速排序

#include<iostream> using namespace std; ////写出快速排序,归并排序与堆排序 int adjustarray(int a[],int left,int right) { int x = a[left]; while(left < right) { while(a[right] > x && left < right) right--; ///注意这个小于的范围 if(left < right ) {a[left]

排序算法----快速排序java

快速排序是对冒泡排序的一种改进,平均时间复杂度是O(nlogn) import java.util.Arrays; import java.util.Scanner; public class test02{ public static void main(String[] args) { int n = 1; while (n != 0){ Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); int s[] = ne

[数据结构和算法]快速排序笔记

特点:1.是冒泡的改进2.是一个递归的过程3.不稳定 4.时间复杂度:O(nlogn) 设要排序的数组是A[0]...A[n-1],首先取数组的第一个数作为关键数据,然后将所有比它小的数都放到它的前面,比他大的都放到他的后面,这个过程被称为一趟快速排序 算法步骤:1.设置两个变量i,j,排序开始i = 0, j = N-1;2.以第一个数组元素作为关键字,Key = A[0];3.从J开始向前搜索,即由后开始向前搜索j--, 找到第一个小于key的值A[j],将A[j]赋值给A[i]4.从I开始

经典排序算法 - 快速排序Quick sort

经典排序算法 - 快速排序Quick sort 原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 举个例子 如无序数组[6 2 4 1 5 9] a),先把第一项[6]取出来, 用[6]依次与其余项进行比较, 如果比[6]小就放[6]前边,2 4 1 5都比[6]小,所以全部放到[6]前边 如果比[6]大就放[6]后边,9比[6]大,放到[6

5.算法-快速排序

//算法-快速排序var cc=cc||consolefunction exchange(A,p1,p2){    if(p1!=p2){        var temp=A[p1]        A[p1]=A[p2]        A[p2]=temp    }}function quicksort(A,p,r){    if(p<r){        var q=partition(A,p,r)        quicksort(A,p,q-1)        quicksort(A,q+