议题:快速排序实现之一(单向遍历)

分析:

  • 算法原理:主要由两部分组成,一部分是递归部分QuickSort,它将调用partition进行划分,并取得划分元素P,然后分别对P之前的部分和P 之后的部分递归调用QuickSort;另一部分是partition,选取划分元素P(随机选取数组中的一个元素,交换到数组末尾位置),定义两个标记 值left和right,随着划分的进行,这两个标记值将数组分成三部分,left之左的部分是小于划分元素P的值,left和right之间的部分是大 于等于划分元素P的值(等于p的值没有必要进行交换),right之右的部分是未划分的部分。运行中right自左向右遍历,left指向最左的一个不小 于P的值,当right遇见小于P的元素就与left当前索引的值交换,right和left同时前进,否则right直接前进,直到数组末尾,最后将P 与left当前指向的值交换,并且返回i的值;

  • 弱势:对于已经排序的序列,运行效率相当于插入排序,因为此时的划分极其不平衡。算法受输入序列的顺序影响较大,不能保证某个元素能放到最终位置;

  • 优势:内循环仅仅是比较数组元素和固定值,这种简单性正是快速排序如此高效的原因。处理划分元素恰好为序列中最大值,或者最小值;

  • 性质:算法不稳定(尚未发现使基于数组的快速排序变得稳定的简单办法),任何相等的元素有可能在左右交换的过程中被重排成不同的序列。快速排序中关键点是划分元素的选取;

  • 时间:运行时间为N㏒N;

样例:

 int partition_1(int *array, int l, int r) {
int temp;
/**
* 利用rand函数随机获取l和r之间的一个元素作为划分值
* 并将其与array[r]进行交换
* */
srand((int)time());
int pivot=rand()%(l+(r-l));
printf("%d\n",pivot);
temp=array[pivot];
array[pivot]=array[r];
array[pivot]=temp;
/**
* 单向扫描:
* right向右遍历array,当遇到小于pivot的元素,则与
* left当前指向的元素进行交换,否则直接跳过,一直到
* 达array的最右边
* right为主动遍历,left为被动遍历
* */
int left=l, right=l;
while(right<r) {
if(array[right]<array[r]) {
/**
* 如果array[r]是array中最大的元素,则right
* 遇到的所有元素都要与left指向的元素进行交换
* 如果left与right相等,则交换是不必要的
* */
if(left!=right) {
temp=array[left];
array[left]=array[right];
array[right]=temp;
}
left++;right++;
} else {
/**
* 如果array[r]是array中最小的元素,则left会一直
* 停留在l处
* */
right++;
}
}
/**
* 最终需要将pivot元素换回其排序最终位置,也就是left当前的位置
* */
temp=array[left];
array[left]=array[r];
array[r]=temp; return left;
} void quickSort_1(int *array, int l, int r) {
/**
* 递归终止条件
* */
if(l>=r) return;
/**
* 利用partition方法获取划分元素
* */
int pivot=partition_1(array, l, r);
/**
* 划分元素已经到达最终位置,所以不用参与进一步处理
* 分别递归处理左右部分的元素
* */
quickSort_1(array, l, pivot-);
quickSort_1(array, pivot+, r);
} int main() {
int array[]={,,,,,};
quickSort_1(array,,);
for(int i=;i<;i++)
printf("%d,",array[i]);
return ;
}

议题:快速排序实现之二(双向遍历)

分析:

  • 算法原理:思想与上一种实现相同,只是使用不同的划分策略。使用left和right将数组划分成三部分,left之前的部分为小于等于划分元素P的 值,right之后的部分为大于划分元素P的值,left和right之间的部分是没有进行划分的区域。外循环使得left自左向右遍历,同时right 自右向左遍历,在这个过程中当left遇见大于P的值则停止,等待right遇见小于等于P的值又停止之后,交换他们的值,这个循环在left和 right相遇或者交叉之后停止。最后交换a[r]和left的值,并返回left;

  • 弱势:当序列已经就绪,并每次划分元素选取为最左边或者最右边的值,一次递归划分仅去除一个元素,既是划分元素本身,程序将递归调用N次,而算法也演变为插入排序,比较次数达到(N+1)N/2次;

  • 优势:快速排序满足分治递推式:CN=2CN/2 + N,最终化解为CN=NlgN;但此种情况需要划分点在序列的中间位置;

  • 性质:算法不稳定,任何相等的元素有可能在交换的过程中被重排成不同的序列。快速排序中关键点是划分元素的选取。这个实现方式与上一个实现最大的差距就在于对等于划分元素值的处理上,还有就是本实现的遍历方式是两边向中间,而并不是只有一边到另外一边;

  • 时间:当每次划分大约都将序列二分划分,运行时间为N㏒N,平均比较次数为2NlgN;最坏情况下,快速排序使用(N+1)N/2次比较;系统堆栈耗用的大小与logN成比例,退化的情况下雨N成比例;

样例:

 int partition_2(int *array, int l, int r) {
int temp;
/**
* 利用rand函数随机获取l和r之间的一个元素作为划分值
* 并将其与array[r]进行交换
* */
srand((int)time());
int pivot=rand()%(l+(r-l));
printf("%d\n",pivot);
temp=array[pivot];
array[pivot]=array[r];
array[pivot]=temp;
/**
* 双向扫描:
* left从array的左边l处开始向右处理,直到r-1
* right从array的右边r-1处开始向左处理,直到l
* left和right都是主动移动
* */
int left=l, right=r-;
while(true) {
/**
* left左边的元素为小于等于array[r]的元素
* 并注意array[r]为最大值的情况,left会一直
* 移动到r
* */
while(array[left]<=array[r] && left<r)
left++;
/**
* right右边的元素为大于array[r]的元素
* 并注意array[r]为最小值的情况,right会一直
* 移动到l-1
* 这里仅使用大于的逻辑关系还可以避免当array
* 都是相同元素的情况时指针交叉的发生
* */
while(array[right]>array[r] && right>=l)
right--;
/**
* 有四种序列情况:
* 1. 一般情况:left和right在序列中间的某个元素交叉
* 2. array[r]是最大值情况:left移动到r,right在r-1
* 3. array[r]是最小值情况:left在l,right移动到l-1
* 4. array所有元素为同一个值:left移动到r,right在r-1
* */
if(left>=right)
break;
/**
* 交换元素
* */
temp=array[left];
array[left]=array[right];
array[right]=temp; left++;right--;
}
/**
* 最终将array[r]的pivot元素与array[left]进行交换
* 由于此时的array[right]比array[r]小,所以只能交换
* array[left]
* */
temp=array[left];
array[left]=array[r];
array[r]=temp;
return left; } void quickSort_2(int *array, int l, int r) {
/**
* 递归终止条件
* */
if(l>=r) return;
/**
* 利用partition方法获取划分元素
* */
int pivot=partition_2(array, l, r);
/**
* 划分元素已经到达最终位置,所以不用参与进一步处理
* 分别递归处理左右部分的元素
* */
quickSort_2(array, l, pivot-);
quickSort_2(array, pivot+, r);
} int main() {
int array[]={,,,,,};
quickSort_2(array,,);
for(int i=;i<;i++)
printf("%d,",array[i]);
return ;
}

笔试算法题(54):快速排序实现之单向扫描、双向扫描(single-direction scanning, bidirectional scanning of Quick Sort)的更多相关文章

  1. 前端如何应对笔试算法题?(用node编程)

    用nodeJs写算法题 咱们前端使用算法的地方不多,但是为了校招笔试,不得不针对算法题去练习呀! 好不容易下定决心 攻克算法题.发现js并不能像c语言一样自建输入输出流.只能回去学习c语言了吗?其实不 ...

  2. 小小c#算法题 - 6 - 快速排序 (QuickSort)

    快速排序是排序算法中效率比较高的一种,也是面试常被问到的问题. 快速排序(Quick Sort)是对冒泡排序的一种改进.它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字 ...

  3. 笔试算法题(03):最小第K个数 & 判定BST后序序列

    出题:输入N个整数,要求输出其中最小的K个数: 分析: 快速排序和最小堆都可以解决最小(大)K个数的问题(时间复杂度为O(NlogN)):另外可以建立大小为K的最大堆,将前K个数不断插入最大堆,对于之 ...

  4. 笔试算法题(17):奇偶数分置数组前后段 & 反序访问链表

    出题:输入一个数组,要求通过交换操作将奇数索引的元素调整到数组前半部分,偶数索引的元素调整到数组后半部分: 分析: 当然如果没有额外要求的话很容易实现,最好使用In-Place的实现策略:考虑插入排序 ...

  5. 笔试算法题(56):快速排序实现之非递归实现,最小k值选择(non-recursive version, Minimal Kth Selection of Quick Sort)

    议题:快速排序实现之五(非递归实现,短序列优先处理,减少递归栈大小) 分析: 算法原理:此算法实现适用于系统栈空间不足够快速排序递归调用的需求,从而使用非递归实现快速排序算法:使用显示下推栈存储快速排 ...

  6. 笔试算法题(27):判断单向链表是否有环并找出环入口节点 & 判断两棵二元树是否相等

    出题:判断一个单向链表是否有环,如果有环则找到环入口节点: 分析: 第一个问题:使用快慢指针(fast指针一次走两步,slow指针一次走一步,并判断是否到达NULL,如果fast==slow成立,则说 ...

  7. 笔试算法题(19):判断两条单向链表的公共节点 & 字符集删除函数

    出题:给定两个单向链表的头结点,判断其是否有公共节点并确定第一个公共节点的索引: 分析: 由于是单向链表,所以每个节点有且仅有一个后续节点,所以只可能是Y型交叉(每条链表中的某个节点同时指向一个公共节 ...

  8. php笔试算法题:顺时针打印矩阵坐标-蛇形算法

    这几天参加面试,本来笔试比较简单,但是在面试的时候,技术面试官说让我现场写一个算法,顺时针打印矩阵的坐标,如图所示 顺序为,0,1,2,3,4,9,14,19,24,23,22,21,20,15,10 ...

  9. 笔试算法题(57):基于堆的优先级队列实现和性能分析(Priority Queue based on Heap)

    议题:基于堆的优先级队列(最大堆实现) 分析: 堆有序(Heap-Ordered):每个节点的键值大于等于该节点的所有孩子节点中的键值(如果有的话),而堆数据结构的所有节点都按照完全有序二叉树 排.当 ...

随机推荐

  1. bzoj 3875: [Ahoi2014&Jsoi2014]骑士游戏【dp+spfa】

    设f[i]为杀死i的最小代价,显然\( f[i]=min(k[i],s[i]+\sum f[to]) \) 但是这个东西有后效性,所以我们使用spfa来做,具体就是每更新一个f[i],就把能被它更新的 ...

  2. bzoj 3398: [Usaco2009 Feb]Bullcow 牡牛和牝牛【dp】

    设f[i]为i为牡牛的方案数,f[0]=1,s为f的前缀和,f[i]=s[max(i-k-1,0)] #include<iostream> #include<cstdio> u ...

  3. bzoj 4568: [Scoi2016]幸运数字【树链剖分+线段树+线性基】

    一眼做法,好处是好想好写坏处是常数大,容易被卡(bzoj loj 洛谷开O2 能AC,不开有90分-- 大概就是树剖之后维护线段树,在线段树的每个节点上上维护一个线性基,暴力\( 60^2 \)的合并 ...

  4. Veeam对于新病毒防御的建议

    Veeam对于新病毒防御的建议 前言 勒索软件GandCrab 上周末,在我们大家晒娃和欢度六一的时候.勒索软件分发平台 GandCrab 宣布将在一个月内关闭其RaaS(勒索软件即服务)业务平台.据 ...

  5. 《windows核心编程系列 》六谈谈线程调度、优先级和关联性

    线程调度.优先级和关联性 每个线程都有一个CONTEXT结构,保存在线程内核对象中.大约每隔20ms windows就会查看所有当前存在的线程内核对象.并在可调度的线程内核对象中选择一个,将其保存在C ...

  6. C++中的四种强制类型转换符详解

    阅读目录 C++即支持C风格的类型转换,又有自己风格的类型转换.C风格的转换格式很简单,但是有不少缺点的: 转换太过随意,可以在任意类型之间转换.你可以把一个指向const对象的指针转换成指向非con ...

  7. STL之map基础知识

    Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候 ...

  8. 洛谷 P2061 [USACO07OPEN]城市的地平线City Horizon

    简化版的矩形面积并,不用线段树,不用离散化,代码意外的简单 扫描线,这里的基本思路就是把要求的图形竖着切几刀分成许多矩形,求面积并.(切法就是每出现一条与y轴平行的线段都切一刀) 对于每一个切出来的矩 ...

  9. 题解报告:hdu 1212 Big Number(大数取模+同余定理)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1212 Problem Description As we know, Big Number is al ...

  10. [BZOJ1083][SCOI2005]繁忙的都市 最小生成树

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1083 由kruskal算法原理可知,我们对一张无向图做普通的最小生成树,连上的最后一条边就 ...