算法初步——two pointers

什么是 two pointers  

  以一个例子引入:给定一个递增的正整数序列和一个正整数 M,求序列中的两个不同位置的数 a 和 b,使得它们的和恰好为 M,输出所有满足条件的方案。

  本题的一个最直观的想法是,使用二重循环枚举序列中的整数 a 和 b,判断它们的和是否为 M。时间复杂度为 O(n2)。

  two pointers 将利用有序序列的枚举特性来有效降低复杂度。它针对本题的算法如下:

  令下标 i 的初值为0,下标 j 的初值为 n-1,即令 i、j 分别指向序列的第一个元素和最后一个元素,接下来根据 a[i]+a[j] 与 M 的大小来进行下面三种选择,使 i 不断向右移动、使 j 不断向左移动,直到 i≥j 成立

    • 若 a[i]+a[j]==M ,说明找到了其中一种方案,令 i=i+1、j=j-1。
    • 若 a[i]+a[j]>M,令 j=j-1。
    • 若 a[i]+a[j]<M,令 i=i+1。

  反复执行上面三个判断,直到 i≥j 成立,时间复杂度为O(n),代码如下:

 1 while(i < j) {
 2     if(a[i]+a[j] == M) {
 3         printf("%d %d\n", i, j);
 4         i++; j--;
 5     } else if(a[i]+a[j] < M) {
 6         i++;
 7     } else {
 8         j--;
 9     }
10 }

 

 

   

  再来看序列合并问题。假设有两个递增序列 A 与 B,要求将它们合并为一个递增序列 C。

  同样的,可以设置两个下标 i 和  j ,初值均为0,表示分别指向序列 A 的第一个元素和序列 B 的第一个元素,然后根据 A[i] 与 B[j] 的大小来决定哪一个放入序列 C。

    • 若 A[i]≤B[j],把 A[i] 加入序列 C 中,并让 i 加1
    • 若 A[i]>B[j],把 B[j] 加入序列 C 中,并让 j 加1    

  上面的分支操作直到 i、j 中的一个到达序列末端为止,然后将另一个序列的所有元素依次加入序列 C 中,代码如下:

 1 int merge(int A[], int B[], int C[], int n, int m) {
 2     int i=0, j=0, index=0;    // i指向A,j指向B,index指向C
 3     while(i<n && j<m) {
 4         if(A[i] <= B[j]) {            // 若 A[i]≤B[j] 
 5             C[index++] = A[i++];
 6         } else {                    // 若 A[i]>B[j]
 7             C[index++] = B[j++];
 8         }
 9     } 
10     while(i<n)    C[index++] = A[i++];    // 若 A 有剩余
11     while(j<m)    C[index++] = B[j++];    // 若 B 有剩余
12     return index;        // 返回 C 长度 
13 }

 

 

 

 

  广义上的 two pointers 是利用问题本身与序列的特性,使用两个下标 i、j 对序列进行扫描(可以同向扫描,也可以反向扫描),以较低的复杂度(一般为 O(n) )解决问题。 

 

 

归并排序

  归并排序是一种基于“归并”思想的排序方法,本节主要介绍其中最基本的 2-路归并排序。2-路归并排序的原理是,将序列两两分组,将序列归并为 n/2 个组,组内单独排序;然后将这些组再两两归并,生成 n/4 个组,组内再单独排序;以此类推,直到只剩下一个组为止。时间复杂度为 O(nlogn)。

  1. 递归实现

  只需反复将当前区间 [left,right] 分为两半,对两个子区间 [left,mid] 与 [mid+1, right] 分别递归进行归并排序,然后将两个已经有序的子区间合并为有序序列即可。代码如下:

 1 const int maxn = 100;
 2 // 将数组A的 [L1,R1] 与 [L2,R2] 合并为有序区间(此处 L2=R1+1 ) 
 3 void merge(int A[], int L1, int R1, int L2, int R2) {
 4     int i=L1, j=L2;
 5     int temp[maxn], index=0;    // temp 临时储存合并序列
 6     while(i<=R1 && j<=R2) {
 7         if(A[i] <= A[j]) {            // 若 A[i] ≤A[j] 
 8             temp[index++] = A[i++];
 9         } else {                    // 若 A[i] > A[j]
10             temp[index++] = A[j++];
11         }
12         while(i <= R1) temp[index++] = A[i++];
13         while(j <= R2) temp[index++] = A[j++];
14         for(int i=0; i<index; ++i) {
15             A[L1+i] = temp[i];    // 将合并后的序列赋值回 A  
16         }
17     } 
18 }
19 // 归并排序递归实现
20 // 只需反复将当前区间 [left,right] 分为两半,对两个子区间 [left,mid] 与 [mid+1, right] 
21 // 分别递归进行归并排序,然后将两个已经有序的子区间合并为有序序列即可。
22 void mergeSort(int A[], int left, int right) {
23     if(left < right) {    // 当 left==right 时,只有一个元素,认定为有序 
24         int mid = (left+right)/2;
25         mergeSort(A, left, mid);            // 分为左区间和右区间 
26         mergeSort(A, mid+1, right);
27         merge(A, left, mid, mid+1, right);    // 将左区间和右区间合并 
28     }
29 }

 

 

 

 

  2.非递归实现

  非递归实现主要考虑到这样一点:每次分组时组内元素个数上限都是2的幂次。于是就可以想到这样的思路:令步长 step 的初值为2,然后将数组中每 step 个元素作为一组,将其内部进行排序;再令 step 乘以2,重复上面的操作,直到 step/2 超过元素个数 n 。代码如下:

 1 const int maxn = 100;
 2 
 3 // 将数组A的 [L1,R1] 与 [L2,R2] 合并为有序区间(此处 L2=R1+1 ) 
 4 void merge(int A[], int L1, int R1, int L2, int R2) {
 5     int i=L1, j=L2;
 6     int temp[maxn], index=0;    // temp 临时储存合并序列
 7     while(i<=R1 && j<=R2) {
 8         if(A[i] <= A[j]) {            // 若 A[i] ≤A[j] 
 9             temp[index++] = A[i++];
10         } else {                    // 若 A[i] > A[j]
11             temp[index++] = A[j++];
12         }
13         while(i <= R1) temp[index++] = A[i++];
14         while(j <= R2) temp[index++] = A[j++];
15         for(int i=0; i<index; ++i) {
16             A[L1+i] = temp[i];    // 将合并后的序列赋值回 A  
17         }
18     } 
19 }
20 
21 // 归并排序非递归实现
22 // 令步长 step 的初值为2,然后将数组中每 step 个元素作为一组,
23 // 将其内部进行排序;再令 step 乘以2,重复上面的操作,直到 step/2 超过元素个数 n 。 
24 void mergeSort(int A[]) {
25     // step 为组内元素个数 
26     for(int step=0; step/2 <= n; step *= 2) {
27         for(int i = 1; i <= n; i += step) {    // 对每一组,数组下标从1开始 
28             int mid = i + step/2 -1;    // 左区间元素个数为 step/2
29             if(mid+1 <= n) {    // 右区间存在元素 
30                 // 左区间为 [left,mid],右区间为 [mid+1, min(i+step-1,n) 
31                 merge(A, i, mid, mid+1, min(i+step-1, n)); 
32             } 
33             
34             /*
35             // 也可以用 sort 代替 merge 函数
36             sort(A+i, A+min(i+step, n+1));         */
37         }
38     }
39 }

 

 

 快速排序 

  快速排序的实现需要先解决这样一个问题:对一个序列 A[1]、A[2]、... 、A[n],调整序列中元素的位置,使得 A[1] (原序列中的 A[1])的左侧元素都不超过 A[1]、右侧所有元素都大于 A[1]。

  下面给出速度最快的做法,思想就是 two pointers:

     1. 先将 A[1] 存至某个临时变量 temp,并令两个下标 left、right 分别指向序列首尾。

     2. 只要 right 指向的元素 A[right] 大于 temp ,就将 right 不断左移;当某个时候 A[right] 小于等于 temp 时,将元素 A[right] 挪到 left 指向的元素 A[left] 处。

     3. 只要 left 指向的元素 A[right] 小于等于 temp ,就将 left 不断右移;当某个时候 A[right] 大于 temp 时,将元素 A[left] 挪到 right指向的元素 A[right] 处。

     4. 重复 2.3 ,直到 left 与 right 相遇,把 temp(也即原 A[1])放到相遇的地方。 

 1 // 对区间 [left,right]进行划分  
 2 int Partition(int A[], int left, int right) {
 3     int temp = A[left];                                // 1.
 4     while(left < right) {
 5         while(left<right && A[right]>temp) right--;    // 2.
 6         A[left] = A[right];
 7         while(left<right && A[left]<=temp) left++;    // 3.
 8         A[right] = A[left]; 
 9     }
10     A[left] = temp;                                    // 4.
11     return left;
12 } 

 

       

  接下来就可以正式实现快速排序算法了。快速排序的思路是:

    1. 调整序列中的元素,使当前序列的最左端的元素在调整后满足左侧所有元素均不超过该元素、右侧所有元素均大于该元素

    2. 对该元素的左侧和右侧分别进行 1 的调整,直到当前调整区间的长度不超过 1

  快速排序的递归实现如下:

1 // 快速排序
2 void quickSort(int A[], int left, int right) {
3     if(left < right) {
4         int pos = Partition(A, left, right);    // 1.
5         quickSort(A, left, pos);                // 2.
6         quickSort(A, pos+1, right);
7     }
8 } 

 

  快速排序算法当序列中元素的排列比较随即时效率最高,但是当序列中元素接近有序时,会达到最坏时间复杂度 O(n2)。

  其中一种解决办法是随机选择主元,也就是对 A[left...right] 来说,不总是用 A[left] 作为主元,而是从 left...right 随机选择一个作为主元。

  下面来看看如何生成随机数。

    • 需要添加 stdlib.h 与 time.h 头文件。
    • 函数开头需加上  srand((unsigned)time(NULL)); ,这个语句将生成随机数的种子
    •   rand() 只能生成 [0,RAND_MAX] 范围内的整数,RAND_MAX 在不同系统环境中不同,假设该系统为 32767
    •  如果想要输出给定范围 [a,b] 内的随机数,需要使用  rand() % (b-a+1) + a  
    •  如果需生成更大的数,例如 [a,b],b 大于 32767,可以使用  (int)(round(1.0*rand()/RAND_MAX*(b-a)+a)) 

  在此基础上继续讨论快排的写法。不妨生成一个范围在 [left,right] 内的随机数 p,然后以 A[p] 作为主元来进行划分。具体做法是:将 A[p] 与 A[left] 交换,然后按原先 Partition 函数的写法即可,代码如下:

1 // 随机选择主元,然后进行划分 
2 int randPartition(int A[], int left, int right) {
3     // 生成 [left,right] 内的随机数 p 
4     int p = (int)(round(1.0*rand()/RAND_MAX*(right-left) +left));
5     swap(A[p], A[left]);    // 交换 A[p] 和 A[left] 
6     
7     // 以下跟 Partition 完全相同 
8     return Partition(A, left, right); 
9 }

 

 

 

 

转载于:https://www.cnblogs.com/coderJiebao/p/Algorithmofnotes08.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369859.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H5 _拖放使用

1 <!DOCTYPE html>2 <html lang"en">3 <head>4 <meta charset"UTF-8">5 <title>拖放API</title>6 <style>7 [iddragme]{8 width: 100px;9 height: 100px; 10 …

从XaaS到Java EE – 2012年哪一种该死的云最适合我?

您是否曾经想过要让Java EE在某个地方启动和运行需要什么&#xff1f; 是的 多年。 从托管我自己的主机开始&#xff0c;转到一些托管产品 &#xff0c;最后偶然发现了PaaS运动。 老实说&#xff0c;我并没有太认真。 我只是想把我的东西放到某个地方&#xff0c;而不在乎解决…

正方体最快最简单画_素描新手入门第一幅画可不只是“正方体”

很多素描教程都把正方体作为入门第一幅画学习内容。这种现象也成了约定俗成的规矩但是&#xff0c;学过画画的人大概都知道有很多人画了多年石膏几何形、静物、人头像甚至半身像全身像。到最后落得只会画这些学过的东西。这就说明学习出了问题。绘画练习一定要弄清楚每个物体练…

重写Alert和confirm方法去除地址显示

//重写alert方法&#xff0c;去掉地址显示window.alertfunction(name){var iframedocument.createElement("IFRAME");iframe.style.display"none";iframe.setAttribute("src",data:text/plain,); document.documentElement.appendChild(iframe);…

VS2015配置内核WDK7600环境,32位下.

VS2015配置内核WDK7600环境,32位下. 学习内核驱动的编写,就要会配置环境.不然总是用记事本编写.比较不方便. 环境配置如下. 1.首先下载WDK7600, 课堂资料代码中已经上传.链接&#xff1a;https://pan.baidu.com/s/1o9PjpUU 密码&#xff1a;k5sp 2.VS2015下载. 这个网络上有很多…

Camel 2.11 –具有URL重写功能的HTTP代理路由

在即将发布的Apache Camel 2.11版本中&#xff0c;我最近添加了对将自定义url重写实现插入基于HTTP的路由&#xff08;http&#xff0c;http4&#xff0c;jetty&#xff09;的支持。 当您使用骆驼代理/桥接HTTP路由时&#xff0c;这使人们可以控制url映射。 例如&#xff0c;假…

我的改进版2048(1)

&#xff08;假设有谁想要这个软件的话&#xff0c;在评论中留一个邮箱吧。&#xff09; 前几天好几次看到有朋友晒出玩2048刷高分的截图。我就想我能不能也做一个2048呢&#xff1f;细致想了想2048游戏的规律&#xff0c;发现事实上逻辑上非常easy&#xff0c;也不用研究什么算…

什么是 HTML5?

HTML5 是下一代的 HTML。 什么是 HTML5&#xff1f; HTML5 将成为 HTML、XHTML 以及 HTML DOM 的新标准。 HTML 的上一个版本诞生于 1999 年。自从那以后&#xff0c;Web 世界已经经历了巨变。 HTML5 仍处于完善之中。然而&#xff0c;大部分现代浏览器已经具备了某些 HTML5 支…

涉及CDI和JSF的过期对话的定制错误页面

自上次写博客以来已经有一段时间了。 我一直在考虑写一些技术博客&#xff0c;但最终却忙于其他事情。 上周&#xff0c;在Coderanch论坛上进行了非常有趣的讨论。 甚至更有趣&#xff0c;因为它涉及JBoss。 熟悉Java EE Web应用程序的开发人员会知道&#xff0c;Web应用程序部…

2020年市场最缺什么_2020年聚合氯化铝市场评述

2020年聚合氯化铝市场评述一、行情概述&#xff1a;今年聚合氯化铝价格整体呈下滑趋势&#xff0c;接近年底价格才有小幅反弹。但不同时期价格有小幅起伏&#xff0c;主要受疫情影响&#xff0c;在下游需求不佳的影响下价格出现下滑。1月受疫情影响&#xff0c;前期停产企业短期…

通过反射来将一个类的内容转换到另外一个类里

主函数&#xff1a; import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.serializer.ValueFilter;import com.google.common.base.Preconditions; import java.lang.reflect.Field;import java.lang.reflect.Modifier; public class leijun {public static void ma…

sql语句中left join和inner join中的on与where的区别分析

sql语句中left join和inner join中的on与where的区别分析 原文:sql语句中left join和inner join中的on与where的区别分析关于SQL SERVER的表联接查询INNER JOIN 、LEFT JOIN和RIGHT JOIN&#xff0c;经常会用到ON和WHERE的条件查询&#xff0c;以前用的时候有时是凭感觉的&…

开发辅助 | 阿里图标库iconfont入门使用

目前大多数的互联网公司&#xff0c;前端开发和UI设计师配合中&#xff0c;针对设计师给图的效果图&#xff0c;前端开发工程师不再像往常一样对于细小图标进行切图&#xff0c;取而代之的是引用阿里图标库&#xff08;http://iconfont.cn/&#xff09;&#xff1b;简单的临时开…

使用Spring Security对RESTful服务进行身份验证

1.概述 本文重点介绍如何针对提供安全服务的安全REST API进行身份验证 -主要是RESTful用户帐户和身份验证服务。 2.目标 首先&#xff0c;让我们看一下参与者-典型的启用了Spring Security的应用程序需要针对某些事物进行身份验证-该事物可以是数据库&#xff0c;LDAP或可以是…

可拖动的弹窗

pc端&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>可拖动的弹窗</title> <style type"text/css"> a{text-decoration: …

向量外积_解析几何 -向量

目录1.向量2.内积3.外积4.混合积5.双重外积6.关系式正文1.向量vector 引入vector O规定O没有确切的方向&#xff0c;即与任何向量不仅平行&#xff0c;而且垂直。申明&#xff1a;本文章的向量为自由向量&#xff0c;即始点不固定的向量&#xff0c;它可以任意的平行移动&#…

HTML5 参数传递

页面显示效果&#xff0c;如下图&#xff1a; 主页面代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><br><br><a href"jssendValue.html?i…

双向@OneToOne主键关联

现在该继续有关Hibernate的文章了。 最后一个致力于单向OneToOne关联 。 因此&#xff0c;今天我将向您展示如何获取双向OneTonOne主键关联 。 本教程中基于前一篇文章的示例。 让我们开始吧。 我将使用以前创建的相同表。 为了建立双向一对一关联&#xff0c;我需要更新两个P…

计量经济学建模_一分钟看完计量经济学

建模是计量的灵魂&#xff0c;所以就从建模开始。一、建模步骤建模步骤&#xff1a;A&#xff0c;理论模型的设计: a&#xff0c;选择变量b&#xff0c;确定变量关系c&#xff0c;拟定参数范围B&#xff0c;样本数据的收集: a&#xff0c;数据的类型b&#xff0c;数据的质量C&a…

如何将视频设置为网页背景

有时候为一个网页添加一个动画效果的背景&#xff0c;会让网页增加一定的韵味&#xff0c;让网页看起来与众不同。 第一步&#xff1a;准备工作 工欲善其事必先利其器&#xff0c;我们首先需要准备一个视频 第二步&#xff1a;html中引入视频 这里我们需要用到了video/标签&…