网站开发 报价,早厦门构网站建设,沃通 wordpress,找装修工人的平台或app快速排序#xff08;Quick Sort#xff09; 基本思想 快速排序由 C. A. R. Hoare 于 1962 年提出#xff0c;是一种基于 分治法 的高效交换排序算法。 任取待排序序列中的一个元素作为 基准值#xff08;pivot#xff09;。将序列划分为两个子区间#xff1a; 左子区间…快速排序Quick Sort基本思想快速排序由C. A. R. Hoare于 1962 年提出是一种基于分治法的高效交换排序算法。任取待排序序列中的一个元素作为基准值pivot。将序列划分为两个子区间左子区间所有元素 基准值右子区间所有元素 基准值对左右子区间递归执行相同操作直到子区间长度 ≤ 1。该递归过程与二叉树的前序遍历高度相似先处理当前节点分区再递归处理左子树和右子树。递归实现Hoare 分区法 左基准版voidSwap(int*a,int*b){inttemp*a;*a*b;*btemp;}//该排序由于固定选取边界值左值作为key所以当数组排序为顺序或逆序时每次遍历交换后key的位置只会在最左侧或最右侧无法将数组分为两部分递归递归深度就从本来理想情况每次key都在中间的logN变成了N容易发生栈溢出。每次处理的数据总量都是N对于理想情况所有数据在每一层都只处理一次大致为N*logN但是对于最坏情况每层数据都要被重复处理情况是n-1n-2...1次合计为n²所以总效率就从N*logN变成了N²。voidQuickSort(int*arr,intleft,intright){if(leftright)return;//其实递归条件就是序列被划分成若只干有一个元素的序列。intbeginleft;//一定不能取left1否则没有检查自身的话原数组本身是升序时会造成交换错误intendright;intkeyleft;while(beginend){while(arr[key]arr[end]beginend)//一定要先移动右指针再移动做左指针arr[key]必须小于等于其要交换的值否则不能保证其交换后顺序完全正确其交换的值如果大于arr[key]那么该值左边的值也可能大于arr[key]不符合快排的要求;先移动右指针再移动左指针可以保证key最后一定停留在右指针end最后停留的位置也就是该位置的值一定小于arr[key],而先走左指针最终停留的位置一定是大于pivot基准的位置。{end--;}while(arr[key]arr[begin]beginend){begin;}Swap(arr[end],arr[begin]);}Swap(arr[begin],arr[key]);//如果取left1原数组升序时此处会造成交换错误因为会将较大值放在最左边但是下次递归时又不检查最左侧keybegin;QuickSort(arr,left,key-1);QuickSort(arr,key1,right);}该排序实现是固定选取下标begin位置作为key使用双指针从数组头和数组尾双向遍历数组将大与基准值的元素交换到右边小于基准值的元素交换到左边最终将key元素交换到两指针相遇的位置将该位置的原元素移动至下标begin。接着进入递归将原数组分为左右两部分借助递归对左右两部分实行相同操作。当“递”至某一部分只剩一个元素时即排序完成可以返回。但是这种以固定位置作为基准值的写法在遇到完全顺序或逆序时就会成为最坏情况因为基准值无法将数组分为两部分会导致递归深度大幅度增加。快速排序统一优化无论是何种思想实现的快速排序针对基准值选取如果该值是待排序部分的最大值或最小值就会大大增加递归深度或者循环层数所以我们需要通过对key进行筛选来进行优化。优化方法主要有两种其一是随机选取基准值第二种方法更为稳定三数取中从待排序数组中取左中右三处坐标取其中中值元素作为基准值可以大大减少选取到最值的概率。intGetMiddle(int*arr,intleft,intright){intmid(leftright)/2;if(arr[left]arr[right]){if(arr[mid]arr[right]){returnright;}elseif(arr[mid]arr[left]){returnmid;}elsereturnleft;}else//arr[left]arr[right]{if(arr[mid]arr[left]){returnleft;}elseif(arr[mid]arr[right]){returnright;}elsereturnmid;}}同时结合插入排序,当元素过少时使用插入排序可以大幅提高运行效率减少递归深度。快速排序前后指针法Prev-Cur Two Pointers核心思想使用两个指针prev和cur从左向右遍历prev维护小于基准值pivot的区域右边界cur负责扫描整个数组当arr[cur] pivot时扩展小值区并在必要时交换。算法步骤选取arr[left]作为pivot初始化prev left,cur left 1遍历cur从left1到right若arr[cur] pivotprev若prev ! cur交换arr[prev]与arr[cur]循环结束后交换arr[left]与arr[prev]使 pivot 归位递归排序[left, prev-1]和[prev1, right]。代码实现voidQuickSortPrevCur(int*arr,intleft,intright)//前后指针实现快速排序{intprevleft;intcurprev1;intkeyleft;if(leftright)return;//其实递归条件就是序列被划分成若只干有一个元素的序列。while(curright){if(arr[cur]arr[key]prev!cur)//与运算是短路求值如果左值为false右值不会执行所以实际执行结果就是当cur遇见大值时prev就不会移动停留在小值的位置上下一个位置为大值,直到cur遇见小值且prev的下一个位置不是curprev会进入那个大值并将它与cur所在的小值交换。根据此规则循环结束后prev 指向的是小于 pivot 的最后一个元素的位置即分区点。pivot 应与 arr[prev] 交换使其归位。{Swap(arr[cur],arr[prev]);}cur;}Swap(arr[prev],arr[key]);//没有在内层交换时prev永远处于小值当cur移动出数组时prev一定在最后一个最小值处也就是大值和小值的分割点。QuickSortPrevCur(arr,left,prev-1);//prev才是此时key所在位置的真正下标QuickSortPrevCur(arr,prev1,right);}非递归快速排序基于栈按自定义压栈顺序实现核心思想使用栈手动模拟递归过程。本实现采用如下约定压栈顺序对于区间[left, right]先压right再压left出栈顺序由于栈是 LIFO后进先出出栈时先取出left再取出right只要压栈与出栈的顺序保持一致逻辑即自洽排序结果正确。代码实现//单次排序intPartSort(int*arr,intleft,intright){intprevleft;intcurprev1;intkeyleft;// 基准值下标while(curright){// 利用短路求值仅当 arr[cur] arr[key] 时才执行 previf(arr[cur]arr[key]prev!cur){Swap(arr[cur],arr[prev]);}cur;}Swap(arr[prev],arr[key]);// 基准值归位returnprev;// 返回基准值最终位置}voidStackQuickSort(int*arr,intleft,intright)//借助栈实现非递归快速排序{Stack s1;STInit(s1);STPush(s1,right);STPush(s1,left);while(!STEmpty(s1))//栈为空时代表所有元素已经排序完成{leftSTTop(s1);STPop(s1);rightSTTop(s1);STPop(s1);intkeyPartSort(arr,left,right);//本质上还是前序遍历if(key1right)//先压右部分数组这样在出栈时相当于递归调用中的最后调用因为排序时要先把所有被分割数组的左边都排序完才能排序右边。{STPush(s1,right);//与最开始保持一至先压要排序列的右边界再压左边界STPush(s1,key1);}if(key-1left)//后压左部分数组这样在出栈时先对左部分数组进行排序下次循环压栈时也是先压右后压左先出左后出右由于左子区间后入栈、先出栈算法会深度优先地处理左侧数组直到叶子节点单元素然后回溯处理右侧子树数组。”//。并且右侧数组出栈时也是从最底层两个元素为一组的序列开始出栈排序。{STPush(s1,key-1);STPush(s1,left);}}}