第1节 认识复杂度、对数器、二分法与异或运算

程序=算法+数据结构,既然算法这么重要,每个人写出来的算法又不一样,那么怎么算是一个好的算法呢?

1、评估算法优劣的核心指标是什么?

  • 时间复杂度(流程决定)
  • 额外空间复杂度(流程决定)
  • 常数项时间(实现细节决定)

(1)常数项时间

什么是常数项时间?

你比如数组的寻址操作,这就是一个常数项时间操作,每次执行时间都是固定时间,与数据量的大小无关,这样的操作就属于常数项时间操作。、

这里再举一个例子!你们不是Java里面经常用到LinkedList吗?这个在底层属于双向链表,如果你要list.get(5),那么并不会像数组寻址那样直接计算偏移量,而是一个一个地去遍历,到了合适的位置才取值,这个就不是常数项时间。

常见的常数时间的操作

  • 常见的算数运算(+、-、*、/、%等)
  • 常见的位运算(>>[带符号位,统一右移,然后看符号位是什么就用什么补]、>>>[不带符号位]、<<、|、&、^等)
  • 赋值、比较、自增、自减操作等
  • 数组寻址操作

(2)时间复杂度

什么是时间复杂度?

了解常数时间有什么用?时间复杂度就是来衡量你这个算法中基本常数操作执行的次数,看到底和数据量大小之间存在什么样的关系?很难理解吧?我们现在用选择排序(思想:每次选择出数组中最小的数值,然后和最小位置的数字进行交换,然后最小位置+1,直到选择排序完毕)来介绍时间复杂度。这里视频中讲了选择排序的常数操作num=XN^2 + YN + Z,想一下为什么?时间复杂度为o(N^2),只需要最高次幂。

如何确定算法流程的总操作数量与样本数量之间的表达式关系?

  1. 想象该算法流程所处理的数据状况,要按照最差的情况来。
  2. 把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作。
  3. 如果数据量为N,看看基本动作的数量和N是什么关系。

时间复杂度怎么表达?

当完成了表达式的建立后,只要把最高阶项留下即可。低阶项都去掉,高阶项的系数也要去掉,记为o(忽略掉系数的高阶项)

时间复杂度的意义?

当我们要处理的样本量很大很大的时候,我们会发现低阶项什么都不是最重要的了,每一项的系数是什么也不是最重要的,真正重要的是最高阶项是什么。时间复杂度是衡量算法流程复杂程度的一种指标,这个指标只与数据量有关,与过程之外的优化无关(一会再讲这一句)。

// 到了这里我们就把选择排序、冒泡排序、插入排序都讲了吧!这三个的时间复杂度都是o(n^2)class Test {// 选择排序思想:每次选择出数组中最小的数值,然后和最小位置的数字进行交换,然后最小位置+1,直到选择排序完毕public static void selectSort(int[] arr) {if (arr == null || arr.length <= 1) {return;}// 这里用i来表示下标for (int i = 0; i < arr.length - 1; i++) {int min_index = i;for (int j = i + 1; j < arr.length; j++) {if (arr[min_index] > arr[j]) {min_index = j;}// 交换一次后我现在是当前最小的,我还要看后面有没有更小的,所以继续循环swap(arr, i, min_index);}}}// 冒泡排序思想:0-1谁大谁往后,1-2谁大谁往后,....,4-5谁大谁往后,这样一轮后,最大的值在最后面;第二轮开始....第三轮开始....,最后排序完毕。public static void bubbleSort(int[] arr) {if (arr == null || arr.length <= 1) {return;}// 0 ~ N-1// 0 ~ N-2// 0 ~ N-3// 0 ~ 1for (int e = arr.length - 1; e > 0; e--) {// 0 1// 1 2// 2 3// e-1 efor (int i = 0; i < e; i++) {if (arr[i] > arr[i + 1]) {swap(arr, i, i + 1);}}}}// 插入排序思想:先让0~1有序,再让0~2有序,.....,最后再让0~N-1有序public static void insertSort(int[] arr) {if (arr == null || arr.length <= 1) {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) {int temp;temp = arr[i];arr[i] = arr[j];arr[j] = temp;}public static void printArr(int[] arr) {for (int e : arr) {System.out.print(e + " ");}System.out.println();}public static void main(String[] args) {}
}

时间复杂度的排名?【看到logN,默认以2为底】

o(1) < o(logN) < o(N) < o(N*logN) < o(N^2) < … < o(N^k) < o(2^n) < … < o(k^n) < o(n!)

(3)额外空间复杂度

什么是空间复杂度?

你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。作为输入参数的空间,不算额外空间,作为输出结果的空间,也不算额外空间。因为这些都是必要的,和现实目标有关的,所以都不算。但是除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去,这部分空间就是额外空间,如果你的流程只需要开辟有限几个变量,额外空间复杂度就是o(1)。

(4)最优解

什么是最优解?

那么我们就可以判断出来什么是最优解?最优解就是在在时间复杂度的指标上要尽可能得低,先满足了时间复杂度最低这个指标之后,使用最少的空间的算法流程,叫做这个问题的最优解。一般来说最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关。

2、认识对数器。

  1. 你想要测的方法a。
  2. 实现复杂度不好但是容易实现的方法b。
  3. 实现一个随机样本产生器。
  4. 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
  5. 如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a和方法b。
  6. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
package class01;import java.util.Arrays;public class Code01_SelectionSort {public static void selectionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}// 0 ~ N-1// 1~n-1// 2for (int i = 0; i < arr.length - 1; i++) { // i ~ N-1// 最小值在哪个位置上  i~n-1int 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);}}public static void swap(int[] arr, int i, int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}// for testpublic static void comparator(int[] arr) {Arrays.sort(arr);}// for testpublic 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 * Math.random());}return arr;}// for testpublic 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 testpublic 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 testpublic 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 testpublic 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);}}

3、认识二分法。

下面用一些题来认识二分。

// 在一个有序数组中,找某个数是否存在
package class01;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;// L..Rwhile (L < R) {// mid = (L+R) / 2;// L 10亿  R 18亿// mid = L + (R - L) / 2// N / 2    N >> 1// X*2+1可以表示为( X<<2 | 1 )mid = L + ((R - L) >> 1); // mid = (L + R) / 2if (sortedArr[mid] == num) {return true;} else if (sortedArr[mid] > num) {R = mid - 1;} else {L = mid + 1;}}return sortedArr[L] == num;}
}
// 在一个有序数组中,找>=某个数最左侧的位置
package class01;
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 testpublic static int test(int[] arr, int value) {for (int i = 0; i < arr.length; i++) {if (arr[i] >= value) {return i;}}return -1;}// for testpublic 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 testpublic 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();}public static void main(String[] args) {int testTime = 500000;int maxSize = 10;int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr = generateRandomArray(maxSize, maxValue);Arrays.sort(arr);int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());if (test(arr, value) != nearestIndex(arr, value)) {printArray(arr);System.out.println(value);System.out.println(test(arr, value));System.out.println(nearestIndex(arr, value));succeed = false;break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");}
}
// 在一个有序数组中,找<=某个数最右侧的位置
package class01;
import java.util.Arrays;public class Code05_BSNearRight {// 在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;L = mid + 1;} else {R = mid - 1;}}return index;}// for testpublic static int test(int[] arr, int value) {for (int i = arr.length - 1; i >= 0; i--) {if (arr[i] <= value) {return i;}}return -1;}// for testpublic 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 testpublic 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();}public static void main(String[] args) {int testTime = 500000;int maxSize = 10;int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr = generateRandomArray(maxSize, maxValue);Arrays.sort(arr);int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());if (test(arr, value) != nearestIndex(arr, value)) {printArray(arr);System.out.println(value);System.out.println(test(arr, value));System.out.println(nearestIndex(arr, value));succeed = false;break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");}}
// 局部最小值问题
package class01;
public class Code06_BSAwesome {public static int getLessIndex(int[] arr) {if (arr == null || arr.length == 0) {return -1; // no exist}if (arr.length == 1 || arr[0] < arr[1]) {return 0;}if (arr[arr.length - 1] < arr[arr.length - 2]) {return arr.length - 1;}// 到了这里,我们就明白趋势了。先下后上,所以一定会存在最小值。int left = 1;int right = arr.length - 2;int mid = 0;while (left < right) {mid = (left + right) / 2;if (arr[mid] > arr[mid - 1]) {right = mid - 1;} else if (arr[mid] > arr[mid + 1]) {left = mid + 1;} else {return mid;}}return left;}
}

4、认识异或运算。

异或运算(^):相同则0,不同则1;同或运算(⊙):相同则1,不同则0。

能长时间记住的概率接近0%,所以,异或运算就记成无进位相加(无进位是什么意思?就是二进制去相加,即便两者都是1,相加结果是2,不要进位,也就是得到0且不要进位)。

(1)异或运算的性质

0^N == N、N^N == 0。

异或运算满足交换律和结合律。

上面的两个性质用无进位相加来理解就非常得容易。

(2)应用

public class Test {public static void main(String[] args) {// 如何不需要额外的变量即可实现两个数字的交换,但是特别注意,只有两个变量用的不是同一个内存的时候才可以这样干!int a = 10;int b = 4;a = a ^ b;b = a ^ b;a = a ^ b;// b = a ^ b ^ b = a ^ 0 = a; 运用交换律和结合律// a = a ^ b = a ^ b ^ a ^ b ^ b = a ^ a ^ b ^ b ^ b = b; 运用交换律和结合律System.out.println("a=" + a + ",b=" + b);}
}
public class Test {// 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这个数字?public static void printOddTimesNum1(int[] arr){int eor = arr[0];for(int i = 1; i < arr.length;i++){eor ^= arr[i];}System.out.println(eor);}public static void main(String[] args) {int[] arr = new int[]{1,1,2,2,3,3,4};printOddTimesNum1(arr);}
}
public class Test {// 怎么把一个整数,提取出最右侧的1来public static int func(int number){// 00000011 01010000// 11111100 10101111 (取反)// 11111100 10110000 (加1)// 00000000 00010000 (与)return number & ((~number) + 1);}public static void main(String[] args) {int num = 234;System.out.println(func(num));}
}
public class Test {// arr中,有两种数,出现奇数次public static void printOddTimesNum2(int[] arr) {int eor = 0;for (int i = 0; i < arr.length; i++) {eor ^= arr[i];}// eor = a ^ b// eor != 0// eor必然有一个位置上是1// 0110010000// 0000010000int rightOne = eor & (~eor + 1); // 提取出最右的1int onlyOne = 0; // eor'for (int i = 0 ; i < arr.length;i++) {//  arr[1] =  111100011110000// rightOne=  000000000010000if ((arr[i] & rightOne) != 0) {onlyOne ^= arr[i];}}System.out.println(onlyOne + " " + (eor ^ onlyOne));}public static void main(String[] args) {}
}
public class Test {// 看一个数字的二进制样式中有几个1public static int func(int num){int count = 0;while(num != 0){int rightOne = num & ((~num) + 1);count++;num ^= rightOne;}return count;}public static void main(String[] args) {int num = 234;System.out.println(func(num));}
}

数据结构与算法JC班-左程云第一节课笔记(认识复杂度、对数器、二分法与异或运算)相关推荐

  1. 数据结构与算法XS班-左程云第一节课笔记(位运算、算法是什么、简单排序)

    第1节 位运算.算法是什么.简单排序 ##这是数据结构与算法新手班-左程云第一节课的笔记## 1. 位运算 // 你们会不会表示一个数字的32位啊? // Java中int类型默认以32位二进制数在计 ...

  2. 数据结构与算法XS班-左程云第八节课笔记(归并排序和快速排序)

    第8节 归并排序和快速排序 ##这是数据结构与算法新手班-左程云第八节课的笔记## 归并排序 归并排序实际上是一个很经典的排序方法,时间复杂度o(N*logN). 递归版本(图解排序算法(四)之归并排 ...

  3. 算法与数据结构全阶班-左程云版(二)基础阶段之3.归并排序和快速排序

    文章目录 前言 1.归并排序 2.快速排序 总结 前言 本文主要介绍了两种排序,归并排序和快速排序,归并排序有递归和非递归2种方式实现,快速排序的升级版为荷兰国旗问题. 1.归并排序 归并排序: 1) ...

  4. CSDN专访左程云,算法之道

    算法的庞大让很多人畏惧,程序员如何正确的学习并应用于面试.工作中呢?今天,CSDN邀请了IBM软件工程师.百度软件工程师.刷题5年的算法热爱者左程云,来担任CSDN社区问答栏目的第二十六期嘉宾,届时会 ...

  5. 一周刷爆LeetCode,算法da神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解 笔记

    一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解 笔记 教程与代码地址 P1 出圈了!讲课之外我们来聊聊 ...

  6. 一看“左程云:200道算法与数据结构”,二刷“阿里云:70+算法题、30种大厂笔试高频知识点”,3月过去终于挺进我梦中的字节!

    不管是学生还是已经工作的人,我想彼此都有一个相同的梦想:进大厂! 眼看着2020年还有个三十来天就要完美收尾了,那么如何才能在未来三个月弯道超车赶上"金三银四的春招",进入梦寐以求 ...

  7. 左程云算法笔记总结-基础篇

    基础01(复杂度.基本排序) 认识复杂度和简单排序算法 时间复杂度 big O 即 O(f(n)) 常数操作的数量写出来,不要低阶项,只要最高项,并且不要最高项的系数 一个操作如果和样本的数据量没有关 ...

  8. 左程云:程序员该如何学习算法?

    大家好,我是左程云.我本科就读于华中科技大学.硕士毕业于在芝加哥大学.先后在IBM.百度.GrowingIO和亚马逊工作,是一个刷题7年的算法爱好者. 我是<程序员代码面试指南--IT名企算法与 ...

  9. 【左程云Java算法】Chapter1-5:用一个栈实现另一个栈的排序

    [左程云Java算法]Chapter1-5:用一个栈实现另一个栈的排序 [题目] 用一个栈实现另一个栈的排序 [要求] 一个栈中元素的类型为整型,现在想将该栈从顶到底按从大到小的顺序排序,只许申请一个 ...

最新文章

  1. 2018-3-7论文网络评论中非结构化信息的表示与研究--------实验分析
  2. Android chromium 1
  3. 掌握 ASP.NET 之路:自定义实体类简介
  4. Binder相关面试总结(四):一次Binder通信的基本流程是什么样?
  5. HDU 1846 Brave Game
  6. k8s安装之Linux centos7升级内核到4.18以上
  7. Session的异常
  8. es 映射 mysql_ElasticSearch系列02:ES基础概念详解
  9. c语言选择结构程序设计笔记,C语言选择结构程序设计.ppt
  10. code review手记3
  11. android.mk if else,gradle - 如何在Android Studio中使用我自己的Android.mk文件 - SO中文参考 - www.soinside.com...
  12. java+ssh实现级联下拉列表(以行业大类和详细类为例)
  13. Python图像旋转任意角度
  14. 远离奸商-查看CPU信息是否被修改
  15. 【每天学点心理学第七期】人性定理:人都是以服务于他自己为目的的!
  16. WinRM 如何设置 TrustedHosts
  17. 求饶不经过原点的旋转轴的旋转矩阵
  18. WorldPress出现Briefly unavailable for scheduled maintenance. Check back in a minute.的解决方法
  19. 死区时间的分析与设置
  20. 2021-2027全球与中国触控板市场现状及未来发展趋势

热门文章

  1. docker文件过大,Docker容器引擎,迁移/var/lib/docker/到本机其它挂载分区或远程主机的某个分区。docker迁移
  2. 从键盘上输入长方形的长和宽,求长方形的周长和面积。
  3. 盘点丨一文了解DAO的现状
  4. “清华女神”回国,赤子之心,如愿以偿
  5. LLVM 之父 Chris Lattner:模块化设计决定 AI 前途,不服来辩
  6. 基于jsp的校园二手物品交易网站
  7. Googe Play App审核注意事项
  8. R语言melt、table、cut函数应用及解释
  9. 嘿!快跟C菌一起来看看这周都更新了些啥
  10. 化学数据分析计算机,化学实验数据分析系统1.1