笔试算法题(54):快速排序实现之单向扫描、双向扫描(single-direction scanning, bidirectional scanning of Quick Sort)
议题:快速排序实现之一(单向遍历)
分析:
-
算法原理:主要由两部分组成,一部分是递归部分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;
样例:
1 int partition_1(int *array, int l, int r) { 2 int temp; 3 /** 4 * 利用rand函数随机获取l和r之间的一个元素作为划分值 5 * 并将其与array[r]进行交换 6 * */ 7 srand((int)time(0)); 8 int pivot=rand()%(l+(r-l)); 9 printf("%d\n",pivot); 10 temp=array[pivot]; 11 array[pivot]=array[r]; 12 array[pivot]=temp; 13 /** 14 * 单向扫描: 15 * right向右遍历array,当遇到小于pivot的元素,则与 16 * left当前指向的元素进行交换,否则直接跳过,一直到 17 * 达array的最右边 18 * right为主动遍历,left为被动遍历 19 * */ 20 int left=l, right=l; 21 while(right<r) { 22 if(array[right]<array[r]) { 23 /** 24 * 如果array[r]是array中最大的元素,则right 25 * 遇到的所有元素都要与left指向的元素进行交换 26 * 如果left与right相等,则交换是不必要的 27 * */ 28 if(left!=right) { 29 temp=array[left]; 30 array[left]=array[right]; 31 array[right]=temp; 32 } 33 left++;right++; 34 } else { 35 /** 36 * 如果array[r]是array中最小的元素,则left会一直 37 * 停留在l处 38 * */ 39 right++; 40 } 41 } 42 /** 43 * 最终需要将pivot元素换回其排序最终位置,也就是left当前的位置 44 * */ 45 temp=array[left]; 46 array[left]=array[r]; 47 array[r]=temp; 48 49 return left; 50 } 51 52 void quickSort_1(int *array, int l, int r) { 53 /** 54 * 递归终止条件 55 * */ 56 if(l>=r) return; 57 /** 58 * 利用partition方法获取划分元素 59 * */ 60 int pivot=partition_1(array, l, r); 61 /** 62 * 划分元素已经到达最终位置,所以不用参与进一步处理 63 * 分别递归处理左右部分的元素 64 * */ 65 quickSort_1(array, l, pivot-1); 66 quickSort_1(array, pivot+1, r); 67 } 68 69 int main() { 70 int array[]={2,5,8,2,1,6}; 71 quickSort_1(array,0,5); 72 for(int i=0;i<6;i++) 73 printf("%d,",array[i]); 74 return 1; 75 }
议题:快速排序实现之二(双向遍历)
分析:
-
算法原理:思想与上一种实现相同,只是使用不同的划分策略。使用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成比例;
样例:
1 int partition_2(int *array, int l, int r) { 2 int temp; 3 /** 4 * 利用rand函数随机获取l和r之间的一个元素作为划分值 5 * 并将其与array[r]进行交换 6 * */ 7 srand((int)time(0)); 8 int pivot=rand()%(l+(r-l)); 9 printf("%d\n",pivot); 10 temp=array[pivot]; 11 array[pivot]=array[r]; 12 array[pivot]=temp; 13 /** 14 * 双向扫描: 15 * left从array的左边l处开始向右处理,直到r-1 16 * right从array的右边r-1处开始向左处理,直到l 17 * left和right都是主动移动 18 * */ 19 int left=l, right=r-1; 20 while(true) { 21 /** 22 * left左边的元素为小于等于array[r]的元素 23 * 并注意array[r]为最大值的情况,left会一直 24 * 移动到r 25 * */ 26 while(array[left]<=array[r] && left<r) 27 left++; 28 /** 29 * right右边的元素为大于array[r]的元素 30 * 并注意array[r]为最小值的情况,right会一直 31 * 移动到l-1 32 * 这里仅使用大于的逻辑关系还可以避免当array 33 * 都是相同元素的情况时指针交叉的发生 34 * */ 35 while(array[right]>array[r] && right>=l) 36 right--; 37 /** 38 * 有四种序列情况: 39 * 1. 一般情况:left和right在序列中间的某个元素交叉 40 * 2. array[r]是最大值情况:left移动到r,right在r-1 41 * 3. array[r]是最小值情况:left在l,right移动到l-1 42 * 4. array所有元素为同一个值:left移动到r,right在r-1 43 * */ 44 if(left>=right) 45 break; 46 /** 47 * 交换元素 48 * */ 49 temp=array[left]; 50 array[left]=array[right]; 51 array[right]=temp; 52 53 left++;right--; 54 } 55 /** 56 * 最终将array[r]的pivot元素与array[left]进行交换 57 * 由于此时的array[right]比array[r]小,所以只能交换 58 * array[left] 59 * */ 60 temp=array[left]; 61 array[left]=array[r]; 62 array[r]=temp; 63 return left; 64 65 } 66 67 void quickSort_2(int *array, int l, int r) { 68 /** 69 * 递归终止条件 70 * */ 71 if(l>=r) return; 72 /** 73 * 利用partition方法获取划分元素 74 * */ 75 int pivot=partition_2(array, l, r); 76 /** 77 * 划分元素已经到达最终位置,所以不用参与进一步处理 78 * 分别递归处理左右部分的元素 79 * */ 80 quickSort_2(array, l, pivot-1); 81 quickSort_2(array, pivot+1, r); 82 } 83 84 int main() { 85 int array[]={2,5,8,2,1,6}; 86 quickSort_2(array,0,5); 87 for(int i=0;i<6;i++) 88 printf("%d,",array[i]); 89 return 1; 90 }