<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Astra</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://astrapub.github.io/"/>
  <updated>2020-02-25T13:26:31.576Z</updated>
  <id>http://astrapub.github.io/</id>
  
  <author>
    <name>Astra</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>10. 数据结构与算法入门 | 散列表</title>
    <link href="http://astrapub.github.io/2020/01/15/alg-10/"/>
    <id>http://astrapub.github.io/2020/01/15/alg-10/</id>
    <published>2020-01-15T12:32:09.000Z</published>
    <updated>2020-02-25T13:26:31.576Z</updated>
    
    <content type="html"><![CDATA[<h3 id="散列表的由来"><a href="#散列表的由来" class="headerlink" title="散列表的由来"></a>散列表的由来</h3><ol><li>散列表来源于数组，它借助散列函数对数组这种数据结构进行扩展，利用的是数组支持按照下标随机访问元素的特性。</li><li>需要存储在散列表中的数据我们称为键，将键转化为数组下标的方法称为散列函数，散列函数的计算结果称为散列值。</li><li>将数据存储在散列值对应的数组下标位置。</li></ol><h3 id="如何设计散列函数？"><a href="#如何设计散列函数？" class="headerlink" title="如何设计散列函数？"></a>如何设计散列函数？</h3><p>总结3点设计散列函数的基本要求</p><ol><li>散列函数计算得到的散列值是一个非负整数。</li><li>若key1=key2，则hash(key1)=hash(key2)</li><li>若key≠key2，则hash(key1)≠hash(key2)<br>正是由于第3点要求，所以产生了几乎无法避免的散列冲突问题。</li></ol><h3 id="散列冲突的解放方法？"><a href="#散列冲突的解放方法？" class="headerlink" title="散列冲突的解放方法？"></a>散列冲突的解放方法？</h3><ol><li><p>常用的散列冲突解决方法有2类：开放寻址法（open addressing）和链表法（chaining）</p></li><li><p>开放寻址法</p><p>①核心思想：如果出现散列冲突，就重新探测一个空闲位置，将其插入。</p><p>②线性探测法（Linear Probing）：</p><p><strong>插入数据</strong>：当我们往散列表中插入数据时，如果某个数据经过散列函数之后，存储的位置已经被占用了，我们就从当前位置开始，依次往后查找，看是否有空闲位置，直到找到为止。</p><p><strong>查找数据</strong>：我们通过散列函数求出要查找元素的键值对应的散列值，然后比较数组中下标为散列值的元素和要查找的元素是否相等，若相等，则说明就是我们要查找的元素；否则，就顺序往后依次查找。如果遍历到数组的空闲位置还未找到，就说明要查找的元素并没有在散列表中。</p><p><strong>删除数据</strong>：为了不让查找算法失效，可以将删除的元素特殊标记为deleted，当线性探测查找的时候，遇到标记为deleted的空间，并不是停下来，而是继续往下探测。</p><p><strong>结论</strong>：最坏时间复杂度为O(n)</p><p>③二次探测（Quadratic probing）：线性探测每次探测的步长为1，即在数组中一个一个探测，而二次探测的步长变为原来的平方。</p><p>④双重散列（Double hashing）：使用一组散列函数，直到找到空闲位置为止。</p><p>⑤线性探测法的性能描述：</p><p>用“装载因子”来表示空位多少，公式：散列表装载因子=填入表中的个数/散列表的长度。<br>装载因子越大，说明空闲位置越少，冲突越多，散列表的性能会下降。</p></li></ol><ol start="3"><li><p>链表法（更常用）</p><p><strong>插入数据</strong>：当插入的时候，我们需要通过散列函数计算出对应的散列槽位，将其插入到对应的链表中即可，所以插入的时间复杂度为O(1)。</p><p><strong>查找或删除数据</strong>：当查找、删除一个元素时，通过散列函数计算对应的槽，然后遍历链表查找或删除。对于散列比较均匀的散列函数，链表的节点个数k=n/m，其中n表示散列表中数据的个数，m表示散列表中槽的个数，所以是时间复杂度为O(k)。</p></li></ol><h2 id="如何设计一个工业级的散列函数？"><a href="#如何设计一个工业级的散列函数？" class="headerlink" title="如何设计一个工业级的散列函数？"></a>如何设计一个工业级的散列函数？</h2><p><strong>思路</strong>：</p><p>何为一个工业级的散列表？工业级的散列表应该具有哪些特性？结合学过的知识，我觉的应该有这样的要求：</p><ol><li>支持快速的查询、插入、删除操作；</li><li>内存占用合理，不能浪费过多空间；</li><li>性能稳定，在极端情况下，散列表的性能也不会退化到无法接受的情况。</li></ol><p><strong>方案</strong>：</p><p>如何设计这样一个散列表呢？根据前面讲到的知识，我会从3个方面来考虑设计思路：</p><ol><li>设计一个合适的散列函数；</li><li>定义装载因子阈值，并且设计动态扩容策略；</li><li>选择合适的散列冲突解决方法。</li></ol><h3 id="如何设计散列函数？-1"><a href="#如何设计散列函数？-1" class="headerlink" title="如何设计散列函数？"></a>如何设计散列函数？</h3><ol><li>要尽可能让散列后的值随机且均匀分布，这样会尽可能减少散列冲突，即便冲突之后，分配到每个槽内的数据也比较均匀。</li><li>除此之外，散列函数的设计也不能太复杂，太复杂就会太耗时间，也会影响到散列表的性能。</li><li>常见的散列函数设计方法：直接寻址法、平方取中法、折叠法、随机数法等。</li></ol><h3 id="如何根据装载因子动态扩容？"><a href="#如何根据装载因子动态扩容？" class="headerlink" title="如何根据装载因子动态扩容？"></a>如何根据装载因子动态扩容？</h3><ol><li><p>如何设置装载因子阈值？</p><p>① 可以通过设置装载因子的阈值来控制是扩容还是缩容，支持动态扩容的散列表，插入数据的时间复杂度使用摊还分析法。</p><p>② 装载因子的阈值设置需要权衡时间复杂度和空间复杂度。如何权衡？如果内存空间不紧张，对执行效率要求很高，可以降低装载因子的阈值；相反，如果内存空间紧张，对执行效率要求又不高，可以增加装载因子的阈值。</p></li><li><p>如何避免低效扩容？分批扩容</p><p>① 分批扩容的插入操作：当有新数据要插入时，我们将数据插入新的散列表，并且从老的散列表中拿出一个数据放入新散列表。每次插入都重复上面的过程。这样插入操作就变得很快了。</p><p>② 分批扩容的查询操作：先查新散列表，再查老散列表。</p><p>③ 通过分批扩容的方式，任何情况下，插入一个数据的时间复杂度都是O(1)。</p></li></ol><h3 id="如何选择散列冲突解决方法？"><a href="#如何选择散列冲突解决方法？" class="headerlink" title="如何选择散列冲突解决方法？"></a>如何选择散列冲突解决方法？</h3><ol><li>常见的两种方法：开放寻址法和链表法。</li><li>大部分情况下，链表法更加普适。而且，我们还可以通过将链表法中的链表改造成其他动态查找数据结构，比如红黑树、跳表，来避免散列表时间复杂度退化成O(n)，抵御散列冲突攻击。</li><li>但是，对于小规模数据、装载因子不高的散列表，比较适合用开放寻址法。</li></ol><h2 id="为什么散列表和链表经常放在一起使用？"><a href="#为什么散列表和链表经常放在一起使用？" class="headerlink" title="为什么散列表和链表经常放在一起使用？"></a>为什么散列表和链表经常放在一起使用？</h2><ol><li>散列表的优点：支持高效的数据插入、删除和查找操作</li><li>散列表的缺点：不支持快速顺序遍历散列表中的数据</li><li>如何按照顺序快速遍历散列表的数据？只能将数据转移到数组，然后排序，最后再遍历数据。</li><li>我们知道散列表是动态的数据结构，需要频繁的插入和删除数据，那么每次顺序遍历之前都需要先排序，这势必会造成效率非常低下。</li><li>如何解决上面的问题呢？就是将散列表和链表（或跳表）结合起来使用。</li></ol><h3 id="散列表和链表如何组合起来使用"><a href="#散列表和链表如何组合起来使用" class="headerlink" title="散列表和链表如何组合起来使用"></a>散列表和链表如何组合起来使用</h3><ol><li><p>LRU（Least Recently Used）缓存淘汰算法</p><p>1.1. LRU缓存淘汰算法主要操作有哪些？主要包含3个操作：</p><p>① 往缓存中添加一个数据；<br>② 从缓存中删除一个数据；<br>③ 在缓存中查找一个数据；<br>④ 总结：上面3个都涉及到查找。</p><p>1.2. 如何用链表实现LRU缓存淘汰算法？</p><p>① 需要维护一个按照访问时间从大到小的有序排列的链表结构。<br>② 缓冲空间有限，当空间不足需要淘汰一个数据时直接删除链表头部的节点。<br>③ 当要缓存某个数据时，先在链表中查找这个数据。若未找到，则直接将数据放到链表的尾部。若找到，就把它移动到链表尾部。<br>④ 前面说了，LRU缓存的3个主要操作都涉及到查找，若单纯由链表实现，查找的时间复杂度很高为O(n)。若将链表和散列表结合使用，查找的时间复杂度会降低到O(1)。</p><p>1.3. 如何使用散列表和链表实现LRU缓存淘汰算法？</p><p>① 使用双向链表存储数据，链表中每个节点存储数据（data）、前驱指针（prev）、后继指针（next）和hnext指针（解决散列冲突的链表指针）。<br>② 散列表通过链表法解决散列冲突，所以每个节点都会在两条链中。一条链是双向链表，另一条链是散列表中的拉链。前驱和后继指针是为了将节点串在双向链表中，hnext指针是为了将节点串在散列表的拉链中。<br>③ LRU缓存淘汰算法的3个主要操作如何做到时间复杂度为O(1)呢？<br>首先，我们明确一点就是链表本身插入和删除一个节点的时间复杂度为O(1)，因为只需更改几个指针指向即可。</p></li></ol><p>接着，来分析查找操作的时间复杂度。当要查找一个数据时，通过散列表可实现在O(1)时间复杂度找到该数据，再加上前面说的插入或删除的时间复杂度是O(1)，所以我们总操作的时间复杂度就是O(1)。</p><ol start="2"><li><p>Redis有序集合</p><p>2.1.什么是有序集合？</p><p>① 在有序集合中，每个成员对象有2个重要的属性，即key（键值）和score（分值）。<br>② 不仅会通过score来查找数据，还会通过key来查找数据。</p><p>2.2. 有序集合的操作有哪些？</p><p>举个例子，比如用户积分排行榜有这样一个功能：可以通过用户ID来查找积分信息，也可以通过积分区间来查找用户ID。这里用户ID就是key，积分就是score。所以，有序集合的操作如下：</p><p>① 添加一个对象；<br>② 根据键值删除一个对象；<br>③ 根据键值查找一个成员对象；<br>④ 根据分值区间查找数据，比如查找积分在[100.356]之间的成员对象；<br>⑤ 按照分值从小到大排序成员变量。</p><p>这时可以按照分值将成员对象组织成跳表结构，按照键值构建一个散列表。那么上面的所有操作都非常高效。</p></li><li><p>Java LinkedHashMap<br>和LRU缓存淘汰策略实现一模一样。支持按照插入顺序遍历数据，也支持按照访问顺序遍历数据。</p></li></ol><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><ol><li><p>Word文档中单词拼写检查功能是如何实现的？<br> <strong>答</strong>：<br>字符串占用内存大小为8字节，20万单词占用内存大小不超过20MB，所以用散列表存储20万英文词典单词，然后对每个编辑进文档的单词进行查找，若未找到，则提示拼写错误。</p></li><li><p>假设我们有10万条URL访问日志，如何按照访问次数给URL排序？<br> <strong>答</strong>：<br>字符串占用内存大小为8字节，10万条URL访问日志占用内存不超过10MB，通过散列表统计url访问次数，然后用TreeMap存储散列表的元素值（作为key）和数组下标值（作为value）</p></li><li><p>有两个字符串数组，每个数组大约有10万条字符串，如何快速找出两个数组中相同的字符串？<br> <strong>答</strong>：<br>分别将2个数组的字符串通过散列函数映射到散列表，散列表中的元素值为次数。注意，先存储的数组中的相同元素值不进行次数累加。最后，统计散列表中元素值大于等于2的散列值对应的字符串就是两个数组中相同的字符串。</p></li><li><p>上面所讲的几个散列表和链表组合的例子里，我们都是使用双向链表。如果把双向链表改成单链表，还能否正常工作？为什么呢？</p><p><strong>答</strong>：</p><p>在删除一个元素时，虽然能 O(1) 的找到目标结点，但是要删除该结点需要拿到前一个结点的指针，遍历到前一个结点复杂度会变为 O(N），所以用双链表实现比较合适。</p></li><li><p>假设猎聘网有10万名猎头，每个猎头可以通过做任务（比如发布职位）来积累积分，然后通过积分来下载简历。假设你是猎聘网的一名工程师，如何在内存中存储这10万个猎头的ID和积分信息，让它能够支持这样几个操作：</p><p>1）根据猎头ID查收查找、删除、更新这个猎头的积分信息；<br>2）查找积分在某个区间的猎头ID列表；<br>3）查找按照积分从小到大排名在第x位到第y位之间的猎头ID列表。</p><p><strong>答</strong>：<br>以积分排序构建一个跳表，再以猎头 ID 构建一个散列表。<br>1）ID 在散列表中所以可以 O(1) 查找到这个猎头；<br>2）积分以跳表存储，跳表支持区间查询；<br>3）这点根据目前学习的知识暂时无法实现，老师文中也提到了。</p></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;散列表的由来&quot;&gt;&lt;a href=&quot;#散列表的由来&quot; class=&quot;headerlink&quot; title=&quot;散列表的由来&quot;&gt;&lt;/a&gt;散列表的由来&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;散列表来源于数组，它借助散列函数对数组这种数据结构进行扩展，利用的是数组支持按照下标随机访问元素
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="散列表" scheme="http://astrapub.github.io/tags/%E6%95%A3%E5%88%97%E8%A1%A8/"/>
    
  </entry>
  
  <entry>
    <title>09. 数据结构与算法入门 | 跳表</title>
    <link href="http://astrapub.github.io/2020/01/15/alg-09/"/>
    <id>http://astrapub.github.io/2020/01/15/alg-09/</id>
    <published>2020-01-15T12:32:09.000Z</published>
    <updated>2020-02-25T13:15:56.450Z</updated>
    
    <content type="html"><![CDATA[<h3 id="什么是跳表？"><a href="#什么是跳表？" class="headerlink" title="什么是跳表？"></a>什么是跳表？</h3><p>为一个值有序的链表建立多级索引，比如每2个节点提取一个节点到上一级，我们把抽出来的那一级叫做索引或索引层。如下图所示，其中down表示down指针，指向下一级节点。以此类推，对于节点数为n的链表，大约可以建立log2n-1级索引。像这种为链表建立多级索引的数据结构就称为跳表。</p><h3 id="跳表的时间复杂度？"><a href="#跳表的时间复杂度？" class="headerlink" title="跳表的时间复杂度？"></a>跳表的时间复杂度？</h3><ol><li><p>计算跳表的高度</p><p>如果链表有n个节点，每2个节点抽取抽出一个节点作为上一级索引的节点，那第1级索引的节点个数大约是n/2，第2级索引的节点个数大约是n/4，依次类推，第k级索引的节点个数就是n/(2^k)。假设索引有h级别，最高级的索引有2个节点，则有n/(2^h)=2，得出h=log2n-1，包含原始链表这一层，整个跳表的高度就是log2n。</p></li><li><p>计算跳表的时间复杂度</p><p>假设我们在跳表中查询某个数据的时候，如果每一层都遍历m个节点，那在跳表中查询一个数据的时间复杂度就是O(m*logn)。那这个m是多少呢？如下图所示，假设我们要查找的数据是x，在第k级索引中，我们遍历到y节点之后，发现x大于y，小于后面的节点z，所以我们通过y的down指针，从第k级下降到第k-1级索引。在第k-1级索引中，y和z之间只有3个节点（包含y和z），所以，我们在k-1级索引中最多只需要遍历3个节点，以此类推，每一级索引都最多只需要遍历3个节点。所以m=3。因此在跳表中查询某个数据的时间复杂度就是O(logn)。</p></li></ol><h3 id="跳表的空间复杂度及如何优化？"><a href="#跳表的空间复杂度及如何优化？" class="headerlink" title="跳表的空间复杂度及如何优化？"></a>跳表的空间复杂度及如何优化？</h3><ol><li><p>计算索引的节点总数</p><p>如果链表有n个节点，每2个节点抽取抽出一个节点作为上一级索引的节点，那每一级索引的节点数分别为：n/2，n/4，n/8，…，8，4，2，等比数列求和n-1，所以跳表的空间复杂度为O(n)。</p></li><li><p>如何优化时间复杂度</p><p>如果链表有n个节点，每3或5个节点抽取抽出一个节点作为上一级索引的节点，那每一级索引的节点数分别为（以3为例）：n/3，n/9，n/27，…，27，9，3，1，等比数列求和n/2，所以跳表的空间复杂度为O(n)，和每2个节点抽取一次相比，时间复杂度要低不少呢。</p></li></ol><h3 id="高效的动态插入和删除？"><a href="#高效的动态插入和删除？" class="headerlink" title="高效的动态插入和删除？"></a>高效的动态插入和删除？</h3><p>跳表本质上就是链表，所以仅插作，插入和删除操时间复杂度就为O(1)，但在实际情况中，要插入或删除某个节点，需要先查找到指定位置，而这个查找操作比较费时，但在跳表中这个查找操作的时间复杂度是O(logn)，所以，跳表的插入和删除操作的是时间复杂度也是O(logn)。</p><h3 id="跳表索引动态更新？"><a href="#跳表索引动态更新？" class="headerlink" title="跳表索引动态更新？"></a>跳表索引动态更新？</h3><p>当往跳表中插入数据的时候，可以选择同时将这个数据插入到部分索引层中，那么如何选择这个索引层呢？可以通过随机函数来决定将这个节点插入到哪几级索引中，比如随机函数生成了值K，那就可以把这个节点添加到第1级到第K级索引中。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;什么是跳表？&quot;&gt;&lt;a href=&quot;#什么是跳表？&quot; class=&quot;headerlink&quot; title=&quot;什么是跳表？&quot;&gt;&lt;/a&gt;什么是跳表？&lt;/h3&gt;&lt;p&gt;为一个值有序的链表建立多级索引，比如每2个节点提取一个节点到上一级，我们把抽出来的那一级叫做索引或索引层。如
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="跳表" scheme="http://astrapub.github.io/tags/%E8%B7%B3%E8%A1%A8/"/>
    
  </entry>
  
  <entry>
    <title>08. 数据结构与算法入门 | 二分查找</title>
    <link href="http://astrapub.github.io/2020/01/15/alg-08/"/>
    <id>http://astrapub.github.io/2020/01/15/alg-08/</id>
    <published>2020-01-15T12:32:08.000Z</published>
    <updated>2020-02-25T13:12:08.664Z</updated>
    
    <content type="html"><![CDATA[<h3 id="什么是二分查找？"><a href="#什么是二分查找？" class="headerlink" title="什么是二分查找？"></a>什么是二分查找？</h3><p>二分查找针对的是一个有序的数据集合，每次通过跟区间中间的元素对比，将待查找的区间缩小为之前的一半，直到找到要查找的元素，或者区间缩小为0。</p><h3 id="时间复杂度分析？"><a href="#时间复杂度分析？" class="headerlink" title="时间复杂度分析？"></a>时间复杂度分析？</h3><ol><li><p>时间复杂度</p></li><li><p>假设数据大小是n，每次查找后数据都会缩小为原来的一半，最坏的情况下，直到查找区间被缩小为空，才停止。所以，每次查找的数据大小是：n，n/2，n/4，…，n/(2^k)，…，这是一个等比数列。当n/(2^k)=1时，k的值就是总共缩小的次数，也是查找的总次数。而每次缩小操作只涉及两个数据的大小比较，所以，经过k次区间缩小操作，时间复杂度就是O(k)。通过n/(2^k)=1，可求得k=log2n，所以时间复杂度是O(logn)。</p></li><li><p>认识O(logn)</p><p>① 这是一种极其高效的时间复杂度，有时甚至比O(1)的算法还要高效。为什么？<br>② 因为logn是一个非常“恐怖“的数量级，即便n非常大，对应的logn也很小。比如n等于2的32次方，也就是42亿，而logn才32。<br>③ 由此可见，O(logn)有时就是比O(1000)，O(10000)快很多。</p></li></ol><h3 id="如何实现二分查找？"><a href="#如何实现二分查找？" class="headerlink" title="如何实现二分查找？"></a>如何实现二分查找？</h3><ol><li><p>循环实现</p><p>代码实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">binarySearch1</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> val)</span></span>&#123;</span><br><span class="line">  <span class="keyword">int</span> start = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">int</span> end = a.length - <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">while</span>(start &lt;= end)&#123;</span><br><span class="line">    <span class="keyword">int</span> mid = start + (end - start) / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">if</span>(a[mid] &gt; val) end = mid - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span>(a[mid] &lt; val) start = mid + <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> mid;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意事项：</strong></p><p>① 循环退出条件是：start&lt;=end，而不是start&lt;end。</p><p>② mid的取值，使用mid=start + (end - start) / 2，而不用mid=(start + end)/2，因为如果start和end比较大的话，求和可能会发生int类型的值超出最大范围。为了把性能优化到极致，可以将除以2转换成位运算，即start + ((end - start) &gt;&gt; 1)，因为相比除法运算来说，计算机处理位运算要快得多。</p><p>③ start和end的更新：start = mid - 1，end = mid + 1，若直接写成start = mid，end=mid，就可能会发生死循环。</p></li><li><p>递归实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">binarySearch</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> val)</span></span>&#123;</span><br><span class="line">  <span class="keyword">return</span> bSear(a, val, <span class="number">0</span>, a.length-<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">bSear</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> val, <span class="keyword">int</span> start, <span class="keyword">int</span> end)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span>(start &gt; end) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">  <span class="keyword">int</span> mid = start + (end - start) / <span class="number">2</span>;</span><br><span class="line">  <span class="keyword">if</span>(a[mid] == val) <span class="keyword">return</span> mid;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span>(a[mid] &gt; val) end = mid - <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">else</span> start = mid + <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">return</span> bSear(a, val, start, end);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="使用条件（应用场景的局限性）"><a href="#使用条件（应用场景的局限性）" class="headerlink" title="使用条件（应用场景的局限性）"></a>使用条件（应用场景的局限性）</h3><ol><li>二分查找依赖的是顺序表结构，即数组。</li><li>二分查找针对的是有序数据，因此只能用在插入、删除操作不频繁，一次排序多次查找的场景中。</li><li>数据量太小不适合二分查找，与直接遍历相比效率提升不明显。但有一个例外，就是数据之间的比较操作非常费时，比如数组中存储的都是长度超过300的字符串，那这是还是尽量减少比较操作使用二分查找吧。</li><li>数据量太大也不是适合用二分查找，因为数组需要连续的空间，若数据量太大，往往找不到存储如此大规模数据的连续内存空间。</li></ol><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><ol><li><p>如何在1000万个整数中快速查找某个整数？</p><p>①1000万个整数占用存储空间为40MB，占用空间不大，所以可以全部加载到内存中进行处理；</p><p>②用一个1000万个元素的数组存储，然后使用快排进行升序排序，时间复杂度为O(nlogn)；</p><p>③在有序数组中使用二分查找算法进行查找，时间复杂度为O(logn)</p></li><li><p>如何编程实现“求一个数的平方根”？要求精确到小数点后6位？</p></li></ol><h3 id="四种常见的二分查找变形问题"><a href="#四种常见的二分查找变形问题" class="headerlink" title="四种常见的二分查找变形问题"></a>四种常见的二分查找变形问题</h3><ol><li>查找第一个值等于给定值的元素</li><li>查找最后一个值等于给定值的元素</li><li>查找第一个大于等于给定值的元素</li><li>查找最后一个小于等于给定值的元素</li></ol><h3 id="适用性分析"><a href="#适用性分析" class="headerlink" title="适用性分析"></a>适用性分析</h3><ol><li>凡事能用二分查找解决的，绝大部分我们更倾向于用散列表或者二叉查找树，即便二分查找在内存上更节省，但是毕竟内存如此紧缺的情况并不多。</li><li>求“值等于给定值”的二分查找确实不怎么用到，二分查找更适合用在”近似“查找问题上。比如上面讲几种变体。</li></ol><h3 id="思考-1"><a href="#思考-1" class="headerlink" title="思考"></a>思考</h3><ol><li><p>如何快速定位出一个IP地址的归属地？</p><p>[202.102.133.0, 202.102.133.255] 山东东营市<br>[202.102.135.0, 202.102.136.255] 山东烟台<br>[202.102.156.34, 202.102.157.255] 山东青岛<br>[202.102.48.0, 202.102.48.255] 江苏宿迁<br>[202.102.49.15, 202.102.51.251] 江苏泰州<br>[202.102.56.0, 202.102.56.255] 江苏连云港</p><p>假设我们有 12 万条这样的 IP 区间与归属地的对应关系，如何快速定位出一个IP地址的归属地呢？</p></li><li><p>如果有一个有序循环数组，比如4，5，6，1，2，3。针对这种情况，如何实现一个求“值等于给定值”的二分查找算法？</p><p>有三种方法查找循环有序数组</p><p><strong>第一种：</strong></p><ol><li>找到分界下标，分成两个有序数组</li><li>判断目标值在哪个有序数据范围内，做二分查找</li></ol><p><strong>第二种：</strong></p><ol><li><p>找到最大值的下标 x;</p></li><li><p>所有元素下标 +x 偏移，超过数组范围值的取模;</p></li><li><p>利用偏移后的下标做二分查找；</p></li><li><p>如果找到目标下标，再作 -x 偏移，就是目标值实际下标。</p><p>两种情况最高时耗都在查找分界点上，所以时间复杂度是 O(N）。</p><p>复杂度有点高，能否优化呢？</p></li></ol><p><strong>第三种：</strong></p><p>我们发现循环数组存在一个性质：以数组中间点为分区，会将数组分成一个有序数组和一个循环有序数组。<br>如果首元素小于 mid，说明前半部分是有序的，后半部分是循环有序数组；<br>如果首元素大于 mid，说明后半部分是有序的，前半部分是循环有序的数组；<br>如果目标元素在有序数组范围中，使用二分查找；<br>如果目标元素在循环有序数组中，设定数组边界后，使用以上方法继续查找。</p><p>时间复杂度为 O(logN)。</p><p>用JavaScript实现的最基本的思考题：<br>array是传入的数组，value是要查找的值<br>思路是通过对比low,high的值来判断value所在的区间，不用多循环一遍找偏移量了~</p></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">search</span>(<span class="params">array,value</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">let</span> low = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> high = array.length - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span>(low &lt;= high)&#123;</span><br><span class="line">    <span class="keyword">let</span> mid = low + ((high - low) &gt;&gt; <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span>(value == array[low]) <span class="keyword">return</span> low;</span><br><span class="line">    <span class="keyword">if</span>(value == array[high]) <span class="keyword">return</span> high;</span><br><span class="line">    <span class="keyword">if</span>(value == array[mid]) <span class="keyword">return</span> mid;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(value &gt; array[mid] &amp;&amp; value &gt; array[high] &amp;&amp; array[mid] &lt; array[low])&#123;</span><br><span class="line">      high = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;<span class="keyword">else</span> <span class="keyword">if</span>(value &lt; array[mid] &amp;&amp; value &lt; array[low] &amp;&amp; array[mid] &lt; array[low])&#123;</span><br><span class="line">      high = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;<span class="keyword">else</span> <span class="keyword">if</span>(value &lt; array[mid] &amp;&amp; value &gt; array[low])&#123;</span><br><span class="line">      high = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">      low = mid + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;什么是二分查找？&quot;&gt;&lt;a href=&quot;#什么是二分查找？&quot; class=&quot;headerlink&quot; title=&quot;什么是二分查找？&quot;&gt;&lt;/a&gt;什么是二分查找？&lt;/h3&gt;&lt;p&gt;二分查找针对的是一个有序的数据集合，每次通过跟区间中间的元素对比，将待查找的区间缩小为之前的
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="二分查找" scheme="http://astrapub.github.io/tags/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/"/>
    
  </entry>
  
  <entry>
    <title>07. 数据结构与算法入门 | 如何实现通用、高性能的排序函数？</title>
    <link href="http://astrapub.github.io/2020/01/15/alg-07/"/>
    <id>http://astrapub.github.io/2020/01/15/alg-07/</id>
    <published>2020-01-15T12:31:20.000Z</published>
    <updated>2020-02-25T12:54:06.238Z</updated>
    
    <content type="html"><![CDATA[<h3 id="如何选择合适的排序算法？"><a href="#如何选择合适的排序算法？" class="headerlink" title="如何选择合适的排序算法？"></a>如何选择合适的排序算法？</h3><ol><li>排序算法一览表</li></ol><table><thead><tr><th>排序算法</th><th>时间复杂度</th><th>是稳定排序？</th><th>是原地排序？</th></tr></thead><tbody><tr><td>冒泡排序</td><td>O(n^2)</td><td>是</td><td>是</td></tr><tr><td>插入排序</td><td>O(n^2)</td><td>是</td><td>是</td></tr><tr><td>选择排序</td><td>O(n^2)</td><td>否</td><td>是</td></tr><tr><td>快速排序</td><td>O(nlogn)</td><td>否</td><td>是</td></tr><tr><td>归并排序</td><td>O(nlogn)</td><td>是</td><td>否</td></tr><tr><td>桶排序</td><td>O(n)</td><td>是</td><td>否</td></tr><tr><td>计数排序</td><td>O(n+k)，k是数据范围</td><td>是</td><td>否</td></tr><tr><td>基数排序</td><td>O(dn)，d是纬度</td><td>是</td><td>否</td></tr></tbody></table><ol start="2"><li>为什选择快速排序？<br>线性排序时间复杂度很低但使用场景特殊，如果要写一个通用排序函数，不能选择线性排序。<br>为了兼顾任意规模数据的排序，一般会首选时间复杂度为O(nlogn)的排序算法来实现排序函数。<br>同为O(nlogn)的快排和归并排序相比，归并排序不是原地排序算法，所以最优的选择是快排。</li></ol><h3 id="如何优化快速排序？"><a href="#如何优化快速排序？" class="headerlink" title="如何优化快速排序？"></a>如何优化快速排序？</h3><p>导致快排时间复杂度降为O(n)的原因是分区点选择不合理，最理想的分区点是：被分区点分开的两个分区中，数据的数量差不多。如何优化分区点的选择？有2种常用方法，如下：</p><ol><li>三数取中法<br>从区间的首、中、尾分别取一个数，然后比较大小，取中间值作为分区点。<br>如果要排序的数组比较大，那“三数取中”可能就不够用了，可能要“5数取中”或者“10数取中”。</li><li>随机法：每次从要排序的区间中，随机选择一个元素作为分区点。</li><li>警惕快排的递归发生堆栈溢出，有2中解决方法，如下：<br>限制递归深度，一旦递归超过了设置的阈值就停止递归。<br>在堆上模拟实现一个函数调用栈，手动模拟递归压栈、出栈过程，这样就没有系统栈大小的限制。</li></ol><h3 id="通用排序函数实现技巧"><a href="#通用排序函数实现技巧" class="headerlink" title="通用排序函数实现技巧"></a>通用排序函数实现技巧</h3><ol><li>数据量不大时，可以采取用时间换空间的思路</li><li>数据量大时，优化快排分区点的选择</li><li>防止堆栈溢出，可以选择在堆上手动模拟调用栈解决</li><li>在排序区间中，当元素个数小于某个常数是，可以考虑使用O(n^2)级别的插入排序</li><li>用哨兵简化代码，每次排序都减少一次判断，尽可能把性能优化到极致</li></ol><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><ol><li>Java中的排序函数都是用什么排序算法实现的？有有哪些技巧？</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;如何选择合适的排序算法？&quot;&gt;&lt;a href=&quot;#如何选择合适的排序算法？&quot; class=&quot;headerlink&quot; title=&quot;如何选择合适的排序算法？&quot;&gt;&lt;/a&gt;如何选择合适的排序算法？&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;排序算法一览表&lt;/li&gt;
&lt;/ol&gt;
&lt;tabl
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="排序" scheme="http://astrapub.github.io/tags/%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>06. 数据结构与算法入门 | 线性排序</title>
    <link href="http://astrapub.github.io/2020/01/15/alg-06/"/>
    <id>http://astrapub.github.io/2020/01/15/alg-06/</id>
    <published>2020-01-15T12:31:19.000Z</published>
    <updated>2020-02-25T13:30:48.659Z</updated>
    
    <content type="html"><![CDATA[<h3 id="线性排序算法介绍"><a href="#线性排序算法介绍" class="headerlink" title="线性排序算法介绍"></a>线性排序算法介绍</h3><ol><li>线性排序算法包括桶排序、计数排序、基数排序。</li><li>线性排序算法的时间复杂度为O(n)。</li><li>此3种排序算法都不涉及元素之间的比较操作，是非基于比较的排序算法。</li><li>对排序数据的要求很苛刻，重点掌握此3种排序算法的适用场景。</li></ol><h3 id="桶排序（Bucket-sort）"><a href="#桶排序（Bucket-sort）" class="headerlink" title="桶排序（Bucket sort）"></a>桶排序（Bucket sort）</h3><ol><li>算法原理：<br>1）将要排序的数据分到几个有序的桶里，每个桶里的数据再单独进行快速排序。<br>2）桶内排完序之后，再把每个桶里的数据按照顺序依次取出，组成的序列就是有序的了。</li><li>使用条件<br>1）要排序的数据需要很容易就能划分成m个桶，并且桶与桶之间有着天然的大小顺序。<br>2）数据在各个桶之间分布是均匀的。</li><li>适用场景<br>1）桶排序比较适合用在外部排序中。<br>2）外部排序就是数据存储在外部磁盘且数据量大，但内存有限无法将整个数据全部加载到内存中。</li><li>应用案例<br>1）需求描述：<br>有10GB的订单数据，需按订单金额（假设金额都是正整数）进行排序<br>但内存有限，仅几百MB<br>2）解决思路：<br>扫描一遍文件，看订单金额所处数据范围，比如1元-10万元，那么就分100个桶。<br>第一个桶存储金额1-1000元之内的订单，第二个桶存1001-2000元之内的订单，依次类推。<br>每个桶对应一个文件，并按照金额范围的大小顺序编号命名（00，01，02，…，99）。<br>将100个小文件依次放入内存并用快排排序。<br>所有文件排好序后，只需按照文件编号从小到大依次读取每个小文件并写到大文件中即可。<br>3）注意点：若单个文件无法全部载入内存，则针对该文件继续按照前面的思路进行处理即可。</li></ol><h3 id="计数排序（Counting-sort）"><a href="#计数排序（Counting-sort）" class="headerlink" title="计数排序（Counting sort）"></a>计数排序（Counting sort）</h3><ol><li><p>算法原理<br>1）计数其实就是桶排序的一种特殊情况。<br>2）当要排序的n个数据所处范围并不大时，比如最大值为k，则分成k个桶<br>3）每个桶内的数据值都是相同的，就省掉了桶内排序的时间。</p></li><li><p>图解</p><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-07.gif" alt="计数排序"></p></li><li><p><strong>案例分析：</strong><br>假设只有8个考生分数在0-5分之间，成绩存于数组A[8] = [2，5，3，0，2，3，0，3]。<br>使用大小为6的数组C[6]表示桶，下标对应分数，即0，1，2，3，4，5。<br>C[6]存储的是考生人数，只需遍历一边考生分数，就可以得到C[6] = [2，0，2，3，0，1]。<br>对C[6]数组顺序求和则C[6]=[2，2，4，7，7，8]，c[k]存储的是小于等于分数k的考生个数。<br>数组R[8] = [0，0，2，2，3，3，3，5]存储考生名次。那么如何得到R[8]的呢？<br>从后到前依次扫描数组A，比如扫描到3时，可以从数组C中取出下标为3的值7，也就是说，到目前为止，包括自己在内，分数小于等于3的考生有7个，也就是说3是数组R的第7个元素（也就是数组R中下标为6的位置）。当3放入数组R后，小于等于3的元素就剩下6个了，相应的C[3]要减1变成6。<br>以此类推，当扫描到第二个分数为3的考生时，就会把它放入数组R中第6个元素的位置（也就是下标为5的位置）。当扫描完数组A后，数组R内的数据就是按照分数从小到大排列的了。</p></li><li><p>使用条件<br>1）只能用在数据范围不大的场景中，若数据范围k比要排序的数据n大很多，就不适合用计数排序；<br>2）计数排序只能给非负整数排序，其他类型需要在不改变相对大小情况下，转换为非负整数；<br>3）比如如果考试成绩精确到小数后一位，就需要将所有分数乘以10，转换为整数。</p></li></ol><h3 id="基数排序（Radix-sort）"><a href="#基数排序（Radix-sort）" class="headerlink" title="基数排序（Radix sort）"></a>基数排序（Radix sort）</h3><ol><li><p>算法原理（以排序10万个手机号为例来说明）<br>1）比较两个手机号码a，b的大小，如果在前面几位中a已经比b大了，那后面几位就不用看了。<br>2）借助稳定排序算法的思想，可以先按照最后一位来排序手机号码，然后再按照倒数第二位来重新排序，以此类推，最后按照第一个位重新排序。<br>3）经过11次排序后，手机号码就变为有序的了。<br>4）每次排序有序数据范围较小，可以使用桶排序或计数排序来完成。</p></li><li><p>图解</p><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-08.gif" alt="计数排序"></p></li><li><p>使用条件<br>1）要求数据可以分割独立的“位”来比较；<br>2）位之间由递进关系，如果a数据的高位比b数据大，那么剩下的地位就不用比较了；<br>3）每一位的数据范围不能太大，要可以用线性排序，否则基数排序的时间复杂度无法做到O(n)。</p></li></ol><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><ol><li>如何根据年龄给100万用户数据排序？</li><li>对D，a，F，B，c，A，z这几个字符串进行排序，要求将其中所有小写字母都排在大写字母前面，但是小写字母内部和大写字母内部不要求有序。比如经过排序后为a，c，z，D，F，B，A，这个如何实现呢？如果字符串中处理大小写，还有数字，将数字放在最前面，又该如何解决呢？</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;线性排序算法介绍&quot;&gt;&lt;a href=&quot;#线性排序算法介绍&quot; class=&quot;headerlink&quot; title=&quot;线性排序算法介绍&quot;&gt;&lt;/a&gt;线性排序算法介绍&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;线性排序算法包括桶排序、计数排序、基数排序。&lt;/li&gt;
&lt;li&gt;线性排序算法的时
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="排序" scheme="http://astrapub.github.io/tags/%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>05. 数据结构与算法入门 | 排序算法(下)</title>
    <link href="http://astrapub.github.io/2020/01/14/alg-05/"/>
    <id>http://astrapub.github.io/2020/01/14/alg-05/</id>
    <published>2020-01-14T14:49:05.000Z</published>
    <updated>2020-02-25T12:49:26.429Z</updated>
    
    <content type="html"><![CDATA[<h3 id="分治思想"><a href="#分治思想" class="headerlink" title="分治思想"></a>分治思想</h3><p>归并排序和快速排序都使用了分治思想。</p><ol><li>分治思想：分治，顾明思意，就是分而治之，将一个大问题分解成小的子问题来解决，小的子问题解决了，大问题也就解决了。</li><li>分治与递归的区别：分治算法一般都用递归来实现的。分治是一种解决问题的处理思想，递归是一种编程技巧。</li></ol><p>分治思想也会在以后的文章继续介绍。</p><h3 id="归并排序（Merge-Sort）"><a href="#归并排序（Merge-Sort）" class="headerlink" title="归并排序（Merge Sort）"></a>归并排序（Merge Sort）</h3><p>先把数组从中间分成前后两部分，然后对前后两部分分别进行排序，再将排序好的两部分合并到一起，这样整个数组就有序了。这就是归并排序的核心思想。如何用递归实现归并排序呢？写递归代码的技巧就是分写得出递推公式，然后找到终止条件，最后将递推公式翻译成递归代码。递推公式怎么写？如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">递推公式：merge_sort(p…r) &#x3D; merge(merge_sort(p…q), merge_sort(q+1…r))</span><br><span class="line">终止条件：p &gt;&#x3D; r 不用再继续分解</span><br></pre></td></tr></table></figure><h4 id="图解："><a href="#图解：" class="headerlink" title="图解："></a>图解：</h4><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-04.gif" alt="归并排序"></p><h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 归并排序算法, A 是数组，n 表示数组大小</span></span><br><span class="line">merge_sort(A, n) &#123;</span><br><span class="line">  merge_sort_c(A, <span class="number">0</span>, n-<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 递归调用函数</span></span><br><span class="line">merge_sort_c(A, p, r) &#123;</span><br><span class="line">  <span class="comment">// 递归终止条件</span></span><br><span class="line">  <span class="keyword">if</span> p &gt;= r then <span class="keyword">return</span></span><br><span class="line">  <span class="comment">// 取 p 到 r 之间的中间位置 q</span></span><br><span class="line">  q = (p+r) / <span class="number">2</span></span><br><span class="line">  <span class="comment">// 分治递归</span></span><br><span class="line">  merge_sort_c(A, p, q)</span><br><span class="line">  merge_sort_c(A, q+<span class="number">1</span>, r)</span><br><span class="line">  <span class="comment">// 将 A[p...q] 和 A[q+1...r] 合并为 A[p...r]</span></span><br><span class="line">  merge(A[p...r], A[p...q], A[q+<span class="number">1</span>...r])</span><br><span class="line">&#125;</span><br><span class="line">merge(A[p...r], A[p...q], A[q+<span class="number">1</span>...r]) &#123;</span><br><span class="line">  <span class="keyword">var</span> i := p，j := q+<span class="number">1</span>，k := <span class="number">0</span> <span class="comment">// 初始化变量 i, j, k</span></span><br><span class="line">  <span class="keyword">var</span> tmp := <span class="keyword">new</span> array[<span class="number">0</span>...r-p] <span class="comment">// 申请一个大小跟 A[p...r] 一样的临时数组</span></span><br><span class="line">  <span class="keyword">while</span> i&lt;=q AND j&lt;=r <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> A[i] &lt;= A[j] &#123;</span><br><span class="line">      tmp[k++] = A[i++] <span class="comment">// i++ 等于 i:=i+1</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      tmp[k++] = A[j++]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 判断哪个子数组中有剩余的数据</span></span><br><span class="line">  <span class="keyword">var</span> start := i，end := q</span><br><span class="line">  <span class="keyword">if</span> j&lt;=r then start := j, end:=r</span><br><span class="line">  <span class="comment">// 将剩余的数据拷贝到临时数组 tmp</span></span><br><span class="line">  <span class="keyword">while</span> start &lt;= end <span class="keyword">do</span> &#123;</span><br><span class="line">    tmp[k++] = A[start++]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 将 tmp 中的数组拷贝回 A[p...r]</span></span><br><span class="line">  <span class="keyword">for</span> i:=<span class="number">0</span> to r-p <span class="keyword">do</span> &#123;</span><br><span class="line">    A[p+i] = tmp[i]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注：merge()合并函数如果借助哨兵代码就会简洁很多</p><h4 id="性能分析"><a href="#性能分析" class="headerlink" title="性能分析"></a>性能分析</h4><p><strong>算法稳定性：</strong></p><p>归并排序稳不稳定关键要看合并函数，也就是两个子数组合并成一个有序数组的那部分代码。在合并的过程中，如果 A[p…q] 和 A[q+1…r] 之间有值相同的元素，那我们就可以像伪代码中那样，先把 A[p…q] 中的元素放入tmp数组，这样 就保证了值相同的元素，在合并前后的先后顺序不变。所以，归并排序是一种稳定排序算法。</p><p><strong>时间复杂度：</strong>分析归并排序的时间复杂度就是分析递归代码的时间复杂度。</p><p>如何分析递归代码的时间复杂度？</p><p>递归的适用场景是一个问题a可以分解为多个子问题b、c，那求解问题a就可以分解为求解问题b、c。问题b、c解决之后，我们再把b、c的结果合并成a的结果。若定义求解问题a的时间是T(a)，则求解问题b、c的时间分别是T(b)和T(c)，那就可以得到这样的递推公式：T(a) = T(b) + T(c) + K，其中K等于将两个子问题b、c的结果合并成问题a的结果所消耗的时间。这里有一个重要的结论：不仅递归求解的问题可以写成递推公式，递归代码的时间复杂度也可以写成递推公式。套用这个公式，那么归并排序的时间复杂度就可以表示为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">T(<span class="number">1</span>) = C； n=<span class="number">1</span> 时，只需要常量级的执行时间，所以表示为 C。</span><br><span class="line">T(n) = <span class="number">2</span>*T(n/<span class="number">2</span>) + n； n&gt;<span class="number">1</span>，其中n就是merge()函数合并两个子数组的的时间复杂度O(n)。</span><br><span class="line">T(n) = <span class="number">2</span>*T(n/<span class="number">2</span>) + n</span><br><span class="line">   = <span class="number">2</span>*(<span class="number">2</span>*T(n/<span class="number">4</span>) + n/<span class="number">2</span>) + n = <span class="number">4</span>*T(n/<span class="number">4</span>) + <span class="number">2</span>*n</span><br><span class="line">   = <span class="number">4</span>*(<span class="number">2</span>*T(n/<span class="number">8</span>) + n/<span class="number">4</span>) + <span class="number">2</span>*n = <span class="number">8</span>*T(n/<span class="number">8</span>) + <span class="number">3</span>*n</span><br><span class="line">   = <span class="number">8</span>*(<span class="number">2</span>*T(n/<span class="number">16</span>) + n/<span class="number">8</span>) + <span class="number">3</span>*n = <span class="number">16</span>*T(n/<span class="number">16</span>) + <span class="number">4</span>*n</span><br><span class="line">   ......</span><br><span class="line">   = <span class="number">2</span>^k * T(n/<span class="number">2</span>^k) + k * n</span><br><span class="line">   ......</span><br></pre></td></tr></table></figure><p>当T(n/2^k)=T(1) 时，也就是 n/2^k=1，我们得到k=log2n。将k带入上面的公式就得到T(n)=Cn+nlog2n。如用大O表示法，T(n)就等于O(nlogn)。</p><p>所以，归并排序的是复杂度时间复杂度就是O(nlogn)。</p><p><strong>空间复杂度</strong>：归并排序算法不是原地排序算法，空间复杂度是O(n)</p><p>为什么？因为归并排序的合并函数，在合并两个数组为一个有序数组时，需要借助额外的存储空间。为什么空间复杂度是O(n)而不是O(nlogn)呢？如果我们按照分析递归的时间复杂度的方法，通过递推公式来求解，那整个归并过程需要的空间复杂度就是O(nlogn)，但这种分析思路是有问题的！因为，在实际上，递归代码的空间复杂度并不是像时间复杂度那样累加，而是这样的过程，即在每次合并过程中都需要申请额外的内存空间，但是合并完成后，临时开辟的内存空间就被释放掉了，在任意时刻，CPU只会有一个函数在执行，也就只会有一个临时的内存空间在使用。临时空间再大也不会超过n个数据的大小，所以空间复杂度是O(n)。</p><h3 id="快速排序（Quicksort）"><a href="#快速排序（Quicksort）" class="headerlink" title="快速排序（Quicksort）"></a>快速排序（Quicksort）</h3><p>快排的思想是这样的：如果要排序数组中下标从p到r之间的一组数据，我们选择p到r之间的任意一个数据作为pivot（分区点）。然后遍历p到r之间的数据，将小于pivot的放到左边，将大于pivot的放到右边，将povit放到中间。经过这一步之后，数组p到r之间的数据就分成了3部分，前面p到q-1之间都是小于povit的，中间是povit，后面的q+1到r之间是大于povit的。根据分治、递归的处理思想，我们可以用递归排序下标从p到q-1之间的数据和下标从q+1到r之间的数据，直到区间缩小为1，就说明所有的数据都有序了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">递推公式：quick_sort(p…r) &#x3D; quick_sort(p…q-1) + quick_sort(q+1, r)</span><br><span class="line">终止条件：p &gt;&#x3D; r</span><br></pre></td></tr></table></figure><h4 id="图解"><a href="#图解" class="headerlink" title="图解"></a>图解</h4><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-05.gif" alt="快速排序"></p><h4 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 快速排序，A 是数组，n 表示数组的大小</span></span><br><span class="line">quick_sort(A, n) &#123;</span><br><span class="line">  quick_sort_c(A, <span class="number">0</span>, n-<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 快速排序递归函数，p,r 为下标</span></span><br><span class="line">quick_sort_c(A, p, r) &#123;</span><br><span class="line">  <span class="keyword">if</span> p &gt;= r then <span class="keyword">return</span></span><br><span class="line">  q = partition(A, p, r) <span class="comment">// 获取分区点</span></span><br><span class="line">  quick_sort_c(A, p, q-<span class="number">1</span>)</span><br><span class="line">  quick_sort_c(A, q+<span class="number">1</span>, r)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//分区函数</span></span><br><span class="line">partition(A, p, r) &#123;</span><br><span class="line">  pivot := A[r]</span><br><span class="line">  i := p</span><br><span class="line">  <span class="keyword">for</span> j := p to r-<span class="number">1</span> <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> A[j] &lt; pivot &#123;</span><br><span class="line">      swap A[i] with A[j]</span><br><span class="line">      i := i+<span class="number">1</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  swap A[i] with A[r]</span><br><span class="line">  <span class="keyword">return</span> i</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>分区函数代码说明：通过游标i把A[p…r-1]分成2部分，A[p…i-1]的元素都是小于pivot的，我们暂且叫它“已处理区间”，A[i+1…r-1]是“未处理区间”。我们每次都从未处理区间取出一个元素A[j]，与poivt相比，如果小于pivot，则将其加入到已处理区间的尾部，也就是A[i]位置。</p><h4 id="性能分析-1"><a href="#性能分析-1" class="headerlink" title="性能分析"></a>性能分析</h4><p><strong>算法稳定性：</strong></p><p>因为分区过程中涉及交换操作，如果数组中有两个8，其中一个是pivot，经过分区处理后，后面的8就有可能放到了另一个8的前面，先后顺序就颠倒了，所以快速排序是<strong>不稳定的排序算法</strong>。比如数组[1,2,3,9,8,11,8]，取后面的8作为pivot，那么分区后就会将后面的8与9进行交换。</p><p><strong>时间复杂度</strong>：最好、最坏、平均情况</p><p>快排也是用递归实现的，所以时间复杂度也可以用递推公式表示。<br>如果每次分区操作都能正好把数组分成大小接近相等的两个小区间，那快排的时间复杂度递推求解公式跟归并的相同。</p><p>T(1) = C； n=1 时，只需要常量级的执行时间，所以表示为 C。<br>T(n) = 2*T(n/2) + n； n&gt;1</p><p>所以，快排的时间复杂度也是O(nlogn)。</p><p>如果数组中的元素原来已经有序了，比如1，3，5，6，8，若每次选择最后一个元素作为pivot，那每次分区得到的两个区间都是不均等的，需要进行大约n次的分区，才能完成整个快排过程，而每次分区我们平均要扫描大约n/2个元素，这种情况下，快排的时间复杂度就是O(n^2)。</p><p>前面两种情况，一个是分区及其均衡，一个是分区极不均衡，它们分别对应了快排的最好情况时间复杂度和最坏情况时间复杂度。那快排的平均时间复杂度是多少呢？T(n)大部分情况下是O(nlogn)，只有在极端情况下才是退化到O(n^2)，而且我们也有很多方法将这个概率降低。</p><p><strong>空间复杂度</strong>：快排是一种原地排序算法，空间复杂度是O(1)。</p><h3 id="归并排序与快速排序的区别"><a href="#归并排序与快速排序的区别" class="headerlink" title="归并排序与快速排序的区别"></a>归并排序与快速排序的区别</h3><p>归并和快排用的都是分治思想，递推公式和递归代码也非常相似，那它们的区别在哪里呢？</p><ol><li>归并排序，是先递归调用，再进行合并，合并的时候进行数据的交换。所以它是自下而上的排序方式。何为自下而上？就是先解决子问题，再解决父问题。</li><li>快速排序，是先分区，在递归调用，分区的时候进行数据的交换。所以它是自上而下的排序方式。何为自上而下？就是先解决父问题，再解决子问题。</li></ol><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><p>如果有多个大日志文件，需要按时间合并为一个文件，但机器内存有限，不足以同时把这些文件读取到内存，可以使用归并排序思想，先构建所有日志的 IO 流，然后没条 IO 流读取一条日志，选取时间戳最小的那个放入新文件，然后该 IO 流读取下一条日志，持续这个操作，直至所有文件读取完毕。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;分治思想&quot;&gt;&lt;a href=&quot;#分治思想&quot; class=&quot;headerlink&quot; title=&quot;分治思想&quot;&gt;&lt;/a&gt;分治思想&lt;/h3&gt;&lt;p&gt;归并排序和快速排序都使用了分治思想。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分治思想：分治，顾明思意，就是分而治之，将一个大问题分解成小
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="排序" scheme="http://astrapub.github.io/tags/%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>04. 数据结构与算法入门 | 排序算法(上)</title>
    <link href="http://astrapub.github.io/2020/01/14/alg-04/"/>
    <id>http://astrapub.github.io/2020/01/14/alg-04/</id>
    <published>2020-01-14T14:49:04.000Z</published>
    <updated>2020-02-25T12:04:23.705Z</updated>
    
    <content type="html"><![CDATA[<h3 id="几种经典排序算法及其时间复杂度级别"><a href="#几种经典排序算法及其时间复杂度级别" class="headerlink" title="几种经典排序算法及其时间复杂度级别"></a>几种经典排序算法及其时间复杂度级别</h3><p>冒泡、插入、选择 O(n^2) 基于比较<br>快排、归并 O(nlogn) 基于比较<br>计数、基数、桶 O(n) 不基于比较</p><h3 id="如何分析一个排序算法？"><a href="#如何分析一个排序算法？" class="headerlink" title="如何分析一个排序算法？"></a>如何分析一个排序算法？</h3><ol><li>学习排序算法的思路？明确原理、掌握实现以及分析性能。</li><li>如何分析排序算法性能？从执行效率、内存消耗以及稳定性3个方面分析排序算法的性能。</li><li>执行效率：从以下3个方面来衡量<br>1）最好情况、最坏情况、平均情况时间复杂度<br>2）时间复杂度的系数、常数、低阶：排序的数据量比较小时考虑<br>3）比较次数和交换（或移动）次数</li><li>内存消耗：通过空间复杂度来衡量。针对排序算法的空间复杂度，引入原地排序的概念，原地排序算法就是指空间复杂度为O(1)的排序算法。</li><li>稳定性：如果待排序的序列中存在值等的元素，经过排序之后，相等元素之间原有的先后顺序不变，就说明这个排序算法时稳定的。</li></ol><h3 id="冒泡排序（Bubble-Sort）"><a href="#冒泡排序（Bubble-Sort）" class="headerlink" title="冒泡排序（Bubble Sort）"></a>冒泡排序（Bubble Sort）</h3><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-01.gif" alt="冒泡排序"></p><ol><li><p>排序原理</p><p>1）冒泡排序只会操作相邻的两个数据。<br>2）对相邻两个数据进行比较，看是否满足大小关系要求，若不满足让它俩互换。<br>3）一次冒泡会让至少一个元素移动到它应该在的位置，重复n次，就完成了n个数据的排序工作。<br>4）优化：若某次冒泡不存在数据交换，则说明已经达到完全有序，所以终止冒泡。</p></li><li><p>性能分析</p><ol><li><p>执行效率：最小时间复杂度、最大时间复杂度、平均时间复杂度<br>最小时间复杂度：数据完全有序时，只需进行一次冒泡操作即可，时间复杂度是O(n)。<br>最大时间复杂度：数据倒序排序时，需要n次冒泡操作，时间复杂度是O(n^2)。<br>平均时间复杂度：通过有序度和逆序度来分析。</p><p><strong>什么是有序度？</strong></p><p>有序度是数组中具有有序关系的元素对的个数，比如[2,4,3,1,5,6]这组数据的有序度就是11，分别是[2,4][2,3][2,5][2,6][4,5][4,6][3,5][3,6][1,5][1,6][5,6]。同理，对于一个倒序数组，比如[6,5,4,3,2,1]，有序度是0；对于一个完全有序的数组，比如[1,2,3,4,5,6]，有序度为n*(n-1)/2，也就是15，完全有序的情况称为满有序度。</p><p>什么是逆序度？逆序度的定义正好和有序度相反。核心公式：逆序度=满有序度-有序度。</p><p>排序过程，就是有序度增加，逆序度减少的过程，最后达到满有序度，就说明排序完成了。</p><p>冒泡排序包含两个操作原子，即比较和交换，每交换一次，有序度加1。不管算法如何改进，交换的次数总是确定的，即逆序度。</p><p>对于包含n个数据的数组进行冒泡排序，平均交换次数是多少呢？最坏的情况初始有序度为0，所以要进行n*(n-1)/2交换。最好情况下，初始状态有序度是n*(n-1)/2，就不需要进行交互。我们可以取个中间值n*(n-1)/4，来表示初始有序度既不是很高也不是很低的平均情况。</p><p>换句话说，平均情况下，需要n*(n-1)/4次交换操作，比较操作可定比交换操作多，而复杂度的上限是O(n^2)，所以平均情况时间复杂度就是O(n^2)。</p><p>以上的分析并不严格，但很实用，这就够了。</p></li><li><p>空间复杂度：每次交换仅需1个临时变量，故空间复杂度为O(1)，是原地排序算法。</p></li><li><p>算法稳定性：如果两个值相等，就不会交换位置，故是稳定排序算法。</p></li></ol></li></ol><h3 id="插入排序（Insertion-Sort）"><a href="#插入排序（Insertion-Sort）" class="headerlink" title="插入排序（Insertion Sort）"></a>插入排序（Insertion Sort）</h3><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-02.gif" alt="插入排序"></p><ol><li><p>算法原理<br>首先，我们将数组中的数据分为2个区间，即已排序区间和未排序区间。初始已排序区间只有一个元素，就是数组的第一个元素。插入算法的核心思想就是取未排序区间中的元素，在已排序区间中找到合适的插入位置将其插入，并保证已排序区间中的元素一直有序。重复这个过程，直到未排序中元素为空，算法结束。</p></li><li><p>性能分析</p><ol><li>时间复杂度：最好、最坏、平均情况<br>如果要排序的数组已经是有序的，我们并不需要搬移任何数据。只需要遍历一遍数组即可，所以时间复杂度是O(n)。如果数组是倒序的，每次插入都相当于在数组的第一个位置插入新的数据，所以需要移动大量的数据，因此时间复杂度是O(n^2)。而在一个数组中插入一个元素的平均时间复杂都是O(n)，插入排序需要n次插入，所以平均时间复杂度是O(n^2)。</li><li>空间复杂度：从上面的代码可以看出，插入排序算法的运行并不需要额外的存储空间，所以空间复杂度是O(1)，是原地排序算法。</li><li>算法稳定性：在插入排序中，对于值相同的元素，我们可以选择将后面出现的元素，插入到前面出现的元素的后面，这样就保持原有的顺序不变，所以是稳定的。</li></ol><h3 id="选择排序（Selection-Sort）"><a href="#选择排序（Selection-Sort）" class="headerlink" title="选择排序（Selection Sort）"></a>选择排序（Selection Sort）</h3><p>选择排序算法的实现思路有点类似插入排序，也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素，将其放到已排序区间的末尾。</p><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/04-03.gif" alt="选择排序"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>冒泡排序不管怎么优化，元素交换的次数是一个固定值，是原始数据的逆序度。插入排序是同样的，不管怎么优化，元素移动的次数也等于原始数据的逆序度。</p><p>但是，从代码实现上来看，冒泡排序的数据交换要比插入排序的数据移动要复杂，冒泡排序需要 3 个赋值操作，而插入排序只需要 1 个：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 冒泡排序中数据的交换操作：</span></span><br><span class="line"><span class="keyword">if</span> (a[j] &gt; a[j+<span class="number">1</span>]) &#123; <span class="comment">// 交换 </span></span><br><span class="line">  <span class="keyword">int</span> tmp = a[j]; </span><br><span class="line">  a[j] = a[j+<span class="number">1</span>]; </span><br><span class="line">  a[j+<span class="number">1</span>] = tmp; </span><br><span class="line">  flag = <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 插入排序中数据的移动操作：</span></span><br><span class="line"><span class="keyword">if</span> (a[j] &gt; value) &#123; </span><br><span class="line">  a[j+<span class="number">1</span>] = a[j]; <span class="comment">// 数据移动</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123; </span><br><span class="line">  <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以在实际使用中插入排序会比冒泡排序更常用。</p><p>要想分析、评价一个排序算法，需要从执行效率、内存消耗和稳定性三个方面来看，下面是这三种排序算法的复杂度分析：</p><table><thead><tr><th></th><th>是否是原地排序</th><th>是否稳定</th><th>最好</th><th>最坏</th><th>平均</th></tr></thead><tbody><tr><td>冒泡排序</td><td>是</td><td>是</td><td>O(n)</td><td>O(n²)</td><td>O(n²)</td></tr><tr><td>插入排序</td><td>是</td><td>是</td><td>O(n)</td><td>O(n²)</td><td>O(n²)</td></tr><tr><td>选择排序</td><td>是</td><td>否</td><td>O(n²)</td><td>O(n²)</td><td>O(n²)</td></tr></tbody></table></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;几种经典排序算法及其时间复杂度级别&quot;&gt;&lt;a href=&quot;#几种经典排序算法及其时间复杂度级别&quot; class=&quot;headerlink&quot; title=&quot;几种经典排序算法及其时间复杂度级别&quot;&gt;&lt;/a&gt;几种经典排序算法及其时间复杂度级别&lt;/h3&gt;&lt;p&gt;冒泡、插入、选择 O
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="排序" scheme="http://astrapub.github.io/tags/%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>03. 数据结构与算法入门 | 递归</title>
    <link href="http://astrapub.github.io/2020/01/14/alg-03/"/>
    <id>http://astrapub.github.io/2020/01/14/alg-03/</id>
    <published>2020-01-14T14:49:03.000Z</published>
    <updated>2020-02-25T12:04:28.808Z</updated>
    
    <content type="html"><![CDATA[<p>一言以蔽之，递归就是自己调用自己的函数，当然这么理解有点不太准确，但是当我们看到了调用自己的函数，就基本可以判断是递归函数。</p><p>递归是一种非常高效、简洁的编码技巧，一种应用非常广泛的算法，比如DFS深度优先搜索、前中后序二叉树遍历等都是使用递归。方法或函数调用自身的方式称为递归调用，调用称为递，返回称为归。</p><p>基本上，所有的递归问题都可以用递推公式来表示，比如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">f(n) &#x3D; f(n-1) + 1;</span><br><span class="line">f(n) &#x3D; f(n-1) + f(n-2);</span><br><span class="line">f(n)&#x3D;n*f(n-1);</span><br></pre></td></tr></table></figure><p>递归其实不太符合我们人脑的思维方式。递归需要满足一下三个条件：</p><ol><li>一个问题的解可以分解为几个子问题的解</li><li>这个问题与分解之后的子问题，除了数据规模不同，求解思路完全一样</li><li>存在递归终止条件</li></ol><p>所以递归代码的编写就需要<strong>写出递推公式，找到终止条件</strong>，即递归条件和基线条件，编写递归代码的关键是，只要遇到递归，我们就把它抽象成一个递推公式，不用想一层层的调用关系，不要试图用人脑去分解递归的每个步骤。</p><p>递归代码要警惕堆栈溢出，可以声明一个全局变量来控制递归的深度，从而避免堆栈溢出。警惕重复计算：通过某种数据结构来保存已经求解过的值，从而避免重复计算。</p><p>笼统的讲，所有的递归代码都可以改写为迭代循环的非递归写法。如何做？抽象出递推公式、初始值和边界条件，然后用迭代循环实现。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;一言以蔽之，递归就是自己调用自己的函数，当然这么理解有点不太准确，但是当我们看到了调用自己的函数，就基本可以判断是递归函数。&lt;/p&gt;
&lt;p&gt;递归是一种非常高效、简洁的编码技巧，一种应用非常广泛的算法，比如DFS深度优先搜索、前中后序二叉树遍历等都是使用递归。方法或函数调用自
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="递归" scheme="http://astrapub.github.io/tags/%E9%80%92%E5%BD%92/"/>
    
  </entry>
  
  <entry>
    <title>02. 数据结构与算法入门 | 一篇文章讲清数组、链表、栈、队列</title>
    <link href="http://astrapub.github.io/2020/01/14/alg-02/"/>
    <id>http://astrapub.github.io/2020/01/14/alg-02/</id>
    <published>2020-01-14T14:49:02.000Z</published>
    <updated>2020-02-25T12:04:33.038Z</updated>
    
    <content type="html"><![CDATA[<h2 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h2><p>数组（Array）是一种线性表数据结构。它用一组连续的内存空间，来存储一组具有相同类型的数据。</p><blockquote><p><strong>线性表</strong>（Linear List）：顾名思义，线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组，链表、队列、栈等也是线性表结构。</p><p><strong>非线性表</strong>，比如二叉树、堆、图等。之所以叫非线性，是因为，在非线性表中，数据之间并不是简单的前后关系。</p></blockquote><p>数组具有<strong>随机访问特性</strong>，并且可以借助 cpu 缓存策略提高效率。数组必须存储在连续的内存空间和相同类型的数据。正是因为这两个限制，它才有了一个堪称“杀手锏”的特性：“随机访问”。但有利就有弊，这两个限制也让数组的很多操作变得非常低效，比如要想在数组中删除、插入一个数据，为了保证连续性，就需要做大量的数据搬移工作。</p><p>数组插入和删除的时间复杂度为O(n)，如果不考虑数据连续性，在插入数据的时候可以先将要插入位置的数据移到最后，然后再将数据插入，时间复杂度可降为 O(1)，删除操作亦然。数组随机访问的时间复杂度为 O(1)。</p><p>使用数组需要警惕数组的访问越界，C语言中更甚。Java 中的 ArrayList 容器，支持动态扩容，但扩容操会产生数据搬移，时间复杂度也是上升到 O(n)，使用时最好事先指定数据大小。</p><p>这里补充一个知识点，为什么数组要从 0 开始编号，而不是从 1 开始？</p><ol><li>从偏移角度理解a[0] 0为偏移量，如果从1计数，会多出K-1。增加cpu负担。为什么循环要写成for(int i = 0;i&lt;3;i++) 而不是for(int i = 0 ;i&lt;=2;i++)。第一个直接就可以算出3-0 = 3 有三个数据，而后者 2-0+1个数据；</li><li>也有一定的历史原因。</li></ol><h2 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h2><ol><li>和数组一样，链表也是一种线性表。</li><li>从内存结构来看，链表的内存结构是不连续的内存空间，是将一组零散的内存块串联起来，从而进行数据存储的数据结构。</li><li>链表中的每一个内存块被称为节点Node。节点除了存储数据外，还需记录链上下一个节点的地址，即后继指针next。</li></ol><p>链表分为单向链表、双向链表、循环链表、双向循环链表。链表的删除和插入操作时间复杂度为O(1)，只需改变指针的值，查找的时间复杂度为O(n)。</p><h4 id="选择数组还是链表？"><a href="#选择数组还是链表？" class="headerlink" title="选择数组还是链表？"></a>选择数组还是链表？</h4><ol><li><p>插入、删除和随机访问的时间复杂度</p><p>数组：插入、删除的时间复杂度是O(n)，随机访问的时间复杂度是O(1)。<br>链表：插入、删除的时间复杂度是O(1)，随机访问的时间复杂端是O(n)。</p></li><li><p>数组缺点</p><p>若申请内存空间很大，比如100M，但若内存空间没有100M的连续空间时，则会申请失败，尽管内存可用空间超过100M。<br>大小固定，若存储空间不足，需进行扩容，一旦扩容就要进行数据复制，而这时非常费时的。 </p></li><li><p>链表缺点</p><p>内存空间消耗更大，因为需要额外的空间存储指针信息。<br>对链表进行频繁的插入和删除操作，会导致频繁的内存申请和释放，容易造成内存碎片，如果是Java语言，还可能会造成频繁的GC（自动垃圾回收器）操作。 </p></li><li><p>如何选择？</p><p>数组简单易用，在实现上使用连续的内存空间，可以借助CPU的缓冲机制预读数组中的数据，所以访问效率更高，而链表在内存中并不是连续存储，所以对CPU缓存不友好，没办法预读。如果代码对内存的使用非常苛刻，那数组就更适合。</p></li></ol><h2 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h2><p><strong>后进者先出，先进者后出，这就是典型的“栈”结构</strong>。有一个非常贴切的例子，就是一摞叠在一起的盘子。我们平时放盘子的时候，都是从下往上一个一个放；取的时候，我们也是从上往下一个一个地依次取，不能从中间任意抽出。</p><p>从栈的操作特性上来看，其实，<strong>栈是一种“操作受限”的线性表</strong>，只允许在端插入和删除数据，只支持两个基本操作：入栈 push()和出栈 pop()。栈根据实现方法不同可以分为顺序栈和链式栈。</p><p>栈的应用：</p><ol><li>函数调用中的应用，操作系统给每个线程分配了一块独立的内存空间；</li><li>栈在表达式求值中的应用（比如：34+13*9+44-12/3）；</li><li>栈在括号匹配中的应用（比如：{}{<a href="">()</a>}），可用于代码编译；</li><li>实现浏览器的前进后退功能</li></ol><blockquote><p><strong>JVM 内存管理中有个“堆栈”的概念和内存中的栈有何区别？</strong></p><p>内存中的堆栈和数据结构堆栈不是一个概念，可以说内存中的堆栈是真实存在的物理区，数据结构中的堆栈是抽象的数据存储结构。</p><p>内存空间在逻辑上分为三部分：代码区、静态数据区和动态数据区，动态数据区又分为栈区和堆区。<br>代码区：存储方法体的二进制代码。高级调度（作业调度）、中级调度（内存调度）、低级调度（进程调度）控制代码区执行代码的切换。</p><p>静态数据区：存储全局变量、静态变量、常量，常量包括final修饰的常量和String常量。系统自动分配和回收。<br>栈区：存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。<br>堆区：new一个对象的引用或地址存储在栈区，指向该对象存储在堆区中的真实数据。</p><p>真实的堆栈应该和计算机架构有关，cpu有栈顶指针寄存器，然后指令有push和pop的指令，这个是数字电路作死的，函数之间的跳变，编译器把要跳的地址赋值给栈顶指针寄存器，然后根据你写的代码编译成push和pop，这部分代码对写高级语言的程序员是透明的，如果写汇编，这些都得自己写</p></blockquote><h2 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h2><p><strong>先进者先出，这就是典型的“队列”</strong>，最基本的操作也是两个：入队 enqueue()，放一个数据到队列尾部；出队 dequeue()，从队列头部取一个元素。同样，用数组实现的队列叫作顺序队列，用链表实现的队列叫作链式队列。和栈一样，队列也是一种操作受限的线性表。</p><h4 id="常用的队列"><a href="#常用的队列" class="headerlink" title="常用的队列"></a>常用的队列</h4><ol><li><p>阻塞队列</p><ul><li>在队列的基础上增加阻塞操作，就成了阻塞队列；</li><li>阻塞队列就是在队列为空的时候，从队头取数据会被阻塞，因为此时还没有数据可取，直到队列中有了数据才能返回；如果队列已经满了，那么插入数据的操作就会被阻塞，直到队列中有空闲位置后再插入数据，然后在返回；</li><li>从上面的定义可以看出这就是一个“生产者-消费者模型”。这种基于阻塞队列实现的“生产者-消费者模型”可以有效地协调生产和消费的速度。当“生产者”生产数据的速度过快，“消费者”来不及消费时，存储数据的队列很快就会满了，这时生产者就阻塞等待，直到“消费者”消费了数据，“生产者”才会被唤醒继续生产。不仅如此，基于阻塞队列，我们还可以通过协调“生产者”和“消费者”的个数，来提高数据处理效率，比如配置几个消费者，来应对一个生产者。</li></ul></li><li><p>并发队列</p><ul><li>在多线程的情况下，会有多个线程同时操作队列，这时就会存在线程安全问题。能够有效解决线程安全问题的队列就称为并发队列；</li><li>并发队列简单的实现就是在enqueue()、dequeue()方法上加锁，但是锁粒度大并发度会比较低，同一时刻仅允许一个存或取操作；</li><li>实际上，基于数组的循环队列利用CAS原子操作，可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。</li></ul></li><li><p>线程池资源枯竭的处理</p><p> 在资源有限的场景，当没有空闲资源时，基本上都可以通过“队列”这种数据结构来实现请求排队。</p></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;数组&quot;&gt;&lt;a href=&quot;#数组&quot; class=&quot;headerlink&quot; title=&quot;数组&quot;&gt;&lt;/a&gt;数组&lt;/h2&gt;&lt;p&gt;数组（Array）是一种线性表数据结构。它用一组连续的内存空间，来存储一组具有相同类型的数据。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;s
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据结构" scheme="http://astrapub.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
      <category term="数组" scheme="http://astrapub.github.io/tags/%E6%95%B0%E7%BB%84/"/>
    
      <category term="链表" scheme="http://astrapub.github.io/tags/%E9%93%BE%E8%A1%A8/"/>
    
      <category term="栈" scheme="http://astrapub.github.io/tags/%E6%A0%88/"/>
    
      <category term="队列" scheme="http://astrapub.github.io/tags/%E9%98%9F%E5%88%97/"/>
    
  </entry>
  
  <entry>
    <title>01. 数据结构与算法入门 | 什么是复杂度分析</title>
    <link href="http://astrapub.github.io/2020/01/14/alg-01/"/>
    <id>http://astrapub.github.io/2020/01/14/alg-01/</id>
    <published>2020-01-14T14:49:01.000Z</published>
    <updated>2020-02-25T12:04:37.009Z</updated>
    
    <content type="html"><![CDATA[<p>复杂度分析分为时间复杂度分析和空间复杂度分析，时间复杂度的全称是渐进时间复杂度，表示算法的执行时间与数据规模之间的增长关系，空间复杂度全称就是渐进空间复杂度（asymptotic space complexity），表示算法的存储空间与数据规模之间的增长关系。</p><h3 id="什么是复杂度分析？"><a href="#什么是复杂度分析？" class="headerlink" title="什么是复杂度分析？"></a>什么是复杂度分析？</h3><ol><li>数据结构和算法解决是“如何让计算机更快时间、更省空间的解决问题”。</li><li>因此需从执行时间和占用空间两个维度来评估数据结构和算法的性能。</li><li>分别用时间复杂度和空间复杂度两个概念来描述性能问题，二者统称为复杂度。</li><li>复杂度描述的是算法执行时间（或占用空间）与数据规模的增长关系。</li></ol><h3 id="为什么使用复杂度分析？"><a href="#为什么使用复杂度分析？" class="headerlink" title="为什么使用复杂度分析？"></a>为什么使用复杂度分析？</h3><ol><li>和性能测试相比，复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点。</li><li>掌握复杂度分析，将能编写出性能更优的代码，有利于降低系统开发和维护成本。</li></ol><h3 id="如何进行复杂度分析？"><a href="#如何进行复杂度分析？" class="headerlink" title="如何进行复杂度分析？"></a>如何进行复杂度分析？</h3><ol><li>大O表示法<br> 算法的执行时间与每行代码的执行次数成正比，用T(n) = O(f(n))表示，其中T(n)表示算法执行总时间，f(n)表示每行代码执行总次数，而n往往表示数据的规模。<br> 以时间复杂度为例，由于时间复杂度描述的是算法执行时间与数据规模的增长变化趋势，所以常量阶、低阶以及系数实际上对这种增长趋势不产决定性影响，所以在做时间复杂度分析时忽略这些项。<br> 空间复杂度分析比时间复杂度分析要简单很多，我们常见的空间复杂度就是 O(1)、O(n)、O(n2)，像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。</li><li>复杂度分析法则<ul><li>单段代码看高频：比如循环。</li><li>多段代码取最大：比如一段代码中有单循环和多重循环，那么取多重循环的复杂度。</li><li>嵌套代码求乘积：比如递归、多重循环等</li><li>多个规模求加法：比如方法有两个参数控制两个循环的次数，那么这时就取二者复杂度相加。</li></ul></li></ol><h3 id="常用的复杂度级别"><a href="#常用的复杂度级别" class="headerlink" title="常用的复杂度级别"></a>常用的复杂度级别</h3><p><strong>多项式阶：</strong><br>随着数据规模的增长，算法的执行时间和空间占用，按照多项式的比例增长。包括，<br>O(1)（常数阶）、O(logn)（对数阶）、O(n)（线性阶）、O(nlogn)（线性对数阶）、O(n^2)（平方阶）、O(n^3)（立方阶）  </p><p><strong>非多项式阶：</strong><br>随着数据规模的增长，算法的执行时间和空间占用暴增，这类算法性能极差。包括，<br>O(2^n)（指数阶）、O(n!)（阶乘阶）</p><h3 id="复杂度分析的4个概念"><a href="#复杂度分析的4个概念" class="headerlink" title="复杂度分析的4个概念"></a>复杂度分析的4个概念</h3><ol><li>最坏情况时间复杂度：代码在最理想情况下执行的时间复杂度。</li><li>最好情况时间复杂度：代码在最坏情况下执行的时间复杂度。</li><li>平均时间复杂度：用代码在所有情况下执行的次数的加权平均值表示。</li><li>均摊时间复杂度：在代码执行的所有复杂度情况中绝大部分是低级别的复杂度，个别情况是高级别复杂度且发生具有时序关系时，可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。</li></ol><h3 id="为什么要引入这4个概念？"><a href="#为什么要引入这4个概念？" class="headerlink" title="为什么要引入这4个概念？"></a>为什么要引入这4个概念？</h3><ol><li>同一段代码在不同情况下时间复杂度会出现量级差异，为了更全面，更准确的描述代码的时间复杂度，所以引入这4个概念。</li><li>代码复杂度在不同情况下出现量级差别时才需要区别这四种复杂度。大多数情况下，是不需要区别分析它们的。</li></ol><h3 id="如何分析平均、均摊时间复杂度？"><a href="#如何分析平均、均摊时间复杂度？" class="headerlink" title="如何分析平均、均摊时间复杂度？"></a>如何分析平均、均摊时间复杂度？</h3><ol><li>平均时间复杂度<br> 代码在不同情况下复杂度出现量级差别，则用代码所有可能情况下执行次数的加权平均值表示。</li><li>均摊时间复杂度<br> 两个条件满足时使用：  <ul><li>代码在绝大多数情况下是低级别复杂度，只有极少数情况是高级别复杂度；</li><li>低级别和高级别复杂度出现具有时序规律。均摊结果一般都等于低级别复杂度。</li></ul></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;复杂度分析分为时间复杂度分析和空间复杂度分析，时间复杂度的全称是渐进时间复杂度，表示算法的执行时间与数据规模之间的增长关系，空间复杂度全称就是渐进空间复杂度（asymptotic space complexity），表示算法的存储空间与数据规模之间的增长关系。&lt;/p&gt;
&lt;h
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="复杂度分析" scheme="http://astrapub.github.io/tags/%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>00. 数据结构与算法入门 | 开篇，如何学习</title>
    <link href="http://astrapub.github.io/2020/01/14/alg-00/"/>
    <id>http://astrapub.github.io/2020/01/14/alg-00/</id>
    <published>2020-01-14T14:49:00.000Z</published>
    <updated>2020-02-25T12:04:43.151Z</updated>
    
    <content type="html"><![CDATA[<h3 id="什么是数据结构，什么是算法"><a href="#什么是数据结构，什么是算法" class="headerlink" title="什么是数据结构，什么是算法"></a>什么是数据结构，什么是算法</h3><p>大部分教材里开篇都会有相关的定义，但是这些定义都很抽象，而且对于我们理解没有什么实质性帮助，所以我们不必死扣定义。那么来讲讲我的理解，数据结构就是指一组数据的存储结构，算法就是操作数据的一组方法。</p><p>可以以图书馆为例，为了方便查找，图书管理员把图书会按照一定规律放到书架上，这就是书籍这种“数据”的存储结构，而怎么才能查到一本书的具体位置，方法有很多，可以一本一本的找，也可以将图书分类然后再查找，这个查找的方法就是算法。</p><p>所以，数据结构和算法是相辅相成的，<strong>数据结构是为算法服务的，算法要作用在特定的数据结构之上</strong>。</p><h3 id="学习数据结构与算法的重要性"><a href="#学习数据结构与算法的重要性" class="headerlink" title="学习数据结构与算法的重要性"></a>学习数据结构与算法的重要性</h3><ol><li>直接好处是能够有写出性能更优的代码。</li><li>算法，是一种解决问题的思路和方法，有机会应用到生活和事业的其他方面。</li><li>长期来看，大脑思考能力是个人最重要的核心竞争力，而算法是为数不多的能够有效训练大脑思考能力的途径之一。</li></ol><h3 id="知识体系"><a href="#知识体系" class="headerlink" title="知识体系"></a>知识体系</h3><p><img src="https://raw.githubusercontent.com/atove/image/master/alg/00-01.jpg" alt="数据结构与算法知识体系"></p><h3 id="学习技巧"><a href="#学习技巧" class="headerlink" title="学习技巧"></a>学习技巧</h3><p>数据结构与算法学习，其实是一个很枯燥的过程，很容易就坚持不下来，其实可以使用学习技巧来提升学习体验：</p><ol><li>边学边练，适度刷题；</li><li>多问、多思考、多互动，最好能和志同道合的朋友一起学习；</li><li>给自己设立一个切实可行的目标，就像打怪升级，坚持下来；</li><li>反复迭代、温故知新，不断沉淀。</li></ol><h3 id="学习的方法论"><a href="#学习的方法论" class="headerlink" title="学习的方法论"></a>学习的方法论</h3><p>首先我认为学习分两点，第一，知识点的学习，主要是记忆和理解，相对简单，有清晰的目标，有良好的正反馈，学会了就是会了，可以拿来炫耀。比如，我可以告诉你帆船是通过走之字型逆风航行的，相信你很容易就能明白并记住这个知识点，但是你就能因此学会了如何驾驶帆船么，并不能，那么久引出了下面的内容；第二，技能的学习，这就需要刻意练习，不断纠正自己的错误，标准不清晰也就不容易量化，不容易形成正反馈，也就容易放弃。最典型的就是学习英语，需要反复练习，编程亦然，需要练习。</p><p>所以我总结以下几点关于学习的方法，希望和大家一起交流：</p><ol><li>首先提高知识的广度，建立知识体系，再去拓展知识的深度；</li><li>制定切实可行的目标，一步一步来；</li><li>建立正反馈系统，可以跟牛人学，也可以和朋友一起学，对于错误能及时发现、及时纠正；</li><li>持续输出，可以写学习笔记、总结，也可以是代码和注释，如果能教会别人效果更佳；</li><li>坚持学习和健身，健身能够增加多巴胺的分泌，能使人身心愉悦，同时能提升延迟满足的能力，提高学习效率。</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;什么是数据结构，什么是算法&quot;&gt;&lt;a href=&quot;#什么是数据结构，什么是算法&quot; class=&quot;headerlink&quot; title=&quot;什么是数据结构，什么是算法&quot;&gt;&lt;/a&gt;什么是数据结构，什么是算法&lt;/h3&gt;&lt;p&gt;大部分教材里开篇都会有相关的定义，但是这些定义都很抽
      
    
    </summary>
    
    
      <category term="算法" scheme="http://astrapub.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="http://astrapub.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据结构" scheme="http://astrapub.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
      <category term="数组" scheme="http://astrapub.github.io/tags/%E6%95%B0%E7%BB%84/"/>
    
      <category term="链表" scheme="http://astrapub.github.io/tags/%E9%93%BE%E8%A1%A8/"/>
    
      <category term="栈" scheme="http://astrapub.github.io/tags/%E6%A0%88/"/>
    
      <category term="队列" scheme="http://astrapub.github.io/tags/%E9%98%9F%E5%88%97/"/>
    
  </entry>
  
  <entry>
    <title>Promise、Async/Await 备忘</title>
    <link href="http://astrapub.github.io/2020/01/06/js-promise/"/>
    <id>http://astrapub.github.io/2020/01/06/js-promise/</id>
    <published>2020-01-06T07:17:56.000Z</published>
    <updated>2020-01-07T06:19:55.568Z</updated>
    
    <content type="html"><![CDATA[<p>Promise 是异步编程的一种解决方案，比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现，ES6 将其写进了语言标准，统一了用法，原生提供了Promise对象。</p><p>Promise 可以理解为一个容器，保存着某个未来才会结束的事件（通常是一个异步操作）的结果。从语法上说，Promise 是一个对象，从它可以获取异步操作的消息。Promise 提供统一的 API，各种异步操作都可以用同样的方法进行处理。</p><p>有all、reject、resolve 这几个方法，原型上有then、catch等方法。  </p><p><strong>有以下特点：</strong></p><ul><li><p>对象的状态不受外界影响。Promise 对象代表一个异步操作，有三种状态：pending（进行中）、fulfilled（已成功）和 rejected（已失败）。只有异步操作的结果，可以决定当前是哪一种状态，任何其他操作都无法改变这个状态。这也是Promise这个名字的由来，它的英语意思就是“承诺”，表示其他手段无法改变。</p></li><li><p>一旦状态改变，就不会再变，任何时候都可以得到这个结果。Promise对象的状态改变，只有两种可能：从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生，状态就凝固了，不会再变了，会一直保持这个结果，这时就称为 resolved（已定型）。如果改变已经发生了，你再对Promise对象添加回调函数，也会立即得到这个结果。这与事件（Event）完全不同，事件的特点是，如果你错过了它，再去监听，是得不到结果的。  </p></li></ul><h2 id="setTimeout、Promise、Async-Await-的区别"><a href="#setTimeout、Promise、Async-Await-的区别" class="headerlink" title="setTimeout、Promise、Async/Await 的区别"></a>setTimeout、Promise、Async/Await 的区别</h2><p>其中<code>setTimeout</code>的回调函数放到宏任务队列里，等到执行栈清空以后执行； <code>promise.then</code>里的回调函数会放到相应宏任务的微任务队列里，等宏任务里面的同步代码执行完再执行；<code>async</code>函数表示函数里面可能会有异步方法，<code>await</code>后面跟一个表达式，<code>async</code>方法执行时，遇到<code>await</code>会立即执行表达式，然后把表达式后面的代码放到微任务队列里，让出执行栈让同步代码先执行。</p><p>也就是说执行顺序是：任务栈（包括<code>await</code><strong>后</strong>面的表达式）&gt; 微任务队列（<code>promise.then</code>回调，<code>await</code><strong>下</strong>面的表达式）&gt; 宏任务队列（<code>setTimeout</code>回调）。</p><h2 id="多个异步任务"><a href="#多个异步任务" class="headerlink" title="多个异步任务"></a>多个异步任务</h2><p>Promise 还可以做更多的事情，比如，有若干个异步任务，需要先做任务1，如果成功后再做任务2，任何任务失败则不再继续并执行错误处理函数。  </p><p>要串行执行这样的异步任务，不用 Promise 需要写一层一层的嵌套代码。有了 Promise，我们只需要简单地写：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">job1.then(job2).then(job3).catch(handleError);</span><br></pre></td></tr></table></figure><p>其中，<code>job1</code>、<code>job2</code>和<code>job3</code>都是 Promise 对象。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://www.jianshu.com/p/7c6e4d21bf77" target="_blank" rel="noopener">https://www.jianshu.com/p/7c6e4d21bf77</a><br><a href="https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7" target="_blank" rel="noopener">https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7</a><br><a href="https://segmentfault.com/a/1190000011296839" target="_blank" rel="noopener">async/await 执行顺序详解</a><br><a href="https://www.jianshu.com/p/b4f0425b22a1" target="_blank" rel="noopener">Promise原理与实现</a><br><a href="http://es6.ruanyifeng.com/#docs/promise" target="_blank" rel="noopener">ES6 入门之 Promise 对象</a>  </p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Promise 是异步编程的一种解决方案，比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现，ES6 将其写进了语言标准，统一了用法，原生提供了Promise对象。&lt;/p&gt;
&lt;p&gt;Promise 可以理解为一个容器，保存着某个未来才会结束的事件（通
      
    
    </summary>
    
    
      <category term="Javascript" scheme="http://astrapub.github.io/categories/Javascript/"/>
    
    
      <category term="js" scheme="http://astrapub.github.io/tags/js/"/>
    
      <category term="Promise" scheme="http://astrapub.github.io/tags/Promise/"/>
    
  </entry>
  
  <entry>
    <title>深刻理解 Javascript 的 this 指向</title>
    <link href="http://astrapub.github.io/2020/01/05/js-this/"/>
    <id>http://astrapub.github.io/2020/01/05/js-this/</id>
    <published>2020-01-05T14:52:35.000Z</published>
    <updated>2020-01-06T03:18:18.977Z</updated>
    
    <content type="html"><![CDATA[<h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>首先我们来看一段代码</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">  a: <span class="number">1</span>,</span><br><span class="line">  foo: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123; <span class="built_in">console</span>.log(<span class="keyword">this</span>.a) &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> foo = obj.foo;</span><br><span class="line"><span class="comment">// 写法一</span></span><br><span class="line">obj.foo()</span><br><span class="line"><span class="comment">// 写法二</span></span><br><span class="line">foo()</span><br></pre></td></tr></table></figure><p>写法一和写法二的执行结果分别是：1、2。之所以运行结果不用，是因为方法中使用了 this 关键字。this 指代方法调用者，即运行时的环境。对于obj.foo()，foo 运行在 obj 中，所以 this 指向 obj。而 foo()，foo 运行在全局，所以 this 指向全局。<br>由此得出结论：</p><ul><li>普通函数的 this 总是指向它的直接调用者。</li><li>在严格模式下，没找到直接调用者，则函数中的 this 是 undefined。</li><li>在默认模式下（非严格模式），没找到直接调用者，则函数中的 this 指向 window。</li></ul><h2 id="this-的由来"><a href="#this-的由来" class="headerlink" title="this 的由来"></a>this 的由来</h2><p>JavaScript 语言之所以有 this 的设计，跟内存里面的数据结构有关系。</p><h4 id="内存数据结果"><a href="#内存数据结果" class="headerlink" title="内存数据结果"></a>内存数据结果</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">foo</span>:  <span class="number">5</span> &#125;;</span><br></pre></td></tr></table></figure><p>上面的代码将一个对象赋值给变量<code>obj</code>。Javascript 引擎会先在内存里面，生成一个对象<code>{ foo: 5 }</code>，然后把这个对象的内存地址赋值给变量<code>obj</code>。<br>也就是说，变量<code>obj</code>是一个地址（reference）。后面如果要读取<code>obj.foo</code>，引擎先从<code>obj</code>拿到内存地址，然后再从该地址读出原始的对象，返回它的<code>foo</code>属性。<br>原始的对象以字典结构保存，每一个属性名都对应一个属性描述对象。举例来说，上面例子的<code>foo</code>属性，实际上是以下面的形式保存的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  foo: &#123;</span><br><span class="line">    [[value]]: <span class="number">5</span></span><br><span class="line">    [[writable]]: <span class="literal">true</span></span><br><span class="line">    [[enumerable]]: <span class="literal">true</span></span><br><span class="line">    [[configurable]]: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，foo属性的值保存在属性描述对象的value属性里面。</p><h4 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h4><p>这样的结构是很清晰的，问题在于属性的值可能是一个函数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">foo</span>: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;&#125; &#125;;</span><br></pre></td></tr></table></figure><p>这时，引擎会将函数单独保存在内存中，然后再将函数的地址赋值给<code>foo</code>属性的<code>value</code>属性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  foo: &#123;</span><br><span class="line">    [[value]]: 函数的地址</span><br><span class="line">    ...</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于函数是一个单独的值，所以它可以在不同的环境（上下文）执行。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">f</span>: f &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 单独执行</span></span><br><span class="line">f()</span><br><span class="line"></span><br><span class="line"><span class="comment">// obj 环境执行</span></span><br><span class="line">obj.f()</span><br></pre></td></tr></table></figure><h4 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h4><p>Javascript 允许在函数体内部，引用当前环境的其他变量。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(x);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上面代码中，函数体里面使用了变量x。该变量由运行环境提供。<br>现在问题就来了，由于函数可以在不同的运行环境执行，所以需要有一种机制，能够在函数体内部获得当前的运行环境（context）。所以，<code>this</code>就出现了，它的设计目的就是在函数体内部，指代函数当前的运行环境。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="keyword">this</span>.x);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码中，函数体里面的<code>this.x</code>就是指当前运行环境的<code>x</code>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="keyword">this</span>.x);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  f: f,</span><br><span class="line">  x: <span class="number">2</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 单独执行</span></span><br><span class="line">f() <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// obj 环境执行</span></span><br><span class="line">obj.f() <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>上面代码中，函数<code>f</code>在全局环境执行，<code>this.x</code>指向全局环境的<code>x</code>。<br><img src="https://www.wangbase.com/blogimg/asset/201806/bg2018061804.png" alt="avatar"><br>在<code>obj</code>环境执行，<code>this.x</code>指向<code>obj.x</code>。<br><img src="https://www.wangbase.com/blogimg/asset/201806/bg2018061805.png" alt="avatar"><br>回到本文开头提出的问题，<code>obj.foo()</code>是通过<code>obj</code>找到<code>foo</code>，所以就是在<code>obj</code>环境执行。一旦<code>var foo = obj.foo</code>，变量<code>foo</code>就直接指向函数本身，所以<code>foo()</code>就变成在全局环境执行。</p><h2 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h2><p>在 ES6 中新增的箭头函数，不仅简化了代码，还解决 this 飘忽不定的指向问题。  </p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    a : <span class="number">1</span>,</span><br><span class="line">    foo : <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        setTimeout(</span><br><span class="line">            <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;<span class="built_in">console</span>.log(<span class="keyword">this</span>.a),<span class="number">3000</span>&#125;)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.foo(); <span class="comment">//undefined</span></span><br></pre></td></tr></table></figure><p>此代码运行结果为 undefined，this 的指向是全局的 window 对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">()=&gt;&#123;<span class="built_in">console</span>.log(<span class="keyword">this</span>)&#125;</span><br></pre></td></tr></table></figure><p>其中()内是要带入的参数，{}内是要执行的语句。箭头函数是函数式编程的一种体现，函数式编程将更多的关注点放在输入和输出的关系，省去了过程的一些因素，因此箭头函数中没有自己的 this，arguments，new target（ES6）和 super(ES6)。箭头函数相当于匿名函数，因此不能使用new来作为构造函数使用。<br>箭头函数中的 this 始终指向其父级作用域中的 this 。换句话说，箭头函数会捕获其所在的上下文的 this 值，作为自己的 this 值。任何方法都改变不了其指向，如 call(), bind(), apply()。在箭头函数中调用 this 时，仅仅是简单的沿着作用域链向上寻找，找到最近的一个 this 拿来使用，它与调用时的上下文无关。我们用代码来解释一下。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>阮一峰的 <a href="http://www.ruanyifeng.com/blog/2018/06/javascript-this.html" target="_blank" rel="noopener">《JavaScript 的 this 原理》</a></li><li><a href="https://www.jianshu.com/p/e5fe25edd78a" target="_blank" rel="noopener">《ES6箭头函数与普通函数的区别》</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;现象&quot;&gt;&lt;a href=&quot;#现象&quot; class=&quot;headerlink&quot; title=&quot;现象&quot;&gt;&lt;/a&gt;现象&lt;/h2&gt;&lt;p&gt;首先我们来看一段代码&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td cla
      
    
    </summary>
    
    
      <category term="Javascript" scheme="http://astrapub.github.io/categories/Javascript/"/>
    
    
      <category term="js" scheme="http://astrapub.github.io/tags/js/"/>
    
      <category term="this" scheme="http://astrapub.github.io/tags/this/"/>
    
      <category term="箭头函数" scheme="http://astrapub.github.io/tags/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0/"/>
    
  </entry>
  
  <entry>
    <title>2019回顾，2020展望</title>
    <link href="http://astrapub.github.io/2020/01/04/review2019/"/>
    <id>http://astrapub.github.io/2020/01/04/review2019/</id>
    <published>2020-01-04T14:12:17.000Z</published>
    <updated>2020-01-05T12:41:49.679Z</updated>
    
    <content type="html"><![CDATA[<p>进入 2020 年已经4个日子了，每次跨年觉得应该有仪式感。当跨年的那一刻，2019像电影一样在脑海过了一遍，不禁要问2019都干了什么。从毕业到现在从事开发工作已经整整5个年头，有感动也有遗憾，5年的时间想想如果能坚持做一件事想想现在应该也小有成就了。</p><p>2019其实是寻找的一年，寻找自己，寻找答案。我不知道答案是否找到了，但确实明白了一些事，不再幻想，不再漫不经心，不再天马星空。越来越明白踏实做事的重要性，不再轻易被情绪控制，不卑不亢。明白世界运行的规律，独立思考，不被洗脑。</p><p>2019也是不破不立的一年，这一年经历了很多，跌宕起伏。值得庆幸的是这一年我跳出了自己的舒适圈，直面自己的弱点，虽然过程会有痛苦，但成长的喜悦难以言表，有些困难过去了就真的过去了。</p><p>目标重要，如何实现目标更重要，还是要有计划，有些看似困难的问题，其实都是有解的，掌握方式方法，刻意去练习，进步会很快。</p><p>2020，持续做好要做的事，丰富自己的知识体系，建立个人品牌，将博客继续写下去，持续写作，如果条件允许出系列教程。</p><p>review 系列也要继续写下去。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;进入 2020 年已经4个日子了，每次跨年觉得应该有仪式感。当跨年的那一刻，2019像电影一样在脑海过了一遍，不禁要问2019都干了什么。从毕业到现在从事开发工作已经整整5个年头，有感动也有遗憾，5年的时间想想如果能坚持做一件事想想现在应该也小有成就了。&lt;/p&gt;
&lt;p&gt;20
      
    
    </summary>
    
    
      <category term="回顾" scheme="http://astrapub.github.io/categories/%E5%9B%9E%E9%A1%BE/"/>
    
    
      <category term="回顾" scheme="http://astrapub.github.io/tags/%E5%9B%9E%E9%A1%BE/"/>
    
      <category term="总结" scheme="http://astrapub.github.io/tags/%E6%80%BB%E7%BB%93/"/>
    
      <category term="2019" scheme="http://astrapub.github.io/tags/2019/"/>
    
  </entry>
  
  <entry>
    <title>Hexo 使用教程</title>
    <link href="http://astrapub.github.io/2020/01/03/hexo-course/"/>
    <id>http://astrapub.github.io/2020/01/03/hexo-course/</id>
    <published>2020-01-03T09:16:49.000Z</published>
    <updated>2020-01-04T08:18:57.647Z</updated>
    
    <content type="html"><![CDATA[<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>首先需要安装 node.js，node.js 安装教程可参看 <a href="https://www.runoob.com/nodejs/nodejs-install-setup.html" target="_blank" rel="noopener">https://www.runoob.com/nodejs/nodejs-install-setup.html</a><br>然后执行<code>npm install -g hexo</code> 或 <code>yarn global add hexo</code> 安装 Hexo。</p><h2 id="建站"><a href="#建站" class="headerlink" title="建站"></a>建站</h2><p>安装 Hexo 完成后，请执行下列命令，Hexo 将会在指定文件夹中新建所需要的文件。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ hexo init &lt;folder&gt;</span><br><span class="line">$ cd &lt;folder&gt;</span><br><span class="line">$ npm i 或 yarn install</span><br></pre></td></tr></table></figure><p>目录结构如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── _config.yml</span><br><span class="line">├── package.json</span><br><span class="line">├── scaffolds</span><br><span class="line">├── source</span><br><span class="line">|   ├── _drafts</span><br><span class="line">|   └── _posts</span><br><span class="line">└── themes</span><br></pre></td></tr></table></figure><p>在执行 hexo init 的时候可能git会报错，可以到 <a href="https://github.com/hexojs/hexo-starter.git" target="_blank" rel="noopener">https://github.com/hexojs/hexo-starter.git</a> 这个地址直接把 Hexo 项目下载下来，然后改成自己项目名字。  </p><h4 id="scaffolds"><a href="#scaffolds" class="headerlink" title="scaffolds"></a>scaffolds</h4><p><a href="https://hexo.io/zh-cn/docs/writing" target="_blank" rel="noopener">模版</a> 文件夹。当您新建文章时，Hexo 会根据 scaffold 来建立文件。<br>Hexo 的模板是指在新建的文章文件中默认填充的内容。例如，如果您修改 scaffold/post.md 中的 Front-matter 内容，那么每次新建一篇文章时都会包含这个修改。</p><h4 id="config-yml"><a href="#config-yml" class="headerlink" title="_config.yml"></a>_config.yml</h4><p>网站的配置信息，可以<a href="https://hexo.io/zh-cn/docs/configuration" target="_blank" rel="noopener">配置</a>网站名称、关键字、语言、作者、时区等信息。</p><h4 id="source"><a href="#source" class="headerlink" title="source"></a>source</h4><p>资源文件夹是存放用户资源的地方。除 _posts 文件夹之外，开头命名为 _ (下划线)的文件 / 文件夹和隐藏的文件将会被忽略。Markdown 和 HTML 文件会被解析并放到 public 文件夹，而其他文件会被拷贝过去。</p><h4 id="themes"><a href="#themes" class="headerlink" title="themes"></a>themes</h4><p>主题文件夹。可以去 <a href="https://hexo.io/themes/" target="_blank" rel="noopener">https://hexo.io/themes/</a> 寻找喜欢的主题。  </p><h2 id="安装主题"><a href="#安装主题" class="headerlink" title="安装主题"></a>安装主题</h2><p>我使用的主题是 <a href="https://blog.cofess.com" target="_blank" rel="noopener">pure</a> </p><h3 id="启用主题"><a href="#启用主题" class="headerlink" title="启用主题"></a>启用主题</h3><p>在你的 hexo 项目文件夹下执行：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone https:&#x2F;&#x2F;github.com&#x2F;cofess&#x2F;hexo-theme-pure.git themes&#x2F;pure</span><br></pre></td></tr></table></figure><p>修改配置文件 <code>hexo/_config.yml</code> 中 <code>theme</code> 为 <code>pure</code>。</p><h3 id="gitalk-评论"><a href="#gitalk-评论" class="headerlink" title="gitalk 评论"></a>gitalk 评论</h3><p>Gitalk 是一个基于 Github Issue 的评论插件，每增加一条评论相应的在 GitHub 项目增加一条 Issue。 </p><h4 id="注册-GitHub-Application"><a href="#注册-GitHub-Application" class="headerlink" title="注册 GitHub Application"></a>注册 GitHub Application</h4><p>到 <a href="https://github.com/settings/applications/new" target="_blank" rel="noopener">https://github.com/settings/applications/new</a> 注册 GitHub Application<br>填写 Application name（项目名称），Homepage URL（博客链接），Application description（描述），authorization callback URL（回调地址，博客链接）等信息，完成注册，获取到 Client ID 和 Client Secret。<br>如果项目在 Organization 下，需要进入到相应的 Organization，点击 Settings &gt; OAuth Apps，注册 GitHub Application。</p><h4 id="配置-config-yml"><a href="#配置-config-yml" class="headerlink" title="配置 _config.yml"></a>配置 _config.yml</h4><p>配置文件位置 themes/pure/_config.yml。<br>修改 comment &gt; type 为：gitalk，将 Client ID 和 Client Secret 填写的相应位置，并填写 owner，admin，repo。owner 为项目所有者，如果是 Organization，这填写 Organization 名称。admin 为你的用户名，repo 对应 GitHub 中的项目名。</p><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><h5 id="创建一个新网站"><a href="#创建一个新网站" class="headerlink" title="创建一个新网站"></a>创建一个新网站</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo init [folder]</span><br></pre></td></tr></table></figure><h5 id="新建一篇文章"><a href="#新建一篇文章" class="headerlink" title="新建一篇文章"></a>新建一篇文章</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new [layout] &lt;title&gt;</span><br></pre></td></tr></table></figure><p>layout 可选，如果没有设置 layout 的话，默认使用 _config.yml 中的 default_layout 参数代替。如果标题包含空格的话，请使用引号括起来。如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new &quot;post title with whitespace&quot;</span><br></pre></td></tr></table></figure><table><thead><tr><th>参数</th><th>描述</th></tr></thead><tbody><tr><td>-p, –path</td><td>自定义新文章的路径</td></tr><tr><td>-r, –replace</td><td>如果存在同名文章，将其替换</td></tr><tr><td>-s, –slug</td><td>文章的 Slug，作为新文章的文件名和发布后的 URL</td></tr></tbody></table><p>默认情况下，Hexo 会使用文章的标题来决定文章文件的路径。对于独立页面来说，Hexo 会创建一个以标题为名字的目录，并在目录中放置一个 index.md 文件。你可以使用 –path 参数来覆盖上述行为、自行决定文件的目录：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new page --path about&#x2F;me &quot;About me&quot;</span><br></pre></td></tr></table></figure><h5 id="生成静态文件"><a href="#生成静态文件" class="headerlink" title="生成静态文件"></a>生成静态文件</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate 或 g</span><br></pre></td></tr></table></figure><table><thead><tr><th>参数</th><th>描述</th></tr></thead><tbody><tr><td>-d, –deploy</td><td>文件生成后立即部署网站</td></tr><tr><td>-w, –watch</td><td>监视文件变动</td></tr></tbody></table><p><strong>示例</strong><br>检测文件变动实时预览：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g -w</span><br></pre></td></tr></table></figure><p>生成文件自动部署：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g -d</span><br></pre></td></tr></table></figure><h5 id="启动服务器"><a href="#启动服务器" class="headerlink" title="启动服务器"></a>启动服务器</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server 或 s</span><br></pre></td></tr></table></figure><p>默认情况下，访问网址为： <a href="http://localhost:4000/。" target="_blank" rel="noopener">http://localhost:4000/。</a></p><h5 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>部署方式请看 <a href="https://hexo.io/zh-cn/docs/one-command-deployment" target="_blank" rel="noopener">https://hexo.io/zh-cn/docs/one-command-deployment</a></p><h5 id="清除缓存文件"><a href="#清除缓存文件" class="headerlink" title="清除缓存文件"></a>清除缓存文件</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;安装&quot;&gt;&lt;a href=&quot;#安装&quot; class=&quot;headerlink&quot; title=&quot;安装&quot;&gt;&lt;/a&gt;安装&lt;/h2&gt;&lt;p&gt;首先需要安装 node.js，node.js 安装教程可参看 &lt;a href=&quot;https://www.runoob.com/nodejs/
      
    
    </summary>
    
    
      <category term="Hexo" scheme="http://astrapub.github.io/categories/Hexo/"/>
    
    
      <category term="Hexo" scheme="http://astrapub.github.io/tags/Hexo/"/>
    
  </entry>
  
  <entry>
    <title>Docker 搭建以太坊私有链</title>
    <link href="http://astrapub.github.io/2018/05/18/eth-docker/"/>
    <id>http://astrapub.github.io/2018/05/18/eth-docker/</id>
    <published>2018-05-18T02:21:03.000Z</published>
    <updated>2020-02-25T12:20:25.332Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Docker-搭建以太坊私有链"><a href="#Docker-搭建以太坊私有链" class="headerlink" title="Docker 搭建以太坊私有链"></a>Docker 搭建以太坊私有链</h1><p>首先需要安装 Docker，Docker 的安装和使用可以参看阮一峰老师的<a href="http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html" target="_blank" rel="noopener">《Docker 入门教程》</a>。<br>Ethereum 官方是支持 docker 的，可以参看<a href="https://github.com/ethereum/go-ethereum" target="_blank" rel="noopener">官方文档</a>。</p><h2 id="1-前期准备"><a href="#1-前期准备" class="headerlink" title="1.前期准备"></a>1.前期准备</h2><p><strong>centOS</strong></p><ol><li>创建目录 ~/works/block-chain/ethereum</li><li>在 ethereum 目录下编写 start-ethereum.sh 文件内容如下<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker stop ethereum-node</span><br><span class="line">docker rm ethereum-node</span><br><span class="line"></span><br><span class="line">docker run -d --name ethereum-node -v /home/linshan/works/block-chain/ethereum:/root \</span><br><span class="line">           -p 8545:8545 -p 30303:30303 -p 8200:8200\</span><br><span class="line">           ethereum/client-go</span><br><span class="line">docker <span class="built_in">exec</span> -it ethereum-node /bin/sh</span><br></pre></td></tr></table></figure>其中 -v /home/linshan/works/block-chain/ethereum:/root 是把我们当前的 ethereum 目录，挂到了docker 的 /root 下。<br>在 Windows 环境下使用  -v /home/linshan/works/block-chain/ethereum:/root  不能启动容器，原因不明，所以在 Windows 下先不要使用目录挂载。</li><li>接下来创建创世区块<br>在 ethereum 目录下编写 genesis.json 文件内容如下<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="attr">"config"</span>: &#123;</span><br><span class="line">                <span class="attr">"chainId"</span>: <span class="number">622</span> ,</span><br><span class="line">                <span class="attr">"homesteadBlock"</span>: <span class="number">0</span>,</span><br><span class="line">                <span class="attr">"eip155Block"</span>: <span class="number">0</span>,</span><br><span class="line">                <span class="attr">"eip158Block"</span>: <span class="number">0</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">"coinbase"</span> : <span class="string">"0x0000000000000000000000000000000000000000"</span>,</span><br><span class="line">        <span class="attr">"difficulty"</span> : <span class="string">"200"</span>,</span><br><span class="line">        <span class="attr">"extraData"</span> : <span class="string">""</span>,</span><br><span class="line">        <span class="attr">"gasLimit"</span> : <span class="string">"0xffffffff"</span>,</span><br><span class="line">        <span class="attr">"nonce"</span> : <span class="string">"0x0000000000000042"</span>,</span><br><span class="line">        <span class="attr">"mixhash"</span> : <span class="string">"0x0000000000000000000000000000000000000000000000000000000000000000"</span>,</span><br><span class="line">        <span class="attr">"parentHash"</span> : <span class="string">"0x0000000000000000000000000000000000000000000000000000000000000000"</span>,</span><br><span class="line">        <span class="attr">"timestamp"</span> : <span class="string">"0x00"</span>,</span><br><span class="line">        <span class="attr">"alloc"</span>: &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>各字段具体用途参看<a href="https://github.com/ethereum/go-ethereum" target="_blank" rel="noopener">官方文档</a>。</li></ol><p><strong>Windows</strong><br>Windows 不必创建 start-ethereum.sh 文件， genesis.json 也可以在 Docker 容器启动后创建。</p><h2 id="2-启动以太坊-Docker-容器"><a href="#2-启动以太坊-Docker-容器" class="headerlink" title="2.启动以太坊 Docker 容器"></a>2.启动以太坊 Docker 容器</h2><p><strong>centOS</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ sudo ./start-ethereum.sh</span><br><span class="line">Error response from daemon: No such container: ethereum-node</span><br><span class="line">5ef5cf1eebe96a0b6f8bc777dc69442d302aed1b086c723fb11360459347cf15</span><br><span class="line">/ <span class="comment">#</span></span><br><span class="line">/ <span class="comment"># ls</span></span><br><span class="line">bin    dev    etc    home   lib    media  mnt    proc   root   run    sbin   srv    sys    tmp    usr    var</span><br><span class="line">/ <span class="comment"># cd /root/</span></span><br><span class="line">~ <span class="comment"># ls</span></span><br><span class="line">genesis.json</span><br></pre></td></tr></table></figure><p><strong>Windows</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name ethereum-node -p 8545:8545 -p 30303:30303 -p 8200:8200 ethereum&#x2F;client-go</span><br></pre></td></tr></table></figure><p>运行成功后执行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it ethereum-node &#x2F;bin&#x2F;sh</span><br></pre></td></tr></table></figure><p>进入 docker 容器命令行<br>因为 Windows 没有挂载共享目录，所以 root 目录下没有 genesis.json 文件，我们要在这里手动创建 genesis.json 文件，内容要和 centOS 的一致。</p><h2 id="3-初始化-geth"><a href="#3-初始化-geth" class="headerlink" title="3.初始化 geth"></a>3.初始化 geth</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">~ <span class="comment"># geth --datadir ./data init ./genesis.json</span></span><br></pre></td></tr></table></figure><p>data 用来存放区块数据</p><h2 id="4-启动私有链"><a href="#4-启动私有链" class="headerlink" title="4.启动私有链"></a>4.启动私有链</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">~ <span class="comment"># geth --datadir ./data --networkid 622 --port 8200 --rpc  --rpcaddr 0.0.0.0  --rpccorsdomain "*" --rpcport 8545 --nodiscover console</span></span><br></pre></td></tr></table></figure><p>geth 的参数参看<a href="https://www.cnblogs.com/tinyxiong/p/7918706.html" target="_blank" rel="noopener">以太坊客户端Geth命令用法-参数详解 </a></p><h2 id="5-连接节点"><a href="#5-连接节点" class="headerlink" title="5.连接节点"></a>5.连接节点</h2><p>启动私有节点后进入 geth 命令行执行：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; admin.nodeInfo.enode</span><br><span class="line">&quot;enode:&#x2F;&#x2F;a1ae9aef2b0575875fc366cf9057e6fe7182068c2f6570859315400b32b7b341bd90e1b65fd59291800f4d6196640420fba52f79ceaff793a3cff51b49281634@[::]:8200?discport&#x3D;0&quot;</span><br></pre></td></tr></table></figure><p>输出的内容就是节点信息，我们在手动连接节点是会用到，注意要把“0.0.0.0“换成你自己的IP，然后将这个信息发送给其他节点。手动连接节点有两种方式：  </p><ol><li>在节点启动时连接：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">geth --datadir .&#x2F;data --networkid 622  --ipcdisable --port 8200 --rpc  --rpcaddr 0.0.0.0  --rpccorsdomain &quot;*&quot; --rpcport 8545 --bootnodes &quot;enode:&#x2F;&#x2F;a1ae9aef2b0575875fc366cf9057e6fe7182068c2f6570859315400b32b7b341bd90e1b65fd59291800f4d6196640420fba52f79ceaff793a3cff51b49281634@[::]:8200&quot; --nodiscover console</span><br></pre></td></tr></table></figure></li><li>使用 admin.addPeer 连接<br>如果节点已经启动可以使用该方法。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">admin.addPeer(&quot;enode:&#x2F;&#x2F;a1ae9aef2b0575875fc366cf9057e6fe7182068c2f6570859315400b32b7b341bd90e1b65fd59291800f4d6196640420fba52f79ceaff793a3cff51b49281634@[::]:8200&quot;)</span><br></pre></td></tr></table></figure>当然不管使用哪种方法连接节点都要保证创世区块文件 genesis.json 一致，还有在启动时 networkid 也要一致。</li></ol><p>至此以太坊私有链已搭建完毕。</p><h2 id="6-geth-docker-常用命令"><a href="#6-geth-docker-常用命令" class="headerlink" title="6.geth docker 常用命令"></a>6.geth docker 常用命令</h2><p><strong>geth命令</strong>  </p><ul><li><p>geth –rpc  –rpcaddr 0.0.0.0  –rpccorsdomain “*”  –datadir ./data  –networkid 622  console 启动节点（<a href="https://www.cnblogs.com/tinyxiong/p/7918706.html）" target="_blank" rel="noopener">https://www.cnblogs.com/tinyxiong/p/7918706.html）</a></p></li><li><p>geth  –datadir “~/ethdev” –dev   以开发方式启动geth （<a href="https://blog.csdn.net/CHENYUFENG1991/article/details/53458175?locationNum=7&amp;fps=1）" target="_blank" rel="noopener">https://blog.csdn.net/CHENYUFENG1991/article/details/53458175?locationNum=7&amp;fps=1）</a></p></li><li><p>geth –dev console 2&gt;&gt;file_to_log_output   进入geth控制台</p></li><li><p>personal.newAccount() 创建账户</p></li><li><p>eth.accounts 查看账户</p></li><li><p>eth.getBalance(eth.accounts[0]) 查看账户余额</p></li><li><p>miner.setEtherbase(eth.accounts[0]) 设置挖矿地址</p></li><li><p>miner.start() 启动挖矿</p></li><li><p>miner.stop() 停止挖矿</p></li><li><p>admin.nodeInfo 节点信息</p></li><li><p>eth.blockNumber 查看区块数</p></li><li><p>exit 退出geth控制台</p></li><li><p>personal.unlockAccount(acc0) 解锁账号</p></li><li><p>eth.sendTransaction({from:acc0,to:acc1,value:web3.toWei(20,”ether”)}) 转账</p></li></ul><p><strong>docker命令</strong></p><ul><li><p>exit 交互型容器退出（<a href="https://blog.csdn.net/u010246789/article/details/53958662）" target="_blank" rel="noopener">https://blog.csdn.net/u010246789/article/details/53958662）</a></p></li><li><p>docker create 创建容器</p></li><li><p>docker start 启动容器</p></li><li><p>docker ps 查看当前运行的容器</p></li><li><p>docker ps -a 查看所有容器，包括停止的。</p></li><li><p>docker stop [NAME]/[CONTAINER ID] 将容器退出。</p></li><li><p>docker kill [NAME]/[CONTAINER ID] 强制停止一个容器。</p></li><li><p>docker rm [NAME]/[CONTAINER ID] 删除容器，不能够删除一个正在运行的容器，会报错。需要先停止容器。</p></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Docker-搭建以太坊私有链&quot;&gt;&lt;a href=&quot;#Docker-搭建以太坊私有链&quot; class=&quot;headerlink&quot; title=&quot;Docker 搭建以太坊私有链&quot;&gt;&lt;/a&gt;Docker 搭建以太坊私有链&lt;/h1&gt;&lt;p&gt;首先需要安装 Docker，Dock
      
    
    </summary>
    
    
      <category term="区块链" scheme="http://astrapub.github.io/categories/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    
    
      <category term="Docker" scheme="http://astrapub.github.io/tags/Docker/"/>
    
      <category term="区块链" scheme="http://astrapub.github.io/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    
      <category term="以太坊" scheme="http://astrapub.github.io/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
    
  </entry>
  
  <entry>
    <title>接入以太坊（Ethereum）测试网络</title>
    <link href="http://astrapub.github.io/2018/03/30/eth-test/"/>
    <id>http://astrapub.github.io/2018/03/30/eth-test/</id>
    <published>2018-03-30T11:01:31.000Z</published>
    <updated>2020-02-25T12:20:56.156Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是测试网络"><a href="#什么是测试网络" class="headerlink" title="什么是测试网络"></a>什么是测试网络</h2><p>以太坊为了方便智能合约的开发、学习和测试，开启了一条全新的区块链，与主网络特性相同，但测试网络中的以太币价值更低，也更容易得到。这样不至于在主网络上开发出现 BUG 造成以太币的损失。</p><p>当然我们也可以搭建私有的测试网络，不过区块链的去中心化特点，需要更多的节点运行才能达到理想效果，好在以太坊有公开的测试网络，而我们接入也更容易。</p><h2 id="以太坊测试网络"><a href="#以太坊测试网络" class="headerlink" title="以太坊测试网络"></a>以太坊测试网络</h2><ul><li><a href="https://ropsten.etherscan.io/" target="_blank" rel="noopener">Ropsten</a></li></ul><p>Ropsten也是以太坊官方提供的测试网络，是为了解决Morden难度炸弹问题而重新启动的一条区块链，目前仍在运行，共识机制为PoW。测试网络上的以太币并无实际价值，因此Ropsten的挖矿难度很低，目前在755M左右，仅仅只有主网络的0.07%。这样低的难度一方面使一台普通笔记本电脑的CPU也可以挖出区块，获得测试网络上的以太币，方便开发人员测试软件，但是却不能阻止攻击。</p><p>PoW共识机制要求有足够强大的算力保证没有人可以随意生成区块，这种共识机制只有在具有实际价值的主网络中才会有效。测试网络上的以太币没有价值，也就不会有强大的算力投入来维护测试网络的安全，这就导致了测试网络的挖矿难度很低，即使几块普通的显卡，也足以进行一次51%攻击，或者用垃圾交易阻塞区块链，攻击的成本及其低廉。</p><p>2017年2月，Ropsten便遭到了一次利用测试网络的低难度进行的攻击，攻击者发送了千万级的垃圾交易，并逐渐把区块Gas上限从正常的4,700,000提高到了90,000,000,000，在一段时间内，影响了测试网络的运行。攻击者发动这些攻击，并不能获得利益，仅仅是为了测试、炫耀、或者单纯觉得好玩儿。</p><ul><li><strong>Rinkeby</strong></li></ul><p>Rinkeby也是以太坊官方提供的测试网络，使用PoA共识机制。与Ropsten不同，以太坊团队提供了Rinkeby的PoA共识机制说明文档，理论上任何以太坊钱包都可以根据这个说明文档，支持Rinkeby测试网络，目前Rinkeby已经开始运行。</p><h2 id="安装以太钱包"><a href="#安装以太钱包" class="headerlink" title="安装以太钱包"></a>安装以太钱包</h2><p>下载地址：<a href="https://github.com/ethereum/mist/releases" target="_blank" rel="noopener">https://github.com/ethereum/mist/releases</a></p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LWVkNzZhMjQzZjNlYTY5MTkucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>目前最新的版本是0.10.0，根据操作系统下载相应版本。</p><p>MIST其实只是以太坊钱包的一个图形界面，后端还是官方的Geth，只是可以使用图形化的方式操作，减少了出错的几率，降低使用门槛。MIST是使用<a href="https://link.zhihu.com/?target=http%3A//electron.atom.io/">Electron</a>开发的，具有跨平台的能力，所以在各个系统上的界面和操作应该是基本一致的。</p><p>第一次启动</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LWQzMGNlZmQyYWQxYzBlNTkucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>稍等一会会出现 LAUNCH APPLICATION 按钮，点击启动钱包。</p><ul><li>切换到 Ropsten 网络</li></ul><p>启动后点击菜单栏 “开发”&gt;“网络” 选择“Ropsten - Test network”</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LWE0ZDUyYTA4YmQxZmM3NGYucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>然后点击菜单栏 “账户”&gt;“新建账户”然后输入密码创建账户</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LTc0N2JlNjVlZGVjOGU4NmUucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>然后可以开启挖矿，用不了多久就可以获得 Ropsten 网络的以太币。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LTkxNGRlMzMyZGMzZjM3ZTQucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><ul><li>切换到 Rinkeby 网络</li></ul><p>同 Ropsten 选择 “Rinkeby - Test network”</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LWM2NDFjZmYxMjQ3MWMzYmQucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>切换完网络后同样需要创建账户。</p><h2 id="获取-Rinkeby-网络的以太币"><a href="#获取-Rinkeby-网络的以太币" class="headerlink" title="获取 Rinkeby 网络的以太币"></a>获取 Rinkeby 网络的以太币</h2><p>Rinkeby测试网络使用的是PoA共识机制，我们不能通过挖矿来获取以太币。</p><p>想获取Rinkeby测试网络中的以太币，需要去申请，这个申请Rinkeby以太币的功能被称为<strong>水龙头(Faucet)</strong>。还真是挺形象的，水龙头会源源不断的产生以太币，并且受到权威节点控制，以确保不会被滥用。</p><p>进入这个水龙头的网站：<a href="https://faucet.rinkeby.io/" target="_blank" rel="noopener">Rinkeby: GitHub Faucet </a></p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LWM3YmY1YzhiZmY5MjA3ZWQucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>为了确保不会出现有人滥用水龙头，无限生成Rinkeby以太币，水龙头需要借助社交账号来确定申请者的身份和配额。目前支持 twitter  , Google Plus , Facebook，取消了 Github 账户。不过最先测试了 Facebook 没有成功，可能是打开方式不对，如果有成功的小伙伴记得告诉我啊。然后是 twitter 亲测可用。</p><p>打开 [twitter](<a href="https://twitter.com/intent/tweet?text=Requesting" target="_blank" rel="noopener">https://twitter.com/intent/tweet?text=Requesting</a> faucet funds into 0x0000000000000000000000000000000000000000 on the %23Rinkeby %23Ethereum test network.)，然后把钱包账户的地址粘贴进去，然后点击 “Tweet”。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LTAxZTQxZDlhYzI3NmZmMDYucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>然后进入 twitter 首页，找到你发的 twitter 点击右上角的小箭头，选择“Copy link to Tweet”。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LTYzYzI5NGM2Y2Y4MWQ3ZTIucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>然后回到水龙头的网站，将拷贝的链接粘贴到输入框，点击 “Give me Ether”有三种选项，前面是获得的以太币数量，后面是冷却时间，在冷却时间过后才能进行下一次以太币申请。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LWFlYTg0YjgwNTY0YzE3YzkucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>如果一切顺利，你会看到你的钱包地址已经多出了申请数量的以太币，我申请了两次，在钱包中还看不到余额，不过在区块浏览器中可以看到 <a href="https://rinkeby.etherscan.io/address/0x53Ac8771A2f7C8730D94Bea19466F05C19aFbE22" target="_blank" rel="noopener">0x53Ac8771A2f7C8730D94Bea19466F05C19aFbE22</a>。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy81MDg4MTQ5LTYxYTJjMTZmNDY5NTc4NjgucG5n?x-oss-process=image/format,png" alt="img">)<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动"></p><p>如果申请的人数很多，需要排队等待一会儿，申请的以太币金额越大，一般需要等待越多的时间才能到账。</p><p><strong>为什么申请测试网络的以太币如此繁琐呢？</strong></p><p>以太币在以太坊平台中的设计功能是用来支付EVM中执行指令消耗的Gas，如果可以被无限制的产生，就会出现有恶意用户出于各种目的，用无限制的以太币换无限制的Gas，在EVM中执行超多的指令，并逐渐抬高区块Gas上限。EVM中的指令要在每一个以太坊节点中执行，这种攻击一旦出现，对网络将会产生很大的影响，所以测试网络中的以太币必须针对每个开发者限量供应。不过这个限量对正常的开发测试来说，几乎不会造成影响。</p><p>如果你看到了这里，并且成功的在自己的电脑上运行了钱包、连接测试网络、申请以太币，那么恭喜你，你已经做好了进一步学习和了解以太坊的准备。</p><p>参考：<a href="https://zhuanlan.zhihu.com/p/29010231" target="_blank" rel="noopener">玩转以太坊(Ethereum)的测试网络</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;什么是测试网络&quot;&gt;&lt;a href=&quot;#什么是测试网络&quot; class=&quot;headerlink&quot; title=&quot;什么是测试网络&quot;&gt;&lt;/a&gt;什么是测试网络&lt;/h2&gt;&lt;p&gt;以太坊为了方便智能合约的开发、学习和测试，开启了一条全新的区块链，与主网络特性相同，但测试网络中的以
      
    
    </summary>
    
    
      <category term="区块链" scheme="http://astrapub.github.io/categories/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    
    
      <category term="区块链" scheme="http://astrapub.github.io/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    
      <category term="以太坊" scheme="http://astrapub.github.io/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
    
      <category term="Ropsten" scheme="http://astrapub.github.io/tags/Ropsten/"/>
    
  </entry>
  
</feed>
