二分算法实例应用(二)

和为给定数 (POJ 4143)

Description

给出若干个整数,询问其中是否有一对数的和等于给定的数。

Input

共三行:

第一行是整数n(0 < n <= 100,000),表示有n个整数。

第二行是n个整数。整数的范围是在0到10^8之间。

第三行是一个整数m(0 <= m <= 2^30),表示需要得到的和。

Output

若存在和为m的数对,输出两个整数,小的在前,大的在后,中间用单个空格隔开。若有多个数对满足条件,选择数对中较小的数更小的。若找不到符合要求的数对,输出一行No。

Sample Input

4
2 5 1 4
6

Sample Output

1 5

算法思想:

  1. 法一

    首先想到暴力破解,采用两重循环,枚举所有可能的取数方法。

    时间复杂度不难得到为O(n^2)。b

    因为本题问题规模达到了十万,若采用暴力破解则会有100亿的数据规模,基本在所有的OJ中都会超时,所以不可取。

  2. 法二

    既然是要找两个数据满足a+b=m,那么我们可以固定一个值a,在数组中找数m-a,若数组为有序数组,那么在数组中查找一个给定数就可以采用二分查找法来降低时间复杂度。

    因此,

    1. 首先我们需对题中输入数组进行排序,可采用快速排序,时间复杂度为 O ( n l o g 2 n ) O(n log_2 n) O(nlog2​n);
    2. 其次,依次固定前n-1个数据,对每一个数据a进行一轮循环判断,判断是否有元素为c-a。每一轮判断中可以采用二分查找法进行搜索判定。因此本步时间复杂度为 O ( n l o g 2 n ) O(n log_2 n) O(nlog2​n)。

    综上,总体时间复杂度为 O ( n l o g 2 n ) O(n log_2 n) O(nlog2​n)。

  3. 法三

    法三是对法二进行的一个优化,在编写法二的程序时,突然想到:既然都是比较数,那和快速排序中一趟的划分关键步骤岂不是有异曲同工之妙嘛,即将大于枢轴元素的元素移至枢轴右侧,将所有小于枢轴元素的元素移至枢轴左侧。在这样的思想下,产生了对法二的进一步优化。

    即:

    1. 同样需要对输入数据进行排序,采用快速排序,时间复杂度为 O ( n l o g 2 n ) O(n log_2 n) O(nlog2​n)。关于排序的时间复杂度在目前能力范围内无法优化。

    2. 对于寻找两个数满足a+b=m这一步,可以考虑:设a为最小元素,即数组左端点;b为最大元素,即数组右端点。

      若(a+b)>m,那么表明,两数之和过大,应该减少最大值,使右端点左移,来减少两数之和。

      若(a+b)<m,那么表明,两数之和过小,应该增大最小值,使左端点右移,来增加两数之和。

      若(a+b)=m,那么便找到了一个解。

      这样的算法在找满足a+b=m的步骤中最多只需遍历一遍数组即可得出解,或判断出是否无解,则本步中时间复杂度为 O ( n ) O(n) O(n)

    综上,总体时间复杂度为 O ( n l o g 2 n ) + O ( n ) = O ( n l o g 2 n ) O(n log_2 n)+O(n)=O(n log_2 n) O(nlog2​n)+O(n)=O(nlog2​n)。虽然总体时间复杂度没有得到改善,但改善了局部的时间复杂度,比之法二,该算法有更好的性能。


代码逻辑:

  1. 法一

    在算法思想章节中已基本概述清楚,由于本算法并未对数据进行任何排序处理,所以得到的解是无序的,但题中要求输出的两个整数,应小的在前,大的在后,若有多个数对满足条件,选择数对中较小的数更小的。所以在求解时,应求尽所有的解,再判断时候满足题设解的条件。

    具体逻辑较为简单不再赘述,代码中有详细说明。

    代码如下:

    void fun1(int n, int *a, int m) {int left = 1e8 + 1, right = 1e8 + 1;//left为组合数据中较小的一个for (int i = 0; i < n - 1; i++) {for (int j = i + 1; j < n; j++) {if (a[i] + a[j] == m) {//成功找到一组数据满足要求if (a[i] < left || a[j] < left) {//判断是否为最小的组合数据if (a[i] < a[j]) {//组合数据中值较小的放左边left = a[i];right = a[j];} else {left = a[j];right = a[i];}}break;}}}if (left == 1e8 + 1)printf("No");//若无解则输出Noelseprintf("%d %d", left, right);//优先输出值较小的组合数据
    }
    
  2. 法二

    1. 首先对输入数组进行快速排序,为便于读着理解,本文采用手写递归快排算法,若读者已经理解快排本质及其运行逻辑,可采用C/C++排序库函数进行排序。

    2. 快排的基本思想是基于分治法的:在待排序的表中任取一个元素pivot作为枢轴元素,通过一趟排序将待排序表划分为两部分,即:左侧,pivot,右侧,左侧元素为所有小于pivot的元素集合,右侧元素为所有大于pivot的元素集合。通过这个一趟快速排序确定了pivot元素在整个数据表中的最终位置,再依次递归的对左侧数据集合进行快排、右侧数据集合进行快排,直至每部分只剩下一个元素或者为空时为止,即表明此时所有元素都放在了最终位置上。

      //快排主函数
      void QuickSort(int a[], int low, int high) {if (low < high) {//递归终止条件int pivotPos = Partition(a, low, high);//划分一次,同时确定一个数的位置QuickSort(a, low, pivotPos - 1);//对该数左边所有元素进行快排QuickSort(a, pivotPos + 1, high);//对该数右边边所有元素进行快排}
      }
      

      一趟快排的划分函数的过程,就是一个交替搜索和交换的过程,划分的效率取决于枢轴元素的选取。

      读者可自行通过Data Structure Visualizations工具进行模拟,理解其精髓本质与运行逻辑。

      https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html

      //一趟划分函数
      int Partition(int a[], int low, int high) {int pivot = a[low];//将当前表中第一个元素作为枢轴,对表进行划分while (low < high) {//循环终止条件while (low < high && a[high] >= pivot)high--;//从右往左找第一个小于枢轴元素的元素a[low] = a[high];//将小于枢轴元素的元素移至枢轴左侧while (low < high && a[low] <= pivot)low++;//从左往右找第一个大于枢轴元素的元素a[high] = a[low];//将大于枢轴元素的元素移至枢轴右侧}a[low] = pivot;//枢轴元素位置确定return low;//返回枢轴元素最终位置
      }
      
    3. 对数据排序完成后,开始依次固定前n-1个数,假设当前为第i个数据元素,此时应对区间[i+1,n]内的数据元素进行二分查找,判断是否有元素满足b=m-a;若有元素满足,则此时的i必为最优解,因为已经对数据进行过排序,i必为最小的解。若区间[i+1,n]内数据元素不存在元素满足b=m-a时,表明当前的第i个元素不为解,应判断下一元素,即i++,直至找到一个解时,退出函数。若当遍历完所以的元素时仍为找到一个解则表明无解。

      代码如下:

      void fun2(int n, int *a, int m) {QuickSort(a, 0, n - 1);//将题设数组进行快速排序//依次遍历从0~n-1的每一个数据元素,采用二分法查找元素(m - a[i])for (int i = 0; i < n - 1; i++) {int left = i + 1, right = n;//因为此时为有序数组,所以对区间[i+1,n]进行二分查找while (left <= right) {int mid = left + (right - left) / 2;int temp = m - a[i];if (temp == a[mid]) {//找到该元素,输出,且由于有序数组,第一个找到的解必为最终解,无须再做法一中的额外判断printf("%d %d", a[i], a[mid]);return;} else if (temp > a[mid]) {//当前中点值小于目标值,修改区间左端点left = mid + 1;} else {//当前中点值大于目标值,修改区间右端点right = mid - 1;}}}printf("No");//若无解则输出No
      }
      
  3. 法三

    1. 同法二,首先进行快速排序,不再赘述

    2. 对数据排序完成后,由上述算法思想可知,应记录解的区间的左右端点,left=0,right=n-1;通过判断左右端点的元素之和是否与m相等来确定是否为解,记temp=a[left] + a[right];

      • 若 temp = m 则表明找到了解,应输出解

      • 若 temp > m 则表明左右端点之和过大,应减少较大的值,即将右端点左移

      • 若 temp < m 则表明左右端点之和过小,应增加较小的值,即将左端点右移

        每次第边界移动后修改temp的值,再循环判断是否满足 temp = m ,直至找到一个解。

      代码如下:

      //借用快排中划分方法中的思想对法二进行进一步优化
      void fun3(int n, int *a, int m) {QuickSort(a, 0, n - 1);//将题设数组进行快速排序int left = 0, right = n - 1;//记录可能的值的左右区间端点int temp = a[left] + a[right];//temp记录区间左右端点元素的和while (left < right && (temp != m)) {//若区间合理且temp≠m,进行边界移动if (temp > m) {//若temp>m则表明左右端点之和过大,应减少较大的值,即将右端点左移right--;}if (temp < m) {//若temp<m则表明左右端点之和过小,应增加较小的值,即将左端点右移left++;}temp = a[left] + a[right];//修改左右端点之和用作下一次判断}if (left < right) {//若left < right,则表明找到了一个区间的左右端点使得其和temp=m;printf("%d %d", a[left], a[right]);} else {//若left < right,则表明无解,则输出Noprintf("No");}
      }
      

代码整合:

//
// Created by Ss1Two on 2023/2/7.
//#include "stdio.h"void fun1(int n, int *a, int m);//法一void fun2(int n, int *a, int m);//法二void fun3(int n, int *a, int m);//法三void QuickSort(int a[], int low, int high);//快速排序函数声明int Partition(int a[], int low, int high);//快速排序一趟划分函数声明int main(void) {int n, arr[100000], m;scanf_s("%d", &n);for (int i = 0; i < n; i++) {scanf_s("%d", &arr[i]);}scanf_s("%d", &m);//    fun1(n, arr, m);
//    fun2(n, arr, m);fun3(n, arr, m);return 0;
}void fun1(int n, int *a, int m) {int left = 1e8 + 1, right = 1e8 + 1;//left为组合数据中较小的一个for (int i = 0; i < n - 1; i++) {for (int j = i + 1; j < n; j++) {if (a[i] + a[j] == m) {//成功找到一组数据满足要求if (a[i] < left || a[j] < left) {//判断是否为最小的组合数据if (a[i] < a[j]) {//组合数据中值较小的放左边left = a[i];right = a[j];} else {left = a[j];right = a[i];}}break;}}}if (left == 1e8 + 1)printf("No");//若无解则输出Noelseprintf("%d %d", left, right);//优先输出值较小的组合数据
}void fun2(int n, int *a, int m) {QuickSort(a, 0, n - 1);//将题设数组进行快速排序//依次遍历从0~n-1的每一个数据元素,采用二分法查找元素(m - a[i])for (int i = 0; i < n - 1; i++) {int left = i + 1, right = n;//因为此时为有序数组,所以对区间[i+1,n]进行二分查找while (left <= right) {int mid = left + (right - left) / 2;int temp = m - a[i];if (temp == a[mid]) {//找到该元素,输出,且由于有序数组,第一个找到的解必为最终解,无须再做法一中的额外判断printf("%d %d", a[i], a[mid]);return;} else if (temp > a[mid]) {//当前中点值小于目标值,修改区间左端点left = mid + 1;} else {//当前中点值大于目标值,修改区间右端点right = mid - 1;}}}printf("No");//若无解则输出No
}//借用快排中划分方法中的思想对法二进行进一步优化
void fun3(int n, int *a, int m) {QuickSort(a, 0, n - 1);//将题设数组进行快速排序int left = 0, right = n - 1;//记录可能的值的左右区间端点int temp = a[left] + a[right];//temp记录区间左右端点元素的和while (left < right && (temp != m)) {//若区间合理且temp≠m,进行边界移动if (temp > m) {//若temp>m则表明左右端点之和过大,应减少较大的值,即将右端点左移right--;}if (temp < m) {//若temp<m则表明左右端点之和过小,应增加较小的值,即将左端点右移left++;}temp = a[left] + a[right];//修改左右端点之和用作下一次判断}if (left < right) {//若left < right,则表明找到了一个区间的左右端点使得其和temp=m;printf("%d %d", a[left], a[right]);} else {//若left < right,则表明无解,则输出Noprintf("No");}
}//快排主函数
void QuickSort(int a[], int low, int high) {if (low < high) {//递归终止条件int pivotPos = Partition(a, low, high);//划分一次,同时确定一个数的位置QuickSort(a, low, pivotPos - 1);//对该数左边所有元素进行快排QuickSort(a, pivotPos + 1, high);//对该数右边边所有元素进行快排}
}//一趟划分
int Partition(int a[], int low, int high) {int pivot = a[low];//将当前表中第一个元素作为枢轴,对表进行划分while (low < high) {//循环终止条件while (low < high && a[high] >= pivot)high--;//从右往左找第一个小于枢轴元素的元素a[low] = a[high];//将小于枢轴元素的元素移至枢轴左侧while (low < high && a[low] <= pivot)low++;//从左往右找第一个大于枢轴元素的元素a[high] = a[low];//将大于枢轴元素的元素移至枢轴右侧}a[low] = pivot;//枢轴元素位置确定return low;//返回枢轴元素最终位置
}

C r e a t e d ⋯ b y ⋯ S s 1 T w o ⋯ o n ⋯ 2023 / 2 / 7 Created\cdots by\cdots Ss1Two\cdots on \cdots 2023/2/7 Created⋯by⋯Ss1Two⋯on⋯2023/2/7

二分算法实例应用(二)相关推荐

  1. C#常用算法实例(二)

    **问题描述:**计算n的阶乘. using System; using System.Collections.Generic; using System.Linq; using System.Tex ...

  2. 计算1至1000间的合数c语言,输出1000以内的素数的算法(实例代码)

    输出1000以内的素数的算法(实例代码) 代码如下所示: 复制代码 代码如下: #include "stdafx.h" #include #include bool IsSushu ...

  3. adaboost算法java_Adaboost 算法实例解析

    Adaboost 算法实例解析 1 Adaboost的原理 1.1 Adaboost基本介绍 AdaBoost,是英文"Adaptive Boosting"(自适应增强)的缩写,由 ...

  4. 聚类算法-最大最小距离算法(实例+代码)

    聚类算法-最大最小距离算法(实例+代码) [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/53708042 目录 聚类算法- ...

  5. php猴子找大王算法,教程方法;php实现猴子选大王问题算法实例电脑技巧-琪琪词资源网...

    琪琪词资源网-教程方法;php实现猴子选大王问题算法实例电脑技巧,以下是给大家带来的教程方法;php实现猴子选大王问题算法实例,大家可以了解一下哦! 下面为你介绍php实现猴子选大王问题算法实例. 本 ...

  6. 2021年SWPUACM暑假集训day1二分算法

    二分算法是什么 二分搜索是一种时间复杂为log2nlog_2nlog2​n的算法,可以用于单调函数求根和单调序列查询的有效算法,即使在数列长度在很大的情况下也能很快对其查询,在此同时二分算法也是一种思 ...

  7. 梯度下降计实例计算(二维)

    梯度下降计实例计算(二维) 文章目录 内容简介 示例说明 绘制曲线 进一步讨论 参考资料 内容简介 梯度下降是在机器学习中重要的计算内容.本文就一个具体的示例,展示如何在梯度下降中. 示例说明 设损失 ...

  8. 二分算法详解:整数二分及浮点数二分算法(Binary Search)(含算法模板)

    一.二分算法简介 当我们要从一个序列中查找一个元素的时候,最简单无脑的方法就是顺序查找法,但由于在大数据情况下爆炸的时间复杂度而舍弃. 最常见的方法是二分查找,也称折半查找(Binary Search ...

  9. 基于马尔可夫过程的一种新型混合PSO粒子群算法(SCI二区高被引文献)介绍及算法复现(使用chatgpt)

    以下是一篇算法领域的SCI二区文献(原文见附件),介绍了一种使用Markov概率转移矩阵对种群拓扑结构进行加权的粒子群算法,相比于标准PSO算法该算法提高了全局覆盖率,更容易跳出局部最优,但是在局部最 ...

最新文章

  1. 现代密码学2.2、2.3--由“一次一密”引出具有完美安全的密码方案共同缺点
  2. SolrCloud6.1.0之SQL查询测试
  3. spark-sql执行时报错:
  4. entOS 7 安装 python3+pip3+chrome+chromedriver+selenium+requests 无GUI运行脚本
  5. leetcode 752. Open the Lock | 752. 打开转盘锁(BFS)
  6. 免费和开源世界里面有很多好的邮件服务器
  7. 2014/5/25 多校
  8. Java 接口中使用数组缺点的理由
  9. 如何理解和使用Java package包
  10. 用cmd 查看本机的IP地址
  11. 华为模拟器 ENSP 教程
  12. 转载python3爬虫(2)下载有固定链接的视频
  13. c# asp.net在线问卷调查系统源码【源码分享】
  14. 神经网络放大图片,神经网络AI滤镜
  15. [Mac] Newifi mini路由器刷breed+Padavan固件
  16. MQ Reason code list
  17. 非支配解Non-dominated solution/帕累托解Pareto set
  18. Java dom4j如何获取,添加,删除,查找,设置Element属性呢?
  19. 浪潮虚拟化服务器参数,浪潮英特尔首发四核服务器虚拟化应用标准
  20. 【转】一篇好文,以在迷茫时阅读

热门文章

  1. 多线程生产者消费者模型
  2. 005_HTML制作炫酷登录界面(CSS精灵图、背景图片局部显示)
  3. java大数据开发是做什么的
  4. matlab 中图的大小_关于matlab绘图中字体及图片大小等的设置
  5. 机器人操作系统ROS 编程开发--详细总结
  6. xml和json转换的在线工具
  7. CC++编程环境搭建-四种常用开发工具(VSCode,Dev,CodeBlocks,Clion)
  8. DevOps第二讲:用Docker部署你的环境
  9. TCP为什么需要3次握手与4次挥手
  10. 华为Mate7-打开开发者选项