1.复杂度、对数器、二分法与异或运算

简介: 1.复杂度、对数器、二分法与异或运算

 

三大排序算法

通过使用对数器来检验,可以保证代码的正确性。

1)选择排序

package com.harrison.one;
import java.util.Arrays;
//选择排序
public class Class01_SelectionSort {
  // 选择排序核心代码
  public static void selectionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
      return;
    }
    for (int i = 0; i < arr.length - 1; i++) {// i~N-1
      // 最小值在哪个位置上,i~N-1
      int minIdx = i;
      for (int j = i + 1; j < arr.length; j++) {// i~N-1上找最小值的下标
        minIdx = arr[j] < arr[minIdx] ? j : minIdx;
      }
      swap(arr, i, minIdx);
    }
  }
  // 交换两个元素
  public static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
  // 产生随机数组,长度随机,且值也随机
  public static int[] generateRandomArray(int maxSize, int maxValue) {
    int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
    for (int i = 0; i < arr.length; i++) {
      arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
    }
    return arr;
  }
  // copy数组
  public static int[] copyArray(int[] arr) {
    if (arr == null) {
      return null;
    }
    int[] res = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
      res[i] = arr[i];
    }
    return res;
  }
  // 写一个比较器的方法:系统提供的排序的方法,肯定是对的
  public static void comparator(int[] arr) {
    Arrays.sort(arr);
  }
  // 比较两个数组是否完全一样
  public static boolean isEqual(int[] arr1, int[] arr2) {
    if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
      return false;
    }
    if (arr1 == null && arr2 == null) {
      return true;
    }
    if (arr1.length != arr2.length) {
      return false;
    }
    for (int i = 0; i < arr1.length; i++) {
      if (arr1[i] != arr2[i]) {
        return false;
      }
    }
    return true;
  }
  //打印数组
  public static void printArray(int[] arr) {
    if (arr == null) {
      return;
    }
    for (int i = 0; i < arr.length; i++) {
      System.out.print(arr[i] + " ");
    }
    System.out.println();
  }
  public static void main(String[] args) {
    int testTime = 500000;
    int maxSize = 100;
    int maxValue = 100;
    boolean succeed = true;
    for (int i = 0; i < testTime; i++) {
      int[] arr1 = generateRandomArray(maxSize, maxValue);
      int[] arr2 = copyArray(arr1);
      selectionSort(arr1);
      comparator(arr2);
      if (!isEqual(arr1, arr2)) {// 相同返回1,不相同返回0
        printArray(arr1);
        printArray(arr2);
        break;
      }
    }
    System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    int[] arr = generateRandomArray(maxSize, maxValue);
    printArray(arr);
    selectionSort(arr);
    printArray(arr);
  }
}

由于只有核心部分代码不一样,所以只放核心部分代码

2)冒泡排序

public static void bubbleSort(int[] arr) {
    if (arr == null || arr.length < 2) {
      return;
    }
    for (int e = arr.length - 1; e > 0; e--) {// 0~e
      for (int i = 0; i < e; i++) {
        if (arr[i] > arr[i + 1]) {
          swap(arr, i, i + 1);
        }
      }
    }
  }

3)插入排序

public static void insertionSort(int[] arr) {
    if (arr == null || arr.length < 2) {// 0~0位置上有序
      return;
    }
    // 1~i位置上有序
    for (int i = 1; i < arr.length; i++) {
      for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
        swap(arr, j, j + 1);
      }
    }
  }

认识随机函数

  • Math.random():[0,1)范围上所有的小数等概率返回一个(前闭后开)
  • Math.random()*N:[0,N)
  • (int)(Math.random()*N):[0,N-1]
  • [0,N]:(int)(N+1)* ((maxValue+1)*Math.random());
  • [-?,+?]:(int)((maxValue+1)* Math.random())-(int)(maxValue*(maxValue+1) *Math.random());

认识二分法

在一个有序数组中:1)找某个数是否存在;2)找>=某个数最左侧的位置;3)找<=某个数最右侧的位置

1)找某个数是否存在

package com.harrison.one;
import java.util.Arrays;
//在一个有序数组中,找某个数是否存在
public class Class04_BSExist {
  // 二分法核心代码
  public static boolean exist(int[] sortedArr, int num) {
    if (sortedArr == null || sortedArr.length == 0) {
      return false;
    }
    int L = 0;
    int R = sortedArr.length - 1;
    int mid = 0;
    while (L < R) {
      mid = L + ((R - L) >> 1);
      if (num == sortedArr[mid]) {
        return true;
      } else if (num < sortedArr[mid]) {
        R = mid - 1;
      } else {
        L = mid + 1;
      }
    }
    return sortedArr[L] == num;
  }
  //另写的一个方法,不使用二分法
  public static boolean test(int[] sortedArr, int num) {
    for (int cur : sortedArr) {
      if (cur == num) {
        return true;
      }
    }
    return false;
  }
  // 产生随机数组,长度随机,且值也随机
  public static int[] generateRandomArray(int maxSize, int maxValue) {
    int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
    for (int i = 0; i < arr.length; i++) {
      arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
    }
    return arr;
  }
  public static void main(String[] args) {
    int testTime = 500000;
    int maxSize = 10;
    int maxValue = 100;
    boolean succeed = true;
    for (int i = 0; i < testTime; i++) {
      int[] arr = generateRandomArray(maxSize, maxValue);
      Arrays.sort(arr);
      int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
      if (test(arr, value) != exist(arr, value)) {
        succeed = false;
        break;
      }
    }
    System.out.println(succeed ? "Nice!" : "Fucking fucked!");
  }
}

2)找>=某个数最左侧的位置

package com.harrison.one;
import java.util.Arrays;
//在一个有序数组中,找>=某个数最左侧的位置
public class Class05_BSNearLeft {
  // 在arr上,找>=value的最左位置
  public static int nearestIndex(int[] arr, int value) {
    int L = 0;
    int R = arr.length - 1;
    int index = -1;// 记录最左的对号
    while (L <= R) {
      int mid = L + ((R - L) >> 1);
      if (value <= arr[mid]) {
        R = mid - 1;
        index = mid;
      } else {
        L = mid + 1;
      }
    }
    return index;
  }
  //另写的一个方法,不使用二分
  public static int test(int[] arr, int value) {
    for (int i = 0; i < arr.length; i++) {
      if (arr[i] >= value) {
        return i;
      }
    }
    return -1;
  }
  // 产生随机数组,长度随机,且值也随机
  public static int[] generateRandomArray(int maxSize, int maxValue) {
    int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
    for (int i = 0; i < arr.length; i++) {
      arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
    }
    return arr;
  }
  // 打印数组
  public static void printArray(int[] arr) {
    if (arr == null) {
      return;
    }
    for (int i = 0; i < arr.length; i++) {
      System.out.print(arr[i] + " ");
    }
    System.out.println();
  }
  public static void main(String[] args) {
    int testTime = 500000;
    int maxSize = 10;
    int maxValue = 100;
    boolean succeed = true;
    for (int i = 0; i < testTime; i++) {
      int[] arr = generateRandomArray(maxSize, maxValue);
      Arrays.sort(arr);
      int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
      if (test(arr, value) != nearestIndex(arr, value)) {
        printArray(arr);
        System.out.println(value);
        System.out.println(test(arr, value));
        System.out.println(nearestIndex(arr, value));
        succeed = false;
        break;
      }
    }
    System.out.println(succeed ? "Nice!" : "Fucking fucked!");
  }
}

3)找<=某个数最右侧的位置

// 在arr上,找<=value的最右位置
  public static int nearestIndex(int[] arr, int value) {
    int L = 0;
    int R = arr.length - 1;
    int index = -1;// 记录最右的对号
    while (L <= R) {
      int mid = L + ((R - L) >> 1);
      if (value >= arr[mid]) {
        L = mid + 1;
        index = mid;
      } else {
        R = mid - 1;
      }
    }
    return index;
  }

补充

使用二分法时,mid=(L+R)/2,如果L和R数值很大很大时,mid会溢出,所以比较好的一种方式是写成:

mid=L+(R-L)/2 => mid=L+((R-L)>>1); >>:带符号右移1位;>>>:不带符号位右移(不管最高位是0还是1,都用0来补)。

另一个写成这样的原因就是位运算就是比除运算快!!!

4)局部最小值问题

题目:给定一个数组,无序,任意两个相邻的数都不相等,返回任意一个局部最小

分析:可以用二分法,不一定要有序才能二分!只要有二分的标准,在二分的过程中,有一种排他性的原则出现,就可以二分!!!

关键代码:mid>mid-1 => right=mid-1 mid>mid+1 => left=mid+1

public static int getLessIndex(int[] arr) {
    if(arr==null || arr.length==0) {
      return -1;//no exist
    }
    if(arr.length==1 || arr[0]<arr[1]) {
      return 0;
    }
    if(arr[arr.length-1]<arr[arr.length-2]) {
      return arr.length-1;
    }
    int left=1;
    int right=arr.length-2;
    int mid=0;
    while(left<right) {
      mid=(left+right)/2;
      if(mid>mid-1) {
        right=mid-1;
      }else if(mid>mid+1) {
        left=mid+1;
      }else {
        return mid;
      }
    }
    return left;
  }

认识异或运算

  • 异或运算:相同为0,不同为1
  • 同或运算:相同为1,不同为0

容易记混!更好的记忆方式:异或运算即无进位相加

异或运算的性质

  • 0^N==N
  • N^N==0
  • 异或运算满足交换律和结合律(即同样一批数异或的结果与顺序无关)

利用异或运算的性质进行骚操作

1)不用额外变量交换两个数

  • int a=甲 int b=乙
  • a=a^b
  • b=a^b
  • a=a^b

前提:a和b不能指向同一片内存,哪怕值相同也行,但是就是不能指向同一片内存

2)一个数组中有一种数出现了奇数次,其它数都出现了偶数次,怎么找到并打印这种数

3)怎么把一个整型的数,提取出最右侧的1来 => int Ans=N&((~N)+1)

4)一个数组中有两种数出现了奇数次,其它数都出现了偶数次,怎么找到并打印这两种数

5)给出一个数,求该数二进制1的个数

public static int bit1Counts(int N) {
    int count=0;
    // N    00100110100
    // rightOne 00000000100
    // N    00100110000
    // rightOne 00000010000 
    while(N!=0) {
      int rightOne=N&((~N)+1);
      count++;
      N^=rightOne;
    }
    return count;
  }

代码:

package com.harrison.one;
//一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数
public class Class07_EvenTimesOddTimes {
  //arr中,只有一种数,出现奇数次
  public static void printOddTimesNum1(int [] arr) {
    int eor=0;
    for(int i=0; i<arr.length; i++) {
      eor^=arr[i];
    }
    System.out.println(eor);
  }
  //arr中,有两种数,出现奇数次
  public static void printOddTimesNum2(int [] arr) {
    int eor=0;
    for(int i=0; i<arr.length; i++) {
      eor^=arr[i];
    }
    //eor==a^b
    //因为a和b不相等,所以eor!=0
    //eor必然在某个位置上有1
    //找eor最右侧的1
    int rightOne=eor&((~eor)+1);
    int onlyOne=0;//eor'
    for(int i=0; i<arr.length; i++) {
      //如果一个数 和 一个位置上有1的数 按位与 不等于0,说明这个数的这个位置上为1
      if((arr[i]&rightOne)!=0) {
        //eor'重新异或arr数组,但是只异或最右侧位置上为1的数
        onlyOne^=arr[i];
      }
    }
    System.out.println(onlyOne+" "+(eor^onlyOne));
  }
  //给出一个数,求该数二进制1的个数
  public static int bit1Counts(int N) {
    int count=0;
    // N    00100110100
    // rightOne 00000000100
    // N    00100110000
    // rightOne 00000010000 
    while(N!=0) {
      int rightOne=N&((~N)+1);
      count++;
      N^=rightOne;
    }
    return count;
  }
  public static void main(String[] args) {
    int a = 5;
    int b = 7;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    System.out.println(a);
    System.out.println(b);
    int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 };
    printOddTimesNum1(arr1);
    int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 };
    printOddTimesNum2(arr2);
  }
}

   

相关文章
|
7月前
|
算法 Java 程序员
认识复杂度、对数器、二分法
认识复杂度、对数器、二分法
59 1
|
7月前
|
算法 程序员
【算法训练-二分查找 四】【模拟二分】X的平方根
【算法训练-二分查找 四】【模拟二分】X的平方根
42 0
|
算法
【算法专题突破】二分查找 - x 的平方根(18)
【算法专题突破】二分查找 - x 的平方根(18)
70 0
|
4月前
|
算法
【算法】二分算法——x的平方根
【算法】二分算法——x的平方根
|
7月前
|
算法 测试技术 C++
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
|
Java
简单计算时间复杂度
简单计算时间复杂度
35 1
|
算法 内存技术
求组合数三种算法
求组合数三种算法
84 0
算法的时间复杂度比较,计算多项式的直接法和秦九韶法
算法的时间复杂度比较,计算多项式的直接法和秦九韶法
算法的时间复杂度比较,计算多项式的直接法和秦九韶法
|
机器学习/深度学习 算法 Java
如此简单的时间复杂度计算方法:大O渐进法,你确定不进来康康
如此简单的时间复杂度计算方法:大O渐进法,你确定不进来康康
166 0
如此简单的时间复杂度计算方法:大O渐进法,你确定不进来康康
|
人工智能 算法
排序不等式(贪心)
复习acwing算法基础课的内容,本篇为讲解基础算法:贪心——排序不等式,关于时间复杂度:目前博主不太会计算,先鸽了,日后一定补上。
106 0
排序不等式(贪心)