文章目录

  • 一. 认识时间复杂度和简单排序算法
    • 1.以选择排序为例
    • 2.异或运算
    • 3.插入排序
    • 4.二分查找
    • 5.对数器
  • 二. 认识O(NlogN)的排序
    • 1.递归==栈的后序遍历
    • 2.归并排序
    • 3.快速排序
  • 三.详解桶排序以及排序内容大总结
    • 1.堆结构(优先级队列)
    • 2.比较器
    • 3.桶排序
    • 4.排序总结
  • 四.链表
    • 1.哈希表和有序表
    • 2.链表
  • 五.二叉树
    • 1.哈希表和有序表
    • 2.BFS宽度优先遍历(层次遍历)
    • 3.套路题(可树型DP)
  • 六.图
    • 1.定义好通用的图所需结构
    • 2.BFS和DFS + 拓扑排序
    • 3.kruskal算法(边)和Prim算法(点)【无向图】
    • 4.Dijkstra:单元最短路径
  • 七.详解前缀树和贪心算法
    • 1.前缀树
    • 2.贪心算法
    • 3.字典序证明
    • 4.其他题目
  • 八.暴力递归
    • 1.N皇后
    • 2.暴力递归冲冲冲
  • Ending

本文是跟着左程云老师在b站发布的视频教程所记录的算法入门笔记,左神算法班
总共分为八节,一些相关代码和重点思路分析 大多已在下文呈现

一. 认识时间复杂度和简单排序算法

1.以选择排序为例

第一遍的时候,查询了n次,比较了n次,交换了1次;第二遍,查询n-1次,比较了n-1次,交换1次;。。。所以时间复杂度是O(n) ^2

算法分析先看时间复杂度指标,再分析不同数据样本下的实际运行时间,也就是"常数项时间"
额外空间复杂度指的是除去存放必要数据外,所需的空间。

2.异或运算

不同为1,相同为0;同时可以理解成无进位相加

 1)0^N=N; N^N=0
 2)交换律:a^b = b^a;结合律 (a^b) ^c= a^(b ^c)
第一题:已知一个数组中,只有一种数出现了奇数次,其余都是出现偶数次,请问怎么找到这种数
第二题:如果两种数出现了奇数次呢,怎么找到? 要求O(N)时间,O(1)空间。

第一题int eor=0和数组中全部异或得到的数就是结果,因为偶数次的结果都没了;相当于消消乐
第二题先按照第一题得到int eor=a^b; 由于a!=b,所以eor!=0
==> eor在32位比特位中至少有1位是1,假设是第8位,那么a和b的第八位一定一个是1,一个是0,不然得不到1这个异或结果
==> int eor2去异或数组中所有第8位不是1/不是0的数,最后结果一定是a or b;相当于将整个数组根据第8位是否是1分成两部分,这两部分分别包含a和b以及其他偶数次的数字
==> 最后再将eor^eor2得到另一个数

int rightOne = eor & (~eor  + 1); //提取最右侧的1  eor和eor的补码进行 同1得1,有0则0 的与运算
int onlyOne = 0;
for (int cur : arr) {if ((cur & rightOne) != 0)  //取出在最右侧1位置,数组中该位置也等于1的值做异或  /  ==0也行onlyOne ^= cur;
}
System.out.println(onlyOne + " " + (eor ^ onlyOne ));

3.插入排序

插入排序不是严格的O(N^2):0-1有序=>0-i有序,而冒泡和选择是。

    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);//相邻做交换}

4.二分查找

1>找一组有序数中≥某个数num的最小数(最左侧位置)
(1)判断mid是否≥num => 若≥num,标记t=mid,令end=mid-1找其左侧;
(2)左侧的mid若小于num,令start=mid+1,重新二分找;若此时mid>=num,由于该arr[mid]肯定小于t,重新令t=mid
(3)以此类推继续二分,就比正常二分多一个t标记。

2>局部最小值:在arr中,数值无序排列且相邻数一定不相等。局部最小指的是[0,N-1]中,arr[0]<arr[1],arr[N-1]<arr[N-2],arr[i-1]<arr[i]<arr[i+1],0<i<N-1,则这三个数局部最小。如果只求一个局部最小,请问时间复杂度能好于O(N)吗
可以,一开始比较start,end是否小于相邻数,都不行,此时这条函数曲线左边下降,右边也是下降;f(0)>f(1)且f(n-1)>f(n-2),中间必有转折点
则比较mid是否小于相邻数;如果此时不行,要么是f(m)>f(m-1)或f(m)>f(m+1),那就把f(m)当成新的start或end;当然f(m)是极大值,那两边都可
优化流程两个方向一是数据状况特殊,二是问题特殊
本题两个都特殊,数据相邻不等,问题求解时左右两边必和问题有关且必能舍弃一边就能采用二分策略

5.对数器

用于代替OJ检测

自己做的随机样本产生器产生的数据分别跑法a,法b,两相比较,若不一,二者至少有1出错

二. 认识O(NlogN)的排序

1.递归==栈的后序遍历


递归如果符合Master公式[算法导论主定理],可以直接用其计算时间复杂度

2.归并排序

准备一个辅助空间(空间复杂度O(N)),合并两个有序数组(各有一个min指针),使合并数组也有序
前面的排序浪费了大量的比较信息,而归并排序将比较行为变成了整体有序的部分去跟下一个更大范围的部分merge

    public static void process(int[] arr, int L, int R){if (L==R)return;int mid = L + ((R-L)>>1); //L+(R-L)/2,防止溢出process(arr,L,mid); //该递归时间复杂度按照Master公式,a=2,b=2,d=1;因此是O(NlogN)process(arr,mid+1,R);merge(arr,L,mid,R);}public static void merge(int[] arr, int L, int M, int R){int[] help = new int[R-L+1]; //辅助空间int i=0; //用于辅助空间的移动下标int p1=L, p2=M+1;while (p1<=M && p2<=R)help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];while (p1<=M)help[i++] = arr[p1++];while (p2<=R) //这两个while必执行一个help[i++] = arr[p2++];for (i=0;i<help.length;i++)arr[L+i] = help[i];}

扩展:


小和问题:
逆向思维反过来,求右边有几个数比它大(Merge),就产生几个这个数的小和。
只有左侧小,才会产生小和(此时乘以右侧排好序的数组剩余长度=>排序不能省略,通过排序知道右侧能产生几个小和),右侧小不产生小和
既不遗漏也不重算,因为merge过程中一定会把一个数扩到整体的,且变成merge变成整体后是不会在这个整体内部产生小和;
也就是说只有在merge时会产生小和,变成有序部分不会产生小和

public static int smallSum(int[] arr) {if (arr == null || arr.length < 2) {return 0;}return mergeSort(arr, 0, arr.length - 1);}public static int mergeSort(int[] arr, int l, int r) {if (l == r) {return 0;}int mid = l + ((r - l) >> 1);return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r); // 左侧小和+右侧小和+两组归并新产生的小和}public static int merge(int[] arr, int l, int m, int r) {int[] help = new int[r - l + 1];int i = 0;int p1 = l;int p2 = m + 1;int res = 0;while (p1 <= m && p2 <= r) {res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; //O(1)的时间找到r-p2+1个右侧产生小和的数【下标计算找】help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; //这里有修改,优先拷贝右侧到help【因为先拷贝左边,不知道右边有几个比左侧该值大的数(下标计算看不出来)】}while (p1 <= m) {help[i++] = arr[p1++];}while (p2 <= r) {help[i++] = arr[p2++];}for (i = 0; i < help.length; i++) {arr[l + i] = help[i];}return res;}

逆序对问题:和上面等效,只是左边比右边大而已。【找右边比左边小的元素】

res += arr[p1] > arr[p2] ? (m - p1 + 1): 0; //改这两句就可以
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; //优先拷贝左侧【与上优先右侧同理】

3.快速排序

O(N^2)=>等差数列或者说partition一直离左侧或者右侧开头很近


第一题一次start和end的快排就解决
或者一开始i=0,小于等于区=null;如果arr[i]≤num,arr[i]和小于等于区下一个数交换,同时≤区右扩且i++
否则不右扩,i++,直到arr[i]≤num {快慢指针}
第二题则是两个区域,左边小于区域,右边大于区域

第三条情况i原地不动是因为,从右侧>区前一个数交换过来的数还没看过,得再拿他过一下情况1,2,3
当i == 大于区域时,停止
快排1.0 以最后一个数为num与大于5区域第一个数做交换,再在左右两侧继续之前行为

               

      
以荷兰国旗为例,一次搞定一批==5的数,比1.0稍快一些。
3.0采用概率随机选择索引范围内一个数做划分,做长期期望后为O(NlogN)

public static void quickSort(int[] arr, int l, int r) {if (l < r) {swap(arr, l + (int) (Math.random() * (r - l + 1)), r);//等概率随机选一个位置和最右位置交换int[] p = partition(arr, l, r); //返回数组长度为2=>划分中间区域的左边界和右边界quickSort(arr, l, p[0] - 1); // <区quickSort(arr, p[1] + 1, r); // >区}}// 默认以arr[r]做划分 arr[r]->p  |  <p    ==p     >p三块区域public static int[] partition(int[] arr, int l, int r) {int less = l - 1; //<区有边界int more = r;    //>区左边界while (l < more) {  //l表示当前数位置 arr[r]为划分值if (arr[l] < arr[r]) {swap(arr, ++less, l++);} else if (arr[l] > arr[r]) {swap(arr, --more, l); //>区左扩} else {l++; //中间区域不管}}swap(arr, more, r); //最后把划分值换过来,此时为中间位置最右边return new int[] { less + 1, more };}

快排的额外空间复杂度是O(logN):差的情况下,每次划分都是拿start和end做划分,且在递归过程中一直保留不释放=->O(N);好的情况是二叉树,排序一小部分,释放一部分=>O(logN)
即使是不用递归写,也要自行压栈,各种情况概率分布后会收敛到O(logN)

三.详解桶排序以及排序内容大总结

1.堆结构(优先级队列)

堆是完全二叉树,满足左右孩子分别是2i+1和2i+2,父节点是(i-1)/2
完全二叉树高度是O(logN),删掉最大值往下调整成堆也是O(logN)
假如有序是自小到大,堆排序就是构建大根堆,把构建好的堆最后一个位置与堆顶做交换,然后heapsize–,此时最后一位是最大值,然后对交换后的结构调整成堆,然后继续上述操作 逐步把大根堆顶放置最后一个(类似冒泡,但每次比较的值少了,且不额外空间复杂度O(1))。

public static void heapSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 0; i < arr.length; i++) { //O(N)heapInsert(arr, i); // O(logN)}int size = arr.length;swap(arr, 0, --size); //最后一个数和堆顶做交换while (size > 0) { //周而复始 从最后一个排序到第一个 O(N)heapify(arr, 0, size); // O(logN)swap(arr, 0, --size); //O(1)}}//每次插入往上比:上public static void heapInsert(int[] arr, int index) {while (arr[index] > arr[(index - 1) / 2]) { //比父亲大,或当index=0时跳出    | (0 - 1) / 2 = 0, int自动抹去小数,-0.5=>0swap(arr, index, (index - 1) /2);index = (index - 1)/2 ;}}//任意位置往下堆化:下,例如删除堆顶后,将最后一个属放置在arr[0]时往下堆化public static void heapify(int[] arr, int index, int size) { //heapsize管着堆的大小int left = index * 2 + 1;while (left < size) { //下方还有(左)孩子int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left; //必须时有右孩子,且右孩子的值大于左孩子的值,右孩子才胜出largest = arr[largest] > arr[index] ? largest : index; //父和较大孩子间,谁值大,把下标给largestif (largest == index) {break;}swap(arr, largest, index);index = largest;left = index * 2 + 1;}}

如果只是给你一些数,只需求构建大根堆,不需要按照一个个往里填充的方式,没必要从后面的叶子节点开始调整,因为他们没有孩子,不需要heapify;所以从倒数第二排最后一个父节点开始向上调整(这样每个父亲节点只需要往下看就行)

for(int i = arr.length/2 -1 ;i>0; i--){heapInsert(arr, i);
}

扩展题

假设k=6,准备一个小根堆,遍历数组将前7个值放进小根堆,然后将堆顶对应的数组元素弹出放在arr[0]处,再把arr[7]放入小根堆,再弹出堆顶到arr[1] (类似滑动窗口,用小根堆找出)

PriorityQueue<Integer> heap = new PriorityQueue<>();

优先级队列(小根堆)底层是数组,但它扩容是成倍扩充 拷贝旧数组到新数组:单次扩容的代价由拷贝决定:O(N); 扩容次数是O(logN); N次均摊下来时间复杂度:O(NlogN)/N =O(logN)
系统提供代码是黑盒:只支持简单的add,poll,不支持自己随便修改堆中任意节点的值,不是做不了而是不够高效

2.比较器

c++:重载运算符
Java:比较两个函数/变量/属性/bean类等的大小:其实也是重载比较运算符

public static class Student {public String name;public int id;public int age;public Student(String name, int id, int age) {this.name = name;this.id = id;this.age = age;}}public static class IdAscendingComparator implements Comparator<Student> {//返回负数的时候,第一个参数排在前面 if(o1.id<o2.id) return -1;//返回正数的时候,第二个参数排在前面 if(o1.id>o2.id) return 1;//返回0,无所谓 return 0;@Overridepublic int compare(Student o1, Student o2) {return o1.id - o2.id;}public static void main(String[] args) {Student student1 = new Student("A", 2, 23);Student student2 = new Student("B", 3, 21);Student student3 = new Student("C", 1, 22);Student[] students = new Student[] { student1, student2, student3 };Arrays.sort(students, new IdAscendingComparator()); //数组和怎么比大小的策略都给//如果不是自小到大,而是大到小,可以调整比较策略Interget[] arr = {5,4,3,2,7,9,0};Arrays.sort(arr, new MyComp ());}public static class MyComp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1; //第二个参数➖第一个参数}}

同理,结合堆,自定义的时候,可以认为返回负数,认为第一个参数放在上面

PriorityQueue<Integer> heap = new PriorityQueue<>(new MyComp());

3.桶排序

之前所有的排序都是基于(数字)比较的排序

比如员工按照年龄排序,申请一个arr[200],下标对应员工年龄,arr[几]++,再把词频数组还原成 原年龄数组很容易;
其余呢,比较

数据结构与算法入门(follow 左神)相关推荐

  1. 如何求解问题--数据结构与算法入门

    如何求解问题–数据结构与算法入门 在学习数据结构与算法前,我们大多有这样的困惑,难道学习了数据结构与算法就能帮助我们解决学习Java.Python时的大作业吗?数据结构与算法是什么? 回答这个问题之前 ...

  2. 数据结构与算法入门---数据结构类型

    数据结构与算法入门---数据结构类型 数据的逻辑结构 数据的逻辑结构指数据元素之间的逻辑哦关系(和实现无关) 分类一:线性结构和非线性结构 线性结构:有且只有一个开始结点和一个终端节点,并且所有节点都 ...

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

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

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

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

  5. 数据结构与算法入门教程(C语言实现版)

    个人简介 作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门. 文章目录 个人简介 C语言数据结构与算法 BF和 ...

  6. 数据结构与算法入门(Java)

    数据结构与算法(Java) 1. 数据结构与算法概述 1.1 什么是数据结构? 官方解释: 数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及他们之间的关系和操作等相关问题的学科. 大白话: ...

  7. 算法第一人左神(左程云):深入解析字节算法面试题与数据结构

    面对市场需求缩水的时期,小编我是如何笑对金三银四,拿到阿里offer的呢? 嘿嘿嘿!今天就来分享一下我的秘密武器:阿里大牛整理的Java核心开发手册,这份手册内容非常之全,涵盖基础知识以及架构进阶. ...

  8. Java数据结构与算法入门

    原文:https://blog.csdn.net/qq_37101453/article/details/80142147 第一部分:Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? ...

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

    第1节 认识复杂度.对数器.二分法与异或运算 程序=算法+数据结构,既然算法这么重要,每个人写出来的算法又不一样,那么怎么算是一个好的算法呢? 1.评估算法优劣的核心指标是什么? 时间复杂度(流程决定 ...

最新文章

  1. 使用elementui实现表单上传功能_elementUI实现自定义上传文件并携带参数
  2. Qt中的QPrintDialog
  3. 【存储知识学习】第五章-5.4虚拟磁盘和5.5卷管理层-《大话存储》阅读笔记
  4. python logger.debug_Python logger模块
  5. 8086汇编常用代码总结(个人向),包含换行函数(过程),执行输出,结束程序
  6. mysql 定时脚本_MySQL定时执行脚本(计划任务)命令实例
  7. java字节码指令简介(仅了解)
  8. 密钥文件登录服务器,密钥文件登录云服务器
  9. 【MATLAB】xx操作总结【更新中】
  10. java 核心技术2_你必须掌握的 21 个 Java 核心技术
  11. Xml之Linq遍历
  12. 浅谈Linux用户权限管理之三(文件与权限的设定)
  13. android camera实例
  14. RaspberryPI 3b 技术总结(包括Linux)
  15. svn删除文件出错的经验总结
  16. Building package xxx:xxx-windows failed with: BUILD_FAILED
  17. 无线信道仿真 matlab,基于Matlab的无线信道仿真.doc
  18. java第十一次作业
  19. 太阳能无线充电系统设计
  20. Makfile: [ GCC编译选项 ] >CFLAGS参数 -c -o

热门文章

  1. oppor11s的位置服务器,OPPOR11s高配版和标准版区别是什么【详细介绍】
  2. 通信原理笔记—2DPSK调制解调系统
  3. 曼陀罗彩绘疗愈系统--艺术疗愈
  4. python安装scipy.misc_如何安装scipy misc packag
  5. 正则表达式转义字符表
  6. FFplay文档解读-28-视频过滤器三
  7. 微信小程序 常见组件
  8. 这款Roguelike+RPG养成游戏为何如此成功?《弓箭传说》节奏点分析
  9. SpringBoot启动Tomcat原理与嵌入式Tomcat实践
  10. [Mugeda HTML5技术教程之4] Studio 概述