算法入门篇 一 时间复杂度

时间复杂度

  • 要求:只要高阶项,不要低阶项
  • 常数操作:操作花费的时间和数据量无关,比如数组寻址,直接利用偏移量找到对应元素的位置;
  • 非常数操作:比如list(链表);查找元素需要遍历链表,元素查找时间和元素对应的位置有关;时间复杂度为O(n)
  • 常数时间操作:两个数相加(+)  相减(-) 左移(<<)  右移(>>) 或运算(|) 与运算(&) 异或运算(^) 

例子  选择排序  引出时间复杂度

package class01;

import java.util.Arrays;

public class Code01_SelectionSort {

	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[j] < arr[minIndex] ? j : minIndex;
			}
			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;
	}

	// 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);
			selectionSort(arr1);
			comparator(arr2);
			if (!isEqual(arr1, arr2)) {
				succeed = false;
				printArray(arr1);
				printArray(arr2);
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");

		int[] arr = generateRandomArray(maxSize, maxValue);
		printArray(arr);
		selectionSort(arr);
		printArray(arr);
	}

}
  • 选择排序:先从[0->N-1]范围内找最小值,对于每个位置上元素的操作时间为c;再从[1->N-1]、[2->N-1]、[3->N-1]等,所以一共需要花费时间为((N)+(N-1)+(N-2)+  + (1))*c = (aN^2 + b*N + k)*c = acN^2 + bcN + kc 只要高阶项,不要低阶项,也不要高阶项的系数,时间复杂度为N^2,即O(N^2)
  • 一个操作如果和样本数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作
  • 表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果是f(N),那么时间复杂度就是O(f(N))
  • 评价一个算法的好坏,先看时间复杂度指标,然后分析不同数据样本下的实际运行时间,也就是“常数项时间” 

冒泡排序

  • 最差情况 每个位置上元素都需要移动
package class01;

import java.util.Arrays;

public class Code02_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];
	}

	// 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);
			bubbleSort(arr1);
			comparator(arr2);
			if (!isEqual(arr1, arr2)) {
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");

		int[] arr = generateRandomArray(maxSize, maxValue);
		printArray(arr);
		bubbleSort(arr);
		printArray(arr);
	}

}

插入排序

  •  时间复杂度 按照最差情况计算
  • 插入排序 和数据情况有关,因此相较于冒泡排序和选择排序 会好
package class01;

import java.util.Arrays;

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);
	}

}

二分查找

  • 每次都是找中点,比较数据,因此时间复杂度是  O(log2N)

在一个有序数组中,找某个数是否存在?二分查找

package com.example.algorithm.demo.class1;

import java.util.Arrays;

public class Code04_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 (sortedArr[mid] == num){
                return true;
            } else if (sortedArr[mid] > num){
                R = mid - 1;
            } else {
                L = mid + 1;
            }
        }
        return sortedArr[L] == num;
    }
    // for test
    public static void comparator(int[] arr) {
        Arrays.sort(arr);
    }
    // for test
    public static void printArray(int[] arr){
        for (int i = 0;i < arr.length;i++){
            System.out.print(arr[i] + " ");
        }
    }
    public static void main(String[] args) {
        int[] arr = {1,2,5,3,1,6,78,9,234,55,666,76};
        comparator(arr);
        printArray(arr);
        System.out.println();
        if (exist(arr,234)){
            System.out.println("存在数据!");
        } else {
            System.out.println("不存在数据!");
        }
    }
}

在一个有序数组中,找大于等于某个数最左侧的位置?(进一步演化)

  • 比如 1223334444555556666667777777
package com.example.algorithm.demo.class1;

import java.util.Arrays;

public class Code05_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 (arr[mid] >= value){
                index = mid;
                R = mid - 1;
            } else {
                L = mid + 1;
            }
        }
        return index;
    }
    // for test
    public static void comparator(int[] arr) {
        Arrays.sort(arr);
    }
    // for test
    public static void printArray(int[] arr){
        for (int i = 0;i < arr.length;i++){
            System.out.print(arr[i] + " ");
        }
    }
    public static void main(String[] args) {
        int[] arr = {1,23,4,5,5,5,6,7,7,8,8,8,8,2,1,2,4,5,3,1,6,78,9,234,55,666,76};
        comparator(arr);
        printArray(arr);
        System.out.println();
        System.out.println("元素7的位置索引是"+ (nearestIndex(arr,7)+1));
    }
}

额外空间复杂度

  • 使用辅助数组,额外空间就不是O(1)

局部最小

  • 要求:无序数组,相邻不等,返回一个局部最小的位置,怎么整?
  • 具体操作:1,先看0位置的元素,如果0位置小于1位置,那么直接返回1位置的元素;2,上一条件不满足,即0位置元素大于1位置的元素,则看N-1位置的元素是否是局部最小,如果是直接返回。如果不是,则表明N-2位置的元素小于N-1位置的元素。表明0-1曲线下降,N-2  到 N-1 曲线上升,表明在0->(N-1)绝对存在局部最小。数学知识
  • 编码:使用二分搜索,判断局部最小;

原理

  • 【0】<【1】,0是局部最小
  • 【N-1】<【N-2】,N-1是局部最小
  • 【i-1】<【i】<【i+1】,i是局部最小

方法

  • 二分法,在0-N一定存在局部最小
  • 二分不一定只能适用于有序数组,这个和题意有关。比如查找一个局部最小的数值

异或计算(无进位相加

  • 0异或N=N  N异或N=0
  • 满足交换律和结合律(具体操作和执行的次序无关)

完成数据的交换

  • 引入第三方变量
int a = 1;
int b = 2;
int c = a;
a = b;
b = c;
  • 单独自身计算,不引入第三方变量
int a = 1;
int b = 2;
a = a + b; //a = 3
b = a - b; //b = 3 - 2 = 1
a = a - b; //a = 3 - 1 = 2
  •  使用异或(前提,值是一样的,但是a和b所处的内存区域是不同的,如果相同会出错)相同会导致数据抵消,二者都变成0
int a = 1;
int b = 2;
a = a 异或 b; //a = 1 异或 2,b = 2
b = a 异或 b; //a = 1 异或 2, b = 1
a = a 异或 b; //a = 1 异或 2 异或 1 = 2, b = 1

即使a和b的元素都是2,也可以实现数据的交换,但是如果a和b指向相同的地址空间,会消除数据,a和b都变成0
  • 错误使用 
  • i和j指向相同的位置,造成数据抹除
int[] arr = {4,5,3}
int i = 0;
int j = 0;

arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];

for(int c = 0; c < arr.length; c++){
    System.out.println(arr[c]);
}

arr[0]=0;arr[1]=5;arr[2]=3;

1,一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数 

public static void printOddTimesNum1(int[] arr){
	int eor = 0;
	for (int cur : arr){
		eor ^= cur;
	}
	System.out.println(eor);
}
  • 只有一个是奇数项,将所有元素都异或,相同元素就会消失,只剩下奇数的元素,利用到了异或的交换律和结合律

2,一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数 例子

【1011,0110,0110,1011,1000,0001】
1,让所有元素异或运算,得到eor变量,这个利用相同位置上1的个数,如果是奇数个,则为1,同理,偶数个1为0
2,第一步得到的eor=1001,因为eor的末尾元素为1,则将所有末尾为1的元素进行第二次异或,得到eor’,具体是【1011,1011,0001】,则eor'=0001,eor'也为第一个奇数个元素
3,将eor和eor'进行异或,eor=1001,eor'=0001,则二者计算得到1000为第二个奇数个的元素

引出新的问题 

  • 上面例子中的eor=1001,我们提取最后面位置上的元素 1,是如何实现的呢?
  • 例子(方法1)
01101000如何操作变成00001000呢?提取到指定位数的1,其余位数全部清零
假设 a = 01101000
1,计算 a-1,目的是打散最末尾的数字1,a-1 = 01100111
2,计算 a同或a-1 = 01100000,目的是清楚原始最右侧的1
3,计算 a异或(a同或a-1)= 00001000
  • 例子(方法2)

a & (~a +1)
a = 01101000, a取反得到10010111,再 +1 得到10011111
a & (~a +1) = 01101000 & 10011111 = 00001000
获取最右边的 1

步骤

  •  先将所有的元素进行异或,使用eor 得到 奇数 a 和 b 的异或,即 eor = a ^ b;
  • 将eor转化为二进制,其中位置为1 的用于区别a 和 b;假设x位置上元素为1,将数组中元素x位置为1的全部进行异或得到eor‘;eor’ = a或者b
  • 再将 eor和 eor‘ 异或得到另外一个元素

代码

public static void printOddTimesNum2(int[] arr){
	int eor = 0;
	for (int i=0;i<arr.length;i++){
		eor ^= arr[i];
		//eor = a ^ b
		//err != 0
		//err必然有一个位置上是1
		int rightOne = eor & (~eor + 1);//提取出最右侧的1
		int onlyOne = 0;//eor'
		for (int cur : arr){
			if((cur & rightOne) == 0){
				onlyOne ^= cur;
			}
		}
	}
	System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
package class01;

public class Code07_EvenTimesOddTimes {

	public static void printOddTimesNum1(int[] arr) {
		int eO = 0;
		for (int cur : arr) {
			eO ^= cur;
		}
		System.out.println(eO);
	}

	public static void printOddTimesNum2(int[] arr) {
		int eO = 0, eOhasOne = 0;
		for (int curNum : arr) {
			eO ^= curNum;
		}
		int rightOne = eO & (~eO + 1);
		for (int cur : arr) {
			if ((cur & rightOne) != 0) {
				eOhasOne ^= cur;
			}
		}
		System.out.println(eOhasOne + " " + (eO ^ eOhasOne));
	}

	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);

	}

}

 对数器

  • 最简单的方法和优化的方法,使用大量的结果分别测试,如果结果是一致的,说明优化的方法是正确的。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值