4张牌打乱从中间剪开并叠放得到8张牌,牌的顺序无关紧要,但剪开后的牌上下叠放,则2张相同的牌相互之间一定相隔4张,不妨假设这8张牌的排列顺序为ABCDABCD
。
无论名字有几个字,即将几张牌放到底部(最末端)得到的依然是个循环序列。比如名字2个字得到CDABCDAB
,名字3个字得到DABCDABC
。
可以将这8张牌排成一个圆环,无论名字是几个字,也即无论从哪张牌作为圆环起点,始终都是在圆环中循环。
观察即可发现,从这个环中任意位置取走连续的3张,则剩下的5张牌都会形成首尾相同的一个序列:
将取走的3张牌插入到序列的中间位置(注意不能插到最底部),则一定能得到一个由8张牌构成的并且首尾相同的序列AxxxxxxA
。
根据规则,将第1张牌隐藏起来,后续经过一系列摆放和丢弃操作,最终目的都是为了将最末尾的牌留下来,而这张牌正好和隐藏的牌相同,可拼在一起,实现了魔术效果。
由于最后结果只和序列最后一张牌有关,其他牌都需要丢弃,此时序列为长度为7的xxxxxxA
,x
表示其他牌,A
表示隐藏牌。
取出序列开头任意的1张2张或3张牌并插入中间,此时并没有改变序列最末尾的牌,而序列中其他牌的位置是对结果没有任何影响的,所以该操作可有可无,刘谦可能只是为了增加神秘感或者让观众更有参与感才加入该步骤。尼格买提可能正是在该步骤将牌插入了末尾,改变了序列中最末尾的牌才导致结果错误。此时序列仍然可看作是xxxxxxA
。
男生丢弃最开头1张牌,形成长度为6的序列xxxxxA
;女生丢弃最开头的2张牌,形成长度为5的序列xxxxA
。
“见证奇迹的时刻”共7个字,则需要将序列最开头的牌移到最末尾共执行7次。男生的牌有6张,6次为一个闭环(6次后序列相当于未发生任何改变),则相当于执行7-6=1
次;同理,女生的牌有5张,则5次为一个闭环,相对于执行7-5=2
次。男女生的最终序列分别为xxxxAx
和xxAxx
。
将序列第1张牌插入末尾,并丢弃第2张,将第3张牌插入末尾,并丢弃第4张,以此类推,直到剩下最后一张。这很容易让人想到一个通用问题,假如有n张牌排成一个圆环,并且从1到n按顺序排列,如果一直按上述规则操作,那么最后剩下那张牌的编号是几呢?
其实这个问题早就有人研究过了,它还有一个经典名字叫做约瑟夫环。答案就是,如果这个n恰好可以表示为2的多少次方,比如16正好是2的4次方,8正好是2的3次方,那么最终留下牌的一定是编号1。
观察可发现,如果n正好是2的x次幂($ n=2^x $),那么第一轮会正好丢弃掉 $ 2^{x-1} $张牌,同时第二轮依然会从编号1开始计数,然后丢弃掉 $ 2^{x-2} $ 张牌……,每一轮都会从编号1开始,直到最后丢弃 $ 2^0=1 $ 张牌只剩下编号1。
换个角度,如果一个数n正好是2的整数次方,那么这个数用二进制表示,它的数位中只会有一个1,比如16的二进制为1000
,8的二进制位100
,它们都只有最高位的一个1。
对于任意数n来说,我们都可以表示为 $ n=2^x+a $ 。我们可以考虑先丢弃掉a张牌,则剩下的 $ 2^x $ 张牌中,第1张牌一定是最终留下的那张,因为如果对剩下的牌重新从1开始编号,这张牌就是编号1。对于要丢弃掉a张牌,由于是留1去1,则需要数到编号2a,才能正好丢弃a张,所以编号2a+1
则是剩下 $ 2^x $ 张牌中的第1张。
比如对于n=7,$ 7=2^2+3 $,a=3,丢弃3张牌好正好剩下 $ 2^2=4 $ 张牌,则走到编号6时正好剩下4张牌,而对于这剩下的4张牌重新编号,则编号7成为了编号1。所以n=7时,答案为7,也就是 $ 2a+1=2*3+1=7 $。
也就是说对于任意数n,$ n=2^x+a $,只要算出a,那么就能计算出答案,为2a+1
。
站在程序员的角度,a的值等于n去除二进制表示中最高位后的值,用代码实现约瑟夫环的这个问题为:
1 |
|
回到魔术中,男生的序列为xxxxAx
,长度为6,相当于n=6
,计算结果为5,而A
对应的编号正好是5;同理女生的序列为xxAxx
,长度为5,n=5
计算结果为3,A
对应的编号正好是3,所以最终一定是A
这张牌保留到最后。
以上,就是整个魔术的秘密。
]]>之前在讨论二叉搜索树的时候,已经介绍过一种可高度平衡的二叉搜索树,即AVL平衡二叉树。现在我们将介绍另一种鼎鼎有名的平衡二叉树-红黑树,它因节点存储红或黑两种颜色之一而命名。相较于AVL树,它是一种弱平衡二叉树,不需要像AVL树那样严格要求平衡,这也导致它的查询效率并没有AVL树那么高,但相对的,它带来的好处是对于调整失衡,红黑树的旋转次数更少,所以频繁的插入或删除操作,红黑树更有优势。
引用自百度百科:
红黑树(Red Black Tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。
在介绍红黑树之前,我们需要先了解一下什么是2-3搜索树。顾名思义,2-3搜索树也是一种搜索树,不过相较于一般的二叉搜索树,它的节点不仅可以存储1个键,还可以存储2个键,也就是说,它包含2种类型的节点:
2-
节点:含有一个键和两条链接,左链接指向的节点的键都小于该节点的键,右链接指向的节点的键都大于该节点的键。这也就是传统二叉搜索树的节点。3-
节点:含有两个键和三条链接,左链接指向的节点的键都小于该节点的小键,右链接指向的节点的键都大于该节点的大键,中链接指向的节点的键都介于该节点两个键之间。简单起见,我们这里提到的2-3查找树都是指完美平衡的2-3查找树,完美平衡表示树中所有的叶子节点到根节点的距离都是相同的。我们之后将介绍如何维持这种平衡,以下是一棵2-3查找树的例子:
要判断一个键是否在树中,我们先将它和根结点中的键比较。如果它和其中任意一个相等,查找命中;否则我们就根据比较的结果找到指向相应区间的链接,并在其指向的子树中递归地继续查找。如果这是个空链接,查找未命中。
以下是2个例子,对于H的命中查找:
对于B的未命中查找:
要在 2-3 树中插入一个新节点,我们可以和二叉查找树一样先进行一次未命中的查找,然后把新节点挂在树的底部。但这样的话就破坏了树的完美平衡性,我们需要继续维持这种平衡性。以下根据2种插入情况分别讨论。
如果未命中的查找结束于一个2-
节点,事情就好办了,我们只要把这个2-
节点替换为一个3-
结点,将要插入的键保存在其中即可,比如在树中插入K:
对K的查找在2-
节点结束
将2-
节点替换为3-
节点
如果未命中的查找结束于一个3-
结点,则相对麻烦一些。我们会得到一个临时的4-
节点,而4-
节点在2-3树中并不被允许,我们需要将它进行分解。分解步骤为将4-
节点分解为3个2-
节点,然后将中键插入到它的父节点中。如果父节点是2-
节点则转变为3-
节点,然后停止;如果父节点是3-
节点,则变成临时的4-
节点,然后继续分解并向上转移,直至最终转移到根节点。根节点分解后,会使树的高度增加1,但仍然维持了树的完美平衡性。比如在树中插入Z:
对Z的查找在3-
节点结束
将3-
节点替换为临时的4-
节点
4-
节点分解为3个2-
节点,中间节点上移至父节点
理解了2-3树的原理,接下来,我们定义一种名为红黑树的数据结构,它使用标准的二叉搜索树(完全由2-
节点构成的树)以及一些额外的信息(颜色,用于替换3-
节点)来模拟2-3树的实现。
我们将树中的链接分为两种类型:
2-
节点连接起来构成一个3-
节点;准确的说,我们将3-
节点表示为一条左斜的红色链接相连的两个2-
节点。根据我们的定义,这棵红黑树应该是一棵满足如下条件的二叉搜索树:
如果将这棵红黑树的所有红链接画平,并将由红链接相连的节点看成一个整体,那么我们会发现,它和2-3树是一模一样的。
除了根节点外,每个节点都只会有一条指向自己的链接(由它的父节点指向它),我们将链接的颜色保存在节点中,同时我们规定指向空节点和根节点的链接颜色为黑色。我们定义如下的数据结构:
1 |
|
我们再封装一个私有方法,以确保可以获得任意节点(包含空节点)和它父节点的链接颜色:
1 |
|
与AVL树类似,我们在执行完某些操作后,可能会出现红色右链接或者两条连续的红色左链接,这破坏了我们之前关于红黑树的约定,需要对其进行修复,其中最基本的操作就是旋转。
其实现目标为将红色右链接转换为红色左链接。
1 |
|
旋转之后,我们返回x
作为新的根节点,同时x
保留了原来根节点h
的颜色。
其实现目标为将红色左链接转换为红色右链接,该旋转目的主要是为了消除连续的红色左链接。
1 |
|
我们通过旋转会消除红色右链接或者两条连续的红色左链接的情况,但最终可能会出现一种情况,亦即某个节点同时拥有左右2个红色子节点。
我们定义一个flipColors
方法来转换一个节点的两个红色子节点的颜色。除了将子结点的颜色由红变黑之外,同时还将父结点的颜色由黑变红。这项操作最重要的性质在于它和旋转操作一样是局部变换,不会影响整棵树的黑色平衡性。
1 |
|
这个操作相当于在2-3树中,我们将临时的4-
节点分解为3个2-
节点,同时让中间节点和父节点相结合,并一直向上转移。
注意:根节点始终是黑色,因为红色的根节点表示根节点是一个3-
节点的一部分,这并不符合实际情况。所以如果我们对根节点进行了flipColors
操作,需要将根节点设置为黑色(实际操作中,每次调整失衡我们都将根节点设置为了黑色)。对根节点进行flipColors
操作,同时也意味着树的黑链接高度增加了1。
我们使用二叉搜索树相同的方式往红黑树中插入节点,插入会发生在树的叶子节点上,可能是2-
节点也可能是3-
节点。我们统一使用红链接将新节点和它的父节点相连。
2-
节点中插入新键新键小于2-
节点的键
此情况无须做任何操作,插入完成后本身就是一个合法的3-
节点。
新键大于2-
节点的键
此情况需进行一次左旋转,将红色右链接转变成左链接,变成一个合法的3-
节点。
3-
节点中插入新键新键大于3-
节点的最大键
此情况无须任何旋转操作。
新键小于3-
节点的最小键
此情况出现了两条连续的红色左链接,需对节点C做右旋转。
新键介于3-
节点两个键之间
此情况出现了红色左链接以及两条连续的红色左链接,需进行两次旋转。
此三种情况最终都会产生一个同时连接到两条红链接的节点,此时我们进行一次颜色转换操作即可完成修复。
针对以上情况总结,我们可以得到如下规律:
对于某个节点调整失衡的实现代码:
1 |
|
最终我们实现的插入操作和普通二叉搜索树几乎一模一样,只是插入完成后,需要由叶子节点至底向上,一步步为查找路径上的节点调整平衡。
1 |
|
红黑树的删除操作较为复杂和难以理解,不过我们这里仍然以2-3树为例,首先明确在2-3树中应该如何操作后仍然保持平衡,再使用我们定义的红黑树来模拟这个过程。简单起见,我们先从删除最小键开始。
在二叉搜索树中,最小键就是树最左边的节点,所以如果某个节点的左节点为空,那么这个节点就是最小节点,在一般二叉搜索树中,我们只需要将这个节点的右节点赋值给它的父节点,即完成了最小节点的删除。但是在2-3树中,我们不能将节点留空,因为这样就失去了平衡。可以考虑这样一种情况,假如我们要删除的最小键是在一个3-
节点中,这样一来当我们删除这个键的时候,3-
节点就变成了2-
节点,对于2-3树来说就依然是平衡的。
接下来,我们定义一个共用方法moveRedLeft
,该方法进行一系列转换,确保最小键一定位于最底端的3-
节点(或4-
节点)中,以下是moveRedLeft
方法进行的2种转换:
从兄弟节点借变成3-
节点后删除
从父节点借变成4-
节点后删除
对于deleteMin
方法,我们自顶向下递归,当我们找到最小节点(最左边的节点)时,直接返回空即可,否则继续对子节点进行变换。该变换可能导致会出现4-
节点,不过不用担心,因为最后我们会通过之前的调整失衡balance
方法自底向上来消除这种情况。
1 |
|
最后,我们以下面这棵红黑树为例,来理解moveRedLeft
方法的实现
首先如果节点本身是3-
节点,或者其左子节点是3-
节点时,则无需任何处理,直接递归其左子节点即可,比如对该树根节点进行moveRedLeft
操作会直接返回。
否则我们进行颜色变换,将父节点融合进来,变成4-
节点:
如果节点的右子节点为2-
节点,则还需要对其进行旋转操作:
1 |
|
处理完毕之后,我们便可毫无顾虑的删除最小键,最后再通过平衡操作,调整成一棵合法的红黑树。
注意到这里的转换颜色操作和之前介绍的flipColors
方法恰好相反,我们可以将该方法抽离出来:如果是自底向上进行调整失衡,我们定义参数top
为true
;如果是自顶向下,融合父节点操作,则定义参数top
为false
。
1 |
|
类似于插入叶子节点,当最终删除最小键后,也需要自底向上平衡到根节点,此时我们需要将根节点置为黑色。
1 |
|
通过以上的例子,可以观察到对于红黑树最小键的删除过程,实际体现在2-3树中的结果如下图所示:
现在我们考虑在红黑树中删除任意键的算法,在删除最小键的时候我们通过变换让最小键位于3-
或4-
节点中再进行删除,其中用到了moveRedLeft
方法,该方法保证让右边的节点3-
节点可以转移到左边到2-
节点,现在当我们要删除的键可能位于右边的2-
节点时,也需要考虑将左边的3-
节点转移到右边的2-
节点中,类似地,我们定义moveRedRight
方法:
1 |
|
相比较moveRedLeft
而言,这里除了颜色转换没用到任何旋转,因为我们约定红连接都是左链接,根据我们的平衡算法,即使没有从左兄弟节点转移到右2-
节点,最后调整平衡的时候也可以恢复正常的,比如删除一个2-
节点H:
而如果要删除的键在左边的2-
节点中,则无法直接恢复平衡,删之前需先旋转:
值得注意的是,因为红链接都是在左侧,所以一个3-
节点中较大的那个键为父节点,假如要删除的是这个大键,那么必须先进行右旋转,让小键提升为父节点再进行删除,而如果要删除的是小键就没有必要了,因为它本身就是一个左子节点。
比如这里如果删除的键是H则需要右旋转,而如果是F则无需旋转:
最后,我们来剖析一下完整的删除算法,首先从根节点开始和删除键进行比较:
moveRedLeft
:moveRedRight
方法融合:1 |
|
下面我们以删除一棵红黑树的根节点为例,通过图片来演示算法是如何运作的:
如果忽略这棵红黑树的颜色属性,那么它就是一棵普通的二叉搜索树,只不过它是高度平衡的,所以查询相关的代码和普通二叉搜索树完全一致。
1 |
|
]]>参考文献:《算法》第四版
在Python中,常用的用于写错误日志的库是logging
。以下是一个简单的示例,演示如何在Python中使用logging
库来记录错误日志:
1 |
|
以上代码创建了一个名为error_logger
的日志记录器,并将错误级别设置为logging.ERROR
。然后,创建了一个文件处理器(FileHandler
)和一个控制台处理器(StreamHandler
),分别用于将日志写入文件和输出到控制台。最后,定义了日志的格式,并将处理器添加到日志记录器中。
在try
块中的代码中,如果发生了错误,可以使用logger.error()
方法将错误信息记录到日志文件和控制台中。
请注意,在使用logging
库之前,需要先导入logging
模块。
logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
这句代码是用于定义日志的格式。
%(asctime)s
:表示日志记录的时间,即当前时间,以可读的形式显示(例如:”2022-09-30 10:30:15”)。%(levelname)s
:表示日志的级别,如DEBUG
、INFO
、WARNING
、ERROR
、CRITICAL
等。%(message)s
:表示实际的日志消息。这样的定义可以让日志以特定的格式显示,方便阅读和分析日志。在这个例子中,日志的格式为:时间 - 日志级别 - 日志消息
。例如:2022-09-30 10:30:15 - ERROR - 这是一个错误示例
。
还可以根据需要自定义日志的格式,使用不同的占位符来输出不同的信息,例如输出日志记录器的名称%(name)s
、输出所在的模块%(module)s
等等。更多的占位符和格式选项可以参考Python官方文档的Logging.Formatter部分。
如果所有错误日志都写入同一个文件,会导致文件越来越大。为了解决这个问题,可以使用logging
库的RotatingFileHandler
或TimedRotatingFileHandler
来分隔文件存储。下面是示例代码:
1.使用RotatingFileHandler
:
1 |
|
使用RotatingFileHandler
时,可以通过maxBytes
参数设置每个日志文件的最大大小,通过backupCount
参数设置最多存储的日志文件数量。
2.使用TimedRotatingFileHandler
:
1 |
|
使用TimedRotatingFileHandler
时,可以通过when
参数指定日志文件的轮转时间间隔(例如:midnight
表示每天生成一个日志文件),通过interval
参数指定轮转时间间隔的数量。
除了midnight
,TimedRotatingFileHandler
的when
参数还支持以下几种有效的取值:
S
: 表示每秒生成一个日志文件。M
: 表示每分钟生成一个日志文件。H
: 表示每小时生成一个日志文件。D
: 表示每天生成一个日志文件。W0
~ W6
: 表示每个星期的周一至周日分别生成一个日志文件。midnight
: 表示每天的午夜(00:00)生成一个日志文件。这些取值可以根据需要灵活选择,以满足不同的日志轮转需求。例如,如果需要每小时生成一个日志文件,可以将when
参数设置为'H'
;如果需要每周生成一个日志文件,可以将when
参数设置为'W0'
~ 'W6'
,其中W0
表示周一,W1
表示周二,依此类推。
注意,这些取值均为大小写敏感。另外,TimedRotatingFileHandler
还支持interval
参数,用于指定轮转时间间隔的数量。例如,interval=2
表示每2个时间单位生成一个日志文件(如每2小时、每2天等)。
更多关于TimedRotatingFileHandler
的详细信息,可以查看Python官方文档的Logging.handlers.TimedRotatingFileHandler部分。
拉取镜像:docker pull jenkinsci/blueocean:latest
创建主目录:mkdir -p /data/jenkins
运行容器:
1 |
|
浏览器访问http://ip:8080进入Jenkins管理界面
初始密码查询:cat /data/jenkins/secrets/initialAdminPassword
默认账号名:admin
如果在页面安装插件失败,也可进入jenkins容器内部用命令安装,比如安装git
插件:
1 |
|
Jenkins在安装插件时,默认使用官方地址下载,有时可能速度太慢导致安装失败,可换成国内镜像地址:
vim /data/jenkins/hudson.model.UpdateCenter.xml
修改改成清华大学镜像地址:
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
安装插件Docker 和 Docker Pipeline
设置maven镜像仓库,创建一个文件/data/jenkins/settings.xml
,里面指定镜像路径:
1 |
|
新建流水线项目,由于这是使用的maven是容器,把配置文件映射进去,表示从阿里云获取maven依赖,这样拉取依赖会快很多;mvn容器默认使用的jdk11,有时候为了使用低版本的java,还需要把java环境变量配置进去:
1 |
|
在Jenkins服务器上创建公私钥对ssh-keygen
;
jenkins中添加私钥:Dashboard -> 凭据 -> 系统 -> 全局凭据
,添加凭据SSH Username with private key
得到唯一凭据id;
在git服务器中添加公钥;
在流水线项目中添加唯一凭据id:
1 |
|
You’re using ‘Known hosts file’ strategy to verify ssh host keys, but your known_hosts file does not exist, please go to ‘Manage Jenkins’ -> ‘Configure Global Security’ -> ‘Git Host Key Verification Configuration’ and configure host key verification.
方式一:
在容器中手动创建文件/root/.ssh/known_hosts
方式二:
修改配置系统管理 -> 全局安全配置
Couldn’t find any revision to build. Verify the repository and branch configuration for this job.
Jenkins调用git时用了默认的master
分支,而gitlab这边使用的是main
分支,指定分支名:
1 |
|
需要安装Publish Over SSH
插件
系统管理 -> 系统配置
中找到Publish over SSH
选项,添加服务器,主要配置如下:
完整部署脚本:
1 |
|
Jenkins操作
安装插件gitlab
项目的构建触发器,勾选gitlab选项
设置Secret token
,最后保存配置
Gitlab操作
项目 -> 设置 -> Webhooks
,创建一个Webhook,主要配置如下3项
Urlis blocked: Requests to the local network are not allowed
在gitlab的admin -> Settings -> Network
的Outbound requests
选项中,勾选Allow requests to the local network from webhooks and integrations
:
可从队列2端进行添加和移除数据,效率比list
高。
1 |
|
基于堆实现的优先队列,默认小顶堆,最小的元素在堆顶。
1 |
|
PriorityQueue
内部使用heapq
函数实现,将基于函数的接口封装为了基于类的接口,同时PriorityQueue
是同步的,提供了锁语义来支持多个并发的生产者和消费者。
默认小顶堆
1 |
|
如果获取的键不存在,则采用指定的构造函数为这个键设置一个默认值。
1 |
|
按顺序存储字典键值,内部由普通字典和双向链表实现。
1 |
|
集合元素是有序的,添加元素后集合仍然保持有序。
1 |
|
字典的键是有序的,可按顺序遍历键。
1 |
|
sortedcontainers
非python默认库,需进行安装pip install sortedcontainers
。
一种特殊的默认初始化字典,值是int
类型,表示键的数量。
1 |
|
1 |
|
1 |
|
通过二分算法快速在有序集合中找到插入点。
1 |
|
1 |
|
1 |
|
有放回抽样排列
1 |
|
数量:$ n^r=4^2=16 $
不放回抽样排列
1 |
|
数量:$ A_n^r=A_4^2=\frac{4!}{(4-2)!}=12 $
不放回抽样组合
1 |
|
数量: $ C_n^r=C_4^2=\frac{4!}{(4-2)!*2!}=6 $
有放回抽样组合
1 |
|
相当于多增加(r-1)
个元素参与选择,这(r-1)
个元素是替换符,可以替换成组合中已存在的元素,形成重复。
数量: $ C_{n+r-1}^r=C_5^2=\frac{5!}{(5-2)!*2!}=10 $
相比itertools.combinations('ABCD', 2)
,多出的4个组合是('A', 'X') ('B', 'X') ('C', 'X') ('D', 'X')
,对应的则是('A', 'A') ('B', 'B') ('C', 'C') ('D', 'D')
。
创建文件/etc/yum.repos.d/Charles.repo
1 |
|
执行安装:
1 |
|
安装完成后,会生成一个/usr/bin/charles
的执行文件,可直接运行。
执行charles
命令报错:because /lib64/libm.so.6: version 'GLIBC_2.27' not found (required by /usr/lib/charles-proxy/jdk/lib/server/libjvm.so)
。
解决办法,安装GLIBC_2.27
库。
下载https://mirrors.tuna.tsinghua.edu.cn/gnu/libc/glibc-2.27.tar.gz
1 |
|
执行configure报错:These critical programs are missing or too old: bison compiler
报错原因为缺少bison
依赖和gcc编译器,以下2个步骤都需要执行:
1.安装bison
1 |
|
2.查看版本gcc --version
,版本过低,需升级gcc
下载并解压
1 |
|
安装依赖:
1 |
|
配置
1 |
|
在执行该步骤如果出现A compiler with support for C++11 language features is required
错误,先安装c++编译器:yum install gcc-c++
编译并安装
1 |
|
该步骤耗时很长,需耐心等候。
执行make
操作时,报各种代码错误,诸如:
error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=]
error: array subscript 1 is outside the bounds of an interior zero-length array ‘struct dtv_slotinfo[0]’ [-Werror=zero-length-bounds]
最终解决方案gcc-10.4.0
搭配glibc-2.32
1 |
|
注意:这里的configure
使用了官方的参数,参数--prefix=/usr
是必须的。最终成功安装glibc-2.32
,该项包含2.27版本。
1 |
|
导出charles根证书
1 |
|
将证书安装到linux系统
1 |
|
charles默认使用的配置文件为~/.charles.config
,将其复制出来并进行修改:
设置访问控制,charles默认有访问限制,将其完全放开,设置如下内容:
1 |
|
启用https代理,在sslLocations
节点下添加如下内容:
1 |
|
配置本地路由,使用百度作为测试:
1 |
|
之后使用新的配置文件启动:
1 |
|
客户端访问https://chls.pro/ssl地址获取并安装charles证书,并将证书受信即可进行https代理。
charles官方文档地址:https://www.charlesproxy.com/documentation
1 |
|
1 |
|
建立软链接
1 |
|
gnu官方地址:http://ftp.gnu.org/gnu/
腾讯云镜像:https://mirrors.cloud.tencent.com/gnu/
清华镜像:https://mirrors.tuna.tsinghua.edu.cn/gnu/
资源 | 对应目录 |
---|---|
gcc | /gcc/ |
glibc | /libc/ |
make | /make/ |
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1j20h7lk5sr7f
]]>使用源码安装nginx
1 |
|
启用 HTTPS 支持
1 |
|
获取用户真实IP支持
1 |
|
与客户端有关的配置主要在 http 块中设置
指令 | 说明 |
---|---|
client_body_buffer_size | 设置读取客户端请求体的缓冲区大小。如果请求体的大小大于缓冲区的大小,则整个或一部分请求体会被写入临时文件。在默认情况下,会为 32 位系统、x86-64 系统设置8KB 的缓冲区,其他 64 位系统为 16KB 的缓冲区 |
client_body_temp_path | 定义存储客户端请求体的临时文件目录,最多可以定义 3 个子集目录 |
client_body_timeout | 定义读取客户端请求体的超时时间,即两个连续的读操作之间的时间间隔。如果超时HTTP 会抛出 408 错误 |
client_header_buffer_size | 设置客户端请求头的缓冲区大小,默认为 1KB |
client_max_body_size | 设置客户端请求的最大主体的大小,默认为 1MB |
client_header_timeout | 设置客户端请求头的超时时间 |
etag | 如果设置为 on,表示静态资源自动生成 ETag 响应头 |
large_client_header_buffers | 设置大型客户端请求头的缓冲区大小 |
keepalive_timeout | 设置连接超时时间。服务器将在超过超时时间后关闭 HTTP 连接 |
send_timeout | 指定客户端的响应超时时间 |
server_names_hash_bucket_size | 设置 server_names(Nginx 中配置的全部域名)散列表的桶的大小,默认值取决于处理器缓存行的大小 |
server_names_hash_max_size | 设置 server_names 散列表的最大值 |
server_tokens | 启用或禁用在错误页面和服务器响应头字段中标识的 Nginx 版本 |
tcp_nodelay | 启用或禁用 TCP_NODELAY 选项。只有当连接保持活动时,才会被启用 |
tcp_nopush | 仅当 sendfile 时使用,能够将响应头和正文的开始部分一起发送 |
分类 | 格式 | 说明 |
---|---|---|
精确匹配 | location = /uri | 优先级最高,匹配命中即退出,需完全匹配才算命中 |
一般匹配 | location /uri | 取最长的匹配,暂存结果并搜索正则匹配 |
location ^~ /uri | 取最长的匹配,之后不再搜索正则匹配 | |
location / | 通用匹配,即一定会被命中 | |
正则匹配 | location ~ uri | 用于正则uri前,表示uri里面包含正则,并且区分大小写 |
location ~* uri | 用于正则uri前,表示uri里面包含正则,不区分大小写 |
location
配置顺序无关。^~
,则不再进行正则匹配,直接返回结果。^~
,则继续进行正则匹配,如正则匹配未命中则返回暂存结果,否则返回正则匹配结果。location
配置顺序有关(从上往下),返回第一个命中的结果。变量名 | 说明 |
---|---|
$arg_name | 指 URL 请求中的参数,name 是参数的名字 |
$args | 代表 URL 中所有请求的参数 |
$binary_remote_addr | 客户端地址以二进制数据的形式出现,通常会和限速模块一起使用 |
$body_bytes_sent | 发送给客户端的字节数,不包含响应头 |
$bytes_sent | 发送给客户端的总字节数 |
$document_uri | 设置$uri 的别名 |
$hostname | 运行 Nginx 的服务器名 |
$http_referer | 表示请求是从哪个页面链接过来的 |
$http_user_agent | 客户端浏览器的相关信息 |
$remote_addr | 客户端 IP 地址 |
$remote_port | 客户端端口号 |
$remote_user | 客户端用户名,通常在 Auth Basic 模块中使用 |
$request_filename | 请求的文件路径,基于 root alias 指令和 URI 请求生成 |
$request_time | 请求被 Nginx 接收后,一直到响应数据返回给客户端所用的时间 |
$request_uri | 请求的 URI,带参数 |
$request | 记录请求的 URL 和 HTTP |
$request_length | 请求的长度,包括请求行、请求头和请求正文 |
$server_name | 虚拟主机的 server_name 的值,通常是域名 |
$server_port | 服务器端口号 |
$server_addr | 服务器的 IP 地址 |
$request_method | 请求的方式,如 POST 或 GET |
$scheme | 请求协议,如 HTTP 或 HTTPS |
$sent_http_name | 任意响应头,name 为响应头的名字,注意 name 要小写 |
$realip_remote_addr | 保留原来的客户地址,在 real_ip 模块中使用 |
$server_protocol | 请求采用的协议名称和版本号,常为 HTTP/1.0 或 HTTP/1.1 |
$uri | 当前请求的 URI,在请求过程中 URI 可能会被改变,例如在内部重定向或使用索引文件时 |
$nginx_version | Nginx 的版本号 |
$pid | worker 进程的 PID |
$pipe | 如果请求是 HTTP 流水线(pipelined)发送的,pipe 值为”p”,否则为”.” |
$connection_requests | 当前通过一个连接获得的请求数量 |
$cookie_name | name 即 Cookie 的名字,可得到 Cookie 的信息 |
$status | HTTP 请求状态 |
$msec | 日志写入时间。单位为秒,精度是毫秒 |
$time_local | 在通用日志格式下的本地时间 |
$upstream_addr | 请求反向代理到后端服务器的 IP 地址 |
$upstream_port | 请求反向代理到后端服务器的端口号请求反向代理到后端服务器的端口号 |
$upstream_response_time | 请求在后端服务器消耗的时间 |
$upstream_status | 请求在后端服务器的 HTTP 响应状态 |
$geoip_city | 城市名称,在 geoip 模块中使用 |
根据参数名跳转不同URL
1 |
|
http跳转到https
1 |
|
1 |
|
使用模块ngx_http_auth_basic_module
1 |
|
密码文件生成语法:
1 |
|
1 |
|
access
、 auth_basic
、auth_request
这三个模块都放行,该才能够继续往下走。若被任意一个模块拒绝,就会返回400或者500系列的状态码。access
阶段的模块放行,就可以继续被执行。Syntax | Default | Content | |
---|---|---|---|
auth_basic string\ | off; | auth_basic off; | http, server, location |
satisfy all\ | any; | satisfy all; | http, server, location |
1 |
|
Nginx 使用ngx_http_proxy_module
来完成对后端服务的代理。
1 |
|
控制请求头和请求体:
proxy_set_body 'b=123xxx'
;控制请求和后端服务器的交互时间:
Syntax | Default | Content |
---|---|---|
proxy_connect_timeout; | proxy_connect_timeout 60s; | http, server, location |
proxy_read_timeout time; | proxy_read_timeout 60s; | http, server, location |
proxy_send_timeout time; | proxy_send_timeout 60s; | http, server, location |
如果需要指向多台服务器就要用到ngx_http_upstream_module模块,它为反向代理提供了负载均衡及故障转移等重要功能。
1 |
|
参数说明:
proxy_next_upstream
设置的条件,就会触发 Nginx 将请求重新转发到下一台后端服务器,并累加出现此状态的服务器的失败次数(当超过max_fails
和fail_timeout
的值时就会设置此服务器为不可用)。如果设置为off
,则表示关闭此功能。Syntax | Default | Content | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
proxy_next_upstream error\ | timeout \ | invalid_header\ | http_500\ | http_502\ | http_503 \ | http_504\ | http_403\ | http_404\ | http_429 \ | non_idempotent\ | off…; | proxy_next_upstream error timeout; | http, server, location |
proxy_next_upstream_tries number; | proxy_next_upstream_tries 0; | http, server, location | |||||||||||
proxy_next_upstream_timeout time; | proxy_next_upstream_timeout 0; | http, server, location |
相同的 URL(包含参数)会进入相同的后端缓存系统:
1 |
|
Syntax | Default | Content |
---|---|---|
hash key | - | upstream |
ip_hash; | - | upstream |
least_conn; | - | upstream |
sticky cookie name … | - | upstream |
在Nginx 中,使用 upstream 进行后端访问默认用的是短连接,但这会增加网络资源的消耗。可以通过配置长连接,来减少因建立连接产生的开销、提升性能:
1 |
|
Syntax | Default | Content |
---|---|---|
keepalive connections; | - | upstream |
keepalive_requests number; | keepalive_requests 1000; | upstream |
keepalive_timeout time; | keepalive_timeout 1h; | upstream |
配置在server
块
请求/1.html
,最终返回3.html
1 |
|
请求/1.html
,最终返回2.html
1 |
|
请求/1.html
,最终返回403状态码(绕过后续rewrite
或return
,但会继续匹配location
)
1 |
|
此时,break
和last
实现效果一致。
配置在location
块
请求/1.html
,最终返回b.html
1 |
|
请求/1.html
,最终返回2.html
(本location
内的rewrite
以及其他location
都不再执行)
1 |
|
请求/1.html
,最终返回a.html
(本location
内的rewrite
和return
不再执行,但会继续匹配location
,这是break
和last
的区别)
1 |
|
总结:
rewrite
配置在server块中,会一直向下执行本区块内的rewrite
,直到结束或碰到break
或last
(break
和last
在server块中实现效果一致),但仍然会匹配location。rewrite
配置在location块中,碰到break
或last
都会结束当前块,但last
会继续匹配location,break
则不会。内部重定向:
1 |
|
域名跳转:
1 |
|
返回任意http状态码:
1 |
|
307和308为HTTP1.1支持的状态码,用于告知客户端跳转过程中请求的方法不变,并保留请求体,用于跳转POST请求。
Syntax | Default | Content |
---|---|---|
return code [text] return code URL; return URL; | - | server,location,if |
为了阻止盗链的情况出现,可以使用ngx_http_referer_module模块。此模块是 Nginx 的内置模块,不需要重新编译。
1 |
|
如果referer不是示例中的这些域名,则会返回403错误。
自定义日志格式:
1 |
|
使用escape=json
则日志内容不会被转义,中文字符可以直接在日志里面显示。
Syntax | Default | Content |
---|---|---|
log_format name [escape=default|json|none] string …; | log_format combined “…”; | http |
日志存储方式:
1 |
|
指令access_log
只记录访问日志,关于错误信息的日志记录在error_log
上。
Syntax | Default | Content |
---|---|---|
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]]; access_log off; | access_log logs/access.log combined; | http, server, location, if in location, limit_except |
如果我们要求一个数组内任意区间的和,最朴素的算法是每次对区间所有元素进行求和运算,时间复杂度为。也可以考虑用前缀和的方式去实现,求和运算的时间复杂度为,但这样一来,如果对数组的某一项进行修改,则要同步维护前缀和数组,这会导致更新操作的时间复杂度由原来的提升为。如果数据量非常巨大,这样的时间复杂度仍然是不被接受的。
树状数组则采用了一种折中方案,它通过将数组进行分组,使得求和与更新的时间复杂度均为。
引用自百度百科:
树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为Fenwick树,最早由Peter M. Fenwick于1994年以A New Data Structure for Cumulative Frequency Tables为题发表在SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和, 区间和。
我们以一个长度为16的数组为例,比如[6,7,4,3,6,2,8,9,3,1,9,0,5,2,1,7]
,我们用这个数组来构建一个树状数组,注意:为方便计算,树状数组的索引从1开始。
树状数组也是一个数组结构,并且它的长度和原始数组的长度相同。我们假设有一个树状数组为BinTree
,它的每一项的值BinTree[i]
表示为以索引i
作为结尾并且长度为lowbit(i)
的子序列之和(本例为求和,所以存储的是子序列之和)。
其中,lowbit
函数的输入为一个任意整数,输出为这个整数最低位的1
所代表的数值。例如,lowbit(12)
,12的二进制表示为1100
,最低位的1
为100
,也即十进制的4,所以函数输出为4。这里传递的入参为数组索引。
lowbit
函数就是树状数组的灵魂所在,稍后我们就能看到树状数组如何巧妙的利用该函数,将查询和更新操作的时间复杂度降低为的。
利用二进制的补码性质,我们用一行代码即可实现lowbit
函数的目标。
1 |
|
假设num
为12,它的二进制我们用8位表示为0000 1100
,则-num
的二进制补码表示为1111 0100
,二者相与得到0000 0100
,除了最低位的1
仍然保留,其余位全部变为0
,这正是我们要的结果。
树状数组可以以的时间复杂度求出任意长度的前缀和。比如求区间[1,11]
之和,我们可以把区间分成[1,8]
,[9,10]
和[11,11]
然后再相加,而这3个区间的和已经存储在树状数组中。参考下图:
通过观察可以发现,11的二进制表示为1011
,其中包含3个1
位,所以被划分为3个区间,3个区间的末尾索引分别为11(0b1011)
,10(0b1010)
和8(0b1000)
,同时它们的长度分别为lowbit(11)=1
,lowbit(10)=2
和lowbit(8)=8
,这3个区间正好覆盖了前11个元素。
所以,当前区间只要减去一个lowbit,即可得到上一个区间:11(0b1011) -> 10(0b1010) -> 8(0b1000)
我们用ask
函数来表示查询方法,代码表示为:
1 |
|
用自然语言可描述为,求以索引i
结尾,并且长度为lowbit(i)
的区间之和,接着去除索引最低位的那个1
,相当于排除掉了lowbit(i)
个数值,同时得到一个缩小的新索引。该问题变成了一个相同但规模更小的子问题,可用递归实现。
利用该方法,我们可以用对数时间求得任意前缀和。现在,对于任意区间的和,我们只需计算出2个前缀和,然后相减即可得到结果。比如求区间[4,9]
之和,我们分别计算出[1,9]
和[1,3]
,再将2者相减。
对于更新来说,如果我们更改了数组中的某个元素值,则所有树状数组中覆盖了该元素索引的区间都应该被更新。同样,我们可以利用lowbit
规律,快速进行更新。值得注意的是,由于树状数组并没有存原始数组的值,所以我们只能更新差异值,而不是直接覆盖。
举个例子,如果我们现在把原数组中索引9的值由3改成5,则差异值为+2
,则树状数组中覆盖了索引9的区间都应该+2
,这些区间在树状数组中对应的索引分别为9,10,12,16。
观察可发现,当前区间加上一个lowbit,即可得到上一个区间:9(0b1001) -> 10(0b1010) -> 12(0b1100) -> 16(0b10000)
。
同理,更新原数组索引7,覆盖了索引7的区间的末尾索引分别为7,8,16:
区间更新路线为:7(0b0111) -> 8(0b1000) -> 16(0b10000)
。
我们用add
函数来表示更新方法,代码表示为:
1 |
|
自然语言描述为不断向上寻找更大的覆盖区间,直到超出最大索引。
因为树状数组的索引从1开始,所以我们构建的树状数组长度相比原数组多1个,树状数组的索引相较于原数组索引需加上1。树状数组的初始值均为0,通过add
方法将原数组的每个值添加进树状数组从而进行初始化。
1 |
|
我们再为这个树状数组扩展2个通用方法,更新数组和任意区间查询,以解决我们开头抛出的问题。
1 |
|
现在我们就可以用树状数组来封装一个普通数组,可以对数组索引进行更新,也能查询任意区间[left,right]
之和。
1 |
|
不用充钱即可在线收听和下载任何歌曲,包括收费歌曲。
本程序为大学时期开发的简单音乐盒程序,后为了听免费听歌,于是在此基础上做了二次开发。
切换到网络歌曲,直接搜索歌曲关键字,双击即可播放。
也可将歌曲添加到指定播放列表,以便将歌曲保存下来。
勾选“边听边下”,歌曲在播放中会自动下载到指定路径。
在播放列表和网络歌曲中均可右键进行批量下载操作。
你完全可以将本软件当成一个专门的下载器,然后在网易云中播放。
在网易云音乐中扫描本软件设定的下载文件夹,将已下载好的歌曲同步到网易云音乐的本地曲库,然后点击关联操作,即可自动匹配歌词,专辑等信息,使用效果更佳。
本软件为绿色版,直接双击exe即可运行,但需要保证电脑上安装.net framework4.5,如没有安装使用过程中可能报错。.net4.5微软官方下载地址
本软件免费使用,仅用于学习交流,不可用于任何商业用途,软件作者不会采用任何收费方式进行谋利行为,如因使用不当所造成后果均由使用者自行承担。
]]>