笔记: 数据结构与算法--时间复杂度二分查找数组
算法复杂度
- 不依赖于环境因素
- 事前分析法
- 计算最坏情况的时间复杂度
- 每一条语句的执行时间都按照t来计算
时间复杂度
- 大O表示法
- n = 数据量 ; f(n) = 实际的执行条数
- 当存在一个n0 , 使得 n > n0,并且 c * g(n) 恒> f(n) : 渐进上界(算法最坏的情况)
- 那么f(n)的时间复杂度 => O(g(n))
- 大O表示法
- f(n)中的常数量省略
- f(n)中较低次幂省略
- log2(n) => log(n)
- 常见的表示
- O(1) > O(log(n)) >> O(n) > O(nlog(n)) >> O(n2) > O(2^n) >> O(n!)
空间复杂度
- 除原始参数以外,额外空间成本
二分查找
基础版
-
步骤
- 有序数组A , 目标值target
- 左右索引值 : i = 0 , j = n - 1;
- 判断 i > j => 结束查找,没找到
- 中间索引 m = ( i + j) / 2 : 整形自动向下取整
- 判断 m > target : j = m -1; -> 跳到第三步继续执行
- 判断 m < target : i = m + 1; -> 跳到第三步继续执行
- 判断 m = target : 得到结果结束程序,返回索引值
-
代码如下
/*** 编写二分查找方法* @param A : 有序数组a* @param target : 目标查找值* @return 返回索引值*/public static int binarySearchBasic(int[] A, int target) {// 定义左右指针int i = 0, j = A.length - 1;// 定义循环条件while (i <= j) {// 定义中间指针m// int m = (i + j) / 2; int m = (i + j) >>> 1; // 判断A[m] 值 与 target值if (target < A[m]){// 中间值大 : 指针[m , j]中的值都会比target值大j = m - 1;} else if (A[m] < target){// 中间值小 : 指针[i , m]中的值都会比target值小i = m + 1;} else {// A[m] == target: 得到结果,结束循环,返回mreturn m;}}//i > j : 结束循环return -1; // 结束循环,返回-1}
-
问题一 : 代码while循环中为什么是i <= j , 而不是 i < j ?
答 : 首先 代码return -1; 本身表示的就是在 i>j 的情况下结束,没有找到,返回-1; , 那么相反对应的循环内就应该为 i <= j
其次, while(i < j ) 表示的是 只有i,j中间的m会参与比较 ; 而while(i <= j) : 表示 i , j所指向的元素 也会参与比较. 当i == j 的时候, m = i = j = (i + j) /2
-
问题二: 代码 int m = (i + j) / 2; 是否有问题?
答: 有问题.
假设 j 的值为整数类型最大值 (Integer.MAX_VALUE - 1) , 并且target值在数组的右侧 (target > A[m]) , 那么我们就需要将 索引i 的值调到m的右侧
即 : i = (i + j) /2 + 1; j = 整数类型最大值 = Integer.MAX_VALUE - 1;
那么根据Java int类型的特性, Java二进制首位为符号位,则会导致下一次进行 m = (i + j) / 2 的时候,使m成为负数
解决办法: 无符号右移运算符 数字 >>> 位数n
在二进制中,二进制码整体向右平移指定位数n就相当于n / 2n , 数字 >>> 1 => 数字 / 2运算取整
改动版
-
步骤
- 让右指针作为边界,必须不参与比较运算
- 循环不能有i=j的情况,如果i=j的话,则会造成m = i = j = (i + j) / 2 , 则不符合第一点j不参与比较运算的条件
- 当m指针所对应的值 > target值时,让j指针=m , 因为m指针对应的值已经参与过比较,并且肯定不等于target值,可作为边界不参与比较运算.
- 如果还是j = m - 1情况, m - 1的指针存在这等于target的可能,于第一点让j作为边界条件不服.
- 并且,当j= m - 1 , 如果要查找的target值不在数组A中时,会出现死循环的情况
-
代码如下
public static int binarySearchAlter(int[] A, int target) {// 改动一: 其中右指针j作为边界, 必须不参与运算比较int i = 0, j = A.length;// 定义循环条件 , 改动二: 由于不让j指针值参与比较, 故不需要i=j的情况,当i=j时,j被带着参与了比较,当target值不是数组值的时候,会导致死循环的情况while (i < j) {int m = (i + j) >>> 1;if (target < A[m]) {// 改动三: j作为边界,不参与比较.故当判断出来target值在m指针左侧时,m指针对应值已经判断过了,不可能和target相等,让j=m,及让j作为一个不可能相等的边界使用j = m;} else if (A[m] < target) {i = m + 1;} else {return m;}}return -1;}
平衡版
-
代码如下
/*** 二分查找平衡版** @param A : 有序数组a* @param target : 目标查找值* @return 返回索引值*/public static int binarySearchBalance(int[] A, int target) {// 定义左右边界,左边i指针对应的值可能target , 右指针j作为边界, 必须不参与运算比较int i = 0, j = A.length;// 定义循环条件 , 目的为缩小比较范围,将最后比较功能放到循环以外// 由i < j => 0 < j - i 改为 1 < j - i; 表示[i , j]区间内待比较的个数是否有1个以上while (1 < j - i) {// 定义中间索引int m = (i + j) >>> 1;if (target < A[m]) {// j作为右边界,不参与比较.故当判断出来target值在m指针左侧时,m指针对应值已经判断过了,不可能和target相等,让j=m,及让j作为一个不可能相等的边界使用j = m;} else {// 及 A[m] <= target的情况,及 i指针对应的值是有可能为target结果的i = m;}}// 将缩减范围后剩余的一个索引i所对应的值与target进行比较if (A[i] == target){return i;} else {return -1;}}
二分查找-Java版源码
- 通过Arrays.binarySearch(int[] arr, int key);方法调用
/*** Searches the specified array of ints for the specified value using the* binary search algorithm. The array must be sorted (as* by the {@link #sort(int[])} method) prior to making this call. If it* is not sorted, the results are undefined. If the array contains* multiple elements with the specified value, there is no guarantee which* one will be found.** @param a the array to be searched* @param key the value to be searched for* @return index of the search key, if it is contained in the array;* otherwise, <code>(-(<i>insertion point</i>) - 1)</code>. The* <i>insertion point</i> is defined as the point at which the* key would be inserted into the array: the index of the first* element greater than the key, or {@code a.length} if all* elements in the array are less than the specified key. Note* that this guarantees that the return value will be >= 0 if* and only if the key is found.*/public static int binarySearch(int[] a, int key) {return binarySearch0(a, 0, a.length, key);}// Like public version, but without range checks.private static int binarySearch0(int[] a, int fromIndex, int toIndex,int key) {int low = fromIndex;int high = toIndex - 1;while (low <= high) {int mid = (low + high) >>> 1;int midVal = a[mid];if (midVal < key)low = mid + 1;else if (midVal > key)high = mid - 1;elsereturn mid; // key found}return -(low + 1); // key not found.}
二分查找对于重复元素查找的处理
获取重复最左侧索引值
-
步骤
- 添加一个结果变量索引值,用来存储当m索引值与target相等时,存储m索引值 ,
- 当相等时,j继续缩小边界,程序继续直到程序结束,获取最左侧结果
- i > j : 结束循环 , 返回结果索引值
-
代码如下
public static int binarySearchLeftMost(int[] A, int target) {// 定义左右指针int i = 0, j = A.length - 1;// 定义结果变量索引值int resIndex = -1;// 定义循环条件while (i <= j) {// 定义中间指针mint m = (i + j) / 2;// 判断A[m] 值 与 target值if (target < A[m]){// 中间值大 : 指针[m , j]中的值都会比target值大j = m - 1;} else if (A[m] < target){// 中间值小 : 指针[i , m]中的值都会比target值小i = m + 1;} else {// A[m] == target: 将结果存储到结果索引值中, 并将右侧边界缩小,继续进行程序,直到程序结束,获取最左侧结果resIndex = m;j = m - 1;}}//i > j : 结束循环 , 返回结果索引值return resIndex;}
-
获取重复最右侧索引值 : 将上放代码第20行 j = m - 1; => 改为 i = m + 1; 即可
修改返回值意义
-
获取<= 索引值 最靠右的索引值结果 , 代码如下:
public static int binarySearchRightMost1(int[] A, int target) {// 定义左右指针int i = 0, j = A.length - 1;// 定义循环条件while (i <= j) {// 定义中间指针mint m = (i + j) / 2;// 判断A[m] 值 与 target值if (target < A[m]) { j = m - 1;} else {i = m + 1;}}//返回 <= 索引值 最靠右的索引值结果return i - 1;}
-
获取 >= target 最靠左的索引位置 , 代码如下:
public static int binarySearchLeftMost1(int[] A, int target) {// 定义左右指针int i = 0, j = A.length - 1;// 定义循环条件while (i <= j) {// 定义中间指针mint m = (i + j) / 2;// 判断A[m] 值 与 target值if (target <= A[m]) {// 中间值>= targetj = m - 1;} else {// 中间值小 : 指针[i , m]中的值都会比target值小i = m + 1;}}//返回 >= target最靠左的索引位置return i;}
应用
- leftMost() :
- 求排名 leftMost() + 1 ;
- 求前任 leftMost() - 1 ;
- rightMost() :
- 求后任 rightMost + 1;
- 最近邻居问题
- 求前任 , 求后任
- 计算两个值 离 本值 更小的
- 范围查询
- x < n : [0 , leftMost(n) - 1]
- x <= n : [0 , rightMost(n)]
- x > n : [rightMost(n) + 1 , ∞]
- x >= n : [leftMost(n) , ∞]
- n <= x <= m : [leftMost(n) , rightMost(m)]
- n < x < m : [rightMost(n) + 1 . leftMost(m) - 1]
性能
- 时间复杂度最坏情况 : O(log(n))
- 空间复杂度 : O(1)
数组
-
连续存储
- -> 故可以通过索引值计算地址
- 公式 : 索引位置 + i * 字节数
-
随机访问时间复杂度: O(1)
-
动态数组类需要三个东西
- 数组容量
- 数组逻辑大小 : 就是数组实际存了几个值
- 静态数组
-
给数组添加元素
-
在末尾添加元素 => 给数组size位置上添加元素 -> 即调用下面方法
-
给数组指定位置添加元素
- 将数组指定位置后的元素后移
- 插入元素到指定位置上
-
插入删除的时间复杂度: O(n)
-
代码如下:
// 给数组添加元素 => 给数组size位置上添加元素private void add(int element){ // arrs[size] = element; // size++;addAppoint(size, element);}// 给数组指定位置添加元素private void addAppoint(int index , int element){if (index >= 0 && index < size){System.arraycopy(arrs, index, arrs, index + 1, size - index);}arrs[index] = element;size++;}
-
-
当数组存储元素达到容量上限,需要考虑数组扩容问题
-
每次添加元素的时候,判断size和capacity
- 定义一个新数组,容量大小是旧数组的1.5倍或两倍
- 将旧数组的数据复制到新数组中
- 将新数组的引用值 赋值 给旧数组
-
使用懒惰初始化思想优化代码
-
代码如下:
private void checkArrsCapacity() {if (size == 0) {arrs = new int[capacity];} else if (capacity == size) {capacity += capacity >> 1; // 扩大数组容量int[] newArrs = new int[capacity];System.arraycopy(arrs, 0, newArrs, 0, size);arrs = newArrs;}}@Test@DisplayName("测试扩容")public void test5() {DynamicArray dynamicArray = new DynamicArray();for (int i = 0; i < 9; i++) {dynamicArray.add(i + 1);}assertIterableEquals(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9), dynamicArray);}
-
-
给动态数组遍历的三种方式
-
使用Consumer函数式接口,实现遍历
// 动态数组的遍历 , 使用Comsumer函数式接口实现public void foreach(Consumer<Integer> consumer){for (int i = 0; i < size; i++) {consumer.accept(arrs[i]);}}@Testpublic void test3() {DynamicArray dynamicArray = new DynamicArray();dynamicArray.add(1);dynamicArray.add(2);dynamicArray.add(3);dynamicArray.foreach(System.out::println);}
-
使用迭代器实现遍历
@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {int i = 0;@Overridepublic boolean hasNext() {return i < size;}@Overridepublic Integer next() {return arrs[i++];}};}@Testpublic void test2() {DynamicArray dynamicArray = new DynamicArray();dynamicArray.add(1);dynamicArray.add(2);dynamicArray.add(3);for (Integer element : dynamicArray) {System.out.println(element);}}
-
使用stream流实现遍历
// 动态数组的遍历: 使用stream流实现public IntStream stream(){return IntStream.of(Arrays.copyOfRange(arrs, 0, size));}@Testpublic void test1() {DynamicArray dynamicArray = new DynamicArray();dynamicArray.add(1);dynamicArray.add(2);dynamicArray.add(3);dynamicArray.stream().forEach(System.out::println);}
-
-
动态数组的删除
- 使用system.arraycopy方法, 将要删除指针后的元素向前移动一位
- 插入删除: O(n)
public int remove(int index) {int removeNum = arrs[index]; // 返回删除数据if (index < size - 1) {System.arraycopy(arrs, index + 1, arrs, index, size - index - 1);}size--;return removeNum;}// 使用断言进行测试@Test@DisplayName("测试删除")public void test4() {DynamicArray dynamicArray = new DynamicArray();dynamicArray.add(1);dynamicArray.add(2);dynamicArray.add(3);dynamicArray.add(4);dynamicArray.add(5);int remove = dynamicArray.remove(3); // System.out.println("remove = " + remove);assertEquals(4 , remove);// 遍历剩余数组元素// dynamicArray.foreach(System.out::println);assertIterableEquals(List.of(1,2,3,5), dynamicArray);}
相关文章:

笔记: 数据结构与算法--时间复杂度二分查找数组
算法复杂度 不依赖于环境因素事前分析法 计算最坏情况的时间复杂度每一条语句的执行时间都按照t来计算 时间复杂度 大O表示法 n 数据量 ; f(n) 实际的执行条数当存在一个n0 , 使得 n > n0,并且 c * g(n) 恒> f(n) : 渐进上界(算法最坏的情况)那么f(n)的时间复杂度 …...

AI绘画教程:Midjourney使用方法与技巧从入门到精通
文章目录 一、《AI绘画教程:Midjourney使用方法与技巧从入门到精通》二、内容介绍三、作者介绍🌤️粉丝福利 一、《AI绘画教程:Midjourney使用方法与技巧从入门到精通》 一本书读懂Midjourney绘画,让创意更简单,让设计…...

Spring-事务管理
1、事务管理 1.1、回滚方式 默认回滚方式:发生运行异常时异常和error时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式 1.2、事务定义接口 1.2.1、事务隔离级别常量 这些常量…...

MySql实战--为什么我的MySQL会“抖”一下
时的工作中,不知道你有没有遇到过这样的场景,一条SQL语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢,并且这样的场景很难复现,它不只随机,而且持续时间还很短…...

【蓝桥杯第十三届省赛B】(部分详解)
九进制转十进制 #include <iostream> #include<math.h> using namespace std; int main() {cout << 2*pow(9,3)0*pow(9,2)2*pow(9,1)2*pow(9,0) << endl;return 0; }顺子日期 #include <iostream> using namespace std; int main() {// 请在此…...

[linux初阶][vim-gcc-gdb] OneCharter: vim编辑器
一.vim编辑器基础 目录 一.vim编辑器基础 ①.vim的语法 ②vim的三种模式 ③三种模式的基本切换 ④各个模式下的一些操作 二.配置vim环境 ①手动配置(不推荐) ②自动配置(推荐) vim是vi的升级版,包含了更加丰富的功能. ①.vim的语法 vim [文件名] ②vim的三种模式 命令…...

【Lazy ORM 框架学习】
Gitee 点赞关注不迷路 项目地址 快速入门 模块所属层级描述快照版本正式版本wu-database-lazy-lambdalambda针对不同数据源wu-database-lazy-orm-coreorm 核心orm核心处理wu-database-lazy-sqlsql核心处理成处理sql解析、sql执行、sql映射wu-elasticsearch-starterESESwu-hb…...

安科瑞路灯安全用电云平台解决方案【电不起火、电不伤人】
背景介绍 近年来 ,随着城市规模的不断扩大 ,路灯事业蓬勃发展。但有的地方因为观念、技术、管理等方面不完善 ,由此引发了一系列安全问题。路灯点多面广 ,一旦漏电就极容易造成严重的人身安全事故。不仅给受害者家庭带来痛苦 &am…...

MYSQL——索引概念索引结构
索引 索引是帮助数据库高效获取数据的排好序的数据结构。 有无索引时,查询的区别 主要区别在于查询速度和系统资源的消耗。 查询速度: 在没有索引的情况下,数据库需要对表中的所有记录进行扫描,以找到符合查询条件的记录&#…...

Linux(CentOS7)配置系统服务以及开机自启动
目录 前言 两种方式 /etc/systemd/system/ 进入 /etc/systemd/system/ 文件夹 创建 nginx.service 文件 重新加载 systemd 配置文件 编辑 配置开机自启 /etc/init.d/ 进入 /etc/init.d/ 文件夹 创建 mysql 文件 编写脚本内容 添加/删除系统服务 配置开机自启 …...

0 决策树基础
目录 1 绪论 2 模型 3 决策树面试总结 1 绪论 决策树算法包括ID3、C4.5以及C5.0等,这些算法容易理解,适用各种数据,在解决各种问题时都有良好表现,尤其是以树模型为核心的各种集成算法,在各个行业和领域都有广泛的…...

Linux速览(2)——环境基础开发工具篇(其一)
本章我们来介绍一些linux的常用工具 目录 一. Linux 软件包管理器 yum 1.什么是软件包? 2. 查看软件包 3. 如何安装软件 4. 如何卸载软件 5.yum补充 6. 关于 rzsz 二. Linux编辑器-vim使用 1. vim的基本概念 2. vim的基本操作 3. vim正常模式命令集 4. vim末行模式…...

AWS SES发送邮件时常见的错误及解决方法?
AWS SES发送邮件如何做配置?使用AWS SES发信的限制? 在使用AWS SES发送邮件时,可能会遇到一些常见的错误。AokSend将介绍一些常见的AWS SES发送邮件错误及其相应的解决方法,帮助用户更好地利用AWS SES进行邮件发送。 AWS SES发送…...

视频基础学习三——视频帧率、码率与分辨率
文章目录 前言一、介绍1.定义2.三者之间的关系 总结 前言 在之前的文章中详细介绍了一些关于图像的色彩与格式,而视频其实就是由一张张图片进行展示呈现出来的。 我们会经常说一段视频的质量好不好,而什么是视频的质量呢?博主的个人理解就是…...

Spring(详细介绍)
目录 一、简介 1、什么是Spring? 2、Spring框架的核心特性 3、优点 二、IOC容器 介绍 1、获取资源的传统方式 2、控制反转方式获取资源 3、DI 4、IOC容器在Spring中的实现 入门案例 1、创建Maven Module 2、引入依赖 3、创建HelloWorld类 4、在Spring的配…...

Kettle使用
1.准备工作 KETTLE-5.4.zip HANA环境192.168.xx.xx 用户名:system 密码:****** 端口号:30015 Oracle环境 192.168.xx.xx 用户名 HANA_TEST 密码 ****** 端口号:31001 配置java环境变量 因为本次数据转换测试为将HANA数据转换到Or…...

互联网摸鱼日报(2024-04-01)
互联网摸鱼日报(2024-04-01) 36氪新闻 「矽迪半导体」获数千万天使轮融资,提供高效功率半导体方案|硬氪首发 本周双碳大事:国资委即将发布央企ESG指导意见;上海发文推动建立产品碳足迹管理体系;隆基新硅片面世 数字…...

pnpm比npm、yarn好在哪里?
前言 pnpm对比npm/yarn的优点: 更快速的依赖下载更高效的利用磁盘空间更优秀的依赖管理 我们按照包管理工具的发展历史,从 npm2 开始讲起: npm2 使用早期的npm1/2安装依赖,node_modules文件会以递归的形式呈现,严格…...

大前端-postcss安装使用指南
PostCSS 是一款强大的 CSS 处理工具,可以用来自动添加浏览器前缀、代码合并、代码压缩等,提升代码的可读性,并支持使用最新的 CSS 语法。以下是一份简化的 PostCSS 安装使用指南: 一、安装 PostCSS 在你的项目目录中,…...

全局UI方法-弹窗三-文本滑动选择器弹窗(TextPickDialog)
1、描述 根据指定的选择范围创建文本选择器,展示在弹窗上。 2、接口 TextPickDialog(options?: TextPickDialogOptions) 3、TextPickDialogOptions 参数名称 参数类型 必填 参数描述 rang string[] | Resource 是 设置文本选择器的选择范围。 selected nu…...

LibreOffice 将word,excel,PowerPoint文件转换PDF
安装LibreOffice并将Word和Excel文件转换为PDF文件,并设置文件存放路径的步骤如下: 1. 安装LibreOffice 如果尚未安装LibreOffice,可以通过以下命令在Ubuntu上安装: sudo apt update sudo apt install libreoffice 2. 使用Li…...

鱼眼相机的测距流程及误差分析[像素坐标系到空间一点以及测距和误差分析]
由于最近在整理单目测距的内容,顺手也总结下鱼眼相机的测距流程和误差分析,如果有错误,还请不吝赐教。 参考链接: 鱼眼镜头的成像原理到畸变矫正(完整版) 相机模型总结(针孔、鱼眼、全景) 三维…...

谈谈Python中的列表、元组、字典和集合的主要区别和用法
谈谈Python中的列表、元组、字典和集合的主要区别和用法 Python是一种功能强大且易于学习的编程语言,它提供了多种数据结构来支持各种编程需求。其中,列表(list)、元组(tuple)、字典(dictionar…...

【WPF应用24】C#中的Image控件详解与应用示例
在C#应用程序开发中,图像显示是一个常见的需求。无论是创建图形界面还是处理图像数据,System.Windows.Controls.Image控件都是实现这一目标的重要工具。本文将详细介绍Image控件的功能、用法、优化技巧以及一些实际应用示例,帮助开发者更好地…...

CTF题型 php://filter特殊编码绕过小汇总
CTF题型 php://filter特殊编码绕过小汇总 文章目录 CTF题型 php://filter特殊编码绕过小汇总特殊编码base64编码string过滤器iconv字符集 例题1.[Newstarctf 2023 week2 include]2.[Ctfshow web 117] php://filter 是一个伪协议,它允许你读取经过过滤器处理的数据流…...

【嵌入式智能产品开发实战】(十二)—— 政安晨:通过ARM-Linux掌握基本技能【C语言程序的安装运行】
目录 程序的安装 程序安装的本质 在Linux下制作软件安装包 政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 收录专栏: 嵌入式智能产品开发实战 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正…...

网络编程的学习1
网络编程 在网络通信协议下,不同计算机上运行的程序,进行数据传输。 三要素 ip:设备在网络中的地址,是唯一的标识。 ipv4:采取32位地址长度,分成4组。 ipv6:采用128位地址长度,分成8组。 …...

spark log4j日志文件动态参数读取
需要在log4j xml文件中设置动态参数,并支持spark任务在集群模式下,动态参数读取正常; 1.log4j配置文件 log4j2.xml <?xml version"1.0" encoding"UTF-8"?> <Configuration status"info" name&quo…...

设计模式,装修模式,Php代码演示,优缺点,注意事项
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许动态地向一个现有对象添加新的功能或行为,而不改变其原始结构。在 PHP 中,可以使用类的继承和组合来实现装饰模式。下面是一个简单的 PHP 装饰模式示例代码&am…...

ubuntu下vscode ctrl+tab松开ctrl后不自动选中文件
vscode用ctrltab切换文件时,松开ctrl键后会自动选中切换的文件。 但是在ubuntu下发现有时不能自动选中切换的文件,需要再次按enter键才能打开文件。 经过测试发现解决方法有两个: 方法1:确认wayland状态,关闭wayland…...