简单排序算法、二分法以及对数器

简单排序算法、二分法以及对数器

选择排序

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

代码:

/**
 * 选择排序
 */
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位置上
        int minIndex = i;
        for(int j = i+1;j < arr.length;j++){ //从i~N-1上找最小值的下标
            minIndex = arr[j] < arr[minIndex]? j : minIndex;
        }
        swap(arr,i,minIndex);
    }
}

// 交换arr的i和j位置上的值
private static void swap(int[] arr, int i, int j) {
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

冒泡排序

重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

/**
 * 冒泡排序
 */
public static void bubbleSort(int[] arr){
    // 数组为空或数组元素为1则不需要进行排序,直接推出即可
    if(arr == null || arr.length<2){
        return;
    }
    for(int i = 0; i < arr.length-1; i++){ // 控制比较轮次,一共 n-1 趟
        for (int j = 0; j < arr.length-1-i; j++){ // 控制两个挨着的元素进行比较
            if(arr[i] > arr[j]){
                swap(arr,arr[i],arr[j]);
            }
        }
    }
}
// 交换arr的i和j位置上的值  ^ 表示异或运算,不同为1相同为0
// 数组中这样交换两个数的前提是 i j 位置上的数一定不相同 否则i j 位置上的数均会变成0
// 不提倡这么写!!!
private 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];
}

补充:不申请额外空间交换两个数的值

此处的前提是a和b在内存中是两块独立的区域!
在这里插入图片描述

补充:异或面试题

在一个arr数组中里面有n种数

1.一种数出现了奇数次,其它数出现了偶数次,如何找出出现了奇数次的数?

2.两种数出现可奇数次,其它数出现了偶数次,如何找出出现了奇数次的数?

解:

  1. 设置一个变量为ero ero=数组中的所有元素进行异或 ero=出现了奇数次的数

因为异或运算满足交换律和结合律,相同为0,出现偶数次的数异或结果为0,任何数与0异或等于它本身。(类似于消消乐)

  1. 设置一个变量为ero 设a、b为出现了奇数次的数 ero=数组中的所有元素进行异或 ero=a^b不等于0 因为是两种数,故a不等于b

a^b != 0 说明a、b至少有一位不一样,假设第8位的数不一样。将所有的数分为两类,第8位为1的数和第8位为0的数(出现偶数次的数也会进行这样的分类),a和b只会占其中的一侧。eor1去异或第8位为1的数,出现偶数次的数依然不干扰抹成0,此时eor1得到a或者b,此时a、b中的另一个数则用ero^ero1得到!

/**
 * 异或运算面试题 第二问
 */
public static void printOddTimesNum2(int[] arr){
    int eor = 0;
    for(int i : arr){
        eor ^= i;
    }
    // eor = a ^ b
    // eor != 0
    // eor必然有一个位置上是1 选择最右侧的数值为1
    // 将一个不为0的数最右侧的1提取出来  ~eor 表示 eor取反
    int rightOne = eor & (~eor + 1); // 也就是说  一个数 & 自身取反+1 则把自己最右侧的数给弄出来了

    int onlyOne = 0; // eor1
    for(int cur : arr){
        if((cur & rightOne) == 1){ //那个位置 等于 1 我才进行异或  相当于两边只要了一侧
            onlyOne ^= cur;
        }
    }

    System.out.println("两个出现了奇数次的数为: "+ onlyOne + " " + (eor ^ onlyOne));

}

插入排序

指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序 。插入排序相当于每次进行比较时先保证前1号元素有序,前2号元素有序,前3号元素有序,前4号元素有序…

/**
 * 插入排序(优于选择排序和冒泡排序)
 */
public static void insertSort(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,i,j+1);
        }
    }
}

二分法

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

  1. 在一个有序数组中,找某个数是否存在

时间复杂度O(LogN)

  1. 在一个有序数组中,找到>=某个数最左侧的位置

eg: 找到 1 2 2 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 >=3最左侧的数?

此处跟第一点的区别是,二分找一个数是否存在只需要找到即可,但是找一个最左侧的一个数需要一直进行二分!

  1. 局部最小值问题

eg: 数组arr中的元素无序,任何两个相邻数不相等,请你找出一个局部的最小数并且时间复杂度小于O(n)。

局部最小: i-1 i i+1 若数组中i位置上的数即小于i-1上的数又小于i+1上的数则称i为局部最小的数。

0 1 2 … M-1 M M+1 …N-2 N-1

第一次二分找到M,若M小于M-1上的数也小于M-2上的数则返回M。否则从某一侧再次二分继续查找,一定会找到一个局部最小的位置!

对数器的概念和使用

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

对数器顾名思义就是将数据进行相关比较的工具,一般在无OJ环境下进行数据的比较测试,既可以实现不依赖线上平台实现自己数据的测试。比如 有两个方法 一个 方法a(想测) 一个方法b(很简单但是效率不高) 你利用一个随机样本产生器产生一系列的数据 同时将方法a产生的数据与方法b产生的数据进行比较,如果数据不一致则说明其中一个方法出现了错误,一致则说明方法正确。 这也就是对数器的原理

/**
 * 生成随机数组
 */
public static int[] generateRandomArray(int maxSize,int maxValue){

    // Math.random() -> [0,1) 所有的小数,等概率返回一个
    // Math.random() * N -> [0,N) 所有小数等概率返回一个
    // (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个
    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 + 1) * Math.random());
    }

    return arr;
}

public static void main(String[] args) {
   // 对数器代码实现举例
   int testTime = 500000; // 表示测试的次数
   int maxSize = 100; // 确保了每次生成的数的大小范围在0~100之间
   int maxValue = 100; // 确保了每一次生成的数值是在0~100之间
   boolean succeed = true;
    for (int i = 0; i < testTime; i++) {
        int[] arr1 = generateRandomArray(maxSize,maxValue);
        int[] arr2 = copyArray(arr1); // 将数组arr1拷贝一份
        insertionSort(arr1);
        comparator(arr2);
        if(!isEqual(arr1,arr2)){
            succeed = false;
            break;
        }
    }
	system.out.println(succeed);
}

猜你喜欢

转载自blog.csdn.net/ailaohuyou211/article/details/127128452
今日推荐