`

精通八大排序算法系列:一、快速排序算法

阅读更多
一、快速排序算法的基本特性
时间复杂度:O(n*lgn)
最坏:O(n^2)
空间复杂度:O(n*lgn)
不稳定。

快速排序是一种排序算法,对包含n个数的输入数组,平均时间为O(nlgn),最坏情况是O(n^2)。
通常是用于排序的最佳选择。因为,排序最快,也只能达到O(nlgn)。


二、快速排序算法的描述
算法导论,第7章
快速排序时基于分治模式处理的,
对一个典型子数组A[p...r]排序的分治过程为三个步骤:
1.分解:
A[p..r]被划分为俩个(可能空)的子数组A[p ..q-1]和A[q+1 ..r],使得
A[p ..q-1] <= A[q] <= A[q+1 ..r]
2.解决:通过递归调用快速排序,对子数组A[p ..q-1]和A[q+1 ..r]排序。
3.合并。



三、快速排序算法

版本一:
QUICKSORT(A, p, r)
1 if p < r
2    then q ← PARTITION(A, p, r)   //关键
3         QUICKSORT(A, p, q - 1)
4         QUICKSORT(A, q + 1, r)

数组划分
快速排序算法的关键是PARTITION过程,它对A[p..r]进行就地重排:
PARTITION(A, p, r)
1  x ← A[r]
2  i ← p - 1
3  for j ← p to r - 1
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
7  exchange A[i + 1] <-> A[r]
8  return i + 1



ok,咱们来举一个具体而完整的例子。
来对以下数组,进行快速排序,
  2   8   7   1   3   5   6   4(主元)


一、

i p/j

  2   8   7   1   3   5   6   4(主元)
j指的2<=4,于是i++,i也指到2,2和2互换,原数组不变。
j后移,直到指向1..
二、
              j(指向1)<=4,于是i++
i指向了8,所以8与1交换。
数组变成了:
       i          j
  2   1   7   8   3   5   6   4
三、j后移,指向了3,3<=4,于是i++
i这是指向了7,于是7与3交换。
数组变成了:
             i         j
  2   1   3   8   7   5   6   4
四、j继续后移,发现没有再比4小的数,所以,执行到了最后一步,
即上述PARTITION(A, p, r)代码部分的 第7行。
因此,i后移一个单位,指向了8
                 i               j
  2   1   3   8   7   5   6   4
A[i + 1] <-> A[r],即8与4交换,所以,数组最终变成了如下形式,
  2   1   3   4   7   5   6   8
ok,快速排序第一趟完成。


4把整个数组分成了俩部分,2 1 3,7 5 6 8,再递归对这俩部分分别快速排序。
i p/j
  2   1   3(主元)
  2与2互换,不变,然后又是1与1互换,还是不变,最后,3与3互换,不变,
最终,3把2 1 3,分成了俩部分,2 1,和3.
再对2 1,递归排序,最终结果成为了1 2 3.

7 5 6 8(主元),7、5、6、都比8小,所以第一趟,还是7 5 6 8,
不过,此刻8把7 5 6 8,分成了  7 5 6,和8.[7 5 6->5 7 6->5 6 7]
再对7 5 6,递归排序,最终结果变成5 6 7 8。

ok,所有过程,全部分析完成。




快速排序算法版本二

不过,这个版本不再选取(如上第一版本的)数组的最后一个元素为主元,
而是选择,数组中的第一个元素为主元。

/**************************************************/
/*  函数功能:快速排序算法                        */
/*  函数参数:结构类型table的指针变量tab          */
/*            整型变量left和right左右边界的下标   */
/*  函数返回值:空                                */
/*  文件名:quicsort.c  函数名:quicksort ()      */
/**************************************************/
void quicksort(table *tab,int left,int right)
{
  int i,j;
  if(left<right)
  {
    i=left;j=right;
    tab->r[0]=tab->r[i]; //准备以本次最左边的元素值为标准进行划分,先保存其值
    do
    {
      while(tab->r[j].key>tab->r[0].key&&i<j)
        j--;        //从右向左找第1个小于标准值的位置j
      if(i<j)                               //找到了,位置为j
      {
        tab->r[i].key=tab->r[j].key;i++;
      }           //将第j个元素置于左端并重置i
      while(tab->r[i].key<tab->r[0].key&&i<j)
        i++;      //从左向右找第1个大于标准值的位置i
      if(i<j)                       //找到了,位置为i
      {
        tab->r[j].key=tab->r[i].key;j--;
      }           //将第i个元素置于右端并重置j
    }while(i!=j);

    tab->r[i]=tab->r[0];         //将标准值放入它的最终位置,本次划分结束
    quicksort(tab,left,i-1);     //对标准值左半部递归调用本函数
    quicksort(tab,i+1,right);    //对标准值右半部递归调用本函数
  }
}

----------------

ok,咱们,还是以上述相同的数组,应用此快排算法的版本二,来演示此排序过程:
这次,以数组中的第一个元素2为主元。
  2(主)  8  7  1  3  5  6  4

请细看:
  2  8  7  1  3  5  6  4
  i->                     <-j
   (找大)               (找小)
一、j
j找第一个小于2的元素1,1赋给(覆盖重置)i所指元素2
得到:
  1  8  7     3  5  6  4
      i       j     
     
二、i
i找到第一个大于2的元素8,8赋给(覆盖重置)j所指元素(NULL<-8)
  1     7  8  3  5  6  4
      i   <-j

三、j
j继续左移,在与i碰头之前,没有找到比2小的元素,结束。
最后,主元2补上。
第一趟快排结束之后,数组变成:
  1  2  7  8  3  5  6  4

第二趟,
        7  8  3  5  6  4
        i->             <-j
         (找大)        (找小)
 
一、j
j找到4,比主元7小,4赋给7所处位置
得到:
        4  8  3  5  6  
        i->                j
二、i
i找比7大的第一个元素8,8覆盖j所指元素(NULL)
        4     3  5  6  8
            i          j
        4  6  3  5     8
            i->       j
                 i与j碰头,结束。
第三趟:
        4  6  3  5  7  8
......
以下,分析原理,一致,略过。
最后的结果,如下图所示:
  1  2  3  4  5  6  7  8
相信,经过以上内容的具体分析,你一定明了了。



完。一月五日补充。
OK,上述俩种算法,明白一种即可。
-------------------------------------------------------------



五、快速排序的最坏情况和最快情况。
最坏情况发生在划分过程产生的俩个区域分别包含n-1个元素和一个0元素的时候,
即假设算法每一次递归调用过程中都出现了,这种划分不对称。那么划分的代价为O(n),
因为对一个大小为0的数组递归调用后,返回T(0)=O(1)。
估算法的运行时间可以递归的表示为:

    T(n)=T(n-1)+T(0)+O(n)=T(n-1)+O(n).
可以证明为T(n)=O(n^2)。

因此,如果在算法的每一层递归上,划分都是最大程度不对称的,那么算法的运行时间就是O(n^2)。
亦即,快速排序算法的最坏情况并不比插入排序的更好。

此外,当数组完全排好序之后,快速排序的运行时间为O(n^2)。
而在同样情况下,插入排序的运行时间为O(n)。

//注,请注意理解这句话。我们说一个排序的时间复杂度,是仅仅针对一个元素的。
//意思是,把一个元素进行插入排序,即把它插入到有序的序列里,花的时间为n。


再来证明,最快情况下,即PARTITION可能做的最平衡的划分中,得到的每个子问题都不能大于n/2.
因为其中一个子问题的大小为|_n/2_|。另一个子问题的大小为|-n/2-|-1.
在这种情况下,快速排序的速度要快得多。为,
      T(n)<=2T(n/2)+O(n).可以证得,T(n)=O(nlgn)。

直观上,看,快速排序就是一颗递归数,其中,PARTITION总是产生9:1的划分,
总的运行时间为O(nlgn)。各结点中示出了子问题的规模。每一层的代价在右边显示。
每一层包含一个常数c。



完。
July、二零一一年一月四日。
=============================================

请各位自行,思考以下这个版本,对应于上文哪个版本?
     HOARE-PARTITION(A, p, r)
1  x ← A[p]
2  i ← p - 1
3  j ← r + 1
4  while TRUE
5      do repeat j ← j - 1
6           until A[j] ≤ x
7         repeat i ← i + 1
8           until A[i] ≥ x
9         if i < j
10            then exchange A[i] ↔ A[j]
11            else return j



   我常常思考,为什么有的人当时明明读懂明白了一个算法,
而一段时间过后,它又对此算法完全陌生而不了解了列?

   我想,究其根本,还是没有彻底明白此快速排序算法的原理,与来龙去脉...
那作何改进列,只能找发明那个算法的原作者了,从原作者身上,再多挖掘点有用的东西出来。

        July、二零一一年二月十五日更新。

=========================================
最后,再给出一个快速排序算法的简洁示例:
    Quicksort函数
void quicksort(int l, int u)
{   int i, m;
    if (l >= u) return;
    swap(l, randint(l, u));
    m = l;
    for (i = l+1; i <= u; i++)
        if (x[i] < x[l])
            swap(++m, i);
    swap(l, m);
    quicksort(l, m-1);
    quicksort(m+1, u);
}

   如果函数的调用形式是quicksort(0, n-1),那么这段代码将对一个全局数组x[n]进行排序。
函数的两个参数分别是将要进行排序的子数组的下标:l是较低的下标,而u是较高的下标。

   函数调用swap(i,j)将会交换x[i]与x[j]这两个元素。
第一次交换操作将会按照均匀分布的方式在l和u之间随机地选择一个划分元素。
分享到:
评论

相关推荐

    精通八大排序算法

    在《精通八大排序算法系列:一、快速排序算法》中,文章详细解释了快速排序的过程,包括如何选择基准元素,以及如何进行分区操作。同时,提供了伪代码和实现代码,帮助读者更好地理解和应用快速排序。 2. **冒泡...

    算法设计、分析与实现从入门到精通:C、C++和Java徐子珊_源代码

    - **排序算法**:包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等,它们是数据处理的基础,理解和掌握各种排序算法的时间复杂度和空间复杂度对优化程序性能至关重要。 - **查找算法**:如线性...

    2023年左程云带你精通数据结构与算法,详解Leetcode经典算法题

    1. 排序算法:包括冒泡排序、选择排序、插入排序、快速排序、归并排序等,它们用于将一组数据按特定顺序排列。 2. 搜索算法:如二分查找、深度优先搜索(DFS)、广度优先搜索(BFS),在数据集中寻找目标元素或遍历...

    《算法设计、分析与实现从入门到精通》源码

    1. **排序算法**:书中可能包括了冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序等经典的排序算法,这些算法在实际应用中广泛使用,是每一位程序员必备的基础知识。源码中,你可以看到每种排序算法在C、...

    算法设计、分析与实现从入门到精通(徐子珊):C、C++和Java

    例如,分治法通过将大问题分解为小问题来求解,如快速排序和归并排序;动态规划则适用于具有重叠子问题和最优子结构的优化问题,如背包问题和斐波那契数列;贪心算法在每一步选择局部最优解,如霍夫曼编码;回溯法则...

    零基础学习算法

    分治策略则是将大问题分解为小问题求解,如快速排序、归并排序等算法就是分治思想的应用。 5. **动态规划**:动态规划是一种解决最优化问题的方法,通过构建子问题的最优解来得到原问题的最优解。如背包问题、最长...

    学习算法之路

    - **快速排序(Quick Sort)**:高效的排序算法,平均时间复杂度为O(n log n),通过递归方式实现。 - **二叉树**:掌握二叉树的基本概念及其遍历方法。 #### 动态规划 - **经典动态规划问题**:如背包问题、最长...

    算法总汇C++数据结构

    1. **排序算法**:冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序等。了解它们的原理、性能分析以及适用场景对于优化代码至关重要。 2. **查找算法**:顺序查找、二分查找、哈希查找等。二分查找在有序...

    算法设计与分析作业和习题

    这个压缩包文件包含了“算法设计与分析”的作业和习题,这通常包括一系列的编程挑战、理论问题和案例分析,旨在帮助学生深入理解和应用相关概念。尽管标题中没有提及具体的教材,但根据描述,这可能是基于一本被广泛...

    坐在马桶上看算法(pdf带目录)

    1. **排序算法**:包括快速排序、归并排序、堆排序、冒泡排序、插入排序等。快速排序以其平均时间复杂度为O(n log n)而著名,归并排序则因其稳定性而受青睐。堆排序利用了堆数据结构的特性,而冒泡和插入排序则适用...

    计算机算法课件

    计算机算法是信息技术领域的核心组成部分,它是一系列精确的指令,用于解决特定问题或执行特定任务。这组课件深入探讨了计算机算法的概念、设计方法和分析技巧,旨在帮助学习者理解并掌握这一关键领域的知识。 在...

    JAVA与数据结构算法

    1. 排序算法:包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等。Java的Collections框架提供了sort方法,内部使用了高效的TimSort算法。 2. 搜索算法:线性搜索、二分搜索(适用于有序数组或搜索树...

    算法导论(程序相关)

    6. **递归与分治**:递归是解决问题的一种通用方法,而分治策略则是将大问题分解为小问题来解决,如快速排序、归并排序、Strassen矩阵乘法等都采用了分治思想。 7. **贪心算法**:贪心算法在每一步选择中都采取在...

    C程序开发范例算法篇程序源码

    算法是一系列精确的步骤或指令,用于解决特定问题或完成特定任务。在计算机科学中,算法是编程的基础,它们可以处理数据、做出决策、进行数学计算、搜索和排序等。 在C语言中,算法通常涉及到以下几个核心领域: 1...

    算法与数据结构(c#)

    1. 排序算法:C#中实现排序的典型算法有冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等。例如,Array类提供Sort()方法,可以对数组进行快速排序。 2. 搜索算法:线性搜索、二分搜索、哈希搜索等。C#的...

    经典数据结构和算法教程

    10. **排序算法**:冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序、计数排序、桶排序和基数排序等都是常见的排序算法,它们在不同场景下有不同的性能表现。 11. **查找算法**:顺序查找、二分查找、...

    数据结构与算法分析(java版)和经典算法大全

    3. **排序和搜索算法**:包括冒泡排序、选择排序、插入排序、快速排序、归并排序、二分查找、哈希查找等。这些算法在处理大量数据时起着关键作用。 4. **图论算法**:如深度优先搜索(DFS)、广度优先搜索(BFS)、...

    20道Python算法题及答案

    1. **排序算法**:Python中常见的排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序等。了解它们的基本原理和时间复杂度对于优化代码性能至关重要。 2. **二分查找**:二分查找是一种在有序数组中查找...

    最优秀的基础算法教案

    它常用于排序、查找和图论等领域,如快速排序、归并排序和大整数乘法等。 **第九课 模拟法** 模拟法是通过模仿现实世界过程或系统的行为来解决问题。在算法中,这可能意味着创建一个模型来复制特定现象,然后根据...

Global site tag (gtag.js) - Google Analytics