算法基础:快排优化:为什么快排都会TLE
在算法训练中,快排应该是基础中的基础了,直接使用前面介绍的快排,无论是单向循环还是双向循环方式,在特定的数据序列下,都有可能出现TLE(Time Limit Exceeded)超时,这篇文章对原因和基准值的优化进行验证和总结。
目录
- 快排的效率的情况
- 基准值选择优化
- 双向循环 vs 单向循环
- 方式1: 折半方式
- 方式2: 随机方式
- 折半方式 vs 随机方式
- 总结
- 附录
- 附录1:缺省单向循环方式
- 附录2: 缺省的双向循环方式
- 附录3: 基准值折半方式的双向循环方式
- 附录4: 基准值随机方式的双向循环方式
快排的效率的情况
快排本身不是一种稳定的算法,在前面的文章中也提到过,缺省的实现中一般直接选取最左或者最右进行,而这种方式之下,如果待排序的序列是已经有序的情况之下,快排的效率直接会恶化到N平方的时间复杂度,相当于排序核心的基准值分割算法几乎没有能起到作用,所以这种情况下出现超时也就可以理解了。这种情况之下,最简单的方式就是首先进行如下基准值选择的优化,这种情况就可以直接解决本身已经有序的大量带排序的算法的排序问题。
基准值选择优化
基准值最为简单的优化方式是可以不改变之前的逻辑,在进行排序之前首先按照某种方式选取一个,放至基准值选取的位置,与之进行交换,相当于打个补丁,之前的代码几乎不必修改。
双向循环 vs 单向循环
使用前文给出的实现示例的情况下,单向循环较为简略,在大量数据的情况下,前者的替换效率整体一般会高一些,但有时也不一定,对于不同的测试用例,单跑一次的效果可能也会不同,可以多试,测测人品。
- 缺省的单向循环
参看附录1的示例代码,执行时对于有序数列的效率如下所示,可以看到对于10万个有序数列,时间消耗已经达到10s级别的程度了
100
Time Used: 0.000014s
10000
Time Used: 0.105468s
100000
Time Used: 10.499863s
- 缺省的双向循环
参看附录2的示例代码,对于10万有序数列,不做任何操作也已经到10s级别了
100
Time Used: 0.000019s
10000
Time Used: 0.130322s
100000
Time Used: 12.846300s
方式1: 折半方式
选取待排序列的左右的中间值作为基准点,比如示例代码可能如下
int key = (left+right)/2;int tmp=arr[left]; arr[left]=arr[key]; arr[key]=tmp;
使用附录3示例代码,运行结果如下所示,可以看到提升2个数量级到千万级别的有序序列,运行时间也仍然在毫秒级别0.665毫秒:
100
Time Used: 0.000005s
10000
Time Used: 0.000442s
100000
Time Used: 0.005531s
1000000
Time Used: 0.059314s
10000000
Time Used: 0.665215s
方式2: 随机方式
选取待排序列的左右的中间值作为基准点,比如示例代码可能如下
int key = rand()%(right - left) + left;int tmp=arr[left]; arr[left]=arr[key]; arr[key]=tmp;
使用附录4的示例代码,执行结果如下所示
100
Time Used: 0.000015s
10000
Time Used: 0.000635s
100000
Time Used: 0.007567s
1000000
Time Used: 0.082657s
10000000
Time Used: 1.008981s
折半方式 vs 随机方式
一般来说,平均而言随机方式比折半方式效果要好一点。当然针对本文示例中已经完全有序的情况,显然折半方式更加接近于标准答案,但是不同的测试用例,平均效果而言,应该来说随机方式还是有其优势的,不过对于应试这种无聊的思路而言,果断换效果比较好的方式就可以了。
总结
使用双向循环+基准值的优化+平台整体优化选项的使用,一般来说可以解决TLE的问题
附录
附录1:缺省单向循环方式
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>int partition(int* arr, int start, int end) {if (start >= end ) return end;int pivot = arr[start];int index = start;for (int i=start+1; i<=end; i++) {if (arr[i] <= pivot) {index++;int tmp=arr[i]; arr[i]=arr[index]; arr[index]=tmp;}}arr[start]=arr[index];arr[index]=pivot;return index;
}void quick_sort(int* arr, int start, int end){if (start >= end) return;int pivot_index = partition(arr,start,end);quick_sort(arr,start,pivot_index-1);quick_sort(arr,pivot_index+1,end);
}int main() {int n = 0;while (scanf("%d",&n) != EOF) {int* array = (int *)malloc(sizeof(int)*n);memset(array,0,sizeof(int)*n);for (int i=0; i<n; i++) array[i]=i+1;time_t start_time, end_time;start_time=clock();quick_sort(array,0,n-1);end_time=clock();printf("Time Used: %fs\n",(double)(end_time-start_time)/CLOCKS_PER_SEC);free(array); array= NULL;}
}
附录2: 缺省的双向循环方式
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>int partition(int* arr, int start, int end) {int left=start, right=end;if (left >= right ) return right;int pivot = arr[left];while (left != right) {while (left < right && arr[right] > pivot) right--;while (left < right && arr[left] <= pivot) left++;if (left < right) {int tmp = arr[left]; arr[left]=arr[right]; arr[right]=tmp;}}arr[start]=arr[left];arr[left]=pivot;return right;
}void quick_sort(int* arr, int start, int end){if (start >= end) return;int pivot_index = partition(arr,start,end);quick_sort(arr,start,pivot_index-1);quick_sort(arr,pivot_index+1,end);
}int main() {int n = 0;while (scanf("%d",&n) != EOF) {int* array = (int *)malloc(sizeof(int)*n);memset(array,0,sizeof(int)*n);for (int i=0; i<n; i++) array[i]=i+1;time_t start_time, end_time;start_time=clock();quick_sort(array,0,n-1);end_time=clock();printf("Time Used: %fs\n",(double)(end_time-start_time)/CLOCKS_PER_SEC);free(array); array= NULL;}
}
附录3: 基准值折半方式的双向循环方式
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>int partition(int* arr, int start, int end) {int left=start, right=end;if (left >= right ) return right;int key = (left+right)/2;int tmp=arr[left]; arr[left]=arr[key]; arr[key]=tmp;int pivot = arr[left];while (left != right) {while (left < right && arr[right] > pivot) right--;while (left < right && arr[left] <= pivot) left++;if (left < right) {int tmp = arr[left]; arr[left]=arr[right]; arr[right]=tmp;}}arr[start]=arr[left];arr[left]=pivot;return right;
}void quick_sort(int* arr, int start, int end){if (start >= end) return;int pivot_index = partition(arr,start,end);quick_sort(arr,start,pivot_index-1);quick_sort(arr,pivot_index+1,end);
}int main() {int n = 0;while (scanf("%d",&n) != EOF) {int* array = (int *)malloc(sizeof(int)*n);memset(array,0,sizeof(int)*n);for (int i=0; i<n; i++) array[i]=i+1;time_t start_time, end_time;start_time=clock();quick_sort(array,0,n-1);end_time=clock();printf("Time Used: %fs\n",(double)(end_time-start_time)/CLOCKS_PER_SEC);free(array); array= NULL;}
}
附录4: 基准值随机方式的双向循环方式
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>int partition(int* arr, int start, int end) {int left=start, right=end;if (left >= right ) return right;int key = rand()%(right - left) + left;int tmp=arr[left]; arr[left]=arr[key]; arr[key]=tmp;int pivot = arr[left];while (left != right) {while (left < right && arr[right] > pivot) right--;while (left < right && arr[left] <= pivot) left++;if (left < right) {int tmp = arr[left]; arr[left]=arr[right]; arr[right]=tmp;}}arr[start]=arr[left];arr[left]=pivot;return right;
}void quick_sort(int* arr, int start, int end){if (start >= end) return;int pivot_index = partition(arr,start,end);quick_sort(arr,start,pivot_index-1);quick_sort(arr,pivot_index+1,end);
}int main() {int n = 0;while (scanf("%d",&n) != EOF) {int* array = (int *)malloc(sizeof(int)*n);memset(array,0,sizeof(int)*n);for (int i=0; i<n; i++) array[i]=i+1;time_t start_time, end_time;start_time=clock();quick_sort(array,0,n-1);end_time=clock();printf("Time Used: %fs\n",(double)(end_time-start_time)/CLOCKS_PER_SEC);free(array); array= NULL;}
}
算法基础:快排优化:为什么快排都会TLE相关推荐
- 【人工智能算法】算法基础之离散优化[旅行商问题 背包问题]
本文重点: 离散VS连续 旅行商问题 背包问题 旅行商问题 旅行商问题(Traveling Salesman Problem, TSP)是一个难以用传统迭代算法求解的NP困难问题,因此经常用模拟退火算 ...
- L-BFGS算法基础_matlab
L-BFGS算法基础_matlab L-BFGS 优化基础 考虑利用递归展开的方式进行求解 第一个循环: 第二个循环: L-BFGS 算法_matlab 初始化和迭代准备 迭代主循环 L-BFGS 双 ...
- 八大排序算法之快速排序(上篇)(未经优化的快排)
目录 一.关于快速排序的总体算法思想 1.冒泡排序(交换排序) (以排升序为例) 2.快速排序的总体思想简介(以排升序为例) 二.快速排序单趟排序的算法接口设计(以排升序为例) 单趟排序实现的方法一: ...
- 数据结构之排序【归并排序和快排的顶级优化和快排的三种原理的实现及分析】 内含动态演示图
文章目录 引言: 1.归并排序(MergeSort) 2.快速排序的优化(顶级优化) 3.快速排序的三种思路的代码实现及分析 4.归并排序和快排第3原理的测试 引言: 刚刚去回顾了一下递归实现的几个小 ...
- 快排三种基本解法以及两种快排优化
/* 快速排序 基本思想 选定每次排序的基准数据 在剩下的位置将小于基准值的数据放在基准值得左边,大于基准值的数据放到基准值的右边 一次划分之后 如果此基准值的左右两边仍存在大于两个数 ...
- c++ 快排优化(三数取中法)
快排优化(三数取中法) 文章目录 快排优化(三数取中法) 前言 一.三数取中法 二.递归思想 三.程序实现过程(代码) 1.取基准数(三数取中) 2.快速排序(递归) 总结 前言 作为刚刚入门c和c+ ...
- 归并排序、快速排序、二路快排、三路快排python实现
源代码: https://github.com/lzneu/Algrithm_python O(n*logn)级别的排序: |-归并排序 分成log(n)个层级 每个层级进行O(n)排序 ...
- 数学之美番外篇:快排为什么那样快
目录 0. 前言 1. 猜数字 2. 称球 3. 排序 3.1 为什么堆排比快排慢 3.2 为什么快排其实也不是那么快 3.3 基排又为什么那么快呢 4. 信息论!信息论? 5. 小结 0. 前言 知 ...
- 分治法:快速排序,3种划分方式,随机化快排,快排快,还是归并排序快?
快速排序不同于之前了解的分治,他是通过一系列操作划分得到子问题,不同之前的划分子问题很简单,划分子问题的过程也是解决问题的过程 我们通常划分子问题尽量保持均衡,而快排缺无法保持均衡 快排第一种划分子问 ...
- 【LeetCode笔记】215. 数组中的第K个最大元素(Java、快排、堆排、并发快排)
文章目录 题目描述 思路 & 代码 快排 基于 Fork / Join 的并发快排 针对 topK 的快排优化 堆排 基本堆排 结合题目的堆排 二刷 题目描述 大名鼎鼎的TOP K,主要考察排 ...
最新文章
- 提高 GPU 训练利用率的Tricks
- 信息安全系统设计基础第三周学习总结
- 【ASP.NET开发】ASP.NET(MVC)三层架构知识的学习总结
- 预留在PP模块里面的作用
- struts2中文件上传
- python案例实操_用案例实操学习Python ,培养编程逻辑思维
- cli版的php.ini路径,CLI 执行 PHP 时自订 php.ini 设定档
- Maven : JsonMappingException: Incompatible Jackson version: 2.9.5
- P2661 信息传递 二分图的最小环
- 抓包教程_最简单的Wireshark抓包TCP/UDP新手5步教程(宽带故障运维必备)
- Android实现保存图片和视频到系统相册
- 【DNN,OFDM检测】基于DNN深度学习网络的OFDM信号检测算法的matlab仿真
- 用辩证数学解答“缸中之脑”
- 【转】死链-百度百科
- 文件Md5计算(C语言版)
- 1162: 6003 星期几?
- 关于计算机知识的内容500字,介绍电脑的作文500字
- “3点钟无眠区块链”的前世今生
- matlab求状态反馈矩阵
- 计算机网络期末复习(已完结)
热门文章
- 美团滑块(1-18,js逆向)
- EFCore实现数据库水平分表的方法
- poi word转html 根号,二次根式计算题-20210402033540.docx-原创力文档
- c语言中数组arr的表示含义
- Windows留后门--教程(一)——Windows系统隐藏账户
- android开发实例之minitwitter登录界面+代码,登录界面_记住密码
- 动态活体检测 | 算法分析
- pytorch入门强化教程——数据加载和处理
- NKOJ 1361 帮帮吉米(Help Jimmy)
- Python 抓取淘宝联盟优惠券