day-8-递归
一、如何理解“递归”递归需要满足的三个条件1. 一个问题的解可以分解为几个子问题的解2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样3. 存在递归终止条件二、如何编写递归代码写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。 编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。 递归代码要警惕堆栈溢出递归代码要警惕重复计算 三、小结递归是一种非常高效、简洁的编码技巧。编写递归代码的关键就是不要把自己绕进去,正确姿势是写出递推公式,找出终止条件,然后再翻译成递归代码。递归代码虽然简洁高效,但是,递归代码也有很多弊端。比如,堆栈溢出、重复计算、函数调用耗时多、空间复杂度高等,所以,在编写递归代码的时候,一定要控制好这些副作用。
day-7-队列
一、如何理解队列队列跟栈一样,也是一种操作受限的线性表数据结构,特点是先进者先出。入队:放一个数据到队列尾部。出队:从队列头部取一个元素。 二、顺序队列和链式队列顺序队列:用数组实现的队列;链式队列:用链表实现的队列。 三、循环队列 四、阻塞队列和并发队列阻塞队列:就是在队列基础上增加了阻塞操作。简单来说,就是在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。 并发队列:线程安全的队列。 五、线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢?我们一般有两种处理策略。第一种是非阻塞的处理方式,直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。 基于链表的实现方式,可以实现一个支持无限排队的无界队列(unbounded...
使用docker-compose 部署MySQL
一、拉取MySQL镜像我这里使用的是MySQL8.0.18,可以自行选择需要的版本。 1docker pull mysql:8.0.18 二、创建挂载目录123mkdir -p /home/docker/mysql8/logmkdir -p /home/docker/mysql8/datamkdir -p /home/docker/mysql8/conf.d 三、添加配置文件my.cnf这里需要给MySQL做点自定义的配置,比如时区字符编码等。 1vi /home/docker/mysql8/conf.d/my.cnf 123456789101112131415161718192021222324252627282930313233343536373839404142434445###### [client]配置模块 ######[client]default-character-set=utf8mb4socket=/var/lib/mysql/mysql.sock###### [mysql]配置模块 ######[mysql]#...
Docker更换镜像源
一、问题在学习Docker的时候遇到pull失败的情况 二、解决方法12345678910sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-'EOF'{ "registry-mirrors": ["https://yxzrazem.mirror.aliyuncs.com"]}EOFsudo systemctl daemon-reloadsudo systemctl restart docker
day-6-栈
一、如何理解“栈”栈是一种“操作受限”的线性表,后进者先出,先进者后出。当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,这时我们就应该首选“栈”这种数据结构。 二、如何实现一个“栈”栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,叫作顺序栈;用链表实现的栈,叫作链式栈。
day-5-链表(下)
一、几个写链表代码的技巧技巧一:理解指针或引用的含义将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。 技巧二:警惕指针丢失和内存泄漏插入结点时,一定要注意操作的顺序。删除链表结点时,也一定要记得手动释放内存空间。 技巧三:利用哨兵简化实现难度哨兵,解决的是国家之间的边界问题。同理,这里说的哨兵也是解决“边界问题”的,不直接参与业务逻辑。 技巧四:重点留意边界条件处理检查链表代码的几个边界条件: 如果链表为空时,代码是否能正常工作? 如果链表只包含一个结点时,代码是否能正常工作? 如果链表只包含两个结点时,代码是否能正常工作? 代码逻辑在处理头结点和尾结点的时候,是否能正常工作? 技巧五:举例画图,辅助思考技巧六:多写多练,没有捷径5个常见的链表操作: 单链表反转 链表中环的检测 两个有序的链表合并 删除链表倒数第 n 个结点 求链表的中间结点
day-4-链表(上)
一、链表结构链表不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。 1.1 单链表链表的插入和删除操作,对应的时间复杂度是O(1)。但链表随机访问,需要 O(n) 的时间复杂度。因为链表中的数据并非连续存储的,所以无法像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,而是需要根据指针一个结点一个结点地依次遍历,直到找到相应的结点。 1.2 循环链表循环链表是一种特殊的单链表。和单链表相比,循环链表的优点是从链尾到链头比较方便。当要处理的数据具有环型结构特点时,就特别适合采用循环链表。 1.3 双向链表双向链表,支持两个方向,每个结点不止有一个后继指针 next 指向后面的结点,还有一个前驱指针 prev 指向前面的结点。双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。 从结构上来看,双向链表可以支持 O(1)...
day-3-数组
一、什么是数组 数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。 这个定义里有几个关键词。 1.1 线性表线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。 概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。 1.2 连续的内存空间和相同类型的数据 很多时候我们并不是要去死记硬背某个数据结构或者算法,而是要学习它背后的思想和处理技巧,这些东西才是有价值的。 二、警惕数组的访问越界问题访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序可能不会报任何错误。 三、容器能否完全替代数组?使用数组更适合的场景: Java ArrayList 无法存储基本类型,如果特别关注性能,或者希望使用基本类型,就可以选用数组。 如果数据大小事先已知,并且对数据的操作非常简单,用不到 ArrayList...
day-2-复杂度分析(下)
一、最好、最坏情况时间复杂度最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度。最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度。 二、平均情况时间复杂度平均时间复杂度:也叫加权平均时间复杂度, 在大多数情况下,我们并不需要区分最好、最坏、平均情况时间复杂度,我们使用一个复杂度就可以满足需求了。只有同一块代码在不同的情况下,时间复杂度有量级的差距,我们才会使用这三种复杂度表示法来区分。 三、均摊时间复杂度对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。 均摊时间复杂度就是一种特殊的平均时间复杂度,没必要花太多精力区分它们。
day-1-复杂度分析(上)
一、为什么需要复杂度分析1.1 事后统计法的局限性 测试结果非常依赖测试环境 测试结果受数据规模的影响很大 1.2 大 O 复杂度表示法大 O 时间复杂度表示法表示代码执行时间随数据规模增长的变化趋势,也叫渐进时间复杂度,简称时间复杂度。 二、时间复杂度分析方法2.1 只关注循环执行次数最多的一段代码2.2 加法法则:总复杂度等于量级最大的那段代码的复杂度2.3 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积三、几种常见时间复杂度分析 3.1 非多项式量级非多项式量级只有两个:O(2的n次方)和O(n!) 3.2 多项式量级a) O(1)一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是O(1)。 b) O(logn)、O(nlogn)对数阶时间复杂度非常常见,同时也是最难分析的一种时间复杂度。归并排序、快速排序的时间复杂度都是O(nlogn)。 c) O(m + n)、O(m * n)T1(m) + T2(n) = O(f(m) + g(n))T1(m) * T2(n) = O(f(m) * ...