文章目录
前言
一、选择排序,冒泡排序,时间复杂度细节的讲解与复杂度分析
估计常数操作的指标
1.1常数操作
例如
- 数组的直接取用,所做的操作其实只是做一个偏移量,它就是一个常熟操作
- 链表得到i位置的值list.get(i),从左往右遍历找到该位置,则不是常数操作
- 同样的还有:加减乘除,位运算
总结:只要与数据量无关,就是常熟操作,与数据量有关的就不是常数操作。
1.2选择排序
1.2.1算法思路
取一个数组,遍历一遍,找出最小(最大值)讲这个数和第一位进行交换,这样第一位就是最大(小)的了。接着对剩下的遍历找到最值与第二位交换这样不断重复就可以找到最小值了1.2.2时间复杂度的分析
1.数组索引第一次N,第二次N-1,第三题N-2等等(N+N-1+N-2+……+1)=O(N^2) 2.比较同数组索引O(N^2) 3.交换共N次 所以常数操作可以表示为aN^2+bN+c 忽略最次数小的项(应为当数据量达到一定程度的时候其影响微乎其微) 则为O(N^2)1.2.3代码实现
public class SelectionSort {
public static void selectionsort(int [] arr) {
if( arr == null && arr.length < 2 ) {
return;
}
int l = arr.length;
for(int i = 0; i < l; i++ ) {
int min = i ;
for( int j = i+1 ; j < l; j++ ) {
min = arr[min] < arr[j] ? min : j;
}
swap(arr, i, min);
}
}
public static void swap(int [] arr, int i , int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
当BIG O()相等时
我们必须实际去跑,去允许去测试。应为对与常数的数量与时间并不是完全成正比,因为没一个常数操作的时间不一样。例如位运算与加减乘除运算的时间效率完全不一样
1.3冒泡排序
1.3.1算法思路
取一个数组交换与比较穿插进行,遍历一遍,比较相邻两个数,然后判断是否需要交换,最后确定一个数的位置。然后遍历N遍即可排序完1.3.2时间复杂度分析
1.显然查找O(N^2)次 2.交换也是O(N^2)次 3.即时间复杂度位O(N^2)1.3.3代码的实现
代码如下
public class BubbleSort {
public static void bubblesort(int[] arr) {
if(arr == null || arr.length < 2) {
return ;
}
for (int e = arr.length -1 ; e > 0 ; e-- ) {
for(int i = 0 ; i < e ; i ++ ){
if(arr[i]>arr[i+1]) {
swap(arr, i ,i+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];
}
}
二、插入排序算法分析与复杂度
1.插入排序
将数组分成两部分,一部分已经排好序,另一部分没排好序。不断的从没排好序的集合中选一个出来放到已排好序的数组中。然后不断的交换值。
2.代码
public class insertionSort {
public static void insertionSort(int [] arr) {
if(arr == null || arr.length < 2) {
return ;
}
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);
}
}
}
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];
}
}
3.时间复杂度
显然从一个集合中取需要N次,然后在另一个集合中交换需要N次。所以时间复杂度为BIG O (N^2)
但是那个只是最差情况,其实最好情况是BIG O(N)
但是这个数值对于算法学习没有用,所以一般不用
还有两个不常用的符号
一个平均时间复杂程度
一个是最简时间复杂程度
只不过对于算法学习没有用就不做多说
三、二分法的详解与扩展
1.二分法详解
@1.在有序的数组中的查找方法,二分查找。基本思想是看中点是不是满足条件,不满足修正端点,再次看中点是否满足题意。
一般思路是:初始化端点和中点
然后while(L<R)
赋值中点
然后是否满足条件 返回
然后情况一,正左端点=中值
情况二,右端点=中值。
(可能出现第三种情况)
2.在一个有序数组中,找某个数是否存在
public static boolean Exist( int [] arr,int num) {
if(arr == null || arr.length == 0) {
return false;
}
int L = 0;
int R = arr.length - 1;
int mid = 0;
while(L<R) {
mid = L + ((R-L) >> 1);
if(arr[mid] == num) {
return true;
} else if(arr[mid]>num) {
R = mid - 1 ;
}else {
L= mid + 1;
}
}
return arr[L] == num;
}
3.在一个有序数组中,找>=某个数最左侧的位置
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 (arr[mid] >= value) {
index = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
return index;
}
4.局部最小值问题
局部最小值问题使用二分法,就证明着,二分法,有时并不需要严格有序,只要能保证二分后,必有问题的一个解就行
局部最小,也就是局部极小值,有点像变化率:
a[i]<a[i-1]且a[i]<a[i+1]有点像极小值
要求:
用时间复杂度BIG O(N)的方法,找出一个数组中的一个局部最小值。且已知相邻两个数组的值不一样
思路:
先判断两个端点:
a[0]<a[1]
a[n-1]<a[n-2]
满足即找到了
然后再踏入二分
public int findPeak(int[] nums) {
int N = nums.length;
if (N == 1) {
return 0;
}
if (nums[0] < nums[1]) {
return 0;
}
if (nums[N - 1] < nums[N - 2]) {
return N - 1;
}
int low = 0, high = N - 1;
while (low < high) {
int mid = (low + high) / 2;
if (nums[mid - 1] > nums[mid] && nums[mid] < nums[mid + 1]) {
return mid;
} else if (nums[mid - 1] < nums[mid] && nums[mid] < nums[mid + 1]) {
high = mid;
} else if (nums[mid - 1] > nums[mid] && nums[mid] > nums[mid + 1]) {
low = mid;
}
}
return -1;
}
四、异或运算的性质与扩展
1)无进位相加
1.无论这个数是什么,只要它与零异或,它就会使本身。因为1^0=1 , 0 ^ 0=0; 2.任何数与自己本身想异或结果为零 N^N=0; 3.异或运算可以理解为无进位的相加2)异或运算的性质
1.N^0=N ;N ^ N =0; 2.满足交换律和结合律 a^b=b ^ a ; a ^ b ^ c = a ^ (b ^ c) 3.无论怎么排列一堆数,他们异或总起来总会是一个数,因为可以用交换律将他们组合成一个异或3)不用额外变量交换两个数
代码如下:
int a=3; int b =4;
a=a^b;
b=a^b;
a=a^b;
### 设计的思想
特别特别注意一定要求交换的两个东西的地址是不同的,值可以一样,但是内存必需不是一个东西。一旦相等就出问题了
4)一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数
算法设计思路
将数组的所有数值异或到同一个值上
这样,出现偶数次的全部变成零,出现奇数次的就成为剩下的
代码
public static void find1(int [] arr) {
int e = 0;
for(int cur : arr) {
e ^= cur;
}
System.out.println(e);
}
5)一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数
算法设计思路
首先异或一次得到两个数的异或结果
然后用这个异或结果对数组进行分类
分类的方法,找出一个二进制位为一的位
在这个位上,两个出现奇数次的数字,值必不相同。
然后但独将两个类分别异或,则得到值
也可以用得到的一个值与一开始异或的值进行异或。即可得到该值。
代码
public static void find2(int [] arr) {
int eo = 0;
for(int cur : arr ) {
eo ^= cur;
}
int rone = 0;
rone = eo &(~eo + 1);
int a = 0 ,b = 0;
for(int cur : arr ) {
if((cur & rone)==0) {
a ^= cur ;
}
b=a^eo;
}
}
五、对数器的概念和使用
1.概念
对数器的概念和使用
1,有一个你想要测的方法a
2,实现复杂度不好但是容易实现的方法b
3,实现一个随机样本产生器
4,把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
5,如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者 方法b
6,当样本数量很多时比对测试依然正确,可以确定方法a已经正确
2.随机样本生成器
2.1Math.random()
1.Math.random()->[0,1) 所有小数等概率返回一个
2.NMath.random()->[0,N)所有小数,等概率返回一个
3.int(NMath.random())->[0,N-1)所有整数返回一个
如何实现正负相减:
int(NMath.random()+1)-int(NMath.random()+1)
3.两排序算法实现对数
注意:
这里主方法里面for循环的使用,它确立测试次数
public class Code03_InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
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);
}
}
}
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];
}
// for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
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;
}
// for test
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;
}
// for test
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;
}
// for test
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();
}
// for test
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);
insertionSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
insertionSort(arr);
printArray(arr);
}
}