算法——认识复杂度、对数器、二分法与异或运算

152 阅读5分钟

1.认识复杂度和简单排序算法

评估算法优劣的核心指标是什么?

  • 时间复杂度(流程决定)
  • 额外空间复杂度(流程决定)
  • 常数项时间(实现细节决定)

何为常数时间操作?

如果一个操作的执行时间不以具体样本量为转移,每次执行时间都是固定时间。这样的操作为常数时间的操作。

常见的常数时间的操作

  • 常见的算数运算(+、-、*、/、%等)
  • 常见的位运算(>>、>>>、<<、<<<、|、&、^等)
  • 赋值、比较、自增、自减操作等
  • 数组寻址操作

如何确定算法流程的总操作数量与样本数量之间的表达式关系?

  1. 想象该算法流程所处理的数据状况,要按照最差情况来。
  2. 把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作。
  3. 如果数据量为N,看看基本动作的数量和N是什么关系

时间复杂度

时间复杂度是一个算法流程中,常数操作数量的一个指标。具体来说先对一个算法流程非常熟悉,然后去写出这个算法流程中,发生了多少常数操作,进而总结出常数操作量的表达方式。

如何确定算法流程的时间复杂度?

当完成了表达式的建立,只留下最高阶项留下即可。低价项都去掉,高阶项的常数也去掉。记为:O(忽略掉系数的高阶项)

选择排序

  • 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
  • 过程:

image.png

  • 代码实现
public static  void selectSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;
    }
    // 0 ~ N-1  找到最小值,在哪,放到0位置上
    // 1 ~ n-1  找到最小值,在哪,放到1 位置上
    // 2 ~ n-1  找到最小值,在哪,放到2 位置上
    int N = arr.length;
    for (int i = 0; i < N; i++) {
        int min = i;
        for (int j = i+1; j < N; j++) {// i ~ N-1 上找最小值的下标 
            min = arr[j] < arr[min] ? j :min;
        }
        swap(arr,i,min);
    }
}
public  static void swap(int[] arr,int i ,int j){
    int tmp = arr[j];
    arr[j] = arr[i];
    arr[i] = tmp;
}

冒泡排序

  • 重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
  • 过程:

image.png

  • 代码实现:
public static void bubbleSort(int[] arr){
    //判断数组是否为空或者数组长度是否只有一个元素
    if(arr == null || arr.length < 2){
        return;
    }
    for (int end = arr.length-1; end >0 ; end--) {
        for (int i = 0; i < end; i++) {
            if(arr[i]>arr[i+1]){
                swap(arr,i,i+1);
            }
        }
    }
}

//交换函数swap
public  static void swap(int[] arr,int i ,int j){
    int tmp = arr[j];
    arr[j] = arr[i];
    arr[i] = tmp;
}

插入排序

  • 对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增 1 的有序表 。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
  • 过程:

image.png

  • 估算时发现这个算法流程的复杂度,会因为数据状况的不同而不同。

image.png

  • 代码实现:
public static void insertionSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;
    }
    for (int i = 1; i <arr.length ; i++) {//0~i做到有序
        for (int j = i-1; j >= 0 && arr[j] > arr[j+1] ; j--) {
            swap(arr,j,j+1);
        }
}
//交换函数swap
public  static void swap(int[] arr,int i ,int j){
    int tmp = arr[j];
    arr[j] = arr[i];
    arr[i] = tmp;
}

额外空间复杂度

  • 你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。
  • 作为输入参数的空间,不算额外空间。
  • 作为输出结果的空间,也不算额外空间。
  • 因为这些都是必要的、和现实目标有关的,所以都不算。
  • 但除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去,这部分空间就是额外空间。
  • 如果你的流程只需要开辟,有限几个变量,额外时间复杂度就是O(1)。

常数项时间

时间复杂度是忽略低价项和所有常数系数的。如果两个时间复杂度一样的算法,还要去在时间上拼优劣,就进入到拼常数时间的阶段,简称拼常数项

算法流程的常数项的比拼方式

  • 放弃理论分析,生成随机数直接测
  • 如果纯理论分析,往往会需要非常多的分析过程。都已经到了具体细节的程度,莫不如交给实验数据好了。

2.对数器

  1. 你想要测得的方法a
  2. 实现复杂度不好但容易实现的方法b
  3. 实现一个随机样本产生器
  4. 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样
  5. 如果有一个随机样本使得对比结果不一致,打印样本进行人工干预,改对方法a和方法b
  6. 当样本数量很多时对比测试依然正确,可以确定方法a已经正确。 image.png

3.二分法

认识二分法

  1. 在一个有序数组中,找某个数是否存在
  2. 在一个有序数组中,找>=某个数最左侧的位置
  3. 在一个有序数组中,找<=某个数最右侧的位置
  4. 局部最小值问题

4.异或运算

认识异或运算

  • 相同为0,不同为1
  • 可以理解为:异或运算就记成无进位相加

image.png
异或运算的性质

  1. 0^N==N,N^N==0
  2. 异或运算满足交换律和结合律

题目一:如何不用额外变量交换两个数

  • 思路:利用N^N==0,a=a^b^a,b=a^b^b
  • 注意:前提是a和b的内存地址不同,地址相同时会被洗成0
  • 代码实现:
public static void swap(int a,int b){
    a=a^b;
    b=a^b;//b=a^b^b-->b=a^0-->b=a
    a=a^b;//a=a^b^a-->a=b^0-->a=b
}

题目二:一个数组中有一种数出现了奇数次,其他都出现了偶数次,怎么找到并打印这种数。

  • 思路:int eor=0;把eor从数组头异或到数组尾,结束时eor就是出现了奇数次的数

image.png

  • 代码实现:
public static void OddTimesNum(int arr[],int len){
    int eor = 0;
    for (int i = 0; i < len; i++) {
        eor ^= arr[i];
    }
    System.out.println("数组中次数为奇数的数为: " + eor);
}

题目三:怎么把一个int类型的数,提取出最右侧的1来。

  • 思路:N & (~N +1)

image.png

  • 代码实现:
int rightOne = eor & (~eor + 1);//提取出最右边的1

题目四:一个数组中有两种数出现了奇数次,其他都出现了偶数次,怎么找到并打印这两种数。

  • 思路:int eor =0;设出现奇数次数为a和b,把eor从数组头异或到数组尾,结束时eor就是a^b
  • 所以eor=a^b (a!=b ——》eor!=0)
  • eor一定在某一位(至少一位)不等于0 ,假设第X位为1,说明a和b在第X位不一样
  • int eor'=0;把eor’从数组头异或到数组尾,只异或数组中那些X位不为0的数,结束时eor'就是a或者b

image.png

  • X位0的数不影响结果,只异或数组中那些X位不为0的数时,other2中1的个数为偶数次全消除,只剩a或b中一个, eor'只能碰到a或者b中一个,得到的结果就是另外一个eor^eor' 是a或b的另外一个
  • 代码实现:
public static void printOddTimesNum2(int[] arr) {
    int eor = 0;
    for (int i=0;i<arr.length;i++) {
        eor^= arr[i];
    }
    //eor=a^b
    //eor!=0
    //eor必然有一个位置是1

    int rightOne = eor & (~eor + 1);//提取出最右边的1

    int eorhasone=0;//eor'
    for (int cur : arr) {
        if ((cur & rightOne) != 0) {
            eorhasone ^= cur;
        }
    }
    System.out.println(eorhasone + " " + (eor ^ eorhasone));
}