复杂度+简单排序算法
时间复杂度
可以用选择排序进行举例,选择排序就是每次遍历确定最小值,将最小值和每次遍历的第一个数交换位置,假设N个数
第一次比,遍历N个数,比较N次,交换1次 (此时第1个数已经确定,不必再动)
第二次比,遍历N-1个数,比较N-1次,交换1次(此时第2个数已经确定,不必再动)
…
以此往复,总时间
- 遍历 N + N-1 + N-2 + …
- 比较 N + N-1 + N-2 + …
- 比较 1 + 1 + 1 + … = N
利用通式表示:a * (N^2) + b * N + c
,因为前两个都是等差数列,等差数列求和必有二次一次常数项,所以将三式整合形成上式,在看时间复杂度的时候只看最高次数,而且舍弃最高次数的系数,因此选择排序的时间复杂度就是 N^2,表示为 O(N^2)
空间复杂度
还是用选择排序举例:
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[minIndex] < arr[j] ? minIndex : j;
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
代码中需要开辟空间的量就是:i、j、minIndex、tmp,并且每次循环都会释放空间进行重新开辟,所以每次都是四个有限变量的空间,所以空间复杂度为O(1)
异或交换两数
异或运算的知识就是相同为0,不同为1,但实际上可以理解为无进位相加
- 第一个数:1 0 1 1 0
- 第二个数:0 0 1 0 1
- 异或结果:1 0 0 1 1
异或的性质:
- 性质1:0 异或任何数还是等于任何数
- 性质2:自己和自己异或结果为0
- 性质3:异或满足交换律、结合律,a和b的异或等于b和a的异或,abc = a(bc)
交换两个数采用异或手段:
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];
}
如何理解上述代码用异或交换两个数?我们假设有这样两个数:a=甲 b=乙,要去交换a和b的值
- 执行a = a ^ b ,现在 a = 甲 ^ 乙,b = 乙
- 执行b = a ^ b ,现在 a = 甲 ^ 乙,b = 甲 ^ 乙 ^ 乙 = 甲 ^ 0 = 甲 (利用结合律和性质1)
- 执行a = a ^ b ,现在 a = 甲 ^ 甲 ^ 乙 = 0 ^乙 = 乙,b = 甲
最后结果a=乙 b=甲,完成交换
- 好处:不用另外开辟一个新的空间变量tmp
- 坏处:要保证两个数的内存地址不是指向同一处,同一处地址会将值变成0
题目:
1)一组数组,一种数出现奇数个,其余数都是偶数个,找出奇数值,要求时间复杂度O(n),空间复杂度O(1)
解答:声明一个变量,用这个变量遍历异或数组每一个数,最后结果就是那个奇数
2)一组数组,两种数(两数不相等)出现奇数个,其余数都是偶数个,找出奇数值,要求时间复杂度O(n),空间复杂度O(1)
解答:声明变量eor、eor‘,设两个为奇数个的数为a、b
- eor遍历异或数组每一个数,得到结果必为 a^b
- a和b不相等,势必导致eor至少有一位为1,则可以按照这一位对数组全部数分类,势必导致a和b不在一类
- eor’遍历异或该位为1的类别的数,得到结果必为a或者b
- eor和eor‘异常得到另外一个数,这样a和b就都找出
public static void printOddTimeNum2(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
int rightOne = eor & (~eor + 1); //取出最右边的1
int onlyOne = 0; //eor'
for (int i = 0; i < arr.length; i++) {
if ((arr[i] & rightOne) == 0) {
onlyOne ^= arr[i];
}
}
System.out.println(onlyOne + " " + (eor ^ onlyOne)); //找出两个数
}
位运算是要比算术运算快的多
插入排序
排序流程:
第一次排序:0位置和0位置比有序,0有序
第二次排序:1位置和0位置比,若0位置大于1位置数,交换位置,0-1有序
第三次排序:2位置和1位置比,若1位置大于2位置数,交换位置;1位置和0位置比,若0位置大于1位置数,交换位置,到前面没有可比较的数停止,0-2有序
…
循环往复
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
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];
}
那明显有时候数据好坏完美影响排序的效率,最差的情况就是完全倒序的,最好的情况只用比较一次,此时时间复杂度如何计算?
- 需要考虑最差情况下的时间复杂度,也就是插入排序的时间复杂度就是
O(N^2)
二分法
有序数组找寻某个数是否存在,可采用二分法
查找流程:
第一次查找:先找到中间数a,和要查找的数比较,看大于还是小于,如果一样那直接找到了,若大于,在大于的那一半内寻找
第二次查找:在大于这一半再找中间数进行比较,看大于还是小于,如果一样那直接找到了,若大于,在大于的那一半内寻找
…
循环往复,假设原来16个,最差的情况一直切一半查找:8、4、2、1,则此时的时间复杂度就是O(log₂N),默认将2省略,就是O(logN)
补充:
-
有序数组,找出≥某个数最左侧的位置
- 利用二分,位置就是被二分的数用一个变量记录,要找最左侧,就看左边二分有没有满足≥该数这个条件,满足,那这个位置记录变量就要随之改变,不满足不变
-
局部最小值问题
:数组无序,但是任何两个相邻数不相等,找出一个局部最小值(分三种情况:为0位置,需要比1位置值小;为n-1位置,需要比n-2位置值小;为中间位置i,需要比i-1,i+1位置值小)要求时间复杂度小于O(N)- 采用二分,先去查验0位置和N-1位置,如何二者其一有一满足上述最小值情况直接返回,如果都不满足,比是下图情况,中间存在局部最小
- 找到中值点看其是否满足上述最小值情况,不满足,必定有一边是上图情况,继续二分,循环往复找到局部最小值
对数器
比较两种方法结果是否相同,机器比较,防止出现异常情况
/**
* 对数器
*/
public class code05 {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
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];
}
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
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 int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
System.arraycopy(arr, 0, res, 0, arr.length);
return res;
}
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == 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 value : arr) {
System.out.print(value + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 1000;
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);
insertionSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "good" : "bad");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
insertionSort(arr);
printArray(arr);
}
}