简单排序算法、二分法以及对数器
选择排序
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
代码:
/**
* 选择排序
*/
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.两种数出现可奇数次,其它数出现了偶数次,如何找出出现了奇数次的数?
解:
- 设置一个变量为ero ero=数组中的所有元素进行异或 ero=出现了奇数次的数
因为异或运算满足交换律和结合律,相同为0,出现偶数次的数异或结果为0,任何数与0异或等于它本身。(类似于消消乐)
- 设置一个变量为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);
}
}
}
二分法
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
- 在一个有序数组中,找某个数是否存在
时间复杂度O(LogN)
- 在一个有序数组中,找到>=某个数最左侧的位置
eg: 找到 1 2 2 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 >=3最左侧的数?
此处跟第一点的区别是,二分找一个数是否存在只需要找到即可,但是找一个最左侧的一个数需要一直进行二分!
- 局部最小值问题
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。否则从某一侧再次二分继续查找,一定会找到一个局部最小的位置!
对数器的概念和使用
- 有一个你想要测的方法a
- 实现复杂度不好但是容易实现的方法b
- 实现一个随机样本产生器
- 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
- 如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者方法b
- 当样本数量很多时比对测试依然正确,可以确定方法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);
}