Two pointer方法
I.何为Two pointer
用两个哨兵指向两个序列,通过利用序列本身的性质减少遍历次数,来更快得解决一些归并问题
基本问题
- 给定一个正整数递增序列和一个正整数M,求序列中两个不同位置的a,b使得a+b==M,打印a,b。
- 合并两个递增正整数序列。
解决方案
若直接二重循环遍历,显然存在一些不必要的遍历步骤。
题目一
递增数组a。对于某个a[i]来说,当找到一个a[j]使得a[i]+a[j]==M时,a[i]+b[j+1]>M显然的,根本无需再往下枚举;同理a[i+1]+b[j]>M也是显然的,无需枚举。只有在a[i+1]+b[j-1]的情况下才有可能再次出现和为M的情况。
所以可用i,j两个哨兵分别指向a,b的首尾,当a[i]+b[j]<M时,i++使得a[i]+a[j]增大;当a[i]+a[j]>M时,j--使得a[i]+a[j]减小;当a[i]+a[j]==M时,i++,j--,在[i+1,j-1]区间内再次判断大小关系。
#include<stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int i=0,j=4;
while(i<=j)
{
if(a[i]+a[j]==6)
{
printf("%d %d\n", a[i],a[j]);
i++;j--;
}
else if(a[i] + a[j] > 6)
j--;
else i++;
}
return 0;
}
由于i只加不减,j只减不加,最终会达到i==j,此时a已枚举完毕。
题目二
当a[i]>b[j-1]&&a[i]<=b[j]时,a[i]插在b[j]之前,而此时a[i+1]必然插在b[j-1]之后,二重循环中a[i+1]和b[0]-b[j-1]之间的比较是不必要的。
所以可创建一个空数组c存储合并后的序列,用i,j,index,分别指向a,b,c的首元素。
当a[i]<b[j]时,将a[i]插入c中,i后移,index后移,即‘c[index++] = a[i++]’
否则,将b[i]插入c中,j后移,index后移,即‘c[index++] = b[j++]’
最后,a或b中若有剩余元素,则全部添加到c中。
#include<stdio.h>
void merge(int a[], int b[], int m, int n)
{
int i = 0, j = 0, index = 0;
int c[100] = {0};
while(i < m && j < n)
{
if(a[i] <= b[j])
c[index++] = a[i++];
else c[index++] = b[j++];
}
while(i < m) c[index++] = a[i++];
while(j < n) c[index++] = b[j++];
i = 0;
while(c[i] != 0) printf("%d ", c[i++]);
}
int main()
{
int a[5] = {1,2,3,4,5}, b[5] = {3,4,6,7,8};
int i=0,j=4;
merge(a,b,5,5);
return 0;
}
II.归并排序
基本思想:有序子序列归并后构成的序列仍有序
时间复杂度
O(nlogn)
递归实现
算法分析(升序为例)
将序列A升序排序即构造递增序列
1.递归表达式:要保证A递增,只需保证A的子序列B,C递增,再将其合并
2.结束条件:当B,C只有一个元素时,可视为递增
3.回溯操作:合并递增的子序列
代码实现
#include<stdio.h>
const int maxn = 100;
/*将数组A[]的[L1,R1],[L2,R2]合并为有序序列*/
/*此时L2 = R1+1*/
void merge(int A[], int L1, int R1, int L2, int R2)
{
int i = L1, j = L2, index = 0;
int tmp[maxn];
while(i <= R1 && j <= R2)
{
if(A[i] <= A[j])
tmp[index++] = A[i++];
else tmp[index++] = A[j++];
}
while(i <= R1) tmp[index++] = A[i++];
while(j <= R2) tmp[index++] = A[j++];
for(i = 0; i < index; i++)
A[L1+i] = tmp[i];
}
void mergeSort(int a[], int left, int right)
{
if(left<right)
{
int mid = (left+right)/2;
mergeSort(a,left,mid);
mergeSort(a,mid+1,right);
merge(a,left,mid,mid+1,right);
}
}
int main()
{
int a[10] = {1,23,54,65,22,3,23,61,15,34};
int i;
mergeSort(a,0,9);
for(i=0; i < 10; i++)
printf("%d ",a[i]);
return 0;
}
迭代实现
《算法笔记》P141
III.快速排序
基本思想:直接确定每个元素在排序后的序列中的位置
算法分析
1.将区间最左侧元素a[left]置位。即保证:a[index]左侧元素都<=a[index],a[index]右侧所有元素值都>a[index]。
2.以a[index]为界限划分序列a,重复上述操作。
将区间最左侧元素a[left]置位:Partition()
调整最左侧元素a[left]到相应位置,使得序列中<=a[left]的元素都在a左侧,>a的元素都在右侧
基本方法:two pointer
关注空穴。
在区间[left, right]中:
将tmp = a[left],此时a[left]为空穴。
比较a[right],若a[right]>tmp,说明a[right]的相对位置正确,不必变动
反复左移right(right--),直到找到a[right]<=tmp,此时该元素a[right]的相对位置不正确,它应该在tmp左边
令a[left] = a[right]来填补a[left]空穴,此时a[right]移动到了正确的相对位置上,且此时a[right]成为空穴
比较a[left],若a[left]<=tmp,说明a[left]的相对位置正确,不必变动
反复右移left(left++),直到找到a[left]>tmp,此时该元素a[left]的相对位置不正确,它应该在tmp右边
令a[right] = a[left]来填补a[right]空穴,此时a[left]移动到了正确的相对位置上,且此时a[left]再次成为空穴
重复2-7操作,直到left == right,此时a[left](或成为a[right])左边的元素都<=tmp,右边的元素都>tmp,即a[left]就是tmp在最终序列中的位置。令 a[left] = tmp。
注:2-4与5-7相对应且不能交换顺序。
代码实现
#include<stdio.h>
int Portition(int a[], int left, int right)
{
int tmp = a[left];
while(left < right)
{
while(left < right && a[right] > tmp) right--; /*反复左移right*/
a[left] = a[right];
while(left < right && a[left ] <= tmp) left ++; /*反复右移left*/
a[right] = a[left];
}
a[left] = tmp; /*把tmp放置在letf\right相遇处*/
return left;
}
快速排序的递归实现
代码
/*输入元素下标*/
void quickSort(int a[], int left, int right)
{
/*区间长度大于1*/
if(left < right)
{
/*将[Left, Right]按a[Left]一分为二*/
int pos = Portition(a, left, right);
quickSort(a, left, pos - 1);
quickSort(a, pos + 1, right);
}
}
现存问题与改进方案
- 问题:当序列基本有序时等价于选择排序,时间复杂度:O(n^2).
- 原因:A[left]为将序列较为均匀地划分
- 改进:随机选取A[left]
randomPortition()
在Portition()开头加两行
int P = (int)(1.0 * rand()/RAND_MAX(right-left)+left);
swap(a[left], a[P]);
总结:随机快排
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int randomPortition(int a[], int left, int right)
{
int P = (int)(1.0 * rand()/RAND_MAX*(right-left)+left);
swap(a[left], a[P]);
int tmp = a[left];
while(left < right)
{
while(left < right && a[right] > tmp) right--;
/*反复左移right*/
a[left] = a[right];
while(left < right && a[left ] <= tmp) left ++;
/*反复右移left*/
a[right] = a[left];
}
a[left] = tmp; /*把tmp放置在letf\right相遇处*/
return left;
}
void quickSort(int a[], int left, int right)
{
/*区间长度大于1*/
if(left < right)
{
/*将[Left, Right]按a[Left]一分为二*/
int pos = randomPortition(a, left, right);
quickSort(a, left, pos - 1);
quickSort(a, pos + 1, right);
}
}
测试程序
int main()
{
int a[10] = {15,23,15,65,22,3,23,61,1,34};
int i;
printf("%d\n", randomPortition(a,0,9));
for(i = 0; i < 10; i++)
printf("%d ",a[i]);
printf("\n");
quickSort(a, 0, 9);
for(i = 0; i < 10; i++)
printf("%d ",a[i]);
return 0;
}
Two pointer方法的更多相关文章
- 三种方法教你HTML实现点击某一个元素之外触发事件
HTML实现点击某一个元素之外触发事件 大致编写的HTML界面渲染后是这个样子的,我们现在想要实现的需求是点击Button所在的div不会触发事件,而在点击Button所在的div之外的区域时会触发事 ...
- 2020你还不会Java8新特性?方法引用详解及Stream 流介绍和操作方式详解(三)
方法引用详解 方法引用: method reference 方法引用实际上是Lambda表达式的一种语法糖 我们可以将方法引用看作是一个「函数指针」,function pointer 方法引用共分为4 ...
- JAVA8学习——深入浅出方法引用(学习过程)
方法引用:method reference 先简单的看一下哪里用到了方法引用: public class MethodReferenceTest { public static void main(S ...
- TMethod
onclick是TNotifyEvent类型; type TNotifyEvent = procedure(Sender: TObject) of object; 就是说他是一个过 ...
- c++数组的引用
引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样.引用的声明方法:类型标识符 &引用名=目标变量名: 引用最大的好处就是提高函数效率以及节省空间; 关键问题一.传递引用 ...
- Leetcode题库——5.最长回文子串
@author: ZZQ @software: PyCharm @file: longestPalindrome.py @time: 2018/9/18 20:06 要求:给定一个字符串 s,找到 s ...
- [GeekBand] C++ 基础知识一 ——通过引用传递数组
本文参考 : C++ Primer (第四版) 7.2.4及 16.1.5 相关章节 GeekBand 侯捷老师,学习笔记 开发环境采用:VS2013版本 关键问题一.传递引用与传指针.传值的区别? ...
- jQuery实现全选与全部选
为了便于用户理解,直接粘贴下面的代码即可 <!DOCTYPE html> <html lang="en"> <head> <meta ch ...
- 2020你还不会Java8新特性?
Java8(1)新特性介绍及Lambda表达式 前言: 跟大娃一块看,把原来的电脑拿出来放中间看视频用 --- 以后会有的课程 难度 深入Java 8 难度1 并发与netty 难度3 JVM 难度4 ...
随机推荐
- 大数据学习day19-----spark02-------0 零碎知识点(分区,分区和分区器的区别) 1. RDD的使用(RDD的概念,特点,创建rdd的方式以及常见rdd的算子) 2.Spark中的一些重要概念
0. 零碎概念 (1) 这个有点疑惑,有可能是错误的. (2) 此处就算地址写错了也不会报错,因为此操作只是读取数据的操作(元数据),表示从此地址读取数据但并没有进行读取数据的操作 (3)分区(有时间 ...
- java中的collection小结
Collection 来源于Java.util包,是非常实用常用的数据结构!!!!!字面意思就是容器.具体的继承实现关系如下图,先整体有个印象,再依次介绍各个部分的方法,注意事项,以及应用场景. ...
- JConsole可视化工具
JConsole基本介绍 Jconsole (Java Monitoring and Management Console),一种基于JMX的可视化监视.管理工具.JConsole 基本包括以下基本功 ...
- Ribbon详解
转自Ribbon详解 简介 Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.通过Spring Cloud的封装,可以让 ...
- 【Linux】【Shell】【Basic】数组
1. 数组: 变量:存储单个元素的内存空间: 数组:存储多个元素的连续的内存空间: 数组名:整个数组只有一个名字: 数组 ...
- 【C/C++】最长不下降子序列/动态规划
#include <iostream> #include <vector> using namespace std; int main() { //输入 int tmp; ve ...
- 【C#】【MySQL】C#连接MySQL数据库(二)解析
C# MySQL 实现简单登录验证 后端代码解析 Visual Studio中使用MySQL的环境配置 下文所有到的代码(前端后端) 请查阅这篇博文 C#连接MySQL数据库(一)代码 获取前端数据 ...
- [BUUCTF]PWN——pwnable_orw
pwnable_orw 附件 步骤: 例行检查,32位程序,开启了canary 本地运行一下程序,看看大概的情况,提示我们输入shellcode 32位ida载入,检索字符串,没看见什么可以直接利用的 ...
- box-shadow(盒子阴影)
box-shadow 属性可以设置一个或多个下拉阴影的框 可以在同一个元素上设置多个阴影效果,并用逗号将他们分隔开.该属性可设置的值包括阴影的X轴偏移量.Y轴偏移量.模糊半径.扩散半径和颜色. 语法: ...
- 月薪过2w的IT程序员都是怎么做到的?
先说结论:要月入过2万,不能仅仅靠技术,更要找个肯给到这份工资的平台.也就是说,尽量去大城市,尽量去大公司. 我在上海,先说下我知道的薪资情况,基本上,只要有3年开发经验,能过大厂或外企的面试, ...