快慢指针的时间复杂度探究
2016-01-29 16:36
309 查看
对于“访问单向链表倒数第k个结点”,比较成熟的算法是快慢指针,即定义两个指针,一个指针先走k个结点,之后两个指针再一起走,从而拿到倒数第k个结点。
快慢指针算法如下:
LinkNode* findNode(LinkNode* head){//找到单向链表倒数第2个结点,快慢指针
LinkNode* temp = head->next->next;
LinkNode* result = head;
while (temp != NULL){
result = result->next;
temp = temp->next;
}
return result;
}
普通算法,需要循环两次
LinkNode* findNode2(LinkNode* head){//找到单向链表倒数第2个结点,循环两次
int len = 0;
LinkNode* result = head;
while (result != NULL){
result = result->next;
len++;
}
result = head;
while (len-- > 2){
result = result->next;
}
return result;
}
刚开始接触快慢指针的时候,觉得这种算法很巧妙,四两拨千斤,只需要循环一次即可解决问题。但后来仔细分析之后发现了点问题,就是快慢指针中虽然只有一个循环,但循环体中却有两个访问语句,这样算起来的话,它的整体访问次数也并没有减少啊,以上面问题为例,两种算法最终都是访问了2n次结点啊。这样想来,快慢指针是不是没有提高运行效率呢?
实验是检验一切问题的标尺
我们只需要简单的做个实验即可:
从上面可以看到,在大数量级下,快慢指针确实效率更高,并且接近一倍。
这说明快慢指针还是依旧的牛逼,那刚开始的怀疑应该怎么解释呢?
这是就需要仔细分析for循环执行过程中的耗时在哪里,有两个一方,一是for循环中的每次循环切换,二是循环体中的语句耗时。
我们可以大胆猜测一下上面的例子中主要的耗时是在每次的循环切换,循环体本身耗时不多,所以两个for循环要比一个for循环慢。
为了验证这个猜测,我们可以再做一个实验:
//对两个数组中的元素进行赋值操作
void change(int* array,int* array2,int n){
int i = 0;
for (int i = 0; i < n; i++){
array[i] = rand();//因为使用了随机树,所以相对耗时
array2[i] = rand();
}
}
void change2(int* array, int* array2, int n){
for (int i = 0; i < n; i++){
array[i] = rand();
}
for (int i = 0; i < n; i++){
array2[i] = rand();
}
}
我们来看一下测试结果
这时我们再看一下发现,把两条语句一起写到一个循环体中已经体现不出来优势了。大致上佐证了刚才的猜测,循环体本身是耗时操作,这样循环本身的切换耗时就不是关键了。
但是对于普通的不是很耗时的操作,把两条写在一块还是能提高效率的,比如快慢指针访问倒数第k个结点。
快慢指针算法如下:
LinkNode* findNode(LinkNode* head){//找到单向链表倒数第2个结点,快慢指针
LinkNode* temp = head->next->next;
LinkNode* result = head;
while (temp != NULL){
result = result->next;
temp = temp->next;
}
return result;
}
普通算法,需要循环两次
LinkNode* findNode2(LinkNode* head){//找到单向链表倒数第2个结点,循环两次
int len = 0;
LinkNode* result = head;
while (result != NULL){
result = result->next;
len++;
}
result = head;
while (len-- > 2){
result = result->next;
}
return result;
}
刚开始接触快慢指针的时候,觉得这种算法很巧妙,四两拨千斤,只需要循环一次即可解决问题。但后来仔细分析之后发现了点问题,就是快慢指针中虽然只有一个循环,但循环体中却有两个访问语句,这样算起来的话,它的整体访问次数也并没有减少啊,以上面问题为例,两种算法最终都是访问了2n次结点啊。这样想来,快慢指针是不是没有提高运行效率呢?
实验是检验一切问题的标尺
我们只需要简单的做个实验即可:
从上面可以看到,在大数量级下,快慢指针确实效率更高,并且接近一倍。
这说明快慢指针还是依旧的牛逼,那刚开始的怀疑应该怎么解释呢?
这是就需要仔细分析for循环执行过程中的耗时在哪里,有两个一方,一是for循环中的每次循环切换,二是循环体中的语句耗时。
我们可以大胆猜测一下上面的例子中主要的耗时是在每次的循环切换,循环体本身耗时不多,所以两个for循环要比一个for循环慢。
为了验证这个猜测,我们可以再做一个实验:
//对两个数组中的元素进行赋值操作
void change(int* array,int* array2,int n){
int i = 0;
for (int i = 0; i < n; i++){
array[i] = rand();//因为使用了随机树,所以相对耗时
array2[i] = rand();
}
}
void change2(int* array, int* array2, int n){
for (int i = 0; i < n; i++){
array[i] = rand();
}
for (int i = 0; i < n; i++){
array2[i] = rand();
}
}
我们来看一下测试结果
这时我们再看一下发现,把两条语句一起写到一个循环体中已经体现不出来优势了。大致上佐证了刚才的猜测,循环体本身是耗时操作,这样循环本身的切换耗时就不是关键了。
但是对于普通的不是很耗时的操作,把两条写在一块还是能提高效率的,比如快慢指针访问倒数第k个结点。
相关文章推荐
- Java线程:新特征-有返回值的线程
- 好的 iOS 代码习惯
- 前端程序员应该知道的 15 个 jQuery 小技巧
- Handler.post执行时所在线程分析
- web.xml加载顺序详解
- 如何使用lua完成复杂AI
- HTML--基础知识
- 一行代码解决IE兼容 本地版
- hdu5483Nux Walpurgis
- IOS 开发: NSBundle
- jquery.datatables中文使用说明
- 由App的启动说起
- mybatis中 排序(将指定的排在后/前面)
- iOS库--.a与.framework
- Mysql 一次性备份导出/导入恢复所有数据库
- lua执行字节码的过程介绍
- Android Handler WeakReference 处理
- 移动app崩溃的测试用例测试
- 利用dns解析来实现网站的负载均衡
- lighttpd 的安装和使用