快速排序
介绍
冒泡排序是两两比较交换,属于交换排序,每轮把一个元素冒泡到数列的一端
快速排序则是在每一轮挑选一个基准元素,并让其他比他大的元素移动到数列的一边,比他小的元素移动到数列的另一边,从而把数据拆成两部分
=》分治法 分而治之
每一轮的比较交换需要把全部元素遍历一遍,时间复杂度O(n)。
假如元素是n个,平均情况需要经历logn轮(能分成几个两部分),因此快速排序的整体平均时间复杂度是O(nlogn)
如果直接选取第一个元素为基准元素,极端情况倒序,退化成O(n^2)
故,随机选择一个元素作为基准元素,并且让基准元素和数列首元素交换位置
当然,随机选中的元素也有可能是数列的最值,同样会影响到分治的效果,所以,快速排序平均时间复杂度O(nlogn),最坏情况O(n^2)
基于双边循环交换
右指针选取<基准元素的值,碰到则停止移动,开始移动左指针
左指针选取>基准元素的值,碰到则停止移动,然后如果left < right就进行元素交换p
重叠则表示两边元素已经交换完,将重叠位置元素与基准元素交换,基准元素就将元素序列分成了两部分,再分别对两边元素序列进行重复操作
/** *基于双边循环的快速排序 */ public static void bilateralCycleSort(int[] arr, int startIndex, int endIndex) { //递归条件 if (startIndex >= endIndex) { return; } // 基准元素 int pivotIndex = partition(arr, startIndex, endIndex); //根据基准元素,分成两部分进行递归排序 bilateralCycleSort(arr, startIndex, pivotIndex - 1); bilateralCycleSort(arr, pivotIndex + 1, endIndex); } /** * 分治(双边循环法) * 实现元素交换并把基准元素放到中间位置,返回基准元素index 就是一轮交换 * * @param arr 待交换的数组 * @param startIndex 起始下标 * @param endIndex 结束下标 * @return */ private static int partition(int[] arr, int startIndex, int endIndex) { // 可以随机选取,这里取第一个位置 int pivot = arr[startIndex]; int left = startIndex; int right = endIndex; while(left != right) { // right指针比较并左移 arr[rihgt]比基准元素大就左移,否则停止 while(left < right && arr[right] > pivot) { right--; } // left指针比较并右移 arr[left]<=基准元素,就右移,否则停止 while(left < right && arr[left] <= pivot) { left++; } // 交换left和right指针指向的元素 左右指针都停止时,arr[left]>pivot,arr[right]<=pivot 交换 if(left < right) { int p = arr[left]; arr[left] = arr[right]; arr[right] = p; } } //左右指针重合 一边比基准元素大,一边比基准元素小,把基准元素换过来就是中间值 arr[startIndex] = arr[left]; arr[left] = pivot; return left; }
基于单边循环
双边循环虽然更加直观,但是代码实现相对繁琐。而单边循环更简单,只从数组一边对元素进行遍历和交换。
也是双指针,不过是单向的,一个mark指针记录小于pivot的边界,一个指针负责遍历并和mark指针指向的元素进行交换(将小于pivot的元素放到mast指针前面得区域,mast指针为小于pivot的元素边界)
过程:
- 选中基准元素,设置一个mark指针指向数列起始位置,代表小于基准元素的区域边界
- 从基准元素的下一个位置开始遍历
- 指向元素>基准元素,继续往后遍历
- 指向元素<基准元素,做两件事,第一:把指针右移1位,因为小于pivot的区域边界增大了1,第二:让最新遍历到的(小于pivot的)元素和mark指针指向的边界元素进行交换以更新边界
- 最后把pivot元素交换到mark指针所在的位置,这一轮比较交换完成
/** * 基于单边循环的快速排序 * @param arr */ public static void singleTrackCycleSort(int[] arr, int startIndex, int endIndex) { //递归结束条件 if(startIndex >= endIndex) { return; } //单边循环法 一轮 int pivotIndex = partitionForSingle(arr, startIndex, endIndex); //分治 singleTrackCycleSort(arr, startIndex,pivotIndex-1); singleTrackCycleSort(arr, pivotIndex+1, endIndex); } /** * 单边循环法 for 快速排序 * 两个指针单向遍历,快指针负责判断元素,慢指针负责与快指针交换位置 * * @param arr * @param startIndex 起始下标 * @param endIndex 结束下标 * @return 分界下边 基准元素index */ private static int partitionForSingle(int[] arr, int startIndex, int endIndex) { //取第一个元素为基准元素,也可以随机选取 int pivot = arr[startIndex]; // mark 指针 代表小于pivot的区间 int mark = startIndex; for (int i = startIndex + 1; i <= endIndex; i++) { // 如果快指针指向元素小于pivot mark右移1位与快指针元素进行交换 扩展小于区域边界 if (arr[i] < pivot) { mark++; int temp = arr[mark]; arr[mark] = arr[i]; arr[i] = temp; } } // 遍历完 把基准元素放到中间位置(与区域边界mark指向元素交换位置) 基准元素位置就此确定 arr[startIndex] = arr[mark]; arr[mark] = pivot; // 返回基准元素索引 return mark; }
作者: deity-night
出处: https://www.cnblogs.com/deity-night/
关于作者:码农
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(***@163.com)咨询.