左神算法——算法基础入门班第一课——认识时间复杂度、选择排序、冒泡排序、插入排序、二分法、异或运算的性质与扩展、对数器的概念和使用、剖析递归行为和递归行为时间复杂度的估算

一、认识时间复杂度

常数时间的操作:一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。

时间复杂度为一个算法流程中,常数操作数量的一个指标。常用O(读作big O)来表示。

具体来说,先要对一个算法流程非常熟悉,然后去写出这个算法流程中,发生了多少常数操作, 进而总结出常数操作数量的表达式。 

在表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果为f(N),那 么时间复杂度为O(f(N))。

评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行 时间,也就是“常数项时间”。

二、选择排序

时间复杂度O(N^2),额外空间复杂度O(1)

/**
 * 选择排序 时间复杂度O(n^2)
 * 从数组第一个元素开始遍历,后一个和前一个元素比较大小,将最小的元素的下标赋值给 minIndex
 * 然后执行swap方法,替换下标为i和minIndex元素的位置,这样每次选择出来的最小的数组就排在前面了
 * 最外层for循环结束以后 整个数组就是从小到大排序的了
 */
	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;

        //交换方法二 异或运算
        //arr[i] = arr[i] ^ arr[minIndex];
        //arr[minIndex] = arr[i] ^ arr[minIndex];
        //arr[i] = arr[i] ^ arr[minIndex];
	}

三、冒泡排序

时间复杂度O(N^2),额外空间复杂度O(1)

/**
 * 冒泡排序 时间复杂度O(n^2)
 * 最外层for循环遍历 n 次,内层for循环先从第一个元素开始和下一个元素比较
 * 然后从第二个元素开始和第一个元素比较
 * 一直到倒数第二个元素和倒数第一个元素比较结束
 * 这样第一次循环就把最大的放在了最后面
 * 继续循环,倒数第二大的放在倒数第二个位置
 * 以此类推.........
 * 最外层for循环结束以后 整个数组就是从小到大排序的了
 */
 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];
	}

四、插入排序

时间复杂度O(N^2),额外空间复杂度O(1) 算法流程按照最差情况来估计时间复杂度

/**
 * 插入排序 时间复杂度(最坏情况) O(n^2) 最好情况O(n)
 * 最外层for循环从第二个元素开始直到最后一个元素
 * 最内层for循环从第一个元素开始,如果j不越界 而且 j下标元素比 j+1 元素大
 * 那么就交换这两个元素的位置
 */
 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];
	}

五、二分法

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

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

2)在一个有序数组中,找>=某个数最左侧的位置

	// 在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;
	}

3)局部最小值问题

六、异或运算的性质与扩展

  1. 0^N == N N^N == 0
  2. 异或运算满足交换律和结合率
  3. 不用额外变量交换两个数
  4. 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数
  5. 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数

题目举例:

  1. 一个数组 1种数字是出现奇数次 其余数字是出现偶数次 筛选出这一种数字是什么
		int eO = 0;
		//遍历数组 做异或运算 偶数次的数字异或都是0 只有奇数次的数字异或后就是这个数字
		for (int cur : arr) {
			eO ^= cur;
		}
		System.out.println(eO);
  1. 一个数组 2种数字是出现奇数次 其余数字是出现偶数次 筛选出这两种数字是什么
        int eO = 0, eOhasOne = 0;
		//假设a和b是两个出现奇数次的数字
		//先把每个数 都异或起来  最后 e0 = a^b (a 异或 b)
		for (int curNum : arr) {
			eO ^= curNum;
		}
		//因为 a ≠ b ,所以 e0 肯定有一位是 1 ,异或相异为 1
		//那么 取出这一位 1
		// ~ 取反 +1  再和 e0 与运算
		int rightOne = eO & (~eO + 1);
		//再遍历数组  所有数字 只要第几位不是 0  那么最后 eOhasOne 就是 a或者b
		for (int cur : arr) {
			if ((cur & rightOne) != 0) {
				eOhasOne ^= cur;
			}
		}
		// eO ^ eOhasOne 就是剩下那个数字了
		System.out.println(eOhasOne + " " + (eO ^ eOhasOne));

七、对数器的概念和使用

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

八、剖析递归行为和递归行为时间复杂度的估算

用递归方法找一个数组中的最大值,系统上到底是怎么做的?
master公式的使用 T(N) = a*T(N/b) + O(N^d)

  1. log(b,a) > d -> 复杂度为O(N^log(b,a))
  2. log(b,a) = d -> 复杂度为O(N^d * logN)
  3. log(b,a) < d -> 复杂度为O(N^d)

补充阅读:www.gocalf.com/blog/algorithm-complexity-and-master- theorem.html

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java天下第1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值