最近学习了极客时间的《数据结构与算法之美》很有收获,记录总结一下。
欢迎学习老师的专栏:数据结构与算法之美
代码地址:http://github.com/peiniwan/Arithmetic
周末你带着女朋友去电影院看电影,女朋友问你,咱们现在坐在第几排啊?电影院里面太黑了,看不清,没法数,现在你怎么办?别忘了你是程序员,这个可难不倒你,递归就开始排上用场了。
于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也问他前面的人。就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。直到你前面的人告诉你他在哪一排,于是你就知道答案了。
我们用递推公式将它表示出来就是这样的:
f(n)=f(n-1)+1 其中,f(1)=1
f(n) 表示你想知道自己在哪一排,f(n-1) 表示前面一排所在的排数,f(1)=1 表示第一排的人知道自己在第一排。有了这个递推公式,我们就可以很轻松地将它改为递归代码,如下:
int f(int n) {
if (n == 1) return 1;
return f(n-1) + 1;
}
写递归代码最关键的是写出递推公式,找到终止条件
爬楼梯
int f(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
return f(n-1) + f(n-2);
}
写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。
人脑几乎没办法把整个“递”和“归”的过程一步一步都想清楚。计算机擅长做重复的事情,所以递归正和它的胃口。
对于递归代码,这种试图想清楚整个递和归过程的做法,实际上是进入了一个思维误区。很多时候,我们理解起来比较吃力,主要原因就是自己给自己制造了这种理解障碍。那正确的思维方式应该是怎样的呢?
如果一个问题 A 可以分解为若干子问题 B、C、D,你可以假设子问题 B、C、D 已经解决,在此基础上思考如何解决问题 A。而且,你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。
因此,编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
不要陷入思维误区。
函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。
那么,如何避免出现堆栈溢出呢?
// 全局变量,表示递归的深度。
int depth = 0;
int f(int n) {
++depth;
if (depth > 1000) throw exception;
if (n == 1) return 1;
return f(n-1) + 1;
}
但这种做法并不能完全解决问题,因为最大允许的递归深度跟当前线程剩余的栈空间大小有关,事先无法计算。如果实时计算,代码过于复杂,就会影响代码的可读性。所以,如果最大深度比较小,比如 10、50,就可以用这种方法,否则这种方法并不是很实用。
为了避免重复计算,我们可以通过一个数据结构(比如散列表)来保存已经求解过的 f(k)
。当递归调用到 f(k) 时,先看下是否已经求解过了。如果是,则直接从散列表中取值返回,不需要重复计算,这样就能避免刚讲的问题了。
public int f(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
// hasSolvedList可以理解成一个Map,key是n,value是f(n)
if (hasSolvedList.containsKey(n)) {
return hasSolvedList.get(n);
}
int ret = f(n-1) + f(n-2);
hasSolvedList.put(n, ret);
return ret;
}
电影院递归代码,空间复杂度并不是 O(1),而是 O(n)。
递归有利有弊,利是递归代码的表达力很强,写起来非常简洁;而弊就是空间复杂度高、有堆栈溢出的风险、存在重复计算、过多的函数调用会耗时较多等问题
电影院修改
int f(int n) {
int ret = 1;
for (int i = 2; i <= n; ++i) {
ret = ret + 1;
}
return ret;
}
但是这种思路实际上是将递归改为了“手动”递归,本质并没有变,而且也并没有解决前面讲到的某些问题,徒增了实现的复杂度。
推荐注册返佣金的这个功能我想你应该不陌生吧?现在很多 App 都有这个功能。这个功能中,用户 A 推荐用户 B 来注册,用户 B 又推荐了用户 C 来注册。我们可以说,用户 C 的“最终推荐人”为用户 A,用户 B 的“最终推荐人”也为用户 A,而用户 A 没有“最终推荐人”。
long findRootReferrerId(long actorId) {
Long referrerId = select referrer_id from [table] where actor_id = actorId;
if (referrerId == null) return actorId;
return findRootReferrerId(referrerId);
}
不过在实际项目中,上面的代码并不能工作,为什么呢?这里面有两个问题。
第一,如果递归很深,可能会有堆栈溢出的问题。
第二,如果数据库里存在脏数据,我们还需要处理由此产生的无限递归问题。比如 demo 环境下数据库中,测试工程师为了方便测试,会人为地插入一些数据,就会出现脏数据。如果 A 的推荐人是 B,B 的推荐人是 C,C 的推荐人是 A,这样就会发生死循环。
第一个问题,我前面已经解答过了,可以用限制递归深度来解决。第二个问题,也可以用限制递归深度来解决。不过,还有一个更高级的处理方法,就是自动检测 A-B-C-A 这种“环”的存在。如何来检测环的存在呢?
调试递归
我们平时调试代码喜欢使用 IDE 的单步跟踪功能,像规模比较大、递归层次很深的递归代码,几乎无法使用这种调试方式。
调试递归:
public class Recursion {
/**
* 求和
*/
public static int summation(int num) {
if (num == 1) {
return 1;
}
return num + summation(num - 1);
}
/**
* 求二进制
*/
public static int binary(int num) {
StringBuilder sb = new StringBuilder();
if (num > 0) {
summation(num / 2);
int i = num % 2;
sb.append(i);
}
System.out.println(sb.toString());
return -1;
}
/**
* 求n的阶乘
*/
public int f(int n) {
if (n == 1) {
return n;
} else {
return n * f(n - 1);
}
}
}
有点像分治,底层必须依赖数组,并且还要求数据是有序的。二分查找更适合处理静态数据,也就是没有频繁的数据插入、删除操作。
这是一个等比数列。其中 n/2k=1 时,k 的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了 k 次区间缩小操作,时间复杂度就是 O(k)。通过 n/2k=1,我们可以求得 k=log2n,所以时间复杂度就是 O(logn)。
public int binarySearch(int[] arr, int k) {
if (arr.length == 0) {
return -1;
}
if (arr[0] == k) {
return 0;
}
int a = 0;
int b = arr.length - 1;
while (a <= b) {
int m = a + (b - a) / 2;
if (k < arr[m]) {
b = m-1;
} else if (k > arr[m]) {
a = m + 1;
} else {
return m;
}
}
return -1;
}
容易出错的 3 个地方
二分查找除了用循环来实现,还可以用递归来实现
// 二分查找的递归实现
public int bsearch(int[] a, int n, int val) {
return bsearchInternally(a, 0, n - 1, val);
}
private int bsearchInternally(int[] a, int low, int high, int value) {
if (low > high) return -1;
int mid = low + ((high - low) >> 1);
if (a[mid] == value) {
return mid;
} else if (a[mid] < value) {
return bsearchInternally(a, mid+1, high, value);
} else {
return bsearchInternally(a, low, mid-1, value);
}
}
首先,二分查找依赖的是顺序表结构,简单点说就是数组。
数组按照下标随机访问数据的时间复杂度是 O(1),而链表随机访问的时间复杂度是 O(n)。所以,如果数据使用链表存储,二分查找的时间复杂就会变得很高。
其次,二分查找针对的是有序数据。
数据必须是有序的。如果数据没有序,我们需要先排序
如果我们的数据集合有频繁的插入和删除操作,要想用二分查找,要么每次插入、删除操作之后保证数据仍然有序,要么在每次二分查找之前都先进行排序。针对这种动态数据集合,无论哪种方法,维护有序的成本都是很高的。
所以,二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用。那针对动态数据集合,如何在其中快速查找某个数据呢?别急,等到二叉树那一节我会详细讲。
再次,数据量太小不适合二分查找。
如果要处理的数据量很小,完全没有必要用二分查找,顺序遍历就足够了。比如我们在一个大小为 10 的数组中查找一个元素,不管用二分查找还是顺序遍历,查找速度都差不多。只有数据量比较大的时候,二分查找的优势才会比较明显。
最后,数据量太大也不适合二分查找。
二分查找的底层需要依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻。比如,我们有 1GB 大小的数据,如果希望用数组来存储,那就需要 1GB 的连续内存空间。
如何在 1000 万个整数中快速查找某个整数?
这个问题并不难。我们的内存限制是 100MB,每个数据大小是 8 字节,最简单的办法就是将数据存储在数组中,内存占用差不多是 80MB,符合内存的限制。借助今天讲的内容,我们可以先对这 1000 万数据从小到大排序,然后再利用二分查找算法,就可以快速地查找想要的数据了。
虽然大部分情况下,用二分查找可以解决的问题,用散列表、二叉树都可以解决。但是,我们后面会讲,不管是散列表还是二叉树,都会需要比较多的额外的内存空间。如果用散列表或者二叉树来存储这 1000 万的数据,用 100MB 的内存肯定是存不下的。而二分查找底层依赖的是数组,除了数据本身之外,不需要额外存储其他信息,是最省内存空间的存储方式,所以刚好能在限定的内存大小下解决这个问题。
十个二分九个错
上一节讲的只是二分查找中最简单的一种情况,在不存在重复元素的有序数组中,查找值等于给定值的元素。最简单的二分查找写起来确实不难,但是,二分查找的变形问题就没那么好写了。
变体一:查找第一个值等于给定值的元素
如下面这样一个有序数组,其中,a[5],a[6],a[7] 的值都等于 8,是重复的数据。我们希望查找第一个等于 8 的数据,也就是下标是 5 的元素。
如果我们用上一节课讲的二分查找的代码实现,首先拿 8 与区间的中间值 a[4] 比较,8 比 6 大,于是在下标 5 到 9 之间继续查找。下标 5 和 9 的中间位置是下标 7,a[7] 正好等于 8,所以代码就返回了。
尽管 a[7] 也等于 8,但它并不是我们想要找的第一个等于 8 的元素,因为第一个值等于 8 的元素是数组下标为 5 的元素。
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == 0) || (a[mid - 1] != value)) return mid;
else high = mid - 1;
}
}
return -1;
}
变体二:查找最后一个值等于给定值的元素
前面的问题是查找第一个值等于给定值的元素,我现在把问题稍微改一下,查找最后一个值等于给定值的元素,又该如何做呢?
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
else low = mid + 1;
}
}
return -1;
}
变体三:查找第一个大于等于给定值的元素
在有序数组中,查找第一个大于等于给定值的元素。比如,数组中存储的这样一个序列:3,4,6,7,10。如果查找第一个大于等于 5 的元素,那就是 6。
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] >= value) {
if ((mid == 0) || (a[mid - 1] < value)) return mid;
else high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
变体四:查找最后一个小于等于给定值的元素
我们来看最后一种二分查找的变形问题,查找最后一个小于等于给定值的元素。比如,数组中存储了这样一组数据:3,5,6,8,9,10。最后一个小于等于 7 的元素就是 6。
public int bsearch7(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else {
if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
else low = mid + 1;
}
}
return -1;
}
我们用的最多的就是编程语言提供的字符串查找函数,比如 Java 中的 indexOf(),Python 中的 find() 函数等,它们底层就是依赖接下来要讲的字符串匹配算法。
BF 算法和 RK 算法、BM 算法和 KMP 算法。
BF 算法中的 BF 是 Brute Force 的缩写,中文叫作暴力匹配算法,也叫朴素匹配算法。
我们在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串 B 就是模式串。我们把主串的长度记作 n,模式串的长度记作 m。因为我们是在主串中查找模式串,所以 n>m。
BF 算法的思想可以用一句话来概括,那就是,我们在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的(看图)。
我们每次都比对 m 个字符,要比对 n-m+1 次,所以,这种算法的最坏情况时间复杂度是 O(n* m)。
/**
* BF算法
* 检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的
*/
public static int bfFind(String S, String T, int pos) {
char[] arr1 = S.toCharArray();
char[] arr2 = T.toCharArray();
int i = pos;
int j = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] == arr2[j]) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
if (j == arr2.length) return i - j;
else return -1;
}
尽管理论上,BF 算法的时间复杂度很高,是 O(n* m),但在实际的开发中,它却是一个比较常用的字符串匹配算法。
第一,实际的软件开发中,大部分情况下,模式串和主串的长度都不会太长。
第二,朴素字符串匹配算法思想简单,代码实现也非常简单。
BF 算法的升级版。
BF每次检查主串与子串是否匹配,需要依次比对每个字符,所以 BF 算法的时间复杂度就比较高,是 O(n* m)。我们对朴素的字符串匹配算法稍加改造,引入哈希算法,时间复杂度立刻就会降低。
RK 算法的思路是这样的:我们通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了(这里先不考虑哈希冲突的问题,后面我们会讲到)。因为哈希值是一个数字,数字之间比较是否相等是非常快速的,所以模式串和子串比较的效率就提高了。
比如要处理的字符串只包含 a~z 这 26 个小写字母,那我们就用二十六进制来表示一个字符串。我们把 a~z 这 26 个字符映射到 0~25 这 26 个数字,a 就表示 0,b 就表示 1,以此类推,z 表示 25。
在十进制的表示法中,一个数字的值是通过下面的方式计算出来的。对应到二十六进制,一个包含 a 到 z 这 26 个字符的字符串,计算哈希的时候,我们只需要把进位从 10 改成 26 就可以。
这种哈希算法有一个特点,在主串中,相邻两个子串的哈希值的计算公式有一定关系。我这有个个例子,你先找一下规律,再来看我后面的讲解。
从这里例子中,我们很容易就能得出这样的规律:相邻两个子串 s[i-1] 和 s[i](i 表示子串在主串中的起始位置,子串的长度都为 m),对应的哈希值计算公式有交集,也就是说,我们可以使用 s[i-1] 的哈希值很快的计算出 s[i] 的哈希值。如果用公式表示的话,就是下面这个样子:
大家好,又见面了,我是你们的朋友全栈君。1、关键词搜索wysiwyg,staticsitegenerator,h5creator,h5edtitor,h5maker2、原理讲解(重点)https://github.com/CntChen/cntchen.github.io/issues/15https://github.com/CntChen/cntchen.github.io/issues/17https://blog.csdn.net/findhappy117/article/details/884139543、对比4、参考项目1、ant-design/ant-design-landing(基于react的着陆页定制) https://github.com/ant-design/ant-design-landing 2、antmotion(蚂蚁动效展示,原则,设计) https://motion.ant.design/index-cn 3、可视化h5编辑 https://github.com/luoye-fe/goh5 https://github.com/zhengguorong
类的分解,抽象类与纯虚函数的需要性 为了不模糊概念在这里我们就简单的阐述一下类的分解,前面的教程我们着重讲述了类的继承,继承的特点就是,派生类继承基类的特性,进行//站点:www.cndev-lab.com //所有稿件均有版权,如要转载,请务必注明出处和作者 #include<iostream> usingnamespacestd; classVehicle { public: Vehicle(floatspeed,inttotal) { Vehicle::speed=speed; Vehicle::total=total; } virtualvoidShowMember()=0;//纯虚函数的定义 protected: floatspeed; inttotal; }; classCar:publicVehicle { public: Car(intaird,floatspeed,inttotal):Vehicle(speed,total) { Car::aird=aird; } virtualvoidShowMember()//派生
一、背景介绍产品的更新迭代必然会伴随着功能的推出和下线,今天我们要讲的便是微信在2014年推出的一个小小的新功能:撤回消息,使用此功能者提神醒脑、神清气爽,但被施用者却可能会抓耳挠腮、咬牙切齿的想知道你到底撤回了啥,这就是所谓的好奇害死猫(Curiositykillsthecat),今天我们就来用Python实现防微信消息撤回,满足一下大家的好奇心! 二、功能设计我们希望当有好友或者群中有人撤回消息的时候,第一时间能把原消息、发送者信息转发到文件助手中(当然你也可以把消息发回给他,哈哈),这样方便我们查看。给大家来演示一下实现的功能。 三、功能实现1.微信撤回消息实现原理我们先来了解一下微信撤回消息的原理:其实微信撤回消息原理就是操作者在点击撤回的时候,客户端会自动发一条系统消息:“xxx撤回了一条消息”,然后对方app端收到消息后解析,替换原消息,改个显示的样式即可猪哥给大家演示一下正常消息和撤回消息的内容到底有什么区别: 正常消息:Content='你好骚啊',大家留意一下MsgId='8784390488772995470' 撤回消息:
3月12日,腾讯云全新发布自主可控金融业务支撑平台,该平台集中整合腾讯云在基础架构、数据库、大数据以及人工智能等技术场景中的优势能力,致力于帮助金融机构在数字化转型过程中构建全方位自主可控技术体系。其中,腾讯云也首次正式对外披露分布式数据库TDSQL的十年自主可控之路。数据作为金融行业的生命线,承载数据的数据库产品,无疑是金融科技的核心,也是当前金融产业安全、自主可控的关键。目前商业银行以及国内金融机构数据库产品几乎被国外商用数据库垄断。腾讯云分布式数据库TDSQL负责人潘安群一方面,由于核心知识产权掌握在国外厂商手中,极有可能发生厂商断供风险,且该行业长期被传统厂商垄断,金融企业IT成本居高不下。另一方面,在金融企业数字化转型的趋势下,尤其互联网化浪潮冲击下,传统银行的集中式IT架构难以适应互联网业务的快速发展,例如春节红包等场景,传统的集中式解决方案无法支撑海量用户。在这种情况下,采用国产自主可控的分布式数据库显得尤为必要。十年探索,TDSQL专为“金融”而生“一款金融级的分布式数据库,必须要经过数年的积淀,以及海量业务场景的锤炼”,腾讯云分布式数据库TDSQL负责人潘安群表示。T
装饰器模式是为已有功能动态的添加更多功能的一种方式。复制优点: 有效的把类的核心职责和装饰功能区分开,职责更细化 UML image.png code publicinterfaceApple{/***描述*/voidres();} publicclassConcreteAppleimplementsApple{@Overridepublicvoidres(){System.out.println("普通的苹果");}} 装饰器基类 publicabstractclassDecoratorimplementsApple{protectedAppleapple;publicDecorator(Appleapple){super();this.apple=apple;}@Overridepublicvoidres(){apple.res();}} publicclassConcreteDecoratorAextendsDecorator{publicConcreteDecoratorA(Appleapple){super(apple);}publicvoidre
在做公交查询系统时,要求用户输入起点和终点。可是如果用户输错了,自己要判断,很麻烦,因为我的算法全是SQL。于是就想了一种折衷的方案:让用户从DropDownList里选。这样既方便了用户,也方便了自己。可是,如果全部站点放入一个DropDownList的话,太多了(我这个济南的有一千多个啊),所以就先选择汉字的拼音首字母,再选择站点,实验证明很方便,速度很快!这里用到了一个汉字转拼音的函数:staticstringExtract_HZ(stringHZ){byte[]ZW=newbyte[2];longHZ_INT;ZW=System.Text.Encoding.Default.GetBytes(HZ);//getthearrayofbytefromthesinglecharinti1=(short)(ZW[0]);inti2=(short)(ZW[1]);HZ_INT=i1*256+i2;//expresstion//HZ_INTmatchtheconstantif((HZ_INT>=45217)&&(HZ_INT<=45252)){return&quo
导读:回看历史,你会发现,金融业是最难实现变革的。但不可避免的是,大银行和创业公司在金融业方面仍取得了巨大的突破,我认为这不是因为他们使用了什么特别的技术,而是因为它们内在的文化差异,多样化的结构刚度和其他具有成本效益的商业模式。为什么一些程序员似乎有某种神奇的能力在眨眼之间从代码中提取其意义? 为了尝试回答这个问题,我深入到了科学所认知的我们如何理解代码的方式中去。事实证明,我们对代码理解心理学有了很多的认识,我们可以用这些知识来改善程序员的程度。它允许你拓展在理解过程中的所有方面,因此你不会在编程技巧上遇到瓶颈。 在这篇文章中,我将看看我们对于程序理解的了解,并讨论了三种可使用的知识,以成为更好的程序员。为了理解代码你必须构建一个心理模型编程的第一步是构建问题的心理模型,以便你可以完成该任务。你的心理模型是理解问题或程序的驱动力。 从屏幕上的代码到头脑中的模型的旅程遵循完全理解的进程。我们对流程的理解绝非完整,但我们所知道的知识可以被用于识别要重点改进的区域。 我们来看看我们如何理解代码。你的心理模型是由通用知识和专业知识之间的配对所构成的你用于理解代码的知识或是通用的编程知识或是
高级持续性威胁(AdvancedPersistentThreat)简称APT,从无到有不过十年时间。而在去年年底,卡巴斯基在预测2016年安全发展趋势时表示:APT将死。 我们反观过去的三个多月,却发现APT攻击行为并未下降,BlackEnergyAPT组织仍在通过Word文档进行针对乌克兰的APT攻击、DarkhotelAPT组织强势回归的第一个目标便是中国电信......事实胜于雄辩,APT确实没有走远。APT的诞生回溯至2005年,西方一些计算机应急响应组织(曾)发布报告,提醒人们注意某些针对性的钓鱼电子邮件会释放木马,小心敏感信息泄露,但那时“APT”一词尚未被发明出来。直到2006年,美国空军(USAF)分析师格雷格·拉特雷上校创造了APT一词,用它来解释情况不明的平民入侵活动,这样就能在不泄露机密的情况下对攻击活动的特征进行讨论。APT混沌的定义虽然安全从业者经常会关注到APT这个词,事实上APT并没有一个的完整定义,这也为安全研究者的测试带来了不小的麻烦。这里整理了几种解释。维基百科:APT高级持续性威胁,是指隐匿而持久的电脑入侵过程,通常由某些人员精心策划,针对特定的目
<SCRIPTtype="text/javascript"Charset="GB2312"> functionconvertCurrency(currencyDigits){ //Constants: varMAXIMUM_NUMBER=99999999999.99; //Predefinetheradixcharactersandcurrencysymbolsforoutput: varCN_ZERO="零"; varCN_ONE="壹"; varCN_TWO="贰"; varCN_THREE="叁"; varCN_FOUR="肆"; varCN_FIVE="伍"; varCN_SIX="陆"; varCN_SEVEN="柒"; varCN_EIGHT="捌"; varCN_NINE="玖"; varCN_TEN="拾&quo
本文作者:MoveMoon[1]最近大家都在谈论两个新的L1(Aptos与Sui),不聊聊好像跟不上时代,要了解他们就需要了解什么是Move,弄清楚共识机制,并了解他们的价值主张。从前...最初Meta(原名Facebook)建立了Diem(原名Libra),想要成为一个基于许可的区块链稳定币支付系统。从本质上讲,他们想在追求其垄断性地位的同时涉足点对点支付。但是,监管和反垄断问题一直困扰他们,他们在今年1月放弃了Diem这个项目。不过,Diem确实留下了一些有用的财富:基于Rust的智能合约和自定义交易编程语言Move。与Solidity相比,Move有许多好处,主要有:与Solidity(有些资产被永久锁定在合约中)相比,Move的资产属性很容易定制,允许资产作为参数在智能合约中流动及由函数返回。这有许多有用的应用,将在后面讲到。安全性:代币和智能合约被存储为"资源(resources)",在Move的代码架构中具有很高的地位,防止它们被复制或销毁。我了解过的开发者,他们也提到Move的开发体验非常优越。它允许资产和合约的真正可组合性。夸张的说,在Solidit
今天使用SQLServer时,遇到使用notin和notexist的查询结果有差异:notin查询没结果。 原因:notin遇到null就不工作了。 摘录: SELECTforeignStockId FROM[Subset].[dbo].[Products]复制 Probablyreturnsa NULL.Try SELECTstock.IdStock ,stock.Descr FROM[Inventory].[dbo].[Stock]stock WHEREstock.IdStockNOTIN(SELECTforeignStockIdFROM[Subset].[dbo].[Products]WHEREforeignStockIdISNOTNULL)复制 aNOTIN(x,y,NULL) Willalwaysreturnnoresultsasitisequivalentto a<>xanda<>yanda<>NULL whichis trueandtrueandunknown Whichevaluatesto u
本章介绍图的拓扑排序。和以往一样,本文会先对拓扑排序的理论知识进行介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现。 目录 1.拓扑排序介绍 2.拓扑排序的算法图解 3.拓扑排序的代码说明 4.拓扑排序的完整源码和测试程序 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列目录 拓扑排序介绍 拓扑排序(TopologicalOrder)是指,将一个有向无环图(DirectedAcyclicGraph简称DAG)进行排序进而得到一个有序的线性序列。 这样说,可能理解起来比较抽象。下面通过简单的例子进行说明! 例如,一个项目包括A、B、C、D四个子部分来完成,并且A依赖于B和D,C依赖于D。现在要制定一个计划,写出A、B、C、D的执行顺序。这时,就可以利用到拓扑排序,它就是用来确定事物发生的顺序的。 在拓扑排序中,如果存在一条从顶点A到顶点B的路径,那么在排序结果中B出现在A的后面。 拓扑排序的算法图解 拓扑排序算法的基本步骤: 1.构造一个队列Q(queue)和拓扑排序的结果队列T(to
概述 说起垃圾收集(GarbageCollection,GC),大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史远远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考: GC需要完成的三件事情: 哪些内存需要回收? 什么时候回收? 如何回收? 经过半个世纪的发展,内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”时代,那为什么我们还要去了解GC和内存分配呢?答案很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 把时间从半个世纪以前拨回到现在,回到我们熟悉的Java语言。第2章介绍了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期
PureStrategyGames 策略博弈说的是有限个玩家,每个玩家都有有限个决策,并且每个玩家的决策必须同时作出(即不能知悉其他人的决策),每个玩家都知道每一种决策组合的结果给每个玩家带来的收益。 形式化地,有 \(N\)个玩家构成有限的玩家集 每个玩家\(i\)都有一个有限的决策集\(A_i\),表示其所有可以选择的决策 一个局面\(a\inA=\prod\limits_{i=1}^NA_i\),包含了场上所有玩家所作出的决策 每个玩家\(i\)都有一个收益函数\(u_i\colonA\mapsto\mathbbK\),其中\(\mathbbK\)是一个全序集 之所以定义为全序集是因为并非所有收益都是可以计算出来的数值,但是我们希望任意两个收益是可以比较好坏的 为了简化,规定 \(a_{i-1}\inA_{-i}=\prod\limits_{i\neqj}A_j\)表示玩家\(i\)的所有对手的某种决策局面 由1,某个局面\(a\)就可以写成是\((a_i,a_{-i})\),注意到我们并不关心局面的枚举顺序,因此这里实际上是集合而不是笛卡尔积.... 记\(B_i(a_{
并发队列的选择 Java的并发包提供了三个常用的并发队列实现,分别是:ArrayBlockingQueue、ConcurrentLinkedQueue和LinkedBlockingQueue 。 ArrayBlockingQueue是**初始容量固定**的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。 ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。 LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。 在请求预处理阶段,由于我们的系统入队需求要远大于出队需求,一般不会出现队空的情况,所以我们可以选择ConcurrentLinkedQueue来作为我们的请求队列实现 1.请求接口的合理设计 一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容,另一个就是参与秒杀的Web后台请求接口。 通常静态HTML等内容,是通过CDN的部署,一般压力不大,核心瓶颈实际上在后台请
<!--https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency>复制 与之对应的有: <!--https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient--> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <
所有的分片规则配置的tableRule标签中: rule标签中的columns标签内填写要分片的表字段,algorithm标签内填写分片所使用的自定义函数名,要与function函数中的name属性保持一致 function函数中的property标签内配置自定义参数。 1)枚举法:sharding-by-intfile <tableRulename="sharding-by-intfile"> <rule> <columns>user_id</columns> <algorithm>hash-int</algorithm> </rule> </tableRule> <functionname="hash-int"class="io.mycat.route.function.Parti
java标签库分分为上述几种,一般经常使用的是核心和函数,接下来会分别讲解这几种,和常见的用法。 一般标签库会和el表达式一起使用,所以在学习标签库前最后也学习下el表达式的使用。 导入后展开 可以从jar包查看相对应的标签库得tld文档,里面会描述每个标签的说明和用法 先从核心标签库开始 tld文档有几个重点,第一个就是uri,这是等下在jsp页面引入标签库时是的uri 基本一个tld文档的重点内容就这么多了,分开看其实也不是很难 1<%@tagliburi="http://java.sun.com/jsp/jstl/core"prefix="c"%>复制 在jsp页面头使用该标签来导入标签库,uri对应tld文档中的uri,prefix标识符 1<jsp:useBeanid="userBean"class="bean.userBean"scope="page"></jsp:useBean><!--创建出一个实体bean--> 2<c:settarget="${userBean}"prope
1、起因 最近发现程序中有一段控制TextBox数字输入的代码,相信大家都不会太陌生,如下: voidint_KeyPress(objectsender,KeyPressEventArgse) { constcharDelete=(char)8; if(!Char.IsDigit(e.KeyChar)&&e.KeyChar!=Delete) { e.Handled=true; } }复制 乍一看,好像没有啥问题,但是却出现了一个bug,能够输入全角的数字,如:0、1、2、3等。错误的根源就是上面代码中用到的IsDigit函数,于是就有了下面的一番探究,让我们来看看IsDigit函数的真面目。 2、IsDigit函数 查阅MSDN,Char.IsDigit 方法是指示某个Unicode字符是否属于十进制数字类别。通过ILSpy查看其源代码: [__DynamicallyInvokable] publicstaticboolIsDigit(charc) { if(char.IsLatin1(c)) { returnc>='0'&&c&l