数据结构第十周笔记——排序(下1)(慕课浙大版本--XiaoYu)

简介: 快速排序

10.1 快速排序

10.1.1 算法概述

快速排序的算法跟归并函数的算法差不多,策略都是分而治之的策略

  1. 分而治之
  1. 主元(pivot)=>中枢枢纽的意思

网络异常,图片无法展示
|

伪码描述

voidQuicksort( ElementTypeA[],intN )

{

   if( N<2 ) return;

   pivot=从A[]中选一个主元;//主元的选择决定了快速排序到底快不快

   将S= { A[] \pivot }将除了主元以外的元素分成两个独立子集://怎么分

       A1= {a属于S|a<=pivot }和A2= {a属于S|a>=pivot };//一部分由小于等于pivot元素来组成的,另一部分由大于等于pivot元素组成

   A[] =Quicksort(A1,N1) U {pivot} UQuicksort(A2,N2);

}

什么是快速排序算法的最好情况?每次正好中分

网络异常,图片无法展示
|

10.1.2 选主元

网络异常,图片无法展示
|

  1. 随机取pivot?rand()函数不便宜=相当花费时间
  2. 取头、中、尾的中位数
  1. 例如8、12、3的中位数就是8
  2. 测试一下pivot()不同的取法和堆运行速度有多大影响?

ElementTypeMedian3( ElementTypeA[],intLeft,intRight )

{

   intCenter= ( Left+Right ) /2;

   if( A[ Left ] >A[ Center ] )//三步的比较跟交换(保证从左到右的大小顺序)。左边比中间大

       Swap( &A[ Left ],&A[ Center ] );

   if( A[ Left ] >A[ Right ] )//左边比右边大

       Swap( &A[ Left ],&A[ Right ] );

   if( A[ Center ] >A[ Right ] )//中间比右边大

       Swap( &A[ Center ],&A[ Right ] );

   //这样三步交换下来,左边一定是最小的那个

   Swap( &A[ Center ],&A[ Right-1 ] );//将pivot藏到右边(为了之后方便,先将Center放到现在需要考虑的子列的最右边),然后就只需要考虑A[Left + 1]....A[ Right - 2]

   returnA[ Right-1];//返回pivot

}

字符串交换(swap)swap操作实现交换两个容器内所有元素的功能。要交换的容器的类型必须匹配: 必须是相同类型的容器,而且所存储的元素类型也必须相同。调用了swap函数后,右操作数原来存储的元素被存放在左操作数中,反之亦然。

10.1.3 子集划分

网络异常,图片无法展示
|

  1. i和j不是C语言的指针意义,而是指向存放位置的意思
  2. 6是主元,被藏到了最右边的位置
  3. 将主元和i与j进行比较(比较完指针i与j当前的位置后向里靠),发现大小符号相反的之后i与j当前的数值对调,如下图
  1. 网络异常,图片无法展示
    |

  2. 网络异常,图片无法展示
    |

  3. 网络异常,图片无法展示
    |

  4. 网络异常,图片无法展示
    |

  5. 网络异常,图片无法展示
    |

以上就是快速排序为什么快的原因:

  1. 每次他选定主元之后,这个主元在子集划分完成以后,他就被一次性的放在他最终的正确位置上
  2. 不像插入排序,每次做了元素交换以后,这个元素所待的位置只是临时的,当下一张新的扑克牌插进来的时候,这些牌所有的都要往后错,一张牌的插入就可能要牵扯到多次的移动

上述快速排序需要考虑的问题:

  1. 如果有元素正好等于pivot怎么办?
  1. 停下来做交换?当所有元素都相等的时候会做很多很多次完全没有用处的交换。但做了很多次无用的交换后最终我们的主元会被换到中间的位置(好处:每次递归的时候这个原始的序列都被基本上等分成两个等长的序列)复杂度NlogN
  2. 不理他,继续移动指针?避免了很多次无用的交换,但每次子集划分的时候,主元都是被放在某一个端点的(就复杂度又变成了N²了)
  3. 结论:还是选择停下来交换划算点

小规模数据的处理

  1. 快速排序的问题
  1. 用递归....
  2. 对小规模的数据(例如N不到100)可能还不如插入排序快
  1. 解决方案
  1. 当递归的数据规模充分小,则停止递归,直接调用简单排序(例如插入排序)
  2. 在程序中定义一个Cutoof的阈值(决定什么时候不递归)

10.1.4 算法实现

voidQuicksort( ElementTypeA[],intLeft,intRight )

{

   if( Cutoff<=Right-Left ){

   Pivot=Median3( A,Left,Right );//pivot是主元的意思,在这里返回的不仅仅只是一个主元的值

   //这里的Left参数是最小值,Right参数是最大值。真正的主元被藏在了Right-1的地方

   i=Left; j=Right-1;

   for(;;){

       while(A[ ++i ] <Pivot ){}

       while(A[ --j ] <Pivot ){}

       if( i<j)

           Swap( &A[i],&A[j] );//i < j则证明中间还有其他元素,这时候就可以调换

       //如果i > j则这个子集划分应该结束了

       elsebreak;

   }

   Swap( &A[i],&A[Right-1]);//把藏在right-1这个位置的主元换到A[i]的位置上面去

   Quicksort(A,Left,i-1);//递归的左半部分

   Quicksort(A,i+1,Right);//递归的右半部分  

   }

   else

       Insertion_Sort(A+Left,Right-Left+1);//Right-Left+1:待排序列的总个数;A+Left:开始的地方

}

快速排序的标准接口应该怎么写?

voidQuick_Sort(ElementTypeA[], intN)

{

  /* 这里写什么?如下*/

   Quicksort( A, 0, N-1 );

}

目录
相关文章
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
41 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
存储 搜索推荐 算法
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
33 1
|
2月前
|
搜索推荐 索引
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(二)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2月前
|
搜索推荐 C++
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2月前
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
05_用一个栈实现另一个栈的排序
05_用一个栈实现另一个栈的排序
|
2月前
|
人工智能 搜索推荐 算法
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(三)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
218 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
29天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
55 5