分布式一致性算法05:NWR

分布式一致性算法05 NWR协议

NWR模型是一种实现分布式一致性的方法,它基于备份(Replicas)的数量和更新(Write)或读取(Read)操作所需的备份数。NWR模型的核心思想是通过调整N、W和R的值来达到一致性的要求。以下是NWR模型的一些基本概念和如何通过一个例子来说明它实现分布式一致性。

NWR模型的基本概念:
N:系统中备份的总数。
W:执行写操作时需要保证成功的备份数量。
R:执行读操作时需要查询的备份数量。

为了保证一致性,必须满足以下条件:
W+R>N

这个不等式确保了在执行读操作时,至少有一个最新的备份被读取到,从而避免了读到过时的数据。

举例说明:

假设我们有一个分布式数据库系统,其中有5个备份节点,即N=5。我们希望确保写操作在至少3个节点上成功(W=3),并且读操作至少查询3个节点(R=3)。

写操作:
客户端向系统发送一个写请求,比如更新某个键值对。
根据W=3的要求,这个写请求必须在至少3个备份节点上成功更新。

写操作的传播:
写请求被发送到所有5个备份节点,等待至少3个节点确认写操作成功。

写操作的确认:
假设有4个节点成功更新了数据,满足了W=3的要求,写操作被认为是成功的。

读操作:
客户端发送一个读请求,希望获取最新的键值对数据。
根据R=3的要求,读请求需要从至少3个备份节点获取数据。

读操作的数据收集:
系统从5个备份节点中的任意3个节点获取数据,由于W+R>N(3+3>5),至少有一个节点上的数据是最新的。

数据的一致性:
客户端收到来自3个节点的响应,可能会发现不同节点的数据版本不同。系统需要一个策略来决定使用哪个节点的数据作为最终结果,例如选择版本号最高的数据。

返回结果:
客户端根据系统的策略得到最终的一致性结果,并将其作为读操作的输出。

通过这种方式,NWR模型确保了即使在分布式系统中存在网络延迟或节点故障的情况下,也能够实现数据的一致性。通过适当选择W和R的值,系统可以在一致性和可用性之间取得平衡。

分布式一致性算法04:RAFT

分布式一致性算法04 RAFT协议

Raft算法是一种用于分布式系统中的一致性算法,它通过选举领导者(Leader)来确保数据的一致性。Raft算法易于理解和实现,并且它通过日志复制(Log Replication)来保持集群中所有节点的状态一致。

Raft算法的关键组件:
领导者(Leader):负责处理所有客户端请求,将日志条目(Log Entries)复制到其他节点。
跟随者(Follower):接收领导者的日志条目并保持与领导者的心跳连接。
候选者(Candidate):当跟随者在一定时间内没有收到领导者的心跳时,它们会变成候选者并尝试成为新的领导者。
日志条目(Log Entries):记录了所有的事务操作,必须在所有节点上顺序一致。

Raft算法的工作流程:

选举领导者:
系统启动或领导者崩溃时,所有跟随者开始新一轮的领导者选举。

日志复制:
领导者接收到客户端的请求后,将其转换为日志条目并追加到自己的日志中。

日志同步:
领导者将新的日志条目发送给所有跟随者,跟随者接收并存储这些日志条目。

日志提交:
当领导者确认日志条目已经被大多数跟随者接收后,它将向跟随者发送提交指令,然后所有节点将日志条目应用到状态机中。

心跳和领导者续租:
领导者定期向跟随者发送心跳以维持其领导者地位,并接收跟随者的确认。

举例说明:
假设我们有一个由三台服务器组成的Raft集群,每台服务器初始状态都是跟随者。

系统启动:
所有跟随者启动后,由于没有领导者,它们开始进入选举超时,并转换为候选者。

选举领导者:
假设服务器A的任期(Term)最大,它收到了超过半数跟随者的投票(服务器B和C的投票),因此服务器A成为领导者。

客户端请求:
客户端向领导者A发送一个写请求,比如设置键值对key1=value1。

日志复制:
领导者A创建一个新的日志条目,包含这个写请求,并将其追加到自己的日志中。

日志同步:
领导者A将这个日志条目发送给跟随者B和C,它们接收并存储这个日志条目。

日志提交:
领导者A等待跟随者B和C的确认,一旦收到大多数确认(这里是B和C),它就向它们发送提交指令。

应用日志:
跟随者B和C接收到提交指令后,将日志条目应用到自己的状态机中,更新键值对。

心跳和续租:
领导者A定期向跟随者B和C发送心跳,以维持其领导者地位,并确保跟随者不会超时转换为候选者。

故障恢复:
如果领导者A发生故障,跟随者B和C会在超时后转换为候选者,并开始新的领导者选举过程。

通过这个例子,我们可以看到Raft算法如何通过领导者选举、日志复制、日志同步、日志提交和心跳机制来确保分布式系统中的数据一致性。

Raft算法中的领导选举过程是其核心机制之一,用于在没有领导者或当前领导者失败时选出新的领导者。以下是Raft算法领导选举过程的示例:

假设我们有一个由五台服务器组成的Raft集群,每台服务器初始状态都是跟随者(Follower)。集群中的每个节点都维护一个当前任期(Term)的记录,任期是一个递增的计数器,每次选举都会增加。

领导选举过程:

选举超时:
每个跟随者节点都有一个随机的选举超时时间,这个时间用来在没有收到领导者的心跳时触发新的选举。假设服务器1、2、3、4、5的超时时间分别随机设置为150ms、300ms、450ms、600ms和750ms。

超时转换:
假设当前没有领导者,并且跟随者节点没有收到任何心跳。当跟随者的选举超时时间到达后,它们会将自己的状态从跟随者转换为候选者(Candidate)。

增加任期:
候选者节点会首先增加自己的当前任期计数(Term),比如从Term 1增加到Term 2。

发送投票请求:
候选者向集群中的所有其他节点(包括其他跟随者和候选者)发送投票请求(RequestVote),包含自己的任期号和自己日志的索引和术语。

收集选票:
收到投票请求的节点会根据以下规则决定是否投票:
如果请求的任期号大于或等于自己的当前任期号,节点会更新自己的任期号并投票给请求者。
如果请求的任期号小于自己的当前任期号,节点会拒绝投票。

确定领导者:
如果一个候选者从集群中大多数节点那里收到投票,它就赢得了选举,成为新的领导者。

发送心跳:
新的领导者会立即向所有节点发送心跳信息,以通知它们自己的存在,并防止它们变成候选者。

跟随者响应:
当跟随者收到心跳信息时,它们会将自己的状态更新为跟随者,并开始接收和复制领导者的日志条目。

示例:
假设在150ms后,服务器1的超时时间最先到达,它变成候选者并开始选举过程。它将自己的任期计数增加到Term 2,并向其他服务器发送投票请求。
服务器2、3、4、5收到投票请求,检查任期计数,发现请求的任期(Term 2)大于它们自己的(Term 1),因此它们投票给服务器1。
服务器1收到超过半数的投票(4票),成为新的领导者。
服务器1开始向所有其他服务器发送心跳,其他服务器收到心跳后,确认服务器1为领导者,并开始跟随它的日志。

通过这个选举过程,Raft算法确保了在没有领导者或领导者失败的情况下,能够快速且一致地选出新的领导者,从而维持集群的一致性和可用性。

Raft算法的日志复制机制通过一系列步骤确保数据的一致性,这些步骤确保了所有跟随者(Follower)节点上的日志与领导者(Leader)节点上的日志保持一致。以下是Raft算法日志复制机制的详细说明:

日志条目的创建:
当领导者接收到来自客户端的请求时,它会创建一个新的日志条目,并将该条目附加到自己的日志末尾。

日志复制:
领导者将新日志条目发送给所有的跟随者。这个过程称为“AppendEntries RPC”,其中包含了日志条目的信息以及领导者的任期号等。

日志存储:
跟随者接收到AppendEntries RPC后,首先会验证领导者的任期号是否有效。如果有效,跟随者会将接收到的日志条目附加到自己的日志末尾。

日志一致性检查:
在存储新日志条目前,跟随者会检查新条目是否与自己日志中的最后一个条目兼容。如果兼容,即前一个日志条目的索引和术语与领导者发送的匹配,跟随者才会继续存储新的日志条目。

日志条目确认:
跟随者在成功存储日志条目后,会向领导者发送一个确认响应(包含当前的任期号和成功标识)。

提交日志条目:
当领导者接收到大多数跟随者的确认响应后,它会将该日志条目标记为“提交”状态,并应用该条目到自己的状态机中。

日志条目应用:
领导者随后会向所有跟随者发送包含已提交日志条目的AppendEntries RPC,指示跟随者也应用这些条目到它们的状态机中。

状态机一致性:
由于所有节点都按照相同的日志条目顺序执行操作,因此所有节点的状态机最终将保持一致。

处理并发日志:
如果领导者在等待日志条目提交的过程中接收到新的客户端请求,它会为每个请求创建新的日志条目,并按照顺序追加到日志中。

日志压缩:
为了减少存储空间的使用,Raft算法会定期进行日志压缩,保留关键的日志信息,而丢弃已经被持久化和应用的日志条目。

通过这个日志复制机制,Raft算法确保了即使在领导者发生故障的情况下,新的领导者也能够从跟随者那里获取完整的日志状态,继续处理客户端请求,并保持集群的数据一致性。

在Raft算法中,如果领导者节点失败,新选举出的领导者将通过以下步骤恢复之前的状态:

日志复制:
一旦新的领导者被选举出来,它将开始向所有跟随者发送心跳信息,同时开始日志复制过程。

日志匹配:
在日志复制之前,新的领导者需要确定自己的日志与跟随者的日志是否一致。它会发送AppendEntries RPC请求给跟随者,并在请求中包含自己日志的最后一条条目的索引和术语。

跟随者的响应:
跟随者接收到AppendEntries RPC后,会检查自己的日志以确定是否可以匹配领导者的日志。如果跟随者的日志在指定索引处的术语和索引与领导者的匹配,它会发送一个成功的响应。

日志不一致的处理:
如果跟随者的日志与领导者的不匹配,它会发送一个失败的响应,并告知领导者自己的日志信息。新的领导者将根据跟随者的响应调整自己的日志,可能会删除或追加日志条目以确保一致性。

日志压缩:
在日志同步过程中,如果发现日志存在空洞(即缺少中间的日志条目),新的领导者将引导跟随者进行日志压缩,删除已经被持久化和应用的日志条目,以减少存储空间并加快同步速度。

日志提交:
新的领导者会等待大多数跟随者确认日志条目的一致性后,才会将日志条目标记为已提交,并应用到状态机。

状态机恢复:
一旦日志同步完成,并且所有已提交的日志条目都已应用到状态机,新的领导者就完全恢复了之前领导者的状态。

继续处理请求:
状态恢复后,新的领导者可以继续处理客户端的请求,并保证集群的数据一致性。

日志的持续同步:
在整个集群运行过程中,领导者会持续地与跟随者同步日志,确保任何时候集群的状态都是一致的。

通过这些步骤,Raft算法确保了即使领导者失败,新的领导者也能够准确地恢复之前的状态,并且继续提供服务。这种机制提高了系统的可靠性和容错能力。

分布式一致性算法03:ZAB

分布式一致性算法03 ZAB协议

ZAB(Zookeeper Atomic Broadcast)协议是为Apache Zookeeper框架设计的一致性协议。它主要用来确保分布式系统中的数据一致性,特别是在Zookeeper这样的分布式协调服务中。ZAB协议具有以下特点:

原子广播:ZAB协议保证了所有事务请求的顺序性和一致性,即客户端对Zookeeper的写请求要么被完全应用,要么完全不应用,不会出现中间状态。
主从架构:在ZAB协议中,有一个主服务器(Leader)负责处理所有事务请求,其他服务器作为从服务器(Follower)接收并复制Leader的数据。
高可用性:如果Leader发生故障,ZAB协议能够通过选举机制快速选出新的Leader,保证系统的持续可用性。

在ZAB协议中,Leader选举是确保集群中有一个单一的领导者来处理所有事务的关键过程。以下是ZAB协议中Leader选举的步骤和示例:

初始化:
集群中的每个服务器(Server)启动时,都处于“Looking”状态,即寻找Leader状态。

投票:
每个处于“Looking”状态的服务器都会发送投票(Vote)给其他服务器,尝试将自己选举为Leader。投票包含服务器的ID(myid)和事务ID(zxid)。

收集选票:
每个服务器在接收到投票后,会比较发送者的zxid和自己的zxid。如果发送者的zxid更大,或者两者zxid相同但发送者的myid更大,则接受投票并更新自己的投票信息。

确定Leader:
如果某个服务器获得了超过半数的投票,它就认为自己被选举为新的Leader,并切换到“Leading”状态。

同步:
新Leader会与集群中的其他服务器(Follower)进行数据同步,确保所有Follower的数据与Leader一致。

广播:
一旦数据同步完成,Leader开始接收客户端的事务请求,并将这些事务广播给所有Follower。

Leader选举示例:
假设我们有一个Zookeeper集群,包含5个服务器,每个服务器的ID分别是1, 2, 3, 4, 5。启动时,所有服务器都处于“Looking”状态。

初始化:
服务器1, 2, 3, 4, 5开始寻找Leader,并发送投票请求,每个服务器的投票都是投给自己。

投票:
假设服务器1的zxid是100,服务器2的zxid是99,服务器3, 4, 5的zxid都是98或更低。
服务器1发送投票请求(zxid=100, myid=1)。

收集选票:
服务器2, 3, 4, 5接收到服务器1的投票请求,比较zxid后发现1的zxid最大,因此它们将自己的投票投给服务器1。

确定Leader:
服务器1收到超过半数(3个)的投票,包括自己的投票,因此它认为自己被选举为Leader,并切换到“Leading”状态。

同步:
服务器1开始与服务器2, 3, 4, 5进行数据同步,确保所有Follower的数据与自己的数据一致。

广播:
数据同步完成后,服务器1作为Leader开始接收客户端的事务请求,并将这些事务以事务Proposal的形式广播给所有Follower。

Follower处理:
Follower接收到Leader的事务Proposal后,会将Proposal写入本地日志,并在大多数Follower确认后,Leader会指示Follower提交事务。

通过这个选举过程,ZAB协议确保了集群中有一个单一的Leader来处理所有的写请求,从而保证了数据的一致性。如果Leader发生故障,集群会重新进行选举过程,选择一个新的Leader。

ZAB协议的工作流程示例:
假设我们有一个由五台服务器组成的Zookeeper集群,其中一台服务器被选举为Leader,其余四台为Follower。

客户端请求:
客户端向集群发送写请求,比如创建一个Znode。

事务提案:
Leader接收到客户端的请求后,会生成一个事务提案(Proposal),并分配一个全局唯一的事务ID。

广播提案:
Leader将事务提案发送给所有Follower,要求它们复制这个提案。

Follower投票:
Follower接收到提案后,会根据自身的数据状态进行投票,并将投票结果发送回Leader。

Leader决定:
当Leader收到超过半数Follower的同意投票后,它会做出决定,并将决定结果通知所有Follower。

提交或回滚:
如果提案被接受,Leader会指示所有Follower提交这个事务,更新它们的状态。如果提案被拒绝或Leader在等待投票结果时发生故障,事务将被回滚。

故障恢复:
如果Leader在处理过程中发生故障,Follower会进入新一轮的Leader选举,并从最新的状态开始同步。

客户端响应:
一旦事务被提交,Leader会向客户端返回操作结果,客户端根据结果进行相应的处理。

ZAB协议的关键点:
顺序一致性:ZAB协议保证了客户端请求的顺序性,即在任意时刻,客户端的请求都会按照某种顺序被处理。
可靠性:即使在部分服务器宕机的情况下,ZAB协议也能够保证系统的可靠性和数据的一致性。
实时性:ZAB协议通过快速的Leader选举和事务处理机制,保证了系统的实时性。

ZAB协议是Zookeeper能够提供高可用性和一致性的关键,它使得Zookeeper成为了分布式系统中广泛使用的协调服务。

在ZAB协议中,Epoch和ZXID是两个关键的概念,它们共同确保了集群状态的一致性。

ZXID(事务ID)
定义:ZXID是一个64位的数字,用于唯一标识Zookeeper中的每个事务。
组成:ZXID的高32位表示Epoch,低32位表示事务计数器(xid)。
作用:
确保事务的全局顺序性。每个事务都有一个唯一的ZXID,保证了事务在所有服务器上的处理顺序一致。
通过比较ZXID,服务器可以确定事务的先后顺序,以及是否已经处理过某个事务。

Epoch(纪元)
定义:Epoch是ZXID的高32位,表示领导者的任期编号。每次新的领导者被选举出来时,Epoch都会增加。
作用:
标识领导者的任期。不同的Epoch代表不同的领导者任期,确保了领导者的唯一性。
处理网络分区和领导者故障恢复的情况。当发生分区或领导者故障时,新的领导者会具有更高的Epoch,从而确保了新的领导者具有最新的状态。

确保集群状态一致性的机制:

领导者选举:
在选举过程中,节点会根据ZXID和Epoch来决定投票。拥有更高Epoch和ZXID的节点更有可能被选举为领导者。

事务处理:
领导者在处理事务时,会为每个事务分配一个新的ZXID,确保每个事务都是顺序处理的。

数据同步:
领导者会将事务日志同步给跟随者。跟随者接收到事务日志后,会按照ZXID的顺序进行处理,确保了数据的一致性。

领导者故障恢复:
当领导者发生故障时,新的领导者会从跟随者中选举出来,并且具有更高的Epoch。这确保了新的领导者能够覆盖旧领导者的状态,维持集群状态的一致性。

跟随者的同步状态:
跟随者在接收到领导者的同步请求时,会根据Epoch和ZXID来确定是否接受同步。如果领导者的Epoch更高,跟随者会更新自己的状态以匹配领导者。

提案的顺序性:
由于ZXID的单调递增特性,保证了提案(Proposal)的顺序性,即使在网络分区的情况下,也能够维持事务的全局顺序。

通过这些机制,ZAB协议能够在分布式环境中有效地维护集群状态的一致性,即使在网络分区和领导者故障的情况下也能够正确地进行恢复和同步。

分布式一致性算法02:Multi-Paxos

分布式一致性算法02 Multi-Paxos算法

Multi-Paxos算法中引入领导者(Leader)的概念是为了提高效率和减少活锁的可能性。下面通过一个例子来说明领导者在Multi-Paxos中的作用:

假设我们有一个分布式系统,由5个节点组成:P1、P2、P3是提议者(Proposer),A1、A2、A3、A4、A5是接受者(Acceptor),L1、L2是学习者(Learner)。在Multi-Paxos中,一个提议者在任何时候可以被选为领导者。

Multi-Paxos算法的基本过程

领导者选举:
系统启动或当前领导者失败时,通过某种机制(例如,基于节点ID或轮询)选举出一个新的领导者,假设P1被选为领导者。

客户端请求:
客户端向系统发送一个写请求,希望将键值对(key, value)存储到数据库中。

提议阶段:
作为领导者的P1,生成一个提议编号(例如100),并准备将客户端的请求作为提议值,向所有接受者发送提议。

预准备阶段(Pre-Prepare):
P1向所有接受者发送预准备消息,包含提议编号和提议值,同时进入等待状态以收集响应。

准备阶段(Prepare):
接受者收到预准备消息后,如果该提议编号大于它们之前见过的任何提议编号,它们将进入承诺状态,并向P1发送已接受该提议编号的消息,同时承诺不会接受任何编号小于100的提议。

提交阶段(Commit):
一旦P1收到来自多数接受者的已接受消息,它将向所有接受者发送提交消息,指示它们可以提交这个提议。

应用阶段(Apply):
接受者在收到提交消息后,将提议的键值对应用到状态机中,并将结果通知学习者。

学习者学习:
学习者接收到足够的信息后,了解哪个提议被提交,并可以通知客户端操作结果。

领导者的作用
避免冲突:在Multi-Paxos中,如果多个提议者同时提出提议,可能会导致活锁。领导者的存在确保了在任意时刻只有一个提议者(即领导者)可以提出提议,从而避免了这种冲突。
提高效率:由于只有领导者可以提出提议,这减少了准备阶段的通信开销,因为不需要所有提议者都发送提议并等待多数接受者的响应。
快速决策:领导者可以更快地做出决策,因为它不需要与其他提议者协调或竞争获得提议权。
容错性:即使领导者失败,系统也可以通过选举新的领导者来继续运作,保持了系统的高可用性。
通过这个例子,我们可以看到领导者在Multi-Paxos算法中的关键作用,它不仅提高了提议的效率,还有助于避免冲突和提高系统的容错性。

Paxos算法和Multi-Paxos算法在性能上的主要区别体现在以下几个方面:
并发性:Paxos算法在每次达成共识时都需要进行两轮消息传递,这限制了它的并发能力。而Multi-Paxos通过引入领导者(Leader)的概念,允许多个提议者并发地提出提议,从而提高了并发性。
消息复杂度:在Paxos算法中,每个提议都需要两轮的通信(Prepare和Accept阶段),这增加了消息的复杂度。Multi-Paxos通过减少通信轮次,允许领导者在一轮中提出多个提议,从而减少了消息的复杂度。
实时性:由于Multi-Paxos允许并发提议,它在实时性方面通常优于Paxos算法。在Paxos算法中,每个提议都需要等待前一个提议完成后才能开始,这可能导致延迟。
容错性:Paxos算法和Multi-Paxos都具有很好的容错性,但Multi-Paxos由于其并发性,在某些容错情况下可能表现更好,例如在领导者失败时可以快速选举新领导者并继续处理提议。
实现复杂度:Paxos算法的实现相对复杂,而Multi-Paxos虽然在理论上提供了并发性的优势,但其实现也引入了额外的复杂性,如领导者选举和故障恢复机制。
优化和变种:Multi-Paxos有许多优化和变种,如Fast Paxos和EPaxos,它们通过进一步减少通信轮次或利用特定的系统特性来提高性能。

总的来说,Multi-Paxos算法在性能上的主要优势是提高了并发性和减少了消息复杂度,但实现起来可能更为复杂。Paxos算法虽然在每次共识时需要更多的通信轮次,但其算法本身是经过严格证明的,保证了一致性。在实际应用中,许多系统采用Multi-Paxos或其变种,如Raft算法,以提高性能。

分布式一致性算法01:Paxos

分布式一致性算法01 Paxos算法

Paxos算法是一种基于消息传递的分布式一致性算法,用于解决在分布式系统中如何就某个值达成一致的问题。它由Leslie Lamport在1990年提出,是分布式计算领域中非常著名的算法之一。

Paxos算法的核心是确保在分布式系统中,即使在某些节点失败的情况下,系统仍然能够就某个提议达成一致。Paxos算法通常涉及三种角色:提议者(Proposer)、接受者(Acceptor)和学习者(Learner)。

Paxos算法的基本过程

提议阶段(Proposal Phase):
提议者提出一个提议(包含一个值和一个唯一的提议编号)。
提议者向所有的接受者发送询问,询问是否可以将这个提议编号的提议设置为当前的值。

接受阶段(Acceptance Phase):
接受者收到提议后,如果认为这个提议是可以接受的(即没有收到编号更大的提议),则进入接受阶段,并告知提议者同意这个提议。
提议者收到多数接受者的同意后,进入提交阶段。

提交阶段(Commit Phase):
提议者向所有接受者发送提交消息,告知他们可以提交之前同意的提议。
接受者收到提交消息后,将提议的值设置为当前值,并通知学习者。

学习阶段(Learning Phase):
学习者从接受者那里学习当前被接受的值。

举例说明
假设我们有一个分布式系统,包含5个节点:P1、P2、P3是提议者,A1、A2、A3、A4、A5是接受者,L1、L2是学习者。

提议阶段:
P1提出一个提议,编号为1,提议值为V1。
P1向所有接受者发送询问,询问是否可以将编号为1的提议设置为V1。

接受阶段:
A1、A2、A3、A4、A5收到询问,如果它们没有收到编号更大的提议,它们会回复P1同意这个提议。
假设P1收到了来自A1、A2、A3的同意,达到了多数(超过半数),P1进入提交阶段。

提交阶段:
P1向所有接受者发送提交消息,告知他们可以提交编号为1的提议V1。
接受者收到提交消息后,将V1设置为当前值。

学习阶段:
学习者L1、L2从接受者那里学习到V1是当前被接受的值。

Paxos算法的关键点
多数同意:在Paxos算法中,只有当提议者收到超过半数接受者的同意时,提议才可能被提交。
唯一性:Paxos算法保证了在任何给定的一轮中,只有一个提议可以被接受。
容错性:即使在一些节点失败的情况下,Paxos算法也能够工作。

Paxos算法的实现和理解都相当复杂,但它是许多现代分布式系统一致性协议的基础,如Raft算法等。

编译guetzli

#1.下载并安装配置vcpkg
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
bootstrap-vcpkg.bat
vcpkg integrate install
vcpkg install libpng

# 如果用CMake,记录下面的输出路径
CMake projects should use:
"-DCMAKE_TOOLCHAIN_FILE=D:/Build/vcpkg/scripts/buildsystems/vcpkg.cmake"

#2.下载guetzli
git clone https://github.com/google/guetzli.git

#3.用VS2017进行编译

#4.测试效果
整体上压缩比率还是很不错的,但这个压缩速度,还是放弃吧。

Update:
如果是图像压缩的话,现在webp格式还是很不错的,可以直接下载工具:
https://developers.google.com/speed/webp/download
https://chromium.googlesource.com/webm/libwebp/

运煤问题

提问:
一个山西煤老板,在矿区开采了3000吨煤需要运送到市场上去卖,从矿区到市场有1000公里。现在有一列烧煤的火车,该火车最多只能装1000吨煤,但其每一公里需要耗一吨煤作为燃料。请问,怎么运送才能运最多的煤到集市?(火车可以调头,火车最终不需要返回煤矿)

约束:
1、火车一定需要调头
2、调头的原则是,消耗的燃料越少越好
3、当煤不足一车时,仍然要当作一车运输

解答:
1、当煤多于2000吨时,火车一定要运三次,同样的路要跑五次
1000吨煤,走五次,可以走1/5路程
2、当煤多余1000吨时,火车一定要运两次,同样的路要跑三次
1000吨煤,走三次,可以走1/3路程
3、最终剩余1000吨时,直接跑到终点
最后剩余路程为1-1/5-1/3=7/15
跑完后剩余的煤为1000-7/15*1000=1000*8/15=533零1/3吨

以上。

排序算法C Part01

1、冒泡排序

//冒泡排序BubbleSort
//每次扫描,比较和交换相邻元素,保证一次扫描后,最大元素在最后一个
//每次扫描后,扫描队列长度减一
//时间复杂度:O(N^2)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void BubbleSort(int Q[], int N)
{
  int i;
  int swap=1;
  int Temp;
  
  while(swap>0)
    {
      swap=0;
      for(i=1;i<N;i++)
	{
	  if(Q[i-1]>Q[i])
	    {
	      swap++;
	      Temp=Q[i-1];
	      Q[i-1]=Q[i];
	      Q[i]=Temp;
	    }
	}
    }
}

2、选择排序

//选择排序SelectionSort
//从头扫描到尾,每次将最小元素放到第一个
//每次扫描后,队列长度减一
//时间复杂度:O(N^2)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void SelectionSort(int Q[], int N)
{
  int i,j,k;
  int Temp;
  for(i=0;i<N;i++)
    {
      k=i;
      for(j=i+1;j<N;j++)
	{
	  if(Q[j]<Q[i])k=j;
	}  

      Temp=Q[k];
      Q[k]=Q[i];
      Q[i]=Temp;
    }
}

3、插入排序

//插入排序InsertionSort
//起始队列长度为1,每次队列长度加1
//通过元素移动,将队列调整为有序状态
//时间复杂度:O(N^2)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void InsertionSort(int Q[], int N)
{
  int i,j;
  int Temp;
  for(i=1;i<N;i++)
    {
      Temp = Q[i];
      for(j=i;j>0 && Q[j-1]>Temp;j--)
	{
	  Q[j]=Q[j-1];
	}

      Q[j]=Temp;
    }
}

4、希尔排序

//希尔排序ShellSort
//指定一个步长,将需要排序的数字,按序号%步长分组
//对每组进行插入排序,然后减小步长,再次分组,排序直到步长为1结束
//时间复杂度:O(n^2)
//空间复杂度:O(n)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void ShellSort(int Q[], int N)
{
  int i,j;
  int Temp;
  int Step;

  for(Step=N/2;Step>0;Step=Step/2)
    {
      for(i=Step;i<N;i++)
	{
	  Temp = Q[i];
	  for(j=i;j>=Step && Q[j-Step]>Temp;j=j-Step)
	    {
	      Q[j]=Q[j-Step];
	    }
	  Q[j]=Temp;
	}
      //dumpArray(Q,N);
    }
}

5、归并排序

//归并排序MergeSort
//二路归并首先将归并长度定为2,在归并集内进行排序
//然后归并长度×2,将有序集合进行归并
//时间复杂度:O(NlogN)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void Merge(int Q[],int TempArray[], int left, int mid, int right)
{
  int i=left,j=mid,k=left;

  while(i<mid && j<=right)
    {
      if(Q[i]>Q[j])
	{
	  TempArray[k++]=Q[j++];
	}
      else
	{
	  TempArray[k++]=Q[i++];
	}
    }

  while(i<mid)
    {
	  TempArray[k++]=Q[i++];
    }

  while(j<=right)
    {
      	  TempArray[k++]=Q[j++];
    }

  for(k=left;k<=right;k++)
    {
      Q[k]=TempArray[k];
    }

}

void MSort(int Q[],int TempArray[], int left, int right)
{
  int center;

  if(left<right)
    {
      center=(left+right)/2;
      MSort(Q,TempArray,left,center);
      MSort(Q,TempArray,center+1,right);
      Merge(Q,TempArray,left,center+1,right);
    }
}

void MergeSort(int Q[], int N)
{
  int *TempArray = malloc(N*sizeof(int));

  if(TempArray!=NULL)
    {
      MSort(Q,TempArray,0,N-1);
    }
  else
    {
      //Todo: deal with not enough memory
      //RaiseError("MergeSort: not enough memory");
    }
}

八后问题Ruby02

Queen.rb

class Queen
  
  def initialize()
    @v=0
  end
  
  def canSetQueen(lst,s,x,y)
      lstNo = Array.new()
    
      for i in 0..s-1 do
        p = Pt.new(x,i)
        lstNo.push(p)
      end
      
      for j in 0..s-1 do
        p = Pt.new(j,y)
        lstNo.push(p)
      end 
      
      x0=x
      y0=y
      while x0<s and y0<s
        p=Pt.new(x0,y0)
        lstNo.push(p)
        x0=x0+1
        y0=y0+1
      end
      
      x0=x
      y0=y
      while x0>=0 and y0>=0
        p=Pt.new(x0,y0)
        lstNo.push(p)
        x0=x0-1
        y0=y0-1
      end
      
      x0=x
      y0=y
      while x0>=0 and y0<s
        p=Pt.new(x0,y0)
        lstNo.push(p)
        x0=x0-1
        y0=y0+1
      end
      
      x0=x
      y0=y
      while x0<s and y0>=0
        p=Pt.new(x0,y0)
        lstNo.push(p)
        x0=x0+1
        y0=y0-1
      end
      
      while(lstNo.length>0)
        p=lstNo.pop
        if(p.y==lst[p.x])
          return false
        end
      end
      
      return true
  end
    
  def findQueen(lst,s,x) 
      
      if(x>=s)
        @v=@v+1
        puts("]>>solution No."+@v.to_s)
        pp lst
        return
      end
      
      for y in 0..s-1 do
        if(canSetQueen(lst,s,x,y))
          lst.push(y)   
          findQueen(lst,s,x+1)
          lst.pop
        end
      end
       
    end
end

class Pt
  attr_accessor:x
  attr_accessor:y
  def initialize(x,y)
      @x=x
      @y=y
  end
end

test.rb

require "pp"
require "./Queen.rb"

len = 8
lst = Array.new()

q=Queen1.new
q.findQueen(lst,len,0)

puts("end")