最近学习了极客时间的《数据结构与算法之美]》很有收获,记录总结一下。
欢迎学习老师的专栏:数据结构与算法之美
代码地址:http://github.com/peiniwan/Arithmetic
复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半。
所有代码的执行时间 T(n) 与每行代码的执行次数成正比。
T(n) =O( f(n) )
int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
总的时间复杂度就等于量级最大的那段代码的时间复杂度
int i = 8; int j = 6; int sum = i + j;
只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
跟数据规模 n 没有关系,都可忽略
i=1; while (i <= n) { i = i * 2; }
从代码中可以看出,变量 i 的值从 1 开始取,每循环一次就乘以 2。当大于 n 时,循环结束。还记得我们高中学过的等比数列吗?实际上,变量 i 的取值就是一个等比数列。如果我把它一个一个列出来,就应该是这个样子的:
2x=n
x=log2n
i=1; while (i <= n) { i = i * 3; }
这段代码的时间复杂度为 O(log3n)。
但是实际上,不管是以 2 为底、以 3 为底,还是以 10 为底,我们可以把所有对数阶的时间复杂度都记为 O(logn)
O(nlogn)
如果一段代码的时间复杂度是 O(logn),我们循环执行 n 遍,时间复杂度就是 O(nlogn) 了。而且,O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)
代码的复杂度由两个数据的规模来决定
int cal(int m, int n) {
int sum_1 = 0;
int i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
从代码中可以看出,m 和 n 是表示两个数据规模。我们无法事先评估 m 和 n 谁的量级大,所以我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以,上面代码的时间复杂度就是 O(m+n)。针对这种情况,原来的加法法则就不正确了
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。
而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。
计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址:
a[i]_address = base_address + i * data_type_size
其中 base_address 是首地址,data_type_size 表示数组中每个元素的大小。我们举的这个例子里,数组中存储的是 int 类型数据,所以 data_type_size 就为 4 个字节。根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址。
但是,如果数组从 1 开始计数,那我们计算数组元素 a[k] 的内存地址就会变为:
a[k]_address = base_address + (k-1)*type_size
对比两个公式,我们不难发现,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。所以数组从0开始。
一个错误:
在面试的时候,常常会问数组和链表的区别,很多人都回答说,“链表适合插入、删除,时间复杂度 O(1);数组适合查找,查找时间复杂度为 O(1)”。
实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为 O(1)。即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)(k在第几个位置)。所以,正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。
ArrayList
最大的优势就是可以将很多数组操作的细节封装起来。比如前面提到的数组插入、删除数据时需要搬移其他数据等。另外,它还有一个优势,就是支持动态扩容。
如果使用 ArrayList,我们就完全不需要关心底层的扩容逻辑,ArrayList 已经帮我们实现好了。每次存储空间不够的时候,它都会将空间自动扩容为 1.5 倍大小。
Java ArrayList 无法存储基本类型,比如 int、long,需要封装为 Integer、Long 类,所以如果特别关注性能,或者希望使用基本类型,就可以选用数组。
从图中看到,数组需要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个 100MB 大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。
而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题。
针对链表的插入和删除操作,只需要考虑相邻结点的指针改变,所以对应的时间复杂度是 O(1)。
从结构上来看,双向链表可以支持 O(1) 时间复杂度的情况下找到前驱结点,正是这样的特点,也使双向链表在某些情况下的插入、删除等操作都要比单链表简单、高效。
除了插入、删除操作有优势之外,对于一个有序链表,双向链表的按值查询的效率也要比单链表高一些。因为,我们可以记录上次查找的位置 p,每次查询时,根据要查找的值与 p 的大小关系,决定是往前还是往后查找,所以平均只需要查找一半的数据。
LinkedHashMap 的实现原理,就会发现其中就用到了双向链表这种数据结构。(用空间换时间)
已知前驱节点
数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out of memory)”。如果声明的数组过小,则可能出现不够用的情况。这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常费时。链表本身没有大小的限制,天然地支持动态扩容,我觉得这也是它与数组最大的区别。
如果你的代码对内存的使用非常苛刻,那数组就更适合你。因为链表中的每个结点都需要消耗额外的存储空间去存储一份指向下一个结点的指针,所以内存消耗会翻倍。而且,对链表进行频繁的插入、删除操作,还会导致频繁的内存申请和释放,容易造成内存碎片,如果是 Java 语言,就有可能会导致频繁的 GC(Garbage Collection,垃圾回收)。
技巧一:理解指针或引用的含义
我们知道,有些语言有“指针”的概念,比如 C 语言;有些语言没有指针,取而代之的是“引用”,比如 Java、Python。不管是“指针”还是“引用”,实际上,它们的意思都是一样的,都是存储所指对象的内存地址。
如果你用的是 Java 或者其他没有指针的语言也没关系,你把它理解成“引用”就可以了。
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
在编写链表代码的时候,我们经常会有这样的代码:p->next=q。这行代码是说,p 结点中的 next 指针存储了 q 结点的内存地址。
还有一个更复杂的,也是我们写链表代码经常会用到的:p->next=p->next->next。这行代码表示,p 结点的 next 指针存储了 p 结点的下下一个结点的内存地址。
技巧三:利用哨兵简化实现难度
针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。
如果我们引入哨兵结点,在任何时候,不管链表是不是空,head 指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。
我画了一个带头链表,你可以发现,哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。
利用哨兵简化编程难度的技巧,在很多代码实现中都有用到,比如插入排序、归并排序、动态规划等。
技巧四:重点留意边界条件处理
我经常用来检查链表代码是否正确的边界条件有这样几个:
技巧五:举例画图,辅助思考
举例法和画图法
比如往单链表中插入一个数据这样一个操作,我一般都是把各种情况都举一个例子,画出插入前和插入后的链表变化,如图所示:
技巧六:多写多练,没有捷径
我精选了 5 个常见的链表操作。你只要把这几个操作都能写熟练,不熟就多写几遍,我保证你之后再也不会害怕写链表代码。
单链表反转
链表中环的检测
两个有序的链表合并
删除链表倒数第 n 个结点
求链表的中间结点
练习题LeetCode对应编号:206,141,21,19,876。大家可以去练习,另外建议作者兄每章直接给出LC的题目编号或链接方便大家练习。
当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。
栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。
// 基于数组实现的顺序栈
public class ArrayStack {
private String[] items; // 数组
private int count; // 栈中元素个数
private int n; //栈的大小
// 初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n) {
this.items = new String[n];
this.n = n;
this.count = 0;
}
// 入栈操作
public boolean push(String item) {
// 数组空间不够了,直接返回false,入栈失败。
if (count == n) return false;
// 将item放到下标为count的位置,并且count加一
items[count] = item;
++count;
return true;
}
// 出栈操作
public String pop() {
// 栈为空,则直接返回null
if (count == 0) return null;
// 返回下标为count-1的数组元素,并且栈中元素个数count减一
String tmp = items[count-1];
--count;
return tmp;
}
}
不管是顺序栈还是链式栈,我们存储数据只需要一个大小为 n 的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。
注意,这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。因为,这 n 个空间是必须的,无法省掉。
所以我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。空间复杂度分析是不是很简单?
时间复杂度也不难。不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是 O(1)。
如果要实现一个支持动态扩容的栈,我们只需要底层依赖一个支持动态扩容的数组就可以了。当栈满了之后,我们就申请一个更大的数组,将原来的数据搬移到新数组中。
栈在函数调用中的应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
int main() {
int a = 1;
int ret = 0;
int res = 0;
ret = add(3, 5);
res = a + ret;
printf("%d", res);
reuturn 0;
}
int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}
栈在表达式求值中的应用
编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。
静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。
先进者先出,这就是典型的“队列”。队列跟栈一样,也是一种操作受限的线性表数据结构。
队列的应用也非常广泛,特别是一些具有某些额外特性的队列,比如循环队列、阻塞队列、并发队列。
跟栈一样,队列可以用数组来实现,也可以用链表来实现。用数组实现的栈叫作顺序栈,用链表实现的栈叫作链式栈。同样,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列。
我们刚才用数组来实现队列的时候,在 tail==n 时,会有数据搬移操作,这样入队操作性能就会受到影响。那有没有办法能够避免数据搬移呢?我们来看看循环队列的解决思路。
循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。
图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,我们放入下标为 7 的位置。但这个时候,我们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,我们将 b 放入下标为 0 的位置,然后 tail 加 1 更新为 1。所以,在 a,b 依次入队之后,循环队列中的元素就变成了下面的样子:
要确定好队空和队满的判定条件。
在用数组实现的非循环队列中,队满的判断条件是 tail == n,队空的判断条件是 head == tail。那针对循环队列,
如何判断队空和队满呢?队列为空的判断条件仍然是 head == tail。但队列满的判断条件就稍微有点复杂了。我画了一张队列满的图
就像我图中画的队满的情况,tail=3,head=4,n=8,所以总结一下规律就是:(3+1)%8=4。多画几张队满的图,你就会发现,当队满时,(tail+1)%n=head。
你有没有发现,当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。
public class CircularQueue {
// 数组:items,数组大小:n
private String[] items;
private int n = 0;
// head表示队头下标,tail表示队尾下标
private int head = 0;
private int tail = 0;
// 申请一个大小为capacity的数组
public CircularQueue(int capacity) {
items = new String[capacity];
n = capacity;
}
// 入队
public boolean enqueue(String item) {
// 队列满了
if ((tail + 1) % n == head) return false;
items[tail] = item;
tail = (tail + 1) % n;
return true;
}
// 出队
public String dequeue() {
// 如果head == tail 表示队列为空
if (head == tail) return null;
String ret = items[head];
head = (head + 1) % n;
return ret;
}
}
阻塞队列其实就是在队列基础上增加了阻塞操作。简单来说,就是在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。
上述的定义就是一个“生产者 - 消费者模型”!是的,我们可以使用阻塞队列,轻松实现一个“生产者 - 消费者模型”!
这种基于阻塞队列实现的“生产者 - 消费者模型”,可以有效地协调生产和消费的速度。当“生产者”生产数据的速度过快,“消费者”来不及消费时,存储数据的队列很快就会满了。这个时候,生产者就阻塞等待,直到“消费者”消费了数据,“生产者”才会被唤醒继续“生产”。
基于阻塞队列,我们还可以通过协调“生产者”和“消费者”的个数,来提高数据的处理效率。比如前面的例子,我们可以多配置几个“消费者”,来应对一个“生产者”。
前面我们讲了阻塞队列,在多线程情况下,会有多个线程同时操作队列,这个时候就会存在线程安全问题,那如何实现一个线程安全的队列呢?
线程安全的队列我们叫作并发队列。最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。这也是循环x队列比链式队列应用更加广泛的原因。在实战篇讲 Disruptor 的时候,我会再详细讲并发队列的应用。
ConcurrentLinkedQueue : 是一个适用于高并发场景下的队列,通过无锁的方式,实现
了高并发状态下的高性能
CAS理论:compare and swap 比较并交换。该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值
线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢?
我们一般有两种处理策略。第一种是非阻塞的处理方式,直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。那如何存储排队的请求呢?
基于链表的实现方式,可以实现一个支持无限排队的无界队列(unbounded queue),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。
而基于数组实现的有界队列(bounded queue),队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。不过,设置一个合理的队列大小,也是非常有讲究的。队列太大导致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能。
除了前面讲到队列应用在线程池请求排队的场景之外,队列可以应用在任何有限资源池中,用于排队请求,比如数据库连接池等。实际上,对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队。
我们知道,树中的元素我们称为节点,图中的元素我们就叫作顶点(vertex)。从我画的图中可以看出来,图中的一个顶点可以与任意其他顶点建立连接关系。我们把这种建立的关系叫作边(edge)。
如何存储微博、微信等社交网络中的好友关系?
我们就拿微信举例子吧。我们可以把每个用户看作一个顶点。如果两个用户之间互加好友,那就在两者之间建立一条边。所以,整个微信的好友关系就可以用一张图来表示。其中,每个用户有多少个好友,对应到图中,就叫作顶点的度(degree),就是跟顶点相连接的边的条数。
如果用户 A 关注了用户 B,我们就在图中画一条从 A 到 B 的带箭头的边,来表示边的方向。如果用户 A 和用户 B 互相关注了,那我们就画一条从 A 指向 B 的边,再画一条从 B 指向 A 的边。我们把这种边有方向的图叫作“有向图”。以此类推,我们把边没有方向的图就叫作“无向图”。
无向图中有“度”这个概念,表示一个顶点有多少条边。在有向图中,我们把度分为入度(In-degree)和出度(Out-degree)。
顶点的入度,表示有多少条边指向这个顶点;顶点的出度,表示有多少条边是以这个顶点为起点指向其他顶点。对应到微博的例子,入度就表示有多少粉丝,出度就表示关注了多少人。
带权图(weighted graph)。在带权图中,每条边都有一个权重(weight),我们可以通过这个权重来表示 QQ 好友间的亲密度。
掌握了图的概念之后,我们再来看下,如何在内存中存储图这种数据结构呢?
我们存储的是稀疏图(Sparse Matrix),也就是说,顶点很多,但每个顶点的边并不多,那邻接矩阵的存储方法就更加浪费空间了。比如微信有好几亿的用户,对应到图上就是好几亿的顶点。但是每个用户的好友并不会很多,一般也就三五百个而已。如果我们用邻接矩阵来存储,那绝大部分的存储空间都被浪费了。
邻接矩阵存储起来比较浪费空间,但是使用起来比较节省时间。
邻接表存储起来比较节省空间,但是使用起来就比较耗时间。
我们可以将邻接表中的链表改成平衡二叉查找树。实际开发中,我们可以选择用红黑树。这样,我们就可以更加快速地查找两个顶点之间是否存在边了。当然,这里的二叉查找树可以换成其他动态数据结构,比如跳表、散列表等。除此之外,我们还可以将链表改成有序动态数组,可以通过二分查找的方法来快速定位两个顶点之间否是存在边。
实现无向图
public class Graph { // 无向图
private int v; // 顶点的个数
private LinkedList<Integer> adj[]; // 邻接表
public Graph(int v) {
this.v = v;
adj = new LinkedList[v];
for (int i=0; i<v; ++i) {
adj[i] = new LinkedList<>();
}
}
public void addEdge(int s, int t) { // 无向图一条边存两次
adj[s].add(t);
adj[t].add(s);
}
}
广度优先搜索(BFS)
直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。
深度优先搜索(DFS)
一、SMB(ServerMessageBlock,SMB)介绍腾讯云文件存储(CloudFileStorage,CFS)除了提供标准的NFS文件系统访问协议之外,还提供了SMB共享文件系统访问协议,SMB在Windows上使用非常广泛(类似于NFS在Linux上的使用),属于主流协议,接下来,我将带领各位快速上手WindowsSMB文件系统配置。腾讯云文件存储概述:https://cloud.tencent.com/document/product/582/9127微软SMBOverview:https://docs.microsoft.com/en-us/windows-server/storage/file-server/file-server-smb-overview腾讯云控制台文件存储直达链接(需要腾讯云登录账号):https://console.cloud.tencent.com/cfsSMB版本与WindowsOS直接的对应关系:SMB版本WindowsOS版本SMB2.0WindowsVista、WindowsServer2008SMB2.1Windows7、Window
#FFT变换是针对一组数值进行运算的,这组数的长度N必须是2的整数次幂,例如64,128,256等等;数值可以是实数也可以是复数,通常我们的时域信号都是实数,因此下面都以实数为例。我们可以把这一组实数想像成对某个连续信号按照一定取样周期进行取样而得来,如果对这组N个实数值进行FFT变换,将得到一个有N个复数的数组,我们称此复数数组为频域信号,此复数数组符合如下规律: #其结果数组有以下特点: #下标为0和N/2的两个复数的虚数部分为0, #下标为i和N-i的两个复数共轭,也就是其虚数部分数值相同、符号相反 #首先下标为0的实数表示了时域信号中的直流成分的多少 #下标为i的复数a+b*j表示时域信号中周期为N/i个取样值的正弦波和余弦波的成分的多少, #其中a表示cos波形的成分,b表示sin波形的成分 importnumpyasnp importmatplotlib importmatplotlib.pyplotasplt pi=np.pi time_len=2.0#时长 N=2000#数据点数,须为偶数,FFT的要求 fs=N/time_len#[Hz]取样频率 f=np.arang
AI科技评论按:现如今,诸如小冰这类闲聊机器人逐渐进入了大众的视野,甚至成为了一部分人打发闲暇时光的伴侣。然而,现在的闲聊机器人在对话的互动性、一致性以及逻辑性上都还存在着一些亟待解决的缺陷。近日的AI研习社大讲堂上,来自清华大学的王延森分享了一篇收录于ACL2018的论文,尝试着利用提问来解决闲聊机器人互动性不足的问题。分享嘉宾:王延森,清华大学计算机系本科生,现于清华大学计算机系人工智能实验室交互式人工智能课题组,师从黄民烈教授从事科研工作,主要研究方向为对话系统、文本生成、逻辑信息处理等。公开课回放地址: http://www.mooc.ai/open/course/532分享主题:如何利用提问增强开放领域对话系统互动性分享提纲:开放领域对话系统的发展现状,存在的问题利用提问解决开放领域对话系统中存在的互动性问题的可行性与核心思路如何利用分类型解码器(typeddecoder)在对话系统中提出恰当而有意义的问题模型在数据集上结果分析以及仍然存在的问题AI研习社将其分享内容整理如下:今天很荣幸可以和大家分享这篇论文,我们把类型解码器运用到开放式对话系统的提问中,来增强系统和人的互动
错误信息如下:5/11/0316:48:15INFOspark.SparkContext:RunningSparkversion1.4.1 15/11/0316:48:15WARNspark.SparkConf:InSpark1.0andlaterspark.local.dirwillbeoverriddenbythevaluesetbytheclustermanager(viaSPARK_LOCAL_DIRSinmesos/standaloneandLOCAL_DIRSinYARN). 15/11/0316:48:15WARNspark.SparkConf: SPARK_JAVA_OPTSwasdetected(setto'-verbose:gc-XX:-UseGCOverheadLimit-XX:+UseCompressedOops-XX:-PrintGCDetails-XX:+PrintGCTimeStamps-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/home/xujingwen/ocdc/spark-1.4.1-
多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。Java多线程面试问题1.进程和线程之间有什么不同?一个进程是一个独立(selfcontained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。2.多线程编程的好处是什么?在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heapmemory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。3.用户线程和守护线程有什么区别?当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程
大数据文摘作品,转载要求见文末作者|Aileen,YaweiXia,魏子敏 英伟达CEO黄仁勋在CES2017大会发表展前主题演讲如果有人说科技圈给人的刻板印象是nerd(书呆子),那他一定没有见识过CES。每年1月召开,这场已经连续举办了50年的国际消费类电子产品展览会(CES)的规模和受到的关注度不亚于各类时尚大秀。而近几年,城会玩儿的科技咖们更是把科技发布会移到了拉斯维加斯这座party之城。今年,汽车科技厂商占据了CES的重头戏。为此,CES还专门开辟了自动驾驶演示场地。大会5日开幕前,多家汽车科技公司已经发布了相关产品。1月4日晚间(北京时间1月5日上午10点30分),今年最重磅的展前主题演讲交给了英伟达(NVIDIA)CEO黄仁勋。据MarketWatch分析,CES把开幕前夜致辞,也是本次展会第一个主题演讲的重任交给英伟达,和这家芯片巨头2016年股价全年累计涨幅高达224%直接相关。高盛认为,英伟达抓住了游戏、虚拟现实、人工智能/机器学习以及汽车等的发展趋势,是半导体行业独特的增长故事,预测2017年股价将继续上涨18%。在整场发布会中,黄仁勋介绍了英伟达在电子游戏、
大家好,又见面了,我是你们的朋友全栈君。背景故事上文提到显卡驱动和CUDA的安装,你们真的因为一切这么流畅么?当然不是,不然我也不会说是“踩坑”之旅了,因为驱动下错了,就搞了半天,这里记录一下如何卸载驱动和CUDA。卸载步骤卸载显卡驱动$sudoapt-get--purgeremovenvidia* $sudoaptautoremove复制卸载CUDA$sudoapt-get--purgeremove"*cublas*""cuda*"复制OK完成,可以重装了。此时检查nvidia-smi如果没有反应或者显示找不到指令,则说明卸载成功;如果卸载失败,可以找到之前安装时的.run文件,命令行执行sudoxxxxxx.run--uninstall来卸载对应版本的显卡驱动。ps.此时重启可能导致图形操作界面无法打开。版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至举报,一经查实,本站将立刻删除。
软件介绍AdobeIllustratorCC2015简称“AICC2015”是一种应用于出版、多媒体和在线图像的工业标准矢量插画的软件。作为一款非常好的矢量图形处理工具。该软件主要应用于印刷出版、海报书籍排版、专业插画、多媒体图像处理和互联网页面的制作等,也可以为线稿提供较高的精度和控制,适合生产任何小型设计到大型的复杂项目。资源获取:威信公众昊:软件小栈后台回复:ai软件安装步骤01注意事项:①电脑需要断网;右键解压下载的文件—>点击【解压到当前文件夹】。02右键安装程序【Set-up】—>点击【以管理员身份运行】。03点击【忽略】。04点击【试用】。05点击【登录】。06点击【以后登录】。07点击【接受】。08更改软件安装的位置,建议安装至除C盘外的其他盘(如不需更改直接点击【安装】即可)。①点击文件夹图标;②选择软件安装的位置;③点击【确定】;④点击【安装】。09正在安装中……10安装完成后,点击【关闭】。11打开之前解压后的文件夹,复制文件【amtlib.dll】。12在桌面,①点击左下角的【开始】按钮;②将软件【AdobeIllustratorCC2015】拖至桌
内容来自王晓华老师 这块内容有点硬核,先做了解,主要学习如何使用迭代解决问题的步骤 在数值分析领域中,人们通常使用迭代法、逼近法和做图等方法来求解一些复杂问题的近似解,其中迭代法是一类利用递推公式或循环算法通过构造序列来求问题近似解的方法,把这种迭代求解数学问题的方法直接体现在算法中,就可以认为是设计领域中的迭代法。 代数法求解低阶非线方程用代数方法求一元非线性方程的解的方法有很多,常用的方法有开平方法、配方法、因式分解法、公式法等,近似求解的方法有作图法以及各种迭代法代数法求解方程虽然准确性好、精度高,但是不利于编制计算机程序,所以在数值分析领域,常用各种迭代法求解一元非线性方程。常用的求解一元非线性方程的方法有二分逼近法和牛顿迭代法, 二分逼近法 牛顿迭代法牛顿迭代法又称为牛顿-拉弗森方法(Newton-RaphsonMethod),它是一种在实数域和复数域上近似求解方程的方法。既然是迭代法,那么牛顿迭代法的算法实现肯定适合用迭代法模式。 导函数的求解与近似公式 牛顿迭代法算法实现根据牛顿迭代法的迭代关系公式,牛顿迭代法
Abstract: 无监督图像到图像的翻译目的是学习不同域图像的一个联合分布,通过使用来自单独域图像的边缘分布。给定一个边缘分布,可以得到很多种联合分布。如果不加入额外的假设条件的话,从边缘分布无法推出联合分布。为了解决这个问题,作者提出了一个shared-latent空间假设并且基于CoupledGANs提出一个无监督的图像到图像的翻译框架 Introduction: 计算机视觉中的许多问题可以被当作是图像到图像的翻译问题,匹配一个域中的图像对应到到另一个域中。如超分辨率可以被当作匹配一张低分辨率图像到对应的高分辨率图像。图像着色可以看作匹配一张灰度图到一张对应的彩色图像。这些问题有监督方式和无监督方式来解决。在有监督情况下,有可用的不同域的成对的图像。在无监督情况下,我们只有两个单独的数据集,其中一个数据集包含一个域的图像,另一个数据集包含了另一个域的图像。没有配对的样本来指导一张图像如何转换到另一个域中的图像。由于缺乏配对的图像,无监督的图像到图像的翻译问题被认为是很难的,但是它是实用的,因为使得数据的收集变得简单。 本文从概率建模的角度来分析图像翻译问题,关键的挑战
一、需求 游戏开发中经常遇到需要以美术字(而非字库)做数字显示的情况,通常美术会提供一组包含单个数字(也会有其它字符)的图片,可能是一张整图,也可能是每个数字分开的散图。 在此我以一张整图这种情况为例,来说明美术字体的具体制作流程。整图如下: 二、准备 整个制作过程需要用到三样工具: 字体数据制作工具 图片切割工具 字体生成工具 1、字体数据制作工具 字体数据制作工具名为BMFont,是一个Windows上的可执行软件,下载网址为:http://www.angelcode.com/products/bmfont/ 这里选择下载64位运行版(单体文件,无需安装) 可也以点这里下载:BMFont64.exe 2、图片切割工具 图片切割工具是Unity中运行的一个工具类,类名为ImageSlicer,放在Editor目录下即可,代码如下: 1/** 2*UnityVersion:2018.3.10f1 3*FileName:ImageSlicer.cs 4*Author:TYQ 5*CreateTime:2019/04/1900:04
今天真是鬼畜的一场考试,考的是XJOI上的题目 第三题看完题解至今仍是一脸懵逼,完全不懂如何实现 (话说出题人是怎么想到把一个二维问题三维化的OwO 算了算了,前两道题都是送分题,怪不得第三题这么难 先说前两题的题解把 首先第一题 我们可以预处理约数个数函数可以O(1)算出某个b对某个f的贡献 然后我们考虑后面的点的添加状态不会影响到前面的点的状态 又发现当我们做到某个点的时候,由于b>0,所以这个点一定存在一种决策使得这个点对答案有贡献 答案显然是所有的v的和,这样我们可以在O(nlogn)的时间内解决这个问题了 不过貌似由于数据太难构造了,所以貌似随手写写随机化也可以过OwO #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> usingnamespacestd; typedeflonglongLL; constintmaxn=200010; intn; intb[ma
参考《自动控制原理》,《混合信号集成电路测试与测量》,《Keithley 236 237SourceMeasureUnitSMU ServiceManual》,《电子系统设计-基础与测量仪器篇》,综合提炼出以下四种四象限电压电流源的结构。 第一种,求和积分结构,利用积分结构使求和结果为零。 缺点是反馈电压时是电压源,反馈电流时是电流源,两种模式必须通过电子开关切换,不适合需要平滑动态切换模式的测试。 优点是固定模式下,输出可以正负两个方向,也就是可以作为任意波形发生器。 采用积分器结构,不需要高压运放,只需要功放的供电电压足够,即可做到高压输出。 原始模型如下: 仿真模型如下: 第二种,误差放大器直接控制输出。 优点电路简单,调试方便。可以实现任意波形发生器功能。 缺点是只适合低压结构。 原始模型如下: 仿真模型如下: 第三种是稳压稳流结构,一三象限是源,二四象限是负载。 优点是电压源和电流源模式自动切换,不需要电子开关。模式通过二极管开关进行自动选择。 缺点是只有一三象限是源,两个源设置的DAC
DocX学习系列 DocX开源WORD操作组件的学习系列一: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_sharp_001_docx1.html DocX开源WORD操作组件的学习系列二: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_csharp_005_docx2.html DocX开源WORD操作组件的学习系列三: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_csharp_006_docx3.html DocX开源WORD操作组件的学习系列四: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_csharp_006_docx4.html 替换文本 privatestaticvoidReplaceText() { Console.WriteLine("ReplaceText()"); File.Copy(@"docs\Lists.docx",@"docs\Repla
题目大意 给你一个有向图,求出图中环的平均值的最小值 环的平均值定义:环中所有的边权和/环中点数量 思路 看到使平均值最大或最小,可以考虑分数规划 分数规划用于解决一些要让平均值最大或最小的问题 具体就是二分答案\(K\) \(\frac{x_1+x_2+x_3+\dots+x_n}{n}\gek\Leftrightarrow(x_1-k)+(x_2-k)+(x_3-k)+\dots+(x_n-k)\ge0\) 很明显,这题完全满足这个分数规划的性质。 故我们枚举一个\(k\),把每条边的边权减去\(k\),再用\(SPFA\)判负环就可以了 具体细节见代码 #include<bits/stdc++.h> usingnamespacestd; constintMAXN=10000+5; structNode{ intnext,to; doublew; }edge[MAXN]; inthead[MAXN],cnt; intn,m; doubled[MAXN]; boolvis[MAXN]; inlineintread(){ inttot=0,f=1;charc=getchar
时间:2016.10.27 ------------------ 前言:我们知道vivado软件是用于xilinx的7系列及以上器件的FPGA开发工具。 随着版本的不断更新,也变得越来越庞大、臃肿! 正经的下载出处当然是xilinx官网。 可是,普通网页下载外国网站资源的速度实在不堪入目。 目前,典型的方法是寻找网盘链接。 不过现阶段国内各大网盘被各种清洗,仅剩百度云盘苟延残喘。 为了免遭和谐,群众多数加密资源。 此外这厮为谋会费,无耻限速。 其实,完全可以使用迅雷来快速下载。 ------------------------------------- 1、登录xilinx官网,并登录xilinx账号; 2、技术支持→(服务)下载与许可; 3、选择相应的vivado版本,如2015.4; 4、找到诸如【VivadoDesignSuite-HLxEditions-SingleFileDownload】→选择windows版本(看个人需要) 5、弹出你的个人账号信息→点击下一步; 6、弹出如下网页下载窗口: 7、复制上面的网址,取消网页下载,并将其黏贴到迅雷新
前端代码: html <inputid="fileUpload"type="file"name="upload"> <inputtype="button"@click="submitfile"value="Upload"> 复制 js submitfile(){ varformData=newFormData() varformData=newwindow.FormData() formData.append("f1",$("#fileUpload")[0].files[0]); console.log(formData) //开始Ajax请求 varoptions={//设置axios的参数 url:this.baseUrl+'/upload_action/', data:formData, method:'post', headers:{ 'Content-Type':'multipart/form-data' } } axios(options).then((res)=>{ console.log(res) }) }, 复制 后
1、SSLClient类 packagecom.fxiaoke.qa.util;importjava.io.IOException;importjava.security.cert.CertificateException;importjava.security.cert.X509Certificate;importjavax.net.ssl.*;importorg.apache.http.conn.ClientConnectionManager;importorg.apache.http.conn.scheme.Scheme;importorg.apache.http.conn.scheme.SchemeRegistry;importorg.apache.http.conn.ssl.SSLSocketFactory;importorg.apache.http.conn.ssl.X509HostnameVerifier;importorg.apache.http.impl.client.DefaultHttpClient;/***用于进行Https请求的HttpClient*@Clas
时间限制:1秒 空间限制:32768K 热度指数:281981 本题知识点: 链表 算法知识视频讲解 题目描述 输入一个链表,反转链表后,输出新链表的表头。 给出代码: /* structListNode{ intval; structListNode*next; ListNode(intx): val(x),next(NULL){ } };*/ classSolution{ public: ListNode*ReverseList(ListNode*pHead){ } }; 复制 我写的代码太丑了,直接贴一段牛客讨论里的代码,写的很清晰 AC代码: 链接:https://www.nowcoder.com/questionTerminal/75e878df47f24fdc9dc3e400ec6058ca 来源:牛客网 //第一种方法是:非递归方法 /* structListNode{ intval; structLi
1、ReplicaSet 当我们定义Pod资源配置清单的时候如果只是通过kind:Pod来进行申明,当我们删除该资源配置清单创建的Pod的时候,Pod会被直接删除,这种情况在生产环境中是相当危险的。 于是我们在线上环境中推荐使用Pod控制器来管理Pod,所谓控制器就是能够管理pod,监测pod运行状况,当pod发生故障,可以自动恢复pod。也就是说能够代我们去管理pod中间层,并帮助我们确保每一个pod资源始终处于我们所定义或者我们所期望的目标状态,一旦pod资源出现故障,那么控制器会尝试重启pod或者里面的容器,如果一直重启有问题的话那么它可能会基于某种策略来进行重新布派或者重新编排;如果pod副本数量低于用户所定义的目标数量,它也会自动补全;如果多余,也会自动终止pod资源。 1.1什么是ReplicaSet? ReplicaSet是kubernetes中的一种副本控制器,简称rs,主要作用是控制由其管理的pod,使pod副本的数量始终维持在预设的个数。它的主要作用就是保证一定数量的Pod能够在集群中正常运行,它会持续监听这些Pod的运行状态,在Pod发生故障时重启pod,pod数