《漫画算法:小灰的算法之旅》读后
第一章:算法概述
略
第二章:数据结构基础
基础定义
同量级函数: 存在函数f(n),使得f(n)/T(n)的极限值为不是0的常数。记作T(n) = O(f(n)),这里的O渐进时间复杂度,简称时间复杂度
if f(n) exist && f(n max)/T(n) !=0
其实就是把时间函数T(n),简化成一个数
随机读取:
比如有个数组,你array[3],就是拿下标为3的数据,这种根据下标读取元素的方式就叫随机读取
物理结构:内存中实实在在存在的就叫物理结构,比如list、array
逻辑结构:靠意淫想象出来的结构,比如redis的跳表
栈:线性的数据结构。先入后出(FILO),比如吧一个球放进只有一个孔的半密闭容器,只能从上往下拿球。最上面的叫栈顶,最下面的叫栈底。方法分别对应压栈/入栈 push和出栈pop
队列:线性的数据结构。先入先出。按照顺序一个一个的执行,不能跳过或者删除。队列的出口叫队头,入口叫队尾。方法分别对应入队enqueue和出队dequeue。需要注意的是出队只允许出对头的那个
循环队列:吧队列想成一个首尾相接的圆环,下次取队头的时候就根据 (队尾下标+1)%数组长度 来算。
栈的一些小应用举例
1.比如某些网页的返回上一次
2.比如递归找到上一层调用,从而找到整个的调用链
队列的一些小应用举例
1.按顺序争夺公平锁的等待队列
2.rabbitmq
优先级队列的小举例:
1.非公平锁里面可能是按照优先级来获取的
散列表/哈希表:其实就是键值对
哈希函数:将键和数据下班进行转换的方法,就叫哈希函数
哈希的写操作:
1.先对key做hash操作,返回值就是找到key的下标位置
具体的是
// 先对key做哈希运算 // 这个>>> 表示无符号右移,如果是正数 则和>>一样,表示除以2的n次方; 忽略符号位,空位都以0补齐 (h = key.hashCode()) ^ (h >>> 16)
2.putValue
如果长度不够就扩容(含负载因子)
如果对应的下标没有就吧这个entry放过去
如果已经存在了,则吧原来的key的值和当前的值做对比。如果是一样的,就覆盖;如果不是一样的,就先变成链表,数量到了8个以上就变成红黑树。这就是所谓的哈希冲突
发生哈希碰撞(就是出现了多个相同的哈希值),jdk7是将新的放到头部去,jdk8是放到数组最后面去。哈希碰撞主要是为了避免哈希碰撞拒绝服务攻击(因为在元素放置过程中,如果一个对象哈希冲突, 都被放置到同一个桶里,则会形成一个链表,我们知道链表查询是线性的,查的慢,会严重影响存取的性能。然后导致cpu大量占用)
至于解决哈希冲突的方法方法,有4个方法
- 开放地址
- 再哈希
- 链地址(这个是hashmap现在用的大value链表的,出现重复的就把它们串在同一个list里面。这个方法叫链地址法)
- 建立公共溢出区
其中开放地址又分为:
- 线性探测再散列
- 二次探测再散列
- 伪随机探测再散列
开放地址寻址法可以理解为:比如我的key的下标是5,那么我继续往下看看下标为6的有没有数据,有的话,去看看7.如果7没有数据,则吧当前数据插进去。ThreadLoack就是用的开放寻址法
map的getValue
1.寻找下标等等和put一样。
2.明显是如果找到了就先比较下字面值,如果一样则返回走,如果不一样则继续往下查。现在知道为什么哈希冲突会增大查询速度吧?
我们知道链表查询是线性的,查的慢,会严重影响存取的性能。然后导致cpu大量占用
扩容
需要注意的是扩容的大小和重新哈希
大小是原来的2倍
重新哈希:遍历原来的数组,吧原来的key全部重新再hash放进新的数组。重哈希的原因是长度扩大后,hash的规则也会跟着改变。经过扩容,如果原来的数据不再哈希,那么所有的老数据都会挤在前面,后面空荡荡的
回到最开始,为什么哈希查询这么快呢?
因为hash是通过下标查询的,所以是O(1)
第三章:树
树的定义:
1.有且仅有一个特定的称为根
2.n>1时候,其余节点可分为m个互不相交的有限集,每一个集合本身又是一棵树
最外层的叫叶子节点。比如图中的3 、6 、18 、21
由同一个节点衍生出来的节点叫兄弟节点,比如4和6 5和10 18和21
树的最大层级数,叫树的高度或者深度 上图的高度是4
二叉树:在普通树的限制上,再加上每个节点最多只有2个子节点,左边的叫左孩子,右边的叫右孩子。这左右的顺序是固定的
二叉树又分城 满二叉树和完全二叉树
满二叉树:要求叶子节点都在同一行,并且每个非叶子节点都存在左右孩子
完全二叉树:满二叉树的弱化版本。叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。上图是符合的
因为二叉树属于逻辑结构,所以还是要通过物理结构来表述的
二叉查找树:在二叉树的基础上新增:
1.如果左子树不为空,那么这下面的所有子节点的的值都小于这个节点的值
2.如果右子树不为空,那么这下面的所有子节点的值都大于这个节点的值
3.左右子树也是二叉查找树(相当于递归了)-
二叉查找树的好处:对于节点分布相对均衡的二叉树来说,搜索节点的时间复杂度是o(logn)
比如上图要查4 ,先查根节点,发现4比7小,所以继续查5,;发现4比5小 所以查左下角的4.找到了。二叉树分成两半,所以搜索节点的时间复杂度是o(logn)。是不是感觉很像redis的跳表?
因为二叉查找树会维持着左右平衡,所以又叫做二叉平衡树
二叉堆:也维持着相对的顺序,但是只要求父节点比左右孩子大
根据遍历节点的关系:
一棵树,总共由三个部分组成,分别为 根、左节点、右节点。英文简称D、L、R。
所以按照顺序 排列组合有六种:DLR、DRL、LDR、LRD、RLD、RDL
因为先遍历左节点和遍历右节点的顺序是没有本质的区别的,就是左撇子和右撇子的区别。所以真正的就3种组合 6/2=3
前序遍历DLR。顾名思义,从根节点开始,先查左节点,然后查右节点。按照上图就是:先查根节点G。G有左节点D,查D。 D有左节点A。A没有左节点,则回到上一层查D。那么查D的右节点;同理,查到完。。。。。。。。。等等等等。所以总体顺序是 G-->D-->A-->F-->E-->M-->H-->Z
中序LDR。L是左节点,所以先左一路走到底,发现走不下去了,回退一格(即父节点),然后走右节点找最左边的节点。按照图中的顺序是A-->D-->E-->F-->G-->H-->M-->Z
后序LRD。输出它的左孩子,然后输出它的右孩子,最后输出该结点。A-->E-->F-->D-->H-->Z-->M-->G 。需要注意的是查到D的时候,先看到D的右节点是M,但是不是马上查M,而是查最左边的H,因为要走到底嘛
大部分可以通过递归来遍历的,都可以用栈来表示
至于通过栈来遍历的话,可以理解为前序遍历++
比如上图,先放G入栈,找到G的左D入栈,继续找到D的左节点A入栈。A没有左右节点,所以A出栈(栈的特点就是先入先出)。重新回到了D,因为自己已经没什么利用价值了,所以D出栈(这里的出栈原因是,因为这是先一直往左,所以D实际上是没有用的),找到D的儿子F入栈,找到F的儿子E入栈,没用了,然后E出栈,然后F出栈。
整体的顺序是G入栈-->D入栈-->A入栈-->A出栈-->D出栈-->F入栈-->E入栈-->E出栈-->F出栈-->G出栈-->M入栈-->H入栈-->H出栈-->M出栈-->Z入栈-->Z出栈
深度优先遍历:直接走到无路可走再回头看继续走到死
广度优先遍历,大家一起放射性的走,一个深度走一步
拿队列来举例子,
比如上图是G入队-->G出队-->D、M入队-->D、M出队-->A、F、H、Z入队-->A、F、H、Z出队-->E入队-->E出队
二叉堆:二叉堆本质上是完全二叉树,只是多了个最大值的最大堆和最小值的最小堆的概念
最大堆的堆顶是整个堆的最大元素,最大堆任何一个父节点的值,都大于等于它左右孩子节点的值;
最小堆的堆顶是整个堆的最小元素,最小堆任何一个父节点的值,都小于等于它左右孩子节点的值。
Heap Visualization
二叉堆的插入、删除、构建,略
1.插入节点
二叉堆的节点插入,插入位置是完全二叉树的最后一个位置
比如这样图准备插入个1,则先放到7的右子节点去(当然,如果7的左右节点都满了,则跑到8的左节点去,以此类推;如果这一行没有了,则跑到最左边去,即9的左节点),发现1比7小,两者换个位置;发现1比3小,和3换个位置,发现比2小,和2换个位置
2.删除节点
二叉堆的节点删除过程和插入过程正好相反,所删除的是处于堆顶的节点。比如我们删除最小堆的堆顶节点1。目前我看到的好像只能删堆顶,知道为啥不?因为它这个是出栈,先从顶上来搞。
比如要删2,;吧最下面的11放到堆顶去(和增加一样,替换的是完全二叉树的最后一个位置),比较11和3、4的大小,发现3比11小,3、11互换位置;同理,比较11和6、7的大小,和6互换位置;比较11和9 、10的大小,和9互换位置,下面没有可以比的了,完事
本质上都是为了维持二叉堆的结构,即 让所有非叶子节点下沉。找到最大的/最小的,然后放到栈顶,然后所有连着的节点都往下走
因为删除和新增,每次都是对左右节点一半做判断,所以时间复杂度是O(logN)。但是堆的构建就是O(n)了,
解释:
自底向上建堆时: 1.最下层的元素n/2个元素不需要移动,剩下的n/2个元素在上面 2.倒数第二层的n/4个元素需要下沉1层,即n/4 *1 3.倒数第三层的n/8个元素需要下沉2层,即n/8*2 4.倒数第三层的n/16个元素需要下沉3层,即n/16*2 所以Sn=0*(n/2)+1*(n/4)+2*(n/8)+3*(n/16)+.............. 两边乘以2 2Sn=0*(n)+1*(n/2)+2*(n/4)+3*(n/8)+.............. 2Sn-Sn=n Sn=n... 所以时间复杂度为O(n) 熟悉不。。。。典型的高中错位相减
参考自为什么建立一个二叉堆的时间为O(N)而不是O(Nlog(N))? - 知乎
二叉堆是实现堆排序和优先队列的基础
优先队列又分成两种:最大优先队列和最小优先队列
顾名思义,就是不管入队顺序怎么样,出队的那个都是当前队列里面最大/最小的元素
是不是感觉和最大/最小二叉堆很像?入队就是放到堆的末尾,然后浮上去。出队就是把顶给拿走咯
因为二叉堆的上浮起和下沉的时间复杂度都是O(logN),所以优先队列入队的入队和出队也都是O(logN)
第三章:排序算法
可以把算法按照时间复杂度分成
O(n2):
- 冒泡排序
- 选择排序
- 插入排序
O(n logn)
- 快排
- 规定排序
- 堆排序
O(1)
- 计数排序
- 桶排序
- 基数排序
一.冒泡
吧相邻的元素两两对比,然后交换顺序。最大的数放到最右边
冒泡是一种稳定排序,因为不会打乱元素相等时原来的顺序
复杂度分析下
1.两两对比,然后交换 花费O(n)
2.要遍历 n-1轮,所以总体是O(n2)
二.鸡尾酒排序
把相邻的元素两两对比,然后交换顺序。最大的数放到最右边,第二轮的时候吧最小的数放最左边,第三轮的时候第二大的放最右边-1的位置。。。以此类推。因为很像摇可乐,但是为了讲究虚伪的优雅,叫鸡尾酒排序更好听点
优势当然是大部分元素已经有序的情况
优点是特定条件下排序的回合数减少了,缺点当然是代码也差不多加了一倍,因为你往右摇和往左摇都要区分的
复杂度分析下
1.先往左遍历一遍 找到最小的,花费O(n)
2.再往右遍历一遍 找到最大的,花费O(n)
.....
因为总共有n个元素,所以总体花费O(n2)
三.快速排序
分而治之,和以前的砍树一样,每次砍一半,直到砍不下去为止.
平均时间复杂度是O(nLog n):假设有n个元素,每个元素都要被作为基准元素,平均情况下 每个元素需要logN轮才可以。所以是O(nLog n)
最坏的时间复杂度是O(n2),比如基准元素取得不好,每次都是当前未排序的元素里面最大/最小的,这样就达不到分而治之的程度了,只能一边倒
因为快拍每次选完基准元素之后,还要进行再排序,即把大的放到右边,小的放到左边。所以又分成两种元素交换的方法,即双边排序和单边排序
双边排序,
搞2个指针,最左边一个,最右边一个。如果右指针比左边的小,则两者交换位置,如果大,则右指针往左移一位,然后继续比较大小。比到左指针没有比较更小的时候,左指针右移,继续比,直到没有的比为止
单边排序,只要一个左指针,然后依次和右边的来比较,同理,比到左指针没有比较更小的时候,左指针右移,继续比,知道没有的比为止
大部分的递归 都可以用堆来代替,比如上面的单边排序--->只要一个左指针,然后依次和右边的来比较,比到左指针没有比较更小的时候,最左边的出栈,然后最左边下一个数据作为左指针,继续比,直到栈空了为止。
四.堆排序
最大/最小二叉堆本质上可以理解为一个有序的数组,那么也可以通过二叉堆来进行排序
步骤
1.先把无需数组构建成二叉堆,时间复杂度O(n)-----》可以理解为插入到空的二叉堆,n个元素就是O(n)
2.构建最大/最小二叉树。需要n-1次(去掉自己那层)调用二叉堆叶子下移的方法,因为二叉每次遍历一半,所以时间复杂度是(n-1)*logN 即时间复杂度为O(nLog n)
堆排序和快拍的对比:
相同点
1.平均时间复杂度都是O(n log n)
2.都是不稳定排序,打乱了原有相同元素的顺序
不同点
1.快排的最坏时间复杂度是O(n2),即基准数每次选的最小边或者最大的;堆排序最坏的时间复杂度在O(nLog n)
2.快排和递归的平均空间复杂度是O(logn),堆排序是O(1)------------------>以前我误认为快排空间复杂度是O(1)。和时间复杂度相关,每次递归需要的空间是固定的,总体空间复杂度即为递归层数,因此平均/最好空间复杂度为O(logn),最坏空间复杂度为O(n)。 因为堆排序是就地排序,空间复杂度为常数:O(1)
五.计数排序
找出最大数m和最小数n,然后建立m-n+1个有序的格子,统计这些数出现的次数
比如 有数据 5,8,7,10,2,5,6,则建立格子2,3,4,5,6,7,8,9,10 ,统计各个数字出现的次数即最终2*1,3*0,4*0,5*2,6*1,7*1,8*1,9*0,10*1
去掉次数为0的 ,即2,5*2,6,7,8,10,把乘号去掉即 2,5,6,7,8,10
适用于取值范围差别不是很大的情况,性能甚至能超过那些时间复杂度为O(logN)的排序。并且排序不适用于整数
但是如果取值范围差别很大,就很浪费了,比如由数据1,10000 排这2个数,就要建10000个格子。而且无法保持数组原有的顺序,比如2个5的顺序你就没法区分哪个在前面
六.桶排序:
先建立n个桶,这个桶里面装一个区间范围。先把所有数据按区间放入不同的桶。然后再对每个桶内部的元素进行排序,有点类似于分而治之。
区间跨度=(最大值-最小值)/(桶的数量-1)
桶排序可以看做是计数排序缺点的改良版本
桶排序的时间复杂度分析:
1.找到数列最大和最小值,花费O(n)
2.创建n个空桶,花费的也是O(n)
3.把原来的数组都放到桶里面,也是O(n)
4.每个桶做排序, 如果元素分布均匀还是 O(n);如果极不均匀,则是O(nLogN)
5.输出新的排序序列,也是O(n)
因为时间复杂度前面的常数是可以省略的,所以总体的 常规情况分布均匀还是O(n),极端最坏的条件是O(nLogN)
排序算法 |
平均时间复杂度 |
最坏时间复杂度 |
最坏情况 |
空间复杂度 |
是否稳定排序(保持顺序) |
冒泡 |
O(n2) |
O(n2) |
已经有序了还会继续O(n2) |
O(1) |
稳定 |
鸡尾酒 |
O(n2) |
O(n2) |
已经有序了还会继续O(n2) |
O(1) |
稳定 |
快排 |
O(nLogN) |
O(n2) |
1.已经有序了,无论是正序还是倒序,还会继续O(n2),就和普通冒泡没区别了 2.所有的元素都相同 |
O(logN) |
不稳定 |
堆排序 |
O(nLogN) |
O(nLogN) |
无 |
O(1)-->数组内构建的 |
不稳定 |
计数排序 |
O(n+m) |
O(n+m) |
最大值m和最小值m的差距过大 |
o(m) |
稳定 |
桶排序 |
O(n) |
O(nLogN) |
第一个桶n-1个元素,第二个桶1个元素 |
O(n) |
稳定 |
LRU:最近最少使用
redis里面经常有
最初的代码如下:key都没有优化,感觉和限流一样
private static final int ARRAYS_MAX_LENGTH = 4; public static void main(String[] args) { List<Integer> array = new ArrayList<>(); add(1, array); add(2, array); add(3, array); add(4, array); add(5, array); add(6, array); add(7, array); add(8, array); System.out.println(array); } public static void add(int addNum, List<Integer> array) { if (array.size() < ARRAYS_MAX_LENGTH) { array.add(array.size(), addNum); return; } // 如果超过了数组最大的长度,移除最左边的 for (int i = 0; i < array.size(); i++) { if (i < array.size() - 1) { array.set(i, array.get(i + 1)); } else { array.set(i, addNum); } } }
// 如果是用map的话,当然 能被优化空间还有很大
/** * @author PrinceCharimgDong * @description: * @date 2021/12/28 18:55 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Node { //双向链表 private Node pre; private Node next; private String key; private String value; // 因为属性引用子属性,导致toString方法会不断调用子节点的属性。最终栈溢出。 所以pre和next不能toString出来 @Override public String toString() { return "Node{" + ", key='" + key + '\'' + ", value='" + value + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; return Objects.equals(pre, node.pre) && Objects.equals(next, node.next) && Objects.equals(key, node.key) && Objects.equals(value, node.value); } @Override public int hashCode() { return Objects.hash(pre, next, key, value); } } class LruDemo { private static final int ARRAYS_MAX_LENGTH = 4; private Node head; private Node end; private Map<String, Node> saveDate; public void add(String key, String value) { Node lruDemo = saveDate.get(key); if (null != lruDemo) { return; } lruDemo = Node.builder().key(key).value(value).build(); if (saveDate.size() == 0) { head = lruDemo; end = null; } //如果超过了长度,则去掉第一个 if (saveDate.size() >= ARRAYS_MAX_LENGTH) { String oldFirstKey = head.getKey(); Node node2 = head.getNext(); saveDate.remove(oldFirstKey); // 修改新的第一个数据 head = node2; } // init上一个的next if (saveDate.size() > 1) { String lastKey2 = end.getKey(); Node lastNode2 = saveDate.get(lastKey2); lastNode2.setNext(lruDemo); saveDate.put(lastKey2, lastNode2); lruDemo.setPre(lastNode2); lruDemo.setNext(null); } saveDate.put(key, lruDemo); end = lruDemo; } } public static void main(String[] args) { LruDemo lruDemo = new LruDemo(); lruDemo.add("1", "a"); lruDemo.add("2", "b"); lruDemo.add("3", "c"); lruDemo.add("2", "b"); lruDemo.add("4", "d"); lruDemo.add("5", "e"); lruDemo.add("6", "f"); lruDemo.add("7", "g"); lruDemo.add("7", "g"); lruDemo.add("8", "h"); System.out.println(); }
A星算法==> A*
我们从起点开始,检查其相邻的方格,然后向四周扩展,直至找到目标。
1.拆成二维坐标系,确定起点和重点
2.忽略障碍物!引入常量,E即当前点和终点的最短直线距离。S即当前点和起点的步数。F即起点到终点的距离
3.从起点开始走,走一步计算下下一个
3.每一步找出最小的F
拆红包的比较公平的小技巧:线段切割法和二倍均值法
n倍均值法:把每个人的预期金额*n,最后一次的取余。
比如 100块钱的红包10个人抢
那么第一个人的概率是 100/10*2,范围在0-20,中位数是10
第2个人的概率是 100/10*2,范围在0-20,中位数是10
第n个人的概率是 100/10*2,范围在0-20,中位数是10
最后一个人的范围是 0-(100-前面所有个人金额加起来的值)
缺点:除了最后一次取余抢,任何一次抢到的金额都不会超过人均金额的n倍,且并不是绝对的任意的随机
线段切割法
将金额看成一条线段,线段的长度放大一百倍,即范围是0到M*100,首先需要生成1-(M*100-1) 中间的(N-1)个随机的且不重复的数,可以使用这(N-1)个数去切割线段,切割后的每一份就是红包的金额数。
2022年1月11日15:13:02
《漫画算法:小灰的算法之旅》读后相关推荐
- 【总结】Java核心技术36讲知识点大纲(转载)
Java核心技术36讲知识点总结大纲 1 Java平台的理解 Java的特性,解释运行和编译运行 2 Exception 和 Error 有什么区别 理解Java的异常体系的设计,Throwable ...
- 【总结】Java核心技术36讲知识点大纲
前段时间在极客时间上购买了杨晓峰老师的<Java核心技术36讲>,趁着这段时间有空,对相关知识点做了一个整体的大纲,也对自己所掌握的Java基础进行了一个复习和梳理,若想深入学习,可以购买 ...
- 杨晓峰-java核心技术36讲(学习笔记)- 第1讲 | 谈谈你对Java平台的理解?
杨晓峰-java核心技术36讲(学习笔记) 接下来我会分享杨晓峰-java核心技术36讲的学习笔记,内容较多,补充了其中一些牛人评论,相对详细(仅供个人学习记录整理,希望大家支持正版:https:// ...
- 读Java核心技术36讲有感——谈谈对Java的理解,谈谈Exception和Error
读过杨晓峰老师的36讲之后,想总结下自己的感想,写下来也有助于记忆,方便以后面试查阅和复习.题目所提到的话题本来是两讲,但是由于感想篇幅较短,所以合成一篇来写. 一.谈谈对Java平台的理解: 1.J ...
- 《java核心技术36讲》学习笔记-------杨晓峰(极客时间)
非常荣幸作为晓峰哥的同事,之前就看过这篇文章,重写读一遍,再学习学习.同时也推荐给大家 一.开篇词 初级.中级:java和计算机科学基础.开源框架的使用:高级.专家:java io/nio.并发.虚拟 ...
- Java核心技术36讲
java平台的理解 谈谈你对 Java 平台的理解?"Java 是解释执行",这句话正确么? Java本身是一种面向对象的语音,最显著的特性有两个方面,一个是所谓的"书写 ...
- Java核心技术36讲(个人整理)
今天我要问你的问题是,谈谈你对 Java 平台的理解? "Java 是解释执行",这句话正确吗? Java特性: 面向对象(封装,继承,多态) 平台无关性(JVM运行.class文 ...
- Java核心技术36讲 第一讲:Java平台的理解
java语言 一次编译,到处运行 GC.Garbage Collection JRE: Java Runtime Environment 包含JVM和Java类库等 JDK: Java Develop ...
- 杨晓峰Java核心36讲学习笔记
最近在极客时间上订阅了Oracle首席工程师杨晓峰的Java核心技术36讲, 接下来会对每一课的学习: 记下学习笔记 有不懂的地方继续深入 一些思考或者总结. 下面从第一课开始,Exception和E ...
- java核心技术精讲-李兴华-专题视频课程
java核心技术精讲-101993人已学习 课程介绍 本课程主要读者全面细致的讲解Java编程的所有核心知识,从基础语法.到面向对象以及Java的实际应用进行完整讲解.官方QQ群:61 ...
最新文章
- php request payload怎么接收,[问题] PHP接收Request payload传递过来的参数
- hexo部署在Github-Page流程
- 深入理解Java虚拟机知乎_深入理解Java虚拟机(类文件结构)
- 第二轮冲次会议第三次
- 你了解京东云区块链吗?点开有详情!
- 模糊综合评价的 matlab,模糊综合评价法代码matlab
- 计算机操作系统第四版课后习题答案(完整版)
- 整理:周鸿祎谈如何写商业计划书
- 免费又好用怎么把文字转换成语音呢?分享我常用的3个配音神器
- 表格标签-表格基本结构
- 360度全景标定方法_一种车辆行驶360度全景行车标定板的制作方法
- 【QT】实现贪吃蛇小游戏(附源码)
- XSSFWorkbook 设置单元格样式_6.6 使用单元格样式
- Docer可视化管理工具Portainer部署
- SpringBoot写一个聊天工具
- 转移神经网络_神经体系结构转移
- Excel 中自定义函数的限制
- DevOps ACA 阿里云效持续交付流水线(十)
- psm进销存管理系统、供应商管理、进货管理、销售管理、仓库管理、采购记录
- 企业组织架构.1___VIE模式
热门文章
- Apaceh的访问控制 日志分割 分析
- HC-SR04超声波传感器使用
- npm ERR! cb() never called! 解决办法
- 爱奇艺2020春季校园招聘全面开启!
- django mysql 时间_Python Django MySQL,时区、日期、时间戳(USE_TZ=True时的时间存储问题)...
- 前后端分离项目token怎么验证_微信端前后端分离开发中token验证和数据的获取...
- 巨人征途的成功 依赖于不处不在的海报
- matlab使用矩形窗设计一个具有线性相位的低通数字滤波器,用矩形窗设计一个FIR线性相位低通数字滤波器...
- 沉浸式状态栏和虚拟键盘冲突
- 图书馆管理系统(C++实现)(含自定义数据库操作)