<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Fomalhaut🥝</title>
  
  
  <link href="https://www.fomal.cc/atom.xml" rel="self"/>
  
  <link href="https://www.fomal.cc/"/>
  <updated>2023-09-14T12:49:57.927Z</updated>
  <id>https://www.fomal.cc/</id>
  
  <author>
    <name>Fomalhaut🥝</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>文言文鉴赏——滕王阁序</title>
    <link href="https://www.fomal.cc/posts/0.html"/>
    <id>https://www.fomal.cc/posts/0.html</id>
    <published>2026-04-26T03:55:15.733Z</published>
    <updated>2023-09-14T12:49:57.927Z</updated>
    
    <content type="html"><![CDATA[<div class='poem'><div class='poem-title'>滕王阁序</div><div class='poem-author'>王勃</div><p>豫章故郡，洪都新府。星分翼轸，地接衡庐。襟三江而带五湖，控蛮荆而引瓯越。物华天宝，龙光射牛斗之墟；人杰地灵，徐孺下陈蕃之榻。雄州雾列，俊采星驰。台隍枕夷夏之交，宾主尽东南之美。都督阎公之雅望，棨戟遥临；宇文新州之懿范，襜帷暂驻。十旬休假，胜友如云；千里逢迎，高朋满座。腾蛟起凤，孟学士之词宗；紫电青霜，王将军之武库。家君作宰，路出名区；童子何知，躬逢胜饯。时维九月，序属三秋。潦水尽而寒潭清，烟光凝而暮山紫。俨骖騑于上路，访风景于崇阿；临帝子之长洲，得天人之旧馆。层峦耸翠，上出重霄；飞阁流丹，下临无地。鹤汀凫渚，穷岛屿之萦回；桂殿兰宫，列冈峦之体势。披绣闼，俯雕甍，山原旷其盈视，川泽纡其骇瞩。闾阎扑地，钟鸣鼎食之家；舸舰弥津，青雀黄龙之舳。云销雨霁，彩彻区明。落霞与孤鹜齐飞，秋水共长天一色。渔舟唱晚，响穷彭蠡之滨；雁阵惊寒，声断衡阳之浦。遥襟甫畅，逸兴遄飞。爽籁发而清风生，纤歌凝而白云遏。睢园绿竹，气凌彭泽之樽；邺水朱华，光照临川之笔。四美具，二难并。穷睇眄于中天，极娱游于暇日。天高地迥，觉宇宙之无穷；兴尽悲来，识盈虚之有数。望长安于日下，目吴会于云间。地势极而南溟深，天柱高而北辰远。关山难越，谁悲失路之人；萍水相逢，尽是他乡之客。怀帝阍而不见，奉宣室以何年？嗟乎！时运不齐，命途多舛。冯唐易老，李广难封。屈贾谊于长沙，非无圣主；窜梁鸿于海曲，岂乏明时？所赖君子见机，达人知命。老当益壮，宁移白首之心；穷且益坚，不坠青云之志。酌贪泉而觉爽，处涸辙以犹欢。北海虽赊，扶摇可接；东隅已逝，桑榆非晚。孟尝高洁，空余报国之情；阮籍猖狂，岂效穷途之哭！勃，三尺微命，一介书生。无路请缨，等终军之弱冠；有怀投笔，慕宗悫之长风。舍簪笏于百龄，奉晨昏于万里。非谢家之宝树，接孟氏之芳邻。他日趋庭，叨陪鲤对；今兹捧袂，喜托龙门。杨意不逢，抚凌云而自惜；钟期既遇，奏流水以何惭？呜呼！胜地不常，盛筵难再；兰亭已矣，梓泽丘墟。临别赠言，幸承恩于伟饯；登高作赋，是所望于群公。敢竭鄙怀，恭疏短引；一言均赋，四韵俱成。请洒潘江，各倾陆海云尔：</p></div>]]></content>
    
    
    <summary type="html">愈少时则闻江南多临观之关，而滕王阁独为第一，有瑰伟绝特之称。————韩愈</summary>
    
    
    
    <category term="鉴赏" scheme="https://www.fomal.cc/categories/%E9%89%B4%E8%B5%8F/"/>
    
    
    <category term="文言文鉴赏" scheme="https://www.fomal.cc/tags/%E6%96%87%E8%A8%80%E6%96%87%E9%89%B4%E8%B5%8F/"/>
    
  </entry>
  
  <entry>
    <title>数据结构习题（二）</title>
    <link href="https://www.fomal.cc/posts/11edfcfa.html"/>
    <id>https://www.fomal.cc/posts/11edfcfa.html</id>
    <published>2024-09-11T02:00:00.000Z</published>
    <updated>2024-08-10T12:01:30.783Z</updated>
    
    <content type="html"><![CDATA[<h1>习题（二）</h1><h2 id="一、选择题">一、选择题</h2><h3 id="1-顺序表中第一个元素的存储地址是100-每个元素的长度为2-则第5个元素的地址是（-）。">1.顺序表中第一个元素的存储地址是100, 每个元素的长度为2, 则第5个元素的地址是（ ）。</h3><p><strong>A. 110</strong></p><p><strong>B. 108</strong></p><p><strong>C. 100</strong></p><p><strong>D. 120</strong></p><p><em>答:B</em></p><h3 id="2-在含n个结点的顺序表中，算法的时间复杂度是O-1-的操作是-。">**2.<strong>在含n个结点的顺序表中，算法的时间复杂度是</strong>O(1)**的操作是( )。</h3><p><strong>A. 访问第i 个结点 (1&lt;=i&lt;=n) 和求第i 个结点的直接前驱 (2&lt;=i&lt;=n)</strong></p><p><strong>B. 在第i 个结点后插入一个新结点 (1&lt;=i&lt;=n)</strong></p><p><strong>C. 删除第1 个结点 (1&lt;=i&lt;=n)</strong></p><p><strong>D. 将n个结点从小到大排序</strong></p><p><em>答：A</em></p><h3 id="3-在一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变，平均要移动的元素个数为（-）。">3.在一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变，平均要移动的元素个数为（ ）。</h3><p><em><strong>A. 8</strong></em></p><p><strong>B. 63.5</strong></p><p><strong>C. 63</strong></p><p><strong>D. 7</strong></p><p><em>答：B。 <strong>顺序表插入算法分析</strong>：$\large E_{ins}=\dfrac{1}{n+1}\sum\limits_{i=1}^{n+1}(n-i+1)=\dfrac{n}{2}$</em></p><h3 id="4-链接存储的存储结构所占存储空间-。">4.链接存储的存储结构所占存储空间( )。</h3><p><strong>A. 分为两部分，一部分存放结点值，另一部分存放表示结点间关系的指针</strong>**</p><p><strong>B. 只有一部分， 存放结点值</strong></p><p><strong>C. 只有一部分， 存储表示结点间关系的指针</strong></p><p><strong>D. 分两部分， 一部分存放结点值，另一部分存放结点所占单元数</strong></p><p><em>答：A</em></p><h3 id="5-线性表若采用链式存储结构，要求内存中可用存储单元的地址（-）。">5.线性表若采用链式存储结构，要求内存中可用存储单元的地址（ ）。</h3><p><strong>A. 必须是连续的</strong></p><p><strong>B. 部分地址必须是连续的</strong></p><p><strong>C. 一定是不连续的</strong></p><p><strong>D. 连续或不连续都可以</strong></p><p><em>答：D</em></p><h3 id="6-线性表-L-在（-）情况下适用千使用链式结构实现。"><strong>6</strong>.线性表 <strong>L</strong> 在（ ）情况下适用千使用链式结构实现。</h3><p><strong>A. 需经常修改 L中的结点值</strong></p><p><strong>B. 需不断对 L 进行删除、 插入</strong></p><p><strong>C. L 中含有大量的结点</strong></p><p><strong>D. L 中结点结构复杂</strong></p><p><em>答：B</em></p><h3 id="7-单链表的存储密度（-）。">7.单链表的存储密度（ ）。</h3><p><strong>A. 大于1</strong></p><p><strong>B. 等于1</strong></p><p><strong>C. 小于1</strong></p><p><strong>D. 不能确定</strong></p><p><em>答：C</em></p><h3 id="8-将两个各有n个元素的有序表归并成一个有序表，-其最少的比较次数是（）。"><strong>8.</strong> 将两个各有n个元素的有序表归并成一个有序表， 其最少的比较次数是（）。</h3><p><strong>A. n</strong></p><p><strong>B. 2n- 1</strong></p><p><strong>C. 2n</strong></p><p><strong>D. n-1</strong></p><p><em>答：A</em></p><h3 id="9-在一个长度为n的顺序表中，在第i个元素-1-i-n-1-之前插入一个新元素时需向后移动（-）-个元素。">9.在一个长度为n的顺序表中，在第i个元素(1&lt;=i&lt;=n+1) 之前插入一个新元素时需向后移动（ ） 个元素。</h3><p><strong>A. n-i</strong></p><p><strong>B. n -i+1</strong></p><p><strong>C. n - i -1</strong></p><p><strong>D. i</strong></p><p><em>答：B,i是第几个位置，x是移动次数，第1个移动n次，第2个移动n-1次最后一个移动0次，可发现</em>$\large i+x=n+1$</p><h3 id="10-线性表-L-a1-a2-…，-an-下列陈述正确的是（-）">10.线性表 L=(a1 , a2, …， an), 下列陈述正确的是（ ）.</h3><p><strong>A. 每个元素都有一个直接前驱和一个直接后继</strong></p><p><strong>B. 线性表中至少有一个元素</strong></p><p><strong>C. 表中诸元素的排列必须是由小到大或由大到小</strong></p><p><strong>D. 除第一个和最后一个元素外， 其余每个元素都有一个且仅有一个直接前驱和直接后继</strong></p><p><em>答：D</em></p><h3 id="一个包括n个结点的有序单链表的时间复杂度是-。">一个包括n个结点的有序单链表的时间复杂度是( )。**</h3><p><strong>A. 0(1)</strong></p><p><strong>B. O(n)</strong></p><p><strong>C. $O(n^2) $</strong></p><p><strong>D. $O(nlog_2 n)$</strong></p><p><em>答：C，单链表创建的时间复杂度是O(n)，而要建立一个有序的单链表，则每生成一个新结点时需要和已有的结点进行比较，确定合适的插入位置，所以时间复杂度是O(n2)。</em></p><h3 id="12-以下陈述错误的是-。">12.以下陈述错误的是( )。</h3><p><strong>A. 求表长、 定位这两种运算在采用顺序存储结构时实现的效率不比采用链式存储结构时实现的效率低</strong></p><p><strong>B. 顺序存储的线性表可以随机存取</strong></p><p><strong>C. 由于顺序存储要求连续的存储区域， 所以在存储管理上不够灵活</strong></p><p><strong>D. 线性表的链式存储结构优于顺序存储结构</strong></p><p><em>答：D</em></p><h3 id="13-在单链表中，-要将s所指结点插入到p所指结点之后，-其语句应为-。">13.在单链表中， 要将s所指结点插入到p所指结点之后， 其语句应为( )。</h3><p><strong>A．s-&gt;next=p+1; p-&gt;next=s;</strong></p><p><strong>B．(*p).next=s; (*s).next=(*p).next;</strong></p><p><strong>C．s-&gt;next=p-&gt;next; p-&gt;next=s-&gt;next;</strong></p><p><strong>D．s-&gt;next=p-&gt;next; p-&gt;next=s;</strong></p><p><em>答：D</em></p><h3 id="14-在双向链表存储结构中，-删除p所指结点时修改指针的操作为（-）。">14.在双向链表存储结构中， 删除p所指结点时修改指针的操作为（ ）。</h3><p><strong>A．p-&gt;next-&gt;prior=p-&gt;prior; p-&gt;prior-&gt;next=p-&gt;next;</strong></p><p><strong>B．p-&gt;next=p-&gt;next-&gt;next; p-&gt;next-&gt;prior=p;</strong></p><p><strong>C．p-&gt;prior-&gt;next=p; p-&gt;prior=p-&gt;prior-&gt;prior;</strong></p><p><strong>D．p-&gt;prior=p-&gt;next-&gt;next; p-&gt;next=p-&gt;prior-&gt;prior;</strong></p><p><em>答：A</em></p><h3 id="15-在双向循环链表中，在-p指针所指的结点后插入-q所指向的新结点，其修改指针的操作是（-）。">15.在双向循环链表中，在 <strong>p</strong>指针所指的结点后插入 <strong>q</strong>所指向的新结点，其修改指针的操作是（ ）。</h3><p><strong>A. p-&gt;next = q; q-&gt;prior = p; p-&gt;next-&gt;prior = q; q-&gt;next = q;</strong>**</p><p><strong>B. p-&gt;next = q; p-&gt;next-&gt;prior = q; q-&gt;prior=p; q-&gt;next = p-&gt;next;</strong></p><p><strong>C. q-&gt;prior = p; q-&gt;next = p-&gt;next; p-&gt;next-&gt;prior = q; p-&gt;next = q;</strong></p><p><strong>D. q-&gt;prior = p; q-&gt;next = p-&gt;next; p-&gt;next = q; p-&gt;next-&gt;prior = q;</strong></p><p><em>答：C</em></p><h2 id="二、算法设计题">二、算法设计题</h2><h3 id="（1）将两个递增的有序链表合并为一个递增的有序链表。不占用其它的存储空间。表中不允许有重复的数据。">（1）将两个递增的有序链表合并为一个递增的有序链表。不占用其它的存储空间。表中不允许有重复的数据。</h3><ul><li><p>算法分析：</p><ul><li>创建一个新表Lc,将La和Lb从头开始进行比较，将其中较小的放在Lc表中，如果两个表中的元素相等，则将La的元素放入Lc，Lb的元素删除,重复这个步骤，直到La或Lb中一个表到达尾节点，将非空表的剩余元素直接连接在Lc表的最后</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">MergeList</span><span class="params">(LINKlIST &amp;La,LinkList &amp;Lb,LinkList &amp;Lc)</span></span><br><span class="line">    </span><br><span class="line">    pa=La-&gt;next; pb=Lb-&gt;next;</span><br><span class="line"></span><br><span class="line">Lc=pc=La;</span><br><span class="line"></span><br><span class="line"><span class="comment">//用La的头结点作为Lc的头结点</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(pa &amp;&amp; pb)&#123;</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">if</span>(pa-&gt;data &lt; pb-&gt;data)&#123;pc-&gt;next=pa;pc=pa;pa=pa-&gt;next;&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//pc指针赋值是为了后移</span></span><br><span class="line">        </span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span>(pa-&gt;data &gt; pb-&gt;data)&#123;pc-&gt;next=pb;pc=pb;pb=pb-&gt;next;&#125;</span><br><span class="line">        </span><br><span class="line">      <span class="keyword">else</span> &#123;pc-&gt;next=pa;pc=pa;pa=pa-&gt;next;</span><br><span class="line">                </span><br><span class="line">          q=pb-&gt;next;delete pb;pb =q;&#125;</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//相同时取La的元素，删除Lb的元素</span></span><br><span class="line"></span><br><span class="line">pc-&gt;next=pa?pa:pb;</span><br><span class="line"></span><br><span class="line"><span class="comment">//插入剩余段</span></span><br><span class="line"></span><br><span class="line">delete Lb;</span><br><span class="line"></span><br><span class="line"><span class="comment">//释放Lb的头结点</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>具体合并过程可看*《数据结构与算法基础 王卓老师的p44》*</p></li></ul><h3 id="（2）将两个非递减的有序链表合并为一个非递增的有序链表。不占用其它的存储空间。表中允许有重复的数据。">（2）将两个非递减的有序链表合并为一个非递增的有序链表。不占用其它的存储空间。表中允许有重复的数据。</h3><ul><li><p>算法分析：</p><ul><li>创建一个新表Lc,将La和Lb从头开始进行比较，将其中较小的放在Lc表中，如果两个表中的元素相等，则将La的元素放入Lc，保留Lb中的元素,重复这个步骤，直到La或Lb中一个表到达尾节点，将非空表的剩余元素直接连接在Lc表的最后</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">MergeList</span><span class="params">(LinkList&amp; La, LinkList&amp; Lb, LinkList&amp; Lc, )</span></span><br><span class="line"></span><br><span class="line">&#123;<span class="comment">//合并链表La和Lb，合并后的新表使用头指针Lc指向</span></span><br><span class="line"></span><br><span class="line">  pa=La-&gt;next;  pb=Lb-&gt;next;</span><br><span class="line"></span><br><span class="line"><span class="comment">//pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点</span></span><br><span class="line"></span><br><span class="line">  Lc=pc=La; <span class="comment">//用La的头结点作为Lc的头结点</span></span><br><span class="line"></span><br><span class="line">  Lc-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span>(pa||pb )</span><br><span class="line"></span><br><span class="line">&#123;<span class="comment">//只要存在一个非空表，用q指向待摘取的元素</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(!pa)  &#123;q=pb;  pb=pb-&gt;next;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//La表为空，用q指向pb，pb指针后移</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(!pb)  &#123;q=pa;  pa=pa-&gt;next;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//Lb表为空，用q指向pa，pa指针后移</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(pa-&gt;data&lt;=pb-&gt;data)  &#123;q=pa;  pa=pa-&gt;next;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//取较小者（包括相等）La中的元素，用q指向pa，pa指针后移</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">else</span> &#123;q=pb;  pb=pb-&gt;next;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//取较小者Lb中的元素，用q指向pb，pb指针后移</span></span><br><span class="line"></span><br><span class="line">     q-&gt;next = Lc-&gt;next;  Lc-&gt;next = q;   </span><br><span class="line"></span><br><span class="line"><span class="comment">//将q指向的结点插在Lc 表的表头结点之后</span></span><br><span class="line"></span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">  delete Lb;             <span class="comment">//释放Lb的头结点</span></span><br><span class="line"></span><br><span class="line">&#125;   </span><br><span class="line"></span><br></pre></td></tr></table></figure></li></ul><h3 id="（3）已知两个链表A和B分别表示两个集合，其元素递增排列。请设计算法求出A与B的交集，并存放于A链表中。">（3）已知两个链表A和B分别表示两个集合，其元素递增排列。请设计算法求出A与B的交集，并存放于A链表中。</h3><ul><li><p>算法描述：</p><ul><li>创建一个新表Lc，pa和pb分别是La和Lb的工作指针，从第一个结点开始依此比较，因为是递增排列，如果两个表中元素相等，则摘取La表中的元素，删除Lb中的元素，如果两者比较时其中一个表的元素较小时，删除此表中较小的元素，指针后移，直到有一个表到达尾节点，依此删除另一个表的剩余元素。</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">Mix</span><span class="params">(LinkList&amp; La, LinkList&amp; Lb, LinkList&amp; Lc)</span>&#123;</span><br><span class="line">    </span><br><span class="line">    pa=La-&gt;next;  pb=Lb-&gt;next;</span><br><span class="line"></span><br><span class="line"><span class="comment">//pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点</span></span><br><span class="line"></span><br><span class="line">Lc=pc=La; </span><br><span class="line"> </span><br><span class="line"><span class="comment">//用La的头结点作为Lc的头结点</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(pa&amp;&amp;pb)&#123;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span>(pa-&gt;data == pb-&gt;data)&#123; </span><br><span class="line">            </span><br><span class="line">            pc-&gt;next=pa;pc=pa;pa=pa-&gt;next;</span><br><span class="line">            </span><br><span class="line">            u=pb;pb=pb-&gt;next; delete u;</span><br><span class="line">            </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"></span><br><span class="line"></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span>(pa-&gt;data &lt; pb-&gt;data) &#123;u=pa;pa=pa-&gt;next; delete u;&#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">else</span> &#123;u=pb; pb = pb-&gt;next; delete u;&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(pa) &#123;u=pa; pa = pa-&gt;next; delete u;&#125;<span class="comment">//释放结点空间</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">while</span>(pb) &#123;u=pb; pb = pb-&gt;next; delete u;&#125;<span class="comment">//释放结点空间</span></span><br><span class="line"></span><br><span class="line">pc-&gt;next=null;</span><br><span class="line">    </span><br><span class="line"><span class="comment">//置链表尾标记。</span></span><br><span class="line"></span><br><span class="line">delete Lb; </span><br><span class="line">    </span><br><span class="line"><span class="comment">//释放Lb的头结点</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="（4）已知两个链表A和B分别表示两个集合，其元素递增排列。请设计算法求出两个集合A和B-的差集（即仅由在A中出现而不在B中出现的元素所构成的集合），并以同样的形式存储，同时返回该集合的元素个数。">（4）已知两个链表A和B分别表示两个集合，其元素递增排列。请设计算法求出两个集合A和B 的差集（即仅由在A中出现而不在B中出现的元素所构成的集合），并以同样的形式存储，同时返回该集合的元素个数。</h3><ul><li><p>算法描述：</p><ul><li>使用指针pre指向前驱结点。pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点，从第一个结点开始进行比较，直到两个链表La或Lb到达尾结点，如果La表中的元素小于Lb表中的元素，pre置为La表的工作指针pa删除Lb表中的元素；如果其中一个表中的元素较小时，删除此表中较小的元素，此表的工作指针后移。当链表La和Lb有一个为空时，依次删除另一个非空表中的所有元素。</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">Difference</span><span class="params">(LinkList&amp; La, LinkList&amp; Lb,<span class="type">int</span> *n)</span></span><br><span class="line"></span><br><span class="line">&#123;<span class="comment">//差集的结果存储于单链表La中，*n是结果集合中元素个数，调用时为0</span></span><br><span class="line"></span><br><span class="line">pa=La-&gt;next; pb=Lb-&gt;next;     </span><br><span class="line"></span><br><span class="line"><span class="comment">//pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点</span></span><br><span class="line"></span><br><span class="line">pre=La;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//pre为La中pa所指结点的前驱结点的指针</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(pa&amp;&amp;pb)</span><br><span class="line"></span><br><span class="line">&#123;<span class="keyword">if</span>（pa-&gt;data &lt; pb-&gt;data）&#123;pre=pa;pa=pa-&gt;next;*n++;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//A链表中当前结点指针后移</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span>（pa-&gt;data &gt; pb-&gt;data）pb=pb-&gt;next;     </span><br><span class="line">     </span><br><span class="line">    <span class="comment">//B链表中当前结点指针后移</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">else</span> &#123;pre-&gt;next=pa-&gt;next;     </span><br><span class="line"></span><br><span class="line">     u=pa; pa=pa-&gt;next; delete u;&#125; </span><br><span class="line">     </span><br><span class="line">               <span class="comment">//处理A，B中元素值相同的结点，应删除</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="（5）设计算法将一个带头结点的单链表A分解为两个具有相同结构的链表B、C，其中B表的结点为A表中值小于零的结点，而C表的结点为A表中值大于零的结点（链表A中的元素为非零整数，要求B、C表利用A表的结点）。">（5）设计算法将一个带头结点的单链表A分解为两个具有相同结构的链表B、C，其中B表的结点为A表中值小于零的结点，而C表的结点为A表中值大于零的结点（链表A中的元素为非零整数，要求B、C表利用A表的结点）。</h3><ul><li><p>算法描述:</p><ul><li>B表的头结点使用原来A表的头结点，为C表新申请一个头结点。从A表的第一个结点开始，依次取其每个结点p，判断结点p的值是否小于0，利用前插法，将小于0的结点插入B表,大于等于0的结点插入C表。</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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="type">void</span> <span class="title function_">DisCompose</span><span class="params">(LinkedList A)</span>&#123; </span><br><span class="line">    B=A;</span><br><span class="line"></span><br><span class="line">B-&gt;next=<span class="literal">NULL</span>; <span class="comment">//B初始化为空表</span></span><br><span class="line"></span><br><span class="line">  C=new LNode;<span class="comment">//为C申请空间</span></span><br><span class="line"></span><br><span class="line">C-&gt;next=<span class="literal">NULL</span>;   <span class="comment">//C初始化为空表</span></span><br><span class="line"></span><br><span class="line">p=A-&gt;next;      <span class="comment">//p为工作指针</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(p!= <span class="literal">NULL</span>)&#123;</span><br><span class="line">        </span><br><span class="line">        r=p-&gt;next;      <span class="comment">//暂存p的后继</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(p-&gt;data &lt; <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">  &#123;p-&gt;next=B-&gt;next; B-&gt;next=p;&#125;<span class="comment">//将小于0的结点链入B表,前插法</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">else</span> &#123;p-&gt;next=C-&gt;next; C-&gt;next=p;&#125;<span class="comment">//将大于等于0的结点链入C表,前插法</span></span><br><span class="line"></span><br><span class="line">p=r;<span class="comment">//p指向新的待处理结点。</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="（6）设计一个算法，通过一趟遍历在单链表中确定值最大的结点。">（6）设计一个算法，通过一趟遍历在单链表中确定值最大的结点。</h3><ul><li><p>算法描述：</p><ul><li>假定第一个结点中数据具有最大值，依次与下一个元素比较，若其小于下一个元素，则设其下一个元素为最大值，反复进行比较，直到遍历完该链表。</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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">ElemType <span class="title function_">Max</span> <span class="params">(LinkList L )</span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(L-&gt;next==<span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">max=L-&gt;next; <span class="comment">//假定第一个结点中数据具有最大值</span></span><br><span class="line"></span><br><span class="line">p=L-&gt;next-&gt;next;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(p != <span class="literal">NULL</span> )&#123;<span class="comment">//如果下一个结点存在</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(p-&gt;data &gt; max-&gt;data) max=p;<span class="comment">//如果p的值大于pmax的值，则重新赋值</span></span><br><span class="line"></span><br><span class="line">p=p-&gt;next;<span class="comment">//遍历链表</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> max-&gt;data;</span><br></pre></td></tr></table></figure></li></ul><h3 id="（7）设计一个算法，通过遍历一趟，将链表中所有结点的链接方向逆转，仍利用原表的存储空间。">（7）设计一个算法，通过遍历一趟，将链表中所有结点的链接方向逆转，仍利用原表的存储空间。</h3><ul><li><p>算法描述：</p><ul><li>从首元结点开始，逐个地把链表L的当前节点p插入新的链表头部</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">inverse</span><span class="params">(LinkList &amp;L)</span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 逆置带头结点的单链表 L</span></span><br><span class="line"></span><br><span class="line">lNode *p,*r; </span><br><span class="line">    </span><br><span class="line">    p=L-&gt;next;  L-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(p)&#123;</span><br><span class="line"></span><br><span class="line">q=p-&gt;next;    <span class="comment">//q为p后一结点，和p同样会后移</span></span><br><span class="line"></span><br><span class="line">p-&gt;next=L-&gt;next;<span class="comment">//p结点后继改为头结点的后继，刚开始为NULL，之后都是有结点的</span></span><br><span class="line"></span><br><span class="line">L-&gt;next=p;   <span class="comment">//头结点后继变为p结点，最先的是1结点，后面依次为 2 3 4 5</span></span><br><span class="line"></span><br><span class="line">p = q;<span class="comment">//p结点后移</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="（8）设计一个算法，删除递增有序链表中值大于mink且小于maxk的所有元素（mink和maxk是给定的两个参数，其值可以和表中的元素相同，也可以不同-）。">（8）设计一个算法，删除递增有序链表中值大于mink且小于maxk的所有元素（mink和maxk是给定的两个参数，其值可以和表中的元素相同，也可以不同 ）。</h3><ul><li><p>算法分析：</p><ul><li>分别查找第一个值&gt;mink的结点和第一个值 ≥maxk的结点，再修改指针，删除值大于mink且小于maxk的所有元素。</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">delete</span><span class="params">(LinkList &amp;L, <span class="type">int</span> mink, <span class="type">int</span> maxk)</span> &#123;</span><br><span class="line"></span><br><span class="line">p=L-&gt;next; <span class="comment">//首元结点</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (p &amp;&amp; p-&gt;data&lt;=mink)</span><br><span class="line"></span><br><span class="line">&#123; pre=p;  p=p-&gt;next; &#125; <span class="comment">//查找第一个值&gt;mink的结点</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (p)</span><br><span class="line"></span><br><span class="line">&#123;<span class="keyword">while</span> (p &amp;&amp; p-&gt;data&lt;maxk) p=p-&gt;next;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查找第一个值 ≥maxk的结点</span></span><br><span class="line"></span><br><span class="line">q=pre-&gt;next;   pre-&gt;next=p;  <span class="comment">// 修改指针</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (q!=p)</span><br><span class="line"></span><br><span class="line">&#123; s=q-&gt;next;  delete q;  q=s; &#125; <span class="comment">// 释放结点空间</span></span><br><span class="line"></span><br><span class="line">&#125;<span class="comment">//if</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="（9）已知p指向双向循环链表中的一个结点，其结点结构为data、prior、next三个域，写出算法change§-交换p所指向的结点和它的前缀结点的顺序。">（9）已知p指向双向循环链表中的一个结点，其结点结构为data、prior、next三个域，写出算法change§,交换p所指向的结点和它的前缀结点的顺序。</h3><ul><li><p>算法分析：</p><ul><li>知道双向循环链表中的一个结点，与前驱交换涉及到四个结点（p结点，前驱结点，前驱的前驱结点，后继结点）六条链。</li></ul></li><li><p>算法实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> Exchange（LinkedList p）</span><br><span class="line"></span><br><span class="line"><span class="comment">//p是双向循环链表中的一个结点，本算法将p所指结点与其前驱结点交换。</span></span><br><span class="line"></span><br><span class="line">&#123;q=p-&gt;llink;</span><br><span class="line"></span><br><span class="line">q-&gt;llink-&gt;rlink=p;   <span class="comment">//p的前驱的前驱之后继为p</span></span><br><span class="line"></span><br><span class="line">p-&gt;llink=q-&gt;llink;   <span class="comment">//p的前驱指向其前驱的前驱。</span></span><br><span class="line"></span><br><span class="line">q-&gt;rlink=p-&gt;rlink;   <span class="comment">//p的前驱的后继为p的后继。</span></span><br><span class="line"></span><br><span class="line">q-&gt;llink=p;          <span class="comment">//p与其前驱交换</span></span><br><span class="line"></span><br><span class="line">p-&gt;rlink-&gt;llink=q;   <span class="comment">//p的后继的前驱指向原p的前驱</span></span><br><span class="line"></span><br><span class="line">p-&gt;rlink=q;          <span class="comment">//p的后继指向其原来的前驱</span></span><br><span class="line"></span><br><span class="line">&#125;  <span class="comment">//算法exchange结束。</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="（10）已知长度为n的线性表A采用顺序存储结构，请写一时间复杂度为O-n-、空间复杂度为O-1-的算法，该算法删除线性表中所有值为item的数据元素。">（10）已知长度为n的线性表A采用顺序存储结构，请写一时间复杂度为O(n)、空间复杂度为O(1)的算法，该算法删除线性表中所有值为item的数据元素。</h3><ul><li><p>算法分析：</p><ul><li>在顺序存储的线性表上删除元素，通常要涉及到一系列元素的移动（删第i个元素，第i+1至第n个元素要依次前移）。本题要求删除线性表中所有值为item的数据元素，并未要求元素间的相对位置不变。因此可以考虑设头尾两个指针（i=1，j=n），从两端向中间移动，凡遇到值item的数据元素时，直接将右端元素左移至值为item的数据元素位置。</li></ul></li><li><p>算法描述：</p><figure class="highlight c"><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="type">void</span> Delete（ElemType A[ ]，<span class="type">int</span>  n）</span><br><span class="line"></span><br><span class="line"><span class="comment">//A是有n个元素的一维数组，本算法删除A中所有值为item的元素。</span></span><br><span class="line"></span><br><span class="line">&#123;i=<span class="number">1</span>；j=n；<span class="comment">//设置数组低、高端指针（下标）。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>（i&lt;j）</span><br><span class="line"></span><br><span class="line">&#123;<span class="keyword">while</span>（i&lt;j &amp;&amp; A[i]!=item）i++;         <span class="comment">//若值不为item，左移指针。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>（i&lt;j）<span class="keyword">while</span>（i&lt;j &amp;&amp; A[j]==item）j--;<span class="comment">//若右端元素为item，指针左移</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>（i&lt;j）A[i++]=A[j--];&#125;</span><br></pre></td></tr></table></figure></li></ul>]]></content>
    
    
    <summary type="html">本文是学完数据结构与算法基础第二章后巩固知识用的习题</summary>
    
    
    
    <category term="习题" scheme="https://www.fomal.cc/categories/%E4%B9%A0%E9%A2%98/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>C语言笔记</title>
    <link href="https://www.fomal.cc/posts/df79b88b.html"/>
    <id>https://www.fomal.cc/posts/df79b88b.html</id>
    <published>2024-08-09T09:34:24.000Z</published>
    <updated>2024-08-10T12:01:30.787Z</updated>
    
    <content type="html"><![CDATA[<h1>C语言</h1><h2 id="1-C语言基础知识">1 C语言基础知识</h2><h3 id="1-1-进位计数制">1.1 进位计数制</h3><h4 id="1-1-1-下标：">1.1.1  下标：</h4><p>​二进制()~2~  B    八进制()~8~ O/Q<br>​十进制()~10~  D    十六进制()~16~ H</p><h4 id="1-1-2-进制转换">1.1.2 进制转换</h4><p>​方法：整数：转几除几，倒取余<br>​小数：转几乘几，正取余</p><h3 id="1-2-字符在计算机内部的表示">1.2 字符在计算机内部的表示</h3><ul><li><p><strong><u>字符</u><strong>在计算机内部以</strong><u>ASCII码</u><strong>存储，占</strong><u>1个字节</u></strong>，**<u>整形数据</u><strong>在计算机内以</strong><u>补码</u>**表示。</p><p>大写字母：A-65，……Z-90</p><p>小写字母：a-97，……z-122</p><p>数字字符：0-48,1-49.……9-57</p><p>空格：32         **\0’**的ASCII码是 <strong>0</strong></p></li><li><p>大小写转换要±32</p></li></ul><h2 id="2-C语言的概述">2 C语言的概述</h2><h3 id="2-1-C语言的基本概念">2.1 C语言的基本概念</h3><ol><li>C语言编写的程序成为**<u>源程序</u><strong>（.c），不可直接执行，先经过</strong><u>编译</u><strong>变成目标程序（.obj），在经过</strong><u>链接</u>**处理后才可运行，处理后生成的文件扩展名是（.exe）</li></ol><figure class="highlight plaintext"><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">graph LR</span><br><span class="line">C语言--&gt;|编写|D[源程序.c]--&gt;|编译|E[目标程序.obj]--&gt;|链接|F[文件.exe]</span><br></pre></td></tr></table></figure><ol start="2"><li>由<strong>高级语言</strong>编写的程序称为**<u>源程序</u>**</li></ol><h3 id="2-2-程序化设计">2.2 程序化设计</h3><ol><li><p>C语言是一种结构化的程序设计语言，它提供了三种**<u>基本结构语句</u><strong>，结构化程序通常由</strong><u>三种基本结构</u><strong>组成，包括</strong><u>顺序</u><strong>，</strong><u>选择</u><strong>，</strong><u>循环</u>**。</p></li><li><p>C程序由**<u>函数</u><strong>组成，函数是组成C程序的</strong><u>基本</u>**单位，<u><strong>语句</strong></u>是C程序的<u><strong>最小单位</strong></u>。</p></li><li><p>C程序总是从**<u>主函数</u><strong>开始执行，由</strong><u>主函数</u><strong>结束，</strong><u>主函数</u>**的位置是任意的</p></li><li><p>语句最后以**<u>分号</u>**结束</p></li><li><p>一行可以写**<u>一条或多条</u><strong>语句，一条语句也可以写</strong><u>一行或多行</u>**</p></li><li><p>标识符</p><ol><li><p>组成：<strong>数字，字母，下划线</strong>，开头只能是**<u>字母或下划线</u>**</p></li><li><p>标识符分为<strong>关键字，预定义标识符，用户标识符</strong>。</p><p>关键字都是小写字母，不能用做用户标识符，预定义标识符包括（define，include，scanf，printf）可以作为用户标识符但不建议。</p><p><strong>标识符区分大小写</strong></p></li></ol></li></ol><h2 id="3-数据类型极其运算">3 数据类型极其运算</h2><h3 id="3-1-数据类型">3.1 数据类型</h3><ol><li>常见的基本数据类型包括**<u>整型</u><strong>，</strong><u>实型</u><strong>，</strong><u>字符型</u>**。<ol><li><p>整形包括**<u>常量</u><strong>，</strong><u>变量</u>**</p><ul><li>常量：在程序运行中，值不可改变的量</li><li>变量：在程序运行中，值可以改变的量</li></ul></li></ol></li></ol><h4 id="3-1-1-整形常量">3.1.1 整形常量</h4><ol><li><p>C语言中<strong>没有二进制</strong></p></li><li><p><strong>八进制</strong>前导**$\large 0$**</p></li><li><p><strong>十六进制</strong>前导**$\large 0x$<strong>或者</strong>$\large 0X$**</p></li><li><p>%d 十进制<br>%o 八进制<br>%x：十六进制数（字母小写）<a href="%E6%B3%A8%E6%84%8F%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E4%B8%8E%E8%BF%9B%E4%BD%8D%E8%AE%A1%E6%95%B0%E5%88%B6%E4%B8%8B%E6%A0%87%E6%9C%89%E5%8C%BA%E5%88%AB">^1</a><br>%X：十六进制数（字母大写）<a href="%E6%B3%A8%E6%84%8F%E5%8D%81%E5%85%AD%E8%BF%9B%E5%88%B6%E4%B8%8E%E8%BF%9B%E4%BD%8D%E8%AE%A1%E6%95%B0%E5%88%B6%E4%B8%8B%E6%A0%87%E6%9C%89%E5%8C%BA%E5%88%AB">^1</a></p><ul><li>输入有前导，<strong><u>==输出无前导==</u></strong></li></ul></li></ol><h4 id="3-1-2-整型变量">3.1.2 整型变量</h4><table><thead><tr><th>数据类型</th><th>型号</th><th>字节数</th></tr></thead><tbody><tr><td>int</td><td>基本型</td><td>TC系统占2个字节</td></tr><tr><td></td><td></td><td>vc系统占4个字节</td></tr><tr><td>short</td><td>短整形</td><td>占2字节</td></tr><tr><td>long</td><td>长整形</td><td>占4字节</td></tr><tr><td>unsigned</td><td>无符号型</td><td></td></tr></tbody></table><ul><li><p><strong><u>未赋值的变量</u><strong>输出时默认为</strong><u>随机值</u></strong></p></li><li><p>当格式字符少于输出项时多余的输出项不输出</p><p>printf（“%d”,a,c）不输出c</p></li><li><p>当格式字符多于输出项时输出随机值</p><p>printf（“%d，%d”,a）输出一个a一个随机值</p></li></ul><h4 id="3-1-3-实型常量">3.1.3 实型常量</h4><ol><li><h5 id="小数点式">小数点式</h5><ol><li>小数点式：由**<u>数字，正负号，小数点</u>**组成<ul><li>小数点式：必须有小数点，至少一边有数字<ul><li>$0.78\Rightarrow.78$   $3.0\Rightarrow 3.$</li></ul></li></ul></li></ol><h5 id="指数形式">指数形式</h5><ol><li><p>由E或者e组成</p><ul><li><p>$12.34E+2\Rightarrow12.34\times10^{+2}$</p></li><li><p>$12.34e-2\Rightarrow12.34\times10^{-2}$</p></li><li><p>E或e的**<u>两边必须有数字</u><strong>，</strong><u>右边</u><strong>（指数部分）必须是</strong><u>整数</u>**</p><p>$3.14E-7.0(X)$  右边的<strong>7.0</strong>是<strong>实型中的小数点式</strong></p></li></ul></li></ol><h4 id="实型变量">实型变量</h4><ul><li>单精度：float        占4字节</li><li>双精度：double    占8字节</li><li>%f：十进制浮点数<ul><li><strong>%f</strong>输出默认保留**==6位小数，不足补0，超过6位四舍五入==**</li><li>当多个类型的值进行<strong>混合运算时</strong>，运算<strong>结果为</strong>字节数**<u>最大的类型</u>*</li></ul></li></ul></li></ol><h4 id="3-1-4-字符常型">3.1.4 字符常型</h4><ul><li><p>常规字符常量</p><ul><li><p><strong>单引号</strong>只能放<strong>一个字符</strong>，<strong><u>一个字符一个字节</u></strong></p></li><li><p>转义字符</p><p>‘\n’:换行</p><p>‘\t’:横向跳格（跳过8列）</p><p>‘\b’:左移覆盖 printf（“abcd\b1234”）;=printf(“abcd\b”)</p><p>‘\’‘’:输出’</p><p>‘\&quot;’:输出”</p><p>‘\\’:输出\</p></li><li><p>八进制转义字符：‘\ddd’<strong>一到三位</strong>八进制表示<strong>一个字符</strong></p><ul><li>‘\101’     ‘\006’ <strong><u>==前导的0可加可不加==</u></strong></li></ul></li><li><p>十六进制转义字符：‘\xhh’<strong>一到二位</strong>十六进制表示<strong>一个字符</strong></p><ul><li>‘\xab’  <strong>==前导x必须有==</strong></li></ul></li></ul></li><li><p>字符串常量</p><ul><li>双引号括起多个字符 例如“ABCD”占**<u>5</u>**个字节<ul><li>字符串后面还有个**‘\0’<strong>，其为</strong>字符串的结束标志**</li></ul></li></ul></li></ul><h4 id="3-1-5-字符型变量">3.1.5 字符型变量</h4><ul><li><p>char  占一个字节</p><p>%c 只能输出一个字符</p></li><li><p>printf（“%c\n”,‘A’） ；  A<br>printf（“%d\n”,‘A’） ；  65</p><ul><li><strong><u>==如果以%d形式输出一个字符，则输出他的ASCII码值。==</u></strong></li></ul></li></ul><h3 id="3-2-常见运算符">3.2 常见运算符</h3><h4 id="3-2-1-算数运算符">3.2.1 算数运算符</h4><ul><li><p>算数运算符：+，-，*，/，%</p><ul><li><p>$/$两边都是整形的话，结果就是一个整形，<strong>任意一个为实型，结果为实型</strong>。</p><ul><li>4+3.0=7.0</li></ul></li><li><p>$%$两边要求是整数，余数正负号与被除数（<u><strong>左边的数</strong></u>）一致。</p><ul><li><p>-7%3=-1</p></li><li><p>并且**<u>求余运算符的对象</u><strong>必须是</strong><u>整型</u>**</p></li><li><p>整型变量只接受整数部分</p><figure class="highlight c"><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"><span class="type">int</span> a=<span class="number">1</span>;</span><br><span class="line">      </span><br><span class="line">   <span class="type">float</span> b=<span class="number">2.5</span>;</span><br><span class="line">      </span><br><span class="line">    a=b;</span><br><span class="line">      </span><br><span class="line">    a=<span class="number">2</span>;</span><br></pre></td></tr></table></figure></li></ul></li></ul></li></ul><h4 id="3-2-2-强制类型转换符">3.2.2 强制类型转换符</h4><ul><li><p>形式：(类型名)表达式</p><p>（int）x/2             解释运算过程：将X强制转换成整形，最后结果除以2</p><p>（int）（x/2）     解释运算过程：将（x/2）的结果强制转换成整形</p><ul><li><p>强制转化只对结果有影响，<strong><u>表达式还是原来的类型并未改变</u></strong></p><ul><li>按照指定宽度输出</li></ul></li><li><p>指定&gt;宽度   <strong>==左补==空格</strong></p></li><li><p>指定&lt;实际   <strong>原样输出</strong></p><figure class="highlight c"><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="type">int</span> a=<span class="number">1234</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;a=%5d\n&quot;</span>,a);a=_1234</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;a=%2d\n&quot;</span>,a);a=<span class="number">1234</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>%m,nf</p></li><li><p>m-代表数据总长（<strong>包含小数点</strong>），n代表小数位数</p><p>实际&gt;n   <strong>四舍五入</strong></p><p>实际&lt;n   <strong>补零</strong></p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">float</span> f=<span class="number">3.1415</span>；</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;f=%7.2f\n&quot;</span>,f);f=_ _ _3.<span class="number">14</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;f=%10.5f\n&quot;</span>,f);f=_ _ _3.<span class="number">14150</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;f=%4.3f\n&quot;</span>,f);f=<span class="number">3.142</span><span class="comment">//实际五位数，大于要求的四位数，原样输出</span></span><br></pre></td></tr></table></figure></li><li><p>int的数据范围是-32768~+32767</p></li><li><p>int a=1；</p><p>for（;a&gt;0;a++）是死循环</p></li><li><p>无符号数据范围0~65535</p><ul><li>无符号65535与int型-1对应</li></ul></li><li><p>%u：无符号十进制整数</p></li></ul><p>数据输入</p><ol><li><p>整型数据输入如果没有其他符号分隔，用空格或回车分隔</p><p>scanf（“%d%d”，&amp;a&amp;b）;    1_2  或者  1回车2</p></li><li><p>指定输入数据的宽度时输入宽度不得少于指定宽度</p></li><li><p>实型数据输入时，<strong>不能指定小数位数</strong></p><p>scanf（“%3.2f”，&amp;f）；（X）</p></li><li><p><strong>%c前不能加空格</strong>，因为空格也是字符，也会被输入进去</p></li><li><p>字符输入函数：getchar（）</p><p>字符输出函数：putchar（）</p><p>只针对一个字符的输入和输出</p><figure class="highlight c"><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="type">char</span> ch;</span><br><span class="line">ch=getchar();</span><br><span class="line"><span class="built_in">putchar</span>(ch);</span><br></pre></td></tr></table></figure></li></ol><ul><li><strong>==将两个整数a和b的值互换后输出==</strong><ul><li>t=a,a=b,b=t;<strong>空瓶子原理</strong>（需要定义第三变量）</li><li>a=a+b；b+a-b；a=a_b;    <strong>和差差</strong>（不需要定义第三变量）</li></ul></li></ul><h2 id="4-选择结构">4 选择结构</h2><h3 id="4-1-关系运算与逻辑运算">4.1 关系运算与逻辑运算</h3><ul><li>C语言中：C语言将<strong>非零值视作真</strong>。<strong>0值视作假</strong></li></ul><h4 id="4-1-1-关系运算">4.1.1 关系运算</h4><ul><li>$&gt;&gt;,&gt;=,&lt;,&lt;=,==,!=$<ul><li>成立 ：1 不成立：0</li></ul></li></ul><h4 id="4-1-2-逻辑运算">4.1.2 逻辑运算</h4><ul><li><p>逻辑运算符：！，&amp;&amp;，||</p><ul><li>&amp;&amp;：有0为0，全1为1<ul><li>条件同时满足</li></ul></li><li>||：有1为1，全0为0<ul><li>只满足一个就可以</li></ul></li><li>！：取反<ul><li>1+2&amp;&amp;3+4结果为1</li></ul></li><li>符号优先级</li></ul><table><thead><tr><th>优先级</th><th style="text-align:center">运算符</th></tr></thead><tbody><tr><td>1</td><td style="text-align:center">！</td></tr><tr><td>2</td><td style="text-align:center">算数运算符<br />*,/,%大于+,-</td></tr><tr><td>3</td><td style="text-align:center">关系运算符<br />&lt;,&gt;,&gt;=,&lt;=大于==,!=</td></tr><tr><td>4</td><td style="text-align:center">&amp;&amp;</td></tr><tr><td>5</td><td style="text-align:center">||</td></tr><tr><td>6</td><td style="text-align:center">赋值运算符</td></tr><tr><td>7</td><td style="text-align:center">逗号运算符</td></tr></tbody></table><ul><li><p>逻辑与运算与逻辑或运算同时出现时，&amp;&amp;运算优先级高，<strong>先结合</strong></p><p>从左向右计算</p><ul><li><strong>++a||++b&amp;&amp;++c=++a||(++b&amp;&amp;++c)</strong></li></ul></li></ul></li></ul><h3 id="4-2-条件运算符号（三目运算符）">4.2 条件运算符号（三目运算符）</h3><ul><li><p>形式：条件1？值1；值2</p><ul><li><p>条件为真，结果为值1；为假：则结果为值2</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> a=<span class="number">2</span>,b=<span class="number">1</span>,c=<span class="number">3</span>,d=<span class="number">4</span>; </span><br><span class="line">    <span class="type">int</span> result=(a&lt;b?a;c&lt;d?a;d);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;结果是：%d\n&quot;</span>,result);<span class="comment">//结果是：2</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="4-3-if语句">4.3 if语句</h3><ul><li><p>单分支if(语句)</p><ul><li>条件为真，执行语句，为假，则不执行</li><li>if只能控制一条语句，<strong><u>==if语句后的分号比较重要==</u></strong></li></ul></li><li><p>双分支if(语句)</p><ul><li>真，执行if语句，假，执行else</li><li>出现多个else时，要注意配对原则<ul><li>就近原则，else总是和**<u>==上面的，最近的，未配对==</u>**的配对</li></ul></li></ul></li></ul><h3 id="4-4-switch语句">4.4 switch语句</h3><ul><li>看括号里的内容，执行与case对应的，顺序执行，直到break跳出循环<ul><li>先进入，后向下</li><li>case后面必须是**<u>整形常量</u><strong>或</strong><u>常量表达式</u>**（switch括号内一样）</li><li><strong>default表示“其他”分支</strong>，可有可无，位置任意</li><li><strong>break跳出循环</strong></li></ul></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> num;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;请输入一个数字（1-3）：&quot;</span>);</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d&quot;</span>, &amp;num);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (num) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;你输入了数字1\n&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;你输入了数字2\n&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;你输入了数字3\n&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;你输入的数字不在范围内\n&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-循环结构">5 循环结构</h2><h3 id="5-1-while循环">5.1 while循环</h3><ul><li><p>形式：while（条件）语句；</p><ul><li><p>先判断条件，<strong>为真时循环</strong>，直到条件为假跳出循环</p></li><li><p>循环三要素：<strong><u>初值，条件，增量</u></strong></p></li><li><p><strong><u>==循环次数看成立几次==</u></strong></p></li><li><p>与if语句相同，while循环只控制一条语句</p></li><li><p>常见用法-倒序输出</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> x = <span class="number">1234</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;倒过来输出的数字是：&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (x &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> digit = x % <span class="number">10</span>; <span class="comment">// 取最后一位数字</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%d&quot;</span>, digit); <span class="comment">// 输出最后一位数字</span></span><br><span class="line">        number /= <span class="number">10</span>; <span class="comment">// 去掉最后一位数字</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="5-2-do-while循环">5.2 do-while循环</h3><ul><li><p>形式：</p><p>do语句;</p><p>while（条件）;</p></li><li><p>先执行语句再判断条件，<strong>为真时循环</strong>，直到条件为假跳出循环</p><ul><li>while后面分号不可以省略，代表循环语句结束</li><li>循环次数也是看<strong>条件成立几次</strong></li><li><strong>循环体执行次数</strong>比条件成立次数多一</li><li><strong><u>do-while语句至少执行一次</u></strong></li></ul></li></ul><h3 id="5-3-for循环">5.3 for循环</h3><ul><li><p>形式：</p><p>for（初值；条件；增量）语句；条件为真，执行语句，然后返回增量</p><ul><li>省略式1，执行式2</li><li>省略式2，默认条件为真</li><li>省略式3，回式2</li><li>式子都可以省，<strong>分号不能省</strong></li></ul></li><li><p><strong>循环次数看条件成立几次</strong></p></li></ul><h3 id="5-4-break和continue">5.4 break和continue</h3><ul><li><p>break语句</p><ul><li>跳出循环，<strong>在那个循环内部，就跳出那个循环</strong></li></ul></li><li><p>continue</p><ul><li><p>结束本次循环，进入下一次循环</p><ul><li>while循环，回条件</li><li>do-while循环，回条件</li><li>forxunhuan,回增量</li></ul></li><li><p>continue只跟循环语句有关系，看见continue就找循环</p></li></ul></li></ul><h3 id="5-5-循环的嵌套">5.5 循环的嵌套</h3><ul><li><p>嵌套循环的次数为    <strong><u>内循环次数X外循环次数</u></strong></p></li><li><p>常用嵌套代码-更相减损法</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> a, b;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;请输入两个整数：&quot;</span>);</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d %d&quot;</span>, &amp;a, &amp;b);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 保证a和b是非负数</span></span><br><span class="line">    <span class="keyword">if</span> (a &lt; <span class="number">0</span>) a = -a;</span><br><span class="line">    <span class="keyword">if</span> (b &lt; <span class="number">0</span>) b = -b;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用更相减损法求最大公约数</span></span><br><span class="line">    <span class="keyword">while</span> (a != b) &#123;</span><br><span class="line">        <span class="keyword">if</span> (a &gt; b) &#123;</span><br><span class="line">            a = a - b;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            b = b - a;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;最大公约数是：%d\n&quot;</span>, a);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>常用嵌套代码-辗转相除法</p><figure class="highlight c"><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="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> a, b;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;请输入两个整数：&quot;</span>);</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%d %d&quot;</span>, &amp;a, &amp;b);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 保证a和b是非负数</span></span><br><span class="line">    <span class="keyword">if</span> (a &lt; <span class="number">0</span>) a = -a;</span><br><span class="line">    <span class="keyword">if</span> (b &lt; <span class="number">0</span>) b = -b;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (b != <span class="number">0</span>) &#123;<span class="comment">//除数</span></span><br><span class="line">        <span class="type">int</span> temp = a % b;<span class="comment">//余数=大%小</span></span><br><span class="line">        a = b ;<span class="comment">//大=小</span></span><br><span class="line">        b = temp;<span class="comment">//小=余</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;最大公约数是：%d\n&quot;</span>, a);<span class="comment">//输出被除数</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h2 id="6-函数">6 函数</h2><h3 id="6-1-函数的定义">6.1 函数的定义</h3><ol><li><strong>函数</strong>是组成c程序的<strong>基本单位</strong>，<strong>语句是最小单位</strong></li><li>C程序总是从**<u>主函数</u><strong>开始执行，由</strong><u>主函数</u><strong>结束，</strong><u>主函数</u>**的位置是任意的</li><li><strong><u>==实参的类型可以与形参类型不一致，以形参的类型为准==</u></strong></li><li>实参和形参各占独立存储单元</li><li><strong>形参</strong>需单独定义，属于定时变量，调用函数时为其分配存储单元，<strong>调用结束后释放存储单元</strong></li><li><strong><u>==函数应先定义后调用==</u></strong></li><li><u><strong>==函数可以嵌套调用，但不可以嵌套定义==</strong></u><ul><li>$/large (int x,int y)（✓）(int x,y)（X）$</li></ul></li><li><strong>函数默认为int型</strong></li><li><u><strong>==函数返回值的类型是由定义函数时所指定的函数类型决定的==</strong></u></li><li>main（）函数不能调用自己，其他函数都可以调用自己</li><li><strong>return语句只能带回一个返回值</strong>，void表示函数无返回值<ul><li><strong>void可以定义函数，也可以定义变量</strong></li><li>函数不一定都有参数</li></ul></li><li><strong><u>==函数的实参传递分为值传递和地址传递==</u></strong></li></ol><h3 id="6-2-函数声明">6.2 函数声明</h3><ul><li>形式：float fun（float x,float y）；<ol><li><strong>先定义，后调用</strong></li><li><strong><u>==先声明，再调用，最后定义==</u></strong></li></ol></li></ul><p>（错题本上有道题可以放这里，这里先按下不表）</p><h3 id="6-3-函数的调用">6.3 函数的调用</h3><ul><li>函数调用形式<ol><li>语句调用    fun（a,b）;</li><li>表达式调用    y=fun（a,b）;</li><li>嵌套调用    将函数的返回值作为实参进行调用<ul><li>fun(a,fun(a,b))</li></ul></li><li><strong>递归调用</strong>    函数直接或间接的调用自己<ol><li>直接递归    A-&gt;A</li><li>间接递归    A-&gt;B-&gt;A</li></ol></li></ol></li></ul><h2 id="7-数组">7 数组</h2><h3 id="7-1-定义">7.1 定义</h3><p>​类型说明符 数组名【常量表达式】；</p><ul><li><p>int  a [ ]；</p></li><li><p>中括号是**<u>整型常量</u><strong>或</strong><u>整型常量表达式</u>**。</p></li><li><p>定义数组时，需要指定数组元素个数，方括号中的整形常量或整型表达式用来表示元素的个数，即数组长度。</p></li><li><p>中括号中的常量可以是符号常量（宏定义中的字符）。</p><ul><li><p>#define N10</p><p>int a [N]；</p></li></ul></li><li><p>不可以给整型数组赋字符串    char s【】;s=“right!”; [x]</p></li></ul><h3 id="7-2-一维数组的应用">7.2  一维数组的应用</h3><ul><li>数组必须<strong>先定义，再使用</strong></li><li>引用形式：数组名【下标】 、<ul><li>数组的下标都是**<u>从0开始</u>**的</li></ul></li><li><u><strong>数组初始化可以少赋值，不能多赋值</strong></u></li><li>未赋值的部分默认为0</li><li><strong><u>数组长度为初始化元素个数</u></strong></li></ul><h3 id="7-3-二维数组的应用">7.3 二维数组的应用</h3><ul><li>引用形式：数组名【下标】【下标】</li><li><strong><u>==行下标可以省略，列下标不能省略==</u></strong></li><li><strong><u>没赋值，则行列都不可省</u></strong></li><li>初始化至少赋一个值</li></ul><h2 id="8-指针">8 指针</h2><ul><li><p>指针的概念</p><ul><li><p><strong><u>==指针就是地址==</u></strong></p><p>变量的指针就是变量的地址</p></li></ul></li><li><p>指针的定义</p><ul><li>基类型*指针变量名   int * p；</li><li>$<em>$ **<u>==只有在定义语句中是指针定义符==</em></u>*</u>**</li></ul></li></ul><h3 id="8-1-指针的赋值">8.1 指针的赋值</h3><ul><li><p>通过取地址运算符：&amp;</p><ul><li><p>int a , * p</p><p>p = &amp; a ;</p></li></ul></li><li><p>通过另一个指针</p><ul><li><p>int a，* p ， * q；</p><p>p = &amp; a ; q = p ;</p></li></ul></li><li><p>空指针的定义</p><ul><li>int * p = NULL ;</li><li>其值为0</li></ul></li><li><p>指针的操作</p><ul><li>&amp;：取地址运算符<ul><li>p = &amp; a</li></ul></li><li>*：取内容运算符<ul><li>* p = a</li></ul></li></ul></li></ul><h3 id="8-2-指针与一维数组">8.2 指针与一维数组</h3><ol><li><p>一维数组的地址</p><ul><li><u><strong>a是数组名，即一维数组的首地址，第0个元素的地址，即&amp;a[0]</strong></u></li></ul></li><li><p>一维数组常用表达式</p><ul><li>a ：&amp; a [0]</li><li>a+i : &amp; a [i]</li><li>* (a+i): a [i]</li></ul></li><li><p>指向一维数组的指针</p><ul><li><p><strong>指针指向哪一位，哪一位就是该指针的首元素</strong></p></li><li><p>*++p：指针后移，取值</p></li><li><p>++*p：先取值，加一</p></li><li><p>*p++：先取值，指针后移</p></li><li><p><strong><u>==数组名是地址常量，不可赋值。指针是变量，可以赋值。==</u></strong></p></li><li><p>指针比较大小就是比所在位置的大小</p></li><li><p><strong>指针可以想减不能相加</strong></p></li></ul></li><li><p>函数之间对一维数组的引用</p><table><thead><tr><th>实参</th><th>形参</th><th>传递方式</th></tr></thead><tbody><tr><td>数组元素 a [1] a [2]</td><td>变量</td><td>值传递</td></tr><tr><td>数组元素地址 &amp; a [1]</td><td>指针</td><td>地址传递</td></tr><tr><td>数组名 a</td><td>指针</td><td>地址传递</td></tr></tbody></table></li></ol><h3 id="8-3-指针与二维数组">8.3 指针与二维数组</h3><ul><li>前面为行，后面为列</li><li><em><em><u>==二维数组引用时出现</em>，[]任意两个代表内容，出现一个代表地址==</u></em>*</li></ul><h3 id="8-4-数组指针与指针数组">8.4 数组指针与指针数组</h3><h4 id="8-4-1-数组指针">8.4.1 数组指针</h4><ul><li>int ( * p ) [4];<ul><li>p为类型的int型数组的指针。数组长度为4</li><li>一个指向M个整形元素的一维数组指针</li></ul></li></ul><h4 id="8-4-2-指针数组">8.4.2 指针数组</h4><ul><li><p>Int * q [3]；</p><ul><li><p>q是由三个指针变量构成的数组，简称指针数组。q[0]，q[1]，q[2]，均为int *</p><p>每一个元素都是一个指针，每个元素都是地址</p><table><thead><tr><th>数组</th><th>数组名</th><th>元素</th></tr></thead><tbody><tr><td>int q [3]</td><td>整形元素</td><td>数值</td></tr><tr><td>Int * q [3]</td><td>整型指针元素</td><td>指针</td></tr></tbody></table></li></ul></li></ul><h3 id="8-5-函数指针和指针函数">8.5 函数指针和指针函数</h3><h4 id="8-5-1-函数指针">8.5.1 函数指针</h4><ul><li>int  ( * p )();<ul><li>说明p是返回值为整形函数的指针</li></ul></li></ul><h4 id="8-5-2-指针函数">8.5.2 指针函数</h4><ul><li><p>int *p()；</p><ul><li>说明函数值为整型指针的函数</li></ul></li><li><p><strong><u>==p先和*结合说明是指针，p先和【】结合，说明是数组，p先和（）结合，说明是函数。==</u></strong></p></li></ul><h2 id="9-字符串">9 字符串</h2><h3 id="9-1-字符数组与字符串">9.1 字符数组与字符串</h3><ol><li><p>字符数组定义</p><p>用来存放字符的数组就是字符数组。</p></li><li><p>字符数组的初始化</p><p><strong>省略的元素默认为‘\0’</strong></p><p>长度默认为初始化元素个数</p></li><li><p>字符数组与字符串</p><p>字符数组中，存放‘\0’，就当做字符串</p></li></ol><h3 id="9-2-字符串指针">9.2 字符串指针</h3><ol><li><p>定义</p><p>char *p；</p></li><li><p>字符串指针赋值</p><p>char *p=“ABCD”;或者 char s[5]={“ABCD”}</p><p><strong>字符串给指针赋值，指针只接收字符串首地址</strong></p></li></ol><h3 id="9-3-字符串的输入与输出">9.3 字符串的输入与输出</h3><ol><li><p>利用scanf和printf</p><p>字符串输出时遇到‘\0’结束</p><p><u><strong>%s:输出一个字符串</strong></u></p><p><u><strong>%c:输出一个字符</strong></u></p></li><li><p>字符串输入输出函数</p><p>输入：gets（）<a href="%E6%B3%A8%E6%84%8F%E4%B8%8E%E5%AD%97%E7%AC%A6%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%9A%84getchar%EF%BC%8Cputchar%E5%81%9A%E5%8C%BA%E5%88%86">^2</a></p><p>输出：puts（）<a href="%E6%B3%A8%E6%84%8F%E4%B8%8E%E5%AD%97%E7%AC%A6%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%9A%84getchar%EF%BC%8Cputchar%E5%81%9A%E5%8C%BA%E5%88%86">^2</a></p><p>专门用于字符串的输入输出</p><p><strong><u>==带空格的字符串不能用scanf输入，必须用gets（）==</u></strong></p></li></ol><h3 id="9-4-字符串函数">9.4 字符串函数</h3><h4 id="9-4-1-字符串长度函数-strlen">9.4.1 <strong>字符串长度函数 strlen</strong></h4><p><u><strong>==strlen（）不包括‘\0’，sizeof（）包括\0’，当参数是数组名时，直接输出数组的长度。==</strong></u></p><h4 id="9-4-2-字符串复制函数-strcpy">9.4.2 <strong>字符串复制函数 strcpy</strong></h4><p>strcpy（字符数组1，字符串2）</p><p><strong>将字符串2复制到字符数组1中，类似替换</strong></p><p><strong><u>==字符数组1的长度不能小于字符串2的长度==</u></strong></p><h4 id="9-4-3-字符串连接函数-strcat">9.4.3 <strong>字符串连接函数 strcat</strong></h4><p>strcat（字符数组1，字符数组2）</p><p><strong>把字符数组2接到字符数组1之后（只有字符串1变化）</strong></p><p><strong>字符数组1必须足够大，以便容纳链接后的新字符串</strong>，链接后字符串1后面的‘\0’取消，只保留新串最后的\0’</p><figure class="highlight c"><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="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 声明并初始化两个字符串</span></span><br><span class="line">    <span class="type">char</span> str1[<span class="number">50</span>] = <span class="string">&quot;Hello, &quot;</span>;</span><br><span class="line">    <span class="type">char</span> str2[] = <span class="string">&quot;World!&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 输出原始字符串</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Before strcat:\n&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;str1: %s\n&quot;</span>, str1);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;str2: %s\n&quot;</span>, str2);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用strcat连接str2到str1</span></span><br><span class="line">    <span class="built_in">strcat</span>(str1+<span class="number">1</span>, str2+<span class="number">1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 输出连接后的字符串</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\nAfter strcat:\n&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;str1: %s\n&quot;</span>, str1);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行结果</p><figure class="highlight c"><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">Before <span class="built_in">strcat</span>:</span><br><span class="line">str1: Hello, </span><br><span class="line">str2: World!</span><br><span class="line"></span><br><span class="line">After <span class="built_in">strcat</span>:</span><br><span class="line">str1: ello, orld!</span><br></pre></td></tr></table></figure><h4 id="9-4-4-字符串比较函数-strcmp">9.4.4 <strong>字符串比较函数 strcmp</strong></h4><ul><li>比较字符串1和字符串2（用ASCII比较）<ul><li>串1大   1</li><li>一样大  0</li><li>串2大   -1</li></ul></li><li><strong>只能通过字符串比较函数比较字符串大小，不能通过关系运算符比较</strong></li></ul><h2 id="10-变量作用域与存储类">10 变量作用域与存储类</h2><h3 id="10-1-作用域">10.1 作用域</h3><h4 id="10-1-1-局部变量">10.1.1 局部变量</h4><ul><li>再复合语句内部或函数内部定义的变量称为局部变量<ul><li>自定义位置起，到符合语句或函数的结束</li></ul></li></ul><h4 id="10-1-2-全局变量">10.1.2 全局变量</h4><ul><li>自函数外部定义的变量称为全局变量<ul><li>自定义位置起，到程序结束</li><li><strong><u>局部变量和全局变量同时存在，先用局部变量</u></strong></li></ul></li></ul><h3 id="10-2-变量的存储类">10.2 变量的存储类</h3><ol><li><p>自动型：auto</p><p>函数中的局部变量，默认都是<strong>自动型</strong>,在调用该函数时系统会给这些变量分配存储空间，函数调用结束时就会自动释放存储空间</p><p>这类局部变量称为自动变量，用关键字auto做存储类别声明</p></li></ol><table><thead><tr><th style="text-align:left">auto</th><th>int</th><th>a</th></tr></thead><tbody><tr><td style="text-align:left">存储类</td><td>类型名</td><td>变量名</td></tr></tbody></table><ul><li><strong><u>变量默认的存储类为auto类型</u></strong></li></ul><ol start="2"><li><p><strong>静态型：static 内部变量,内部函数</strong></p><p><strong>调用结束后不消失而<u>累计</u>，即其占用的存储单元<u>不释放</u></strong></p></li><li><p><strong>寄存器型：register</strong></p></li><li><p><strong>extern 外部变量，外部函数</strong></p></li></ol><h3 id="10-3-动态存储方式与静态存储方式">10.3 动态存储方式与静态存储方式</h3><ul><li><p>静态区</p><ul><li>变量类型：静态局部变量，全局变量或外部变量</li><li>生存期：整个程序</li><li>初始化在编译阶段,初值为0</li></ul></li><li><p>动态区</p><ul><li>变量类型：动态变量</li><li>生存期：他所在的局部</li><li>初值为随机值</li></ul></li><li><p>变量</p><ul><li>局部变量<ul><li>动态</li><li>静态</li></ul></li><li>全局变量<ul><li>静态</li></ul></li></ul></li></ul><table><thead><tr><th></th><th>局部变量</th><th>动态</th></tr></thead><tbody><tr><td><strong>变量</strong></td><td></td><td><strong>静态</strong></td></tr><tr><td></td><td><strong>全局变量</strong></td><td><strong>静态</strong></td></tr></tbody></table><ul><li>int x;  其中的$X$是动态变量，初值为随机</li><li>static  int x；其中的$X$是静态变量，初值为0</li></ul><h2 id="11-编译预处理">11 编译预处理</h2><p>C语言提供的预处理功能主要有三种：<strong><u>==宏定义，文件包含和条件编译==</u></strong></p><h3 id="11-1-宏定义">11.1 宏定义</h3><ul><li>形式：<ul><li>#define 宏名  替换文本<ul><li>#defube B  5</li></ul></li><li>#define 宏名（参数） 替换文本<ul><li>#define f(x)  x*x</li></ul></li></ul></li><li><strong><u>预处理命令不是C语句，不用加分号</u></strong></li><li><strong>宏名习惯用大写字母，但是也可以用小写</strong></li></ul><h3 id="11-2-文件包含">11.2 文件包含</h3><ul><li><p>形式：</p><ul><li><p>#include&lt;文件名&gt;系统目录</p></li><li><p>#include“文件名”      系统目录+用户目录</p><ul><li><p>stdio.h  标准输入输出头文件</p><ul><li>标准库函数</li></ul></li><li><p>string.h  字符串函数头文件</p><ul><li><p>strcmp</p><p>strlen</p></li></ul></li><li><p>math.h  数学函数头文件</p><ul><li><p>sqrt</p><p>fabs</p></li></ul></li></ul></li></ul></li><li><p>提问方式</p><ul><li>只问头文件<ul><li>stdio.h</li><li>string.h</li><li>math.h</li></ul></li><li>文件包含的整体格式<ul><li>#include&lt;stdio.h&gt;</li><li>#include&lt;string.h&gt;</li><li>#include&lt;math.h&gt;</li></ul></li><li>问是什么命令<ul><li>文件包含/#include</li></ul></li></ul></li><li><p>例题</p></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAX( a , b ) ( a ) &gt; ( b ) ? ( a ) ; ( b )</span></span><br><span class="line">main() &#123;</span><br><span class="line">    <span class="type">int</span> x , y , z ;</span><br><span class="line">    x = <span class="number">10</span> ;</span><br><span class="line">    y = <span class="number">15</span> ;</span><br><span class="line">    z = <span class="number">10</span> * MAX( x , y );<span class="comment">//先不要计算，先把宏定义替换带入进去</span></span><br><span class="line">    <span class="comment">//z=10*( x ) &gt; ( y ) ? ( x ) ; ( y )</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,z);<span class="comment">//结果为10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="11-3-条件编译">11.3 条件编译</h3><p><strong>不在考纲内，不想学了</strong></p><h2 id="12-结构体">12 结构体</h2><p>在C语言中，数组是表示类型相同的数据，而结构体则是若干类型不同数据项的集合；</p><h3 id="12-1-自定义类型">12.1 自定义类型</h3><ul><li>形式：typedef 类型 别名；<ul><li>typedef int  SER;<ul><li>int 是本名 SER是别名（都是大写）</li><li>SER a；就相当于 int a;了</li></ul></li></ul></li></ul><h3 id="12-2-结构体">12.2 结构体</h3><h4 id="12-2-1-结构体的定义">12.2.1 结构体的定义</h4><ul><li><p>形式：struct student</p><p>​{成员列表}： 结构体中成员类型可以相同也可以不同</p><ul><li><u><strong>struct：结构体类型关键字</strong></u></li><li><u><strong>student：结构体名/类型名</strong></u></li><li><u><strong>struct student：结构体类型/结构体类型名</strong></u></li></ul></li></ul><h4 id="12-2-2-结构体变量的定义">12.2.2 结构体变量的定义</h4><ul><li><p>先说明结构体类型，再定义结构体变量</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">student</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> num;</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>]</span><br><span class="line">&#125;;<span class="comment">//分号很重要</span></span><br><span class="line">sruct student a,*b,c[<span class="number">3</span>];</span><br></pre></td></tr></table></figure></li><li><p>说明结构体类型的同时定义结构体变量</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">student</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> num;</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>]</span><br><span class="line">&#125;a,*b,c[<span class="number">3</span>];</span><br></pre></td></tr></table></figure></li><li><p>直接定义结构体变量</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> num;</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>]</span><br><span class="line">&#125;a,*b,c[<span class="number">3</span>];</span><br></pre></td></tr></table></figure></li><li><p>使用用户自定义类型定义结构体变量</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">student</span>&#123;</span><span class="comment">//struct student是本名</span></span><br><span class="line">    <span class="type">int</span> num;</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>]</span><br><span class="line">&#125;STD;<span class="comment">//STD是别名</span></span><br><span class="line">STD a,*b,c[<span class="number">3</span>];<span class="comment">//等价 于sruct student a,*b,c[3];</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="12-2-3-结构体变量的初始化">12.2.3 结构体变量的初始化</h4><ul><li><p>结构体变量的初始化</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">data</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> a;</span><br><span class="line">    <span class="type">float</span> b;</span><br><span class="line">&#125;s=&#123;<span class="number">12</span>,<span class="number">24.56</span>&#125;;</span><br></pre></td></tr></table></figure></li><li><p>结构体数组的初始化</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">data</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> a;</span><br><span class="line">    <span class="type">float</span> b;</span><br><span class="line">&#125;c[<span class="number">2</span>]=&#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>&#125;;</span><br></pre></td></tr></table></figure></li><li><p>结构体变量所占空间大小</p><ul><li><p><strong><u>等于所有成员只和</u></strong></p></li><li><p><u><strong>==结构体指针变量占空间大小分为两种情况==</strong></u></p><ul><li><p>TC系统（默认）下：2个字节</p></li><li><p>VC系统下：4个字节</p><table><thead><tr><th>系统</th><th>指针</th><th>int</th><th>其他</th></tr></thead><tbody><tr><td>TC</td><td>2B</td><td>2B</td><td>不变</td></tr><tr><td>VC</td><td>4B</td><td>4B</td><td>不变</td></tr></tbody></table></li></ul></li></ul></li></ul><h4 id="12-2-4-结构体成员引用">12.2.4 结构体成员引用</h4><ol><li><strong>结构体变量名.成员名字</strong></li><li><strong>结构体指针-&gt;成员名</strong></li><li>*<em>（<em>结构体指针名）.成员名</em></em><ul><li><strong>“.”：成员运算符   “-&gt;”：指向运算符</strong></li></ul></li></ol><h3 id="12-3-链表">12.3 链表</h3><ol><li><p>处理动态链表所需的函数</p><ul><li><p><strong>函数malloc（）和calloc（）为链表分配存储空间，free（）释放存储空间</strong></p><ul><li><p>malloc（）分配变量</p><figure class="highlight c"><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"><span class="type">int</span> *p;</span><br><span class="line">p=(<span class="type">int</span>*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>)(<span class="type">int</span>);</span><br></pre></td></tr></table></figure></li><li><p>calloc（）分配数组</p></li></ul></li></ul></li><li><p>指向自身的结构体类型</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">node</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>静态链表</p><ol><li><p>定义指向自身的结构体类型（数据结构里的链表）</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">node</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li><p>struct node a,b,c,*p; 定义结构体变量</p></li><li><p>a.data=1；b.data=2;c.data=3; 给数据域赋值</p></li><li><p>a.next=&amp;b;b.next=&amp;c;c.next=NULL; 连接</p></li></ol></li><li><p>链表的插入与删除</p><ol><li><p>将q节点插入到p节点后</p><p>q-&gt;next=p-&gt;next;   先连后面</p><p>p-&gt;next=q;   再连前面</p></li><li><p>将q节点从链表中删除（q节点前为p节点）</p><p>p-&gt;next=q-&gt;next;</p><p>free(q);</p></li></ol></li></ol><h2 id="13-位运算">13 位运算</h2><ol><li>位运算符和位运算<ol><li><strong>按位与&amp;：有0为0，全1为1</strong></li><li><strong>按位或|：有1为1，全0为0</strong></li><li><strong>按位异或^：相同为0，不同为1</strong></li><li><strong>按位取反~：相当于-(x+1)</strong></li><li><strong>位左移&lt;&lt;：左移几位乘几个2</strong></li><li><strong>位右移&gt;&gt;：右移几位除几个2（舍弃余数）</strong></li></ol></li></ol><table><thead><tr><th></th><th>0</th><th>1</th></tr></thead><tbody><tr><td>按位&amp;</td><td>清零</td><td>不变</td></tr><tr><td>按位|</td><td>不变</td><td>变1</td></tr><tr><td>按位^</td><td>不变</td><td>求反</td></tr></tbody></table><ul><li><strong><u>==位运算针对的都是二进制数==</u></strong><ul><li>例题</li></ul></li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line">main ()&#123;</span><br><span class="line">    <span class="type">char</span> a,b;</span><br><span class="line">    a=<span class="number">4</span>|<span class="number">3</span>;<span class="comment">//4=&gt;0100  3=&gt;0011</span></span><br><span class="line">    b=<span class="number">4</span>&amp;<span class="number">3</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d %d\n&quot;</span>,a,b);<span class="comment">//a=7 b=0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>优先级：~，&lt;&lt;,&gt;&gt;,&amp;,^,|<ul><li>例题</li></ul></li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line">main ()&#123;</span><br><span class="line">    <span class="type">char</span> a=<span class="number">3</span>,b=<span class="number">6</span>，c;<span class="comment">//a=00000011</span></span><br><span class="line">    c=a^b&lt;&lt;<span class="number">2</span>;<span class="comment">//先算b&lt;&lt;2=24b=00011000</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%x&quot;</span>,c);<span class="comment">//00011011输出为1b，输出无前导</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="14-文件">14 文件</h2><ul><li><p>根据数据的组织形式，C语言将文件分为**<u>ASCII码/文本文件</u><strong>和</strong><u>二进制文件</u>**两种类型</p></li><li><p>文件指针</p><ul><li><p>FILE 文件指针名</p><p>FILE *fp；</p></li></ul></li><li><p>文件函数</p><ul><li><p><strong>文件的打开</strong></p><p>FILE *fp；</p><p>fp=fopen(“a.txt”,”r”);</p><p>以只读的方式打开文件a.txt，返回一个文件型指针，若没有打开文件返回空指针NULL。</p></li><li><p><strong>文件的关闭</strong></p><p>fclose（fp）；</p><p>关闭文件指针fp所指的文件</p></li><li><p><strong>读字符函数 fgetc()</strong></p><p>ch=fgetc(fp);</p><p>从文件指针fp所指的文件中读取一个字符赋给字符变量ch。</p></li><li><p><strong>写字符函数 fputc（）</strong></p><p>fputc(‘a’,fp)；字符常量</p><p>fputc(ch,fp)；字符变量</p><p>讲一个字符写入到文件指针fp所指的文件中</p></li><li><p><strong>读字符串函数 fgets（）</strong></p><p>fgets（str,11,fp）;</p><p>从fp所指的文件中读取**<u>10个</u><strong>字符放入字符输入数组str中，在最后一个（第10个）字符后面</strong><u>加1个‘/0’</u>**</p></li><li><p><strong>写字符串函数 fputs（）</strong></p><p>fputs(“abcd”,fp);字符串常量</p><p>将字符串写入到fp所指的文件中</p><p>fputs(str,fp);字符数组名</p><p>将字符数组中的字符串写入到fp所指的文件中.</p></li><li><p>int a,b;</p><p>fscanf(fp,“%d%d”,&amp;a&amp;b);</p><p>从fp所指的文件中<strong>读取</strong>两个<strong>十进制</strong>整数分别赋给整形变量a和b。</p></li><li><p>int a,b;</p><p>fprintf(fp,“%d%d”,&amp;a&amp;b);</p><p>将整形变量a和b的值以<strong>十进制</strong>的形式<strong>写入</strong>到fp所指的文件夹中</p></li><li><p>数据块读函数fread（）;</p><p>fread（a,4,5,fp）；</p><p>从fp所指的文件中，<strong>每次读4个字节的数据<u>放入数组a中</u>，连续读5次。</strong></p></li><li><p>数据块写函数fwrite（）；</p><p>fwrite（a,4,5,fp）</p><p>从a数组的首地址开始，<strong>每次将4个字节的数据<u>写入到fp</u>所指的文件中，连续写5次。</strong></p></li><li><p>rewind（）；</p><p>将fp所指文件的位置**指针（插入点）移动到文件首部****</p></li><li><p>feof（fp）；</p><ul><li>判断fp所指的文件是否处于结束位置，<u><strong>处于结束位置返回1，否则返回0</strong></u></li><li>E0F含义是指文件结束标志</li></ul></li><li><p>fseek（文件指针，位移量，起始点）；</p><p>fseek(fp,0L,SEEK_SET);等价于rewind（fp）</p><p>fseek(fp,0L,2);指针位于文件末尾</p></li></ul><table><thead><tr><th>起始点</th><th>表示符号</th><th>数字表示</th></tr></thead><tbody><tr><td>文件首</td><td>SEEK_SET</td><td>0</td></tr><tr><td>当前位置</td><td>SEEK_CUR</td><td>1</td></tr><tr><td>文件末尾</td><td>SEEK_END</td><td>2</td></tr></tbody></table></li></ul><table><thead><tr><th>打开方式</th><th>具体含义</th></tr></thead><tbody><tr><td>“rt”</td><td>只读打开一个文本文件，只允许读数据。</td></tr><tr><td>“wt”</td><td>只写打开或建立一个文本文件，只允许写数据。</td></tr><tr><td>“at”</td><td>追加打开一个文本文件，并在文件末尾写数据。</td></tr><tr><td>“rb&quot;</td><td>只读打开一个二进制文件，只允许读数据。</td></tr><tr><td>“ wb ”</td><td>只写打开或建立一个二进制文件，只允许写数据。</td></tr><tr><td>“ ab”</td><td>追加打开一个二进制文件，并在文件末尾写数据。</td></tr><tr><td>“rt+”</td><td>读写打开一个文本文件，允许读和写。</td></tr><tr><td>“wt+”</td><td>读写打开或建立一个文本文件，允许读写。</td></tr><tr><td>“at+”</td><td>读写打开一个文本文件，允许读，或在文件末追加数据。</td></tr><tr><td>“rb+”</td><td>读写打开一个二进制文件，允许读和写。</td></tr><tr><td>“wb+”</td><td>读写打开或建立一个二进制文件，允许读和写。</td></tr><tr><td>“ab+”</td><td>写打开一个二进制文件，允许读，或在文件末追加数据。</td></tr></tbody></table><ul><li><p><strong><u>==r 读     w 写    a 追加     b二进制文件    t 文本文件    +可读可写==</u></strong></p><ul><li><strong>新建的文件用w开头</strong></li><li>若要打开一个***<u>==新的==</u>***二进制文件，该文件要既能读也能写，则文件方式字符串应是“wb+”</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line">main()</span><br><span class="line">&#123; FILE *fp;</span><br><span class="line"><span class="type">char</span> str[<span class="number">10</span>];</span><br><span class="line">fp=fopen(<span class="string">&quot;myfile.dat&quot;</span>,<span class="string">&quot;w&quot;</span>); <span class="comment">//新建文件夹</span></span><br><span class="line"><span class="built_in">fputs</span>(<span class="string">&quot;abc&quot;</span>,fp);<span class="comment">//字符串abc写进去</span></span><br><span class="line">fclose(fp);<span class="comment">//关上</span></span><br><span class="line">fp=fopen(<span class="string">&quot;myfile.dat&quot;</span>,<span class="string">&quot;a+&quot;</span>);<span class="comment">//追加形式打开可读可写</span></span><br><span class="line"><span class="built_in">fprintf</span>(fp,<span class="string">&quot;%d&quot;</span>,<span class="number">28</span>);<span class="comment">//把28写入</span></span><br><span class="line">rewind(fp);<span class="comment">//回到开头u</span></span><br><span class="line"><span class="built_in">fscanf</span>(fp,<span class="string">&quot;%s&quot;</span>,str);<span class="comment">//读字符串</span></span><br><span class="line"><span class="built_in">puts</span>(str);<span class="comment">//写字符串abc28</span></span><br><span class="line">fclose(fp);<span class="comment">//关闭文件</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul>]]></content>
    
    
    <summary type="html">本文是C语言课程的复习记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第八章</title>
    <link href="https://www.fomal.cc/posts/46119d0b.html"/>
    <id>https://www.fomal.cc/posts/46119d0b.html</id>
    <published>2023-10-23T09:49:17.000Z</published>
    <updated>2023-10-23T10:01:44.312Z</updated>
    
    <content type="html"><![CDATA[<h2 id="8-排序">8 排序</h2><h3 id="8-1-基本概念和排序方法概述">8.1 基本概念和排序方法概述</h3><h4 id="8-1-1-排序的基本概念">8.1.1 排序的基本概念</h4><ul><li><p>排序：将一组杂乱无章的数据按照一定规律顺次排列起来，即，将无需数列排成一个有序序列（由小到大或由大到小）的运算。</p><ul><li>如果参加排序的数据结点包含多个数据域，那么排序往往是针对其中某个域而言</li><li>排序方法的分类：<ul><li>按<strong>数据存储介质</strong>：内部排序和外部排序<ul><li>内部排序：数据量不大，数据在外存，无需内外存交换数据</li><li>外部排序：数据量较大，数据在外存（文件排序）<ul><li>外部排序时，要将数据分批调入内存来排序，中间结果还要及时放入外存，显然外部排序要复杂得多</li></ul></li></ul></li><li>按<strong>比价器个数</strong>：串行排序和并行排序<ul><li>串行排序：单处理机（同一时刻比较一对元素）</li><li>并行排序：多处理机（同一时刻比较多对元素）</li></ul></li><li>按<strong>主要操作</strong>：比较排序和基数排序<ul><li>比较排序：用比较的方法<ul><li>插入排序、交换排序、选择排序、归并排序</li></ul></li><li>基数排序：不比较元素的大小，仅仅根据元素本身的取值确定其有序位置</li></ul></li><li>按<strong>辅助空间</strong>：原地排序和非原地排序<ul><li>原地排序：辅助空间用量为O(1)的排序方法（所占用的辅助存储空间与参与排序的数据量大小无关）</li><li>非原地排序：辅助空间用量超过O(1)的排序方法</li></ul></li><li>按<strong>稳定性</strong>：稳定排序和非稳定排序<ul><li>稳定排序：能够使任何数值相等的元素，排序以后相对次序不变。</li><li>非稳定排序：不是稳定排序的方法</li></ul></li><li>按<strong>自然性</strong>：自然排序和非自然排序<ul><li>自然排序：输入数据越有序，排序的速度越快的排序方法</li><li>非自然排序：不是自然排序的方法</li></ul></li></ul></li></ul></li><li><p>接下来的学习内容：</p></li><li><p>按排序依据原则：</p><ul><li>插入排序：直接插入排序、折半插入排序、希尔排序</li><li>交换排序：冒泡排序、快速排序</li><li>选择排序：简单选择排序、堆排序</li><li>归并排序:：2-路归并排序</li><li>基数排序</li></ul></li><li><p>按排序所需工作量</p><ul><li>简单的排序方法：$ T(n)=O(n^2)$</li><li>基数排序：$ T(n)=O(d.n)$</li><li>先进的排序方法：$ T(n)=O(nlogn)$</li></ul></li><li><p>存储结构——记录序列以顺序表存储</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXSIZE 20<span class="comment">//设记录不超过20个</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> KeyType;<span class="comment">//设关键字为int型</span></span><br></pre></td></tr></table></figure><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Typedef <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    KeyType key;<span class="comment">//关键字</span></span><br><span class="line">    infoType otherinfo;<span class="comment">//其他数据项</span></span><br><span class="line">&#125;RedType;</span><br></pre></td></tr></table></figure><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Typedef <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//定义顺序表的结构</span></span><br><span class="line">    RedType r[MAXSIZE +<span class="number">1</span>];<span class="comment">//存储顺序表的向量</span></span><br><span class="line">    <span class="comment">//r[0]一般作哨兵或缓冲区</span></span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-2-插入排序">8.2 插入排序</h3><ul><li>基本思想：<ul><li>在有序序列中插入一个元素，保持序列有序，有序长度不断增加</li></ul></li><li>插入排序的种类<ul><li>顺序法定位插入位置——直接插入排序</li><li>二分法定位插入位置——二分插入排序</li><li>缩小增量多遍插入排序——希尔排序</li></ul></li></ul><h4 id="8-2-1-直接插入排序">8.2.1 直接插入排序</h4><ul><li><p>直接插入排序——采用顺序查找法查找插入位置</p><ol><li><p>复制插入元素</p></li><li><p>记录后移，查找插入位置</p><figure class="highlight c"><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"><span class="keyword">for</span>(j=i<span class="number">-1</span>;j&gt;=<span class="number">0</span>&amp;&amp;x&lt;a[j]；j--)</span><br><span class="line">    a[j+<span class="number">1</span>]=a[j];</span><br></pre></td></tr></table></figure></li><li><p>插入到正确位置</p></li></ol></li><li><p>直接插入排序，使用“哨兵”</p><ol><li>复制为哨兵   L.r[0]=L.r[i];</li><li>记录后移，查找插入位置</li><li>插入到正确位置</li></ol></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">InserSort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i,j;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">2</span>;i&lt;=L.length;++i)&#123;</span><br><span class="line">        <span class="keyword">if</span>(L.r[i].key&lt;L.r[i<span class="number">-1</span>].key)&#123;</span><br><span class="line">            L.r[<span class="number">0</span>]=L.r[i];</span><br><span class="line">            <span class="keyword">for</span>(j=i<span class="number">-1</span>;L.r[<span class="number">0</span>].key&lt;L.r[j].key;--j)&#123;</span><br><span class="line">                L.r[j+<span class="number">1</span>]=L.r[j];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        L.r[j+<span class="number">1</span>]=L.r[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>平均的情况：<ul><li>比较次数$\sum\limits^{n-1}_{i=1} \dfrac{i+1}{2}=\dfrac{1}{4}(n+2)(n-1) $</li><li>移动次数$\sum\limits^{n-1}_{i=1} （\dfrac{i+1}{2}+1）=\dfrac{1}{4}(n+6)(n-1) $</li></ul></li><li>时间复杂度结论<ul><li>原始数据越接近有序，排序速度越快</li><li>最坏情况下（输入数据是逆有序的） $Tw(n)=O(n^2)$</li><li>平均情况下，耗时差不多是最坏情况的一半 $Te(n)=O(n^2)$</li><li>要提高查找速度<ul><li>减少元素的比较次数</li><li>减少元素的移动次数</li></ul></li></ul></li></ul><h4 id="8-2-2-折半插入排序">8.2.2 折半插入排序</h4><figure class="highlight c"><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="type">void</span> <span class="title function_">BinsertSort</span><span class="params">(SqList &amp;L)</span>｛   <span class="comment">//对顺序表L做折半插入排序</span></span><br><span class="line"><span class="title function_">for</span> <span class="params">(i=<span class="number">2</span>;i&lt;=L.length;++i)</span>&#123;</span><br><span class="line">    L.r[O]=L.r[i];</span><br><span class="line">low=l;high=i<span class="number">-1</span>;</span><br><span class="line"><span class="keyword">while</span>(low&lt;=high)&#123;</span><br><span class="line">        m=(low+high)/<span class="number">2</span>;</span><br><span class="line"><span class="keyword">if</span>(L.r[O].key&lt;L.r[mid].key) high=mid<span class="number">-1</span>;<span class="comment">//插入点在前一子表</span></span><br><span class="line"><span class="keyword">else</span> low=mid+<span class="number">1</span>;<span class="comment">//插入点在后一子表</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (j=i一l;j&gt;=high+l; --j) L.r[j+l]=L.r(j]; <span class="comment">//记录后移</span></span><br><span class="line">L.r[high+l]=L.r[O]; <span class="comment">//将r[O]即原r[i], 插入到正确位置</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>算法特点：<ul><li>稳定排序</li><li>应为要进行折半查找，所以只能用于顺序结构，不能用于链式结构</li><li>适合初始记录无序，n较大时的情况</li></ul></li><li>算法分析：<ul><li>时间复杂度为$O(n^2)$</li><li>空间复杂度为$O(1)$</li></ul></li></ul><h4 id="8-2-3-希尔排序">8.2.3 希尔排序</h4><ul><li><p>基本思想：</p><ul><li>先将整个待排记录序列分割成<strong>若干子序列</strong>，分别进行<strong>直接插入排序</strong>待整个序列中的记录“<strong>基本有序</strong>”时，再对全体记录进行一次直接插入排序。</li></ul></li><li><p>算法特点：</p><ul><li>一次移动，移动位置较大，跳跃式地接近排序后的最终位置</li><li>最后一次只需要少量移动</li><li>增量序列必须是递减的，最后一个必须是1</li><li>增量序列应该是互质的</li></ul></li><li><p>算法举例：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231019164250.png" alt=""></p></li><li><p>算法代码：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">shellsort</span><span class="params">(Sqlist &amp;L,<span class="type">int</span> dlta[],<span class="type">int</span> t)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(k=<span class="number">0</span>;k&lt;t;++k)</span><br><span class="line">        shellinsert(L,dlta[k]);<span class="comment">//一趟增量为dlta[k]的插入排序</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c"><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="type">void</span> shellinert（SqList &amp;L，<span class="type">int</span> dk)</span><br><span class="line">    <span class="keyword">for</span>(i=dk+<span class="number">1</span>;i&lt;=L.length;++i)</span><br><span class="line">        <span class="keyword">if</span>(r[i].key&lt;r[i-dk].key)&#123;</span><br><span class="line">            r[<span class="number">0</span>]=r[i];</span><br><span class="line">            <span class="keyword">for</span>(j=i-dk;j&gt;<span class="number">0</span> &amp;&amp;（r[<span class="number">0</span>].key&lt;r[j].key);j=j-dk)</span><br><span class="line">                r[j+dk]=r[j];</span><br><span class="line">            r[j+dk]=r[<span class="number">0</span>]</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure></li><li><p>算法分析：</p><ul><li>时间复杂度是n和d的函数：<ul><li>$O(N^{1.25})到O(1.6n^{1.25})——经验公式 $</li><li>时间复杂度为$O(1)$</li><li>是一种<strong>不稳定</strong>的排序方法</li></ul></li></ul></li></ul><h3 id="8-3-交换排序">8.3 交换排序</h3><h4 id="8-3-1-冒泡排序">8.3.1 冒泡排序</h4><ul><li>基本思想：每趟不断将记录两两比较，并按“前小后大”规则交换</li></ul><figure class="highlight c"><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="type">void</span> <span class="title function_">bubble_sort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="type">int</span> m,j,i; RedType x;</span><br><span class="line">    <span class="keyword">for</span>(m=<span class="number">1</span>;m&lt;=n<span class="number">-1</span>;m++)&#123;</span><br><span class="line">        <span class="keyword">for</span>(j=<span class="number">1</span>j=n-m;j++)</span><br><span class="line">            <span class="keyword">if</span>(L.r[j].key&gt;L.r[j+<span class="number">1</span>].key)&#123;</span><br><span class="line">                x=L.r[j];</span><br><span class="line">                L.r[j]=L.r[j+<span class="number">1</span>];</span><br><span class="line">                r[j+<span class="number">1</span>]=x</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>改进的冒泡排序算法</p><figure class="highlight c"><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="type">void</span> <span class="title function_">bubble_sort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="type">int</span> m,j,i,flag=<span class="number">1</span>; RedType x; </span><br><span class="line">    <span class="keyword">for</span>(m=<span class="number">1</span>;m&lt;=n<span class="number">-1</span>&amp;&amp;flag==<span class="number">1</span>;m++)&#123;<span class="comment">//用flag作为是否有交换的标记</span></span><br><span class="line">        flag=<span class="number">0</span>；</span><br><span class="line">        <span class="keyword">for</span>(j=<span class="number">1</span>j=n-m;j++)</span><br><span class="line">            <span class="keyword">if</span>(L.r[j].key&gt;L.r[j+<span class="number">1</span>].key)&#123;</span><br><span class="line">           flag=<span class="number">1</span> <span class="comment">//若发生交换，flag置为1</span></span><br><span class="line">                x=L.r[j];</span><br><span class="line">                L.r[j]=L.r[j+<span class="number">1</span>];</span><br><span class="line">                r[j+<span class="number">1</span>]=x</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>冒泡排序算法评价</p><ul><li>冒泡排序最好时间复杂度是$O(n)$</li><li>冒泡排序最坏时间复杂度为$(n^2)$</li><li>冒泡排序平均时间复杂度为$O(n^2)$</li><li>冒泡排序算法中增加一个辅助空间temp，辅助空间为$S(n)=O(1)$</li><li>冒泡排序是<strong>稳定的</strong></li></ul></li></ul><h4 id="8-3-2-快速排序">8.3.2 快速排序</h4><ul><li><p>基本思想</p><ul><li>任取一个元素<strong>为中心</strong></li><li>所有比他<strong>小的元素一律前放</strong>，比他<strong>大的后放</strong>，形成<strong>左右两个子表</strong></li><li>对各子表重新选择中心元素并<strong>依此规则调整</strong></li><li>知道每个子表的元素<strong>只剩一个</strong></li></ul></li><li><p>快速排序演示</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231020151722.png" alt=""></p></li><li><p>算法图解：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231020152334.png" alt=""></p></li><li><p>排序算法：</p><figure class="highlight c"><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="type">int</span> <span class="title function_">partition</span><span class="params">(SqList &amp;L,<span class="type">int</span> low,<span class="type">int</span> high)</span>&#123;</span><br><span class="line">    L.r[<span class="number">0</span>]=L.r[low];<span class="comment">//用子表的第一个记录做枢轴记录</span></span><br><span class="line">    pivotkey=L.r[low].key;<span class="comment">//枢轴记录关键字保存在pivotkey中</span></span><br><span class="line">    <span class="keyword">while</span>(low&lt;high)&#123;<span class="comment">//从表的两端交替地向中间扫描</span></span><br><span class="line">        <span class="keyword">while</span>(low&lt;high&amp;&amp;L.[high].key&gt;=pivotkey)--high;</span><br><span class="line">        L.r[low]=L.r[high];<span class="comment">//将比枢轴记录小的移动到低端</span></span><br><span class="line">        <span class="keyword">while</span>(low&lt;high&amp;&amp;L.[low].key&gt;=pivotkey)++low;</span><br><span class="line">        L.r[high]=L.r[low];<span class="comment">//将比枢轴记录大的移动到高端</span></span><br><span class="line">    &#125;</span><br><span class="line">    L.r[low]=L.r[<span class="number">0</span>];<span class="comment">//枢轴记录到位</span></span><br><span class="line">    <span class="keyword">return</span> low;<span class="comment">//返回枢轴记录</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">QSort</span><span class="params">(SqList &amp;L,<span class="type">int</span> low,<span class="type">int</span> high)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(low&lt;high)&#123;</span><br><span class="line">        pivotloc=Partition(L,low,high);<span class="comment">//将L.r[low,high]一分为二。pivotloc是枢轴记录</span></span><br><span class="line">        QSort(L,low,pivotloc<span class="number">-1</span>);<span class="comment">//对左子表递归排序</span></span><br><span class="line">        QSort(L,pivotloc+<span class="number">1</span>,high);<span class="comment">//对右子表递归排序</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">QuickSort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    QSort(L,<span class="number">1</span>,L.length);<span class="comment">//对顺序表L做快速排序</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>快速排序算法分析：</p><ul><li><p>时间复杂度</p><ul><li>平均计算时间是$O(nlog_2n)$</li><li>实验表明快速排序是我们所讨论的所有内排序方法中最好的一个。</li></ul></li><li><p>空间复杂度</p><ul><li>快速排序不是原地排序</li><li>平均情况下：需要$O(logn)$的栈空间</li><li>最坏情况下：栈空间可达$O(n)$</li></ul></li><li><p>快速排序不适于队原本有序或基本有序的记录序列进行排序</p></li></ul></li></ul><h3 id="8-4-选择排序">8.4 选择排序</h3><h4 id="8-4-1-简单选择排序">8.4.1 简单选择排序</h4><ul><li><p>基本思想：</p><ul><li>在待排序的数据中选出最大(小)的元素放在其最终的位置。</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231020171702.png" alt=""></p></li><li><p>算法代码</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">SelectSort</span><span class="params">(SqList &amp;K)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;L.length;++i)&#123;</span><br><span class="line">        k=i;</span><br><span class="line">        <span class="keyword">for</span>(j=i+<span class="number">1</span>;j&lt;=L.length;j++)</span><br><span class="line">            <span class="keyword">if</span>(L.r[j].key&lt;L.r[k].key)k=j;<span class="comment">//记录最小值位置</span></span><br><span class="line">        <span class="keyword">if</span>(k!=i)&#123;</span><br><span class="line">            x=L.r[i];</span><br><span class="line">            L.r[i]=L.r[k];</span><br><span class="line">            L.r[k]=x;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>算法分析</p><ul><li>时间复杂度：$O(n^2)$</li><li>空间复杂度：$O(1)$</li></ul></li><li><p>简单选择排序是<strong>不稳定</strong>排序</p></li></ul><h4 id="8-2-2-堆排序">8.2.2 堆排序</h4><ul><li><p>若有n个元素${a_1,a_2,……,a_n}$满足<br>$$<br>\left{<br>\begin{array}{l}<br>a_i&lt;=a_{2i}\<br>a_i&lt;=a_{2i+1}\<br>\end{array}<br>\right.<br>\quad\text{或者}\quad<br>\begin{cases}<br>a_i&gt;=a_{2i}\<br>a_i&gt;=a_{2i+1}\<br>\end{cases}<br>$$</p><ul><li><p>则分别称该序列为小根堆和大根堆。</p></li><li><p>从堆 定义可以看出，堆实质是满足如下性质的完全二叉树：二叉树中任一非叶子结点均小于(大于)它的孩子结点</p></li></ul></li><li><p>若在输出<strong>堆顶</strong>的最小值 (最大值) 后，使得剩余n-1个元素的序列重又建成一个堆，则得到n个元素的次小值 (次大值) …如此反复，便能得到一个有序序列，这个过程称之为<strong>堆排序</strong>。</p></li><li><p>堆的调整</p><ul><li>小根堆<ol><li>输出堆顶元素之后，以堆中<strong>最后一个元素替代之</strong></li><li>.然后将根结点值与左、右子树的根结点值进行比较，并与其中<strong>小者进行交换</strong>，</li><li>重复上述操作，直至叶子结点，将得到新的堆，称这个从堆顶至叶子的调整过程为“<strong>筛选</strong>“</li></ol></li></ul></li><li><p>算法描述</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">HeapAdjust</span><span class="params">(elem R[],<span class="type">int</span> s, <span class="type">int</span> m)</span>&#123;</span><br><span class="line">    rc=R[s];</span><br><span class="line">    <span class="keyword">for</span>(j=<span class="number">2</span>*S;J&lt;=M;J*=<span class="number">2</span>)&#123;<span class="comment">//沿key较大的孩子结点向下筛选</span></span><br><span class="line">        <span class="keyword">if</span>(j&lt;m&amp;&amp;R[j]&lt;R[j+<span class="number">1</span>])++j;<span class="comment">//j为key较大的记录的下标</span></span><br><span class="line">        <span class="keyword">if</span>(rc&gt;=R[j])<span class="keyword">break</span>;</span><br><span class="line">        R[s]=R[j];</span><br><span class="line">        s=j;<span class="comment">//rc应插入在位置s上</span></span><br><span class="line">    &#125;</span><br><span class="line">    R[s]=rc;<span class="comment">//插入</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>堆的建立</p><ul><li><p>单结点的二又树是堆;</p></li><li><p>在完全二叉树中所有以叶子结点 (序号i &gt; n/2) 为根的子树是堆这样，我们只需依次将以序号为$n/2，n/2-1，…,1$的结点为根的子树均调整为堆即可。</p><p>从最后一个非叶子结点开始，以此向前调整</p><ol><li>调整从第$n/2$个元素开始，将以该元素为根的二叉树调整为堆</li><li>将以序号为$n/2 - 1$的结点为根的二叉树调整为堆</li><li>再将以序号为$n/2 - 2$的结点为根的二又树调整为堆</li><li>再将以序号为$n/2 - 3$的结点为根的二又树调整为堆</li></ol></li><li><p>通过以上分析可知：</p><ul><li>若对一个无序序列建堆，然后输出根；重复该过程就可以由一个无序序列输出有序序列。</li><li>实质上，堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的</li></ul></li></ul></li><li><p>堆排序算法</p><figure class="highlight c"><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"><span class="type">void</span> HeapSort（elem R[])&#123;<span class="comment">//对R[1]到R[n]进行堆排序</span></span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span>(i=n/<span class="number">2</span>;i&gt;=<span class="number">1</span>;i--)</span><br><span class="line">        HeapAdjust(R,i,n);<span class="comment">//建立初始堆</span></span><br><span class="line">    <span class="keyword">for</span>(i=n;i&gt;<span class="number">1</span>;i--)&#123;<span class="comment">//进行n-1趟排序</span></span><br><span class="line">        Swap(R[<span class="number">1</span>],R[i]);<span class="comment">//根与最后一个元素交换</span></span><br><span class="line">        HeapAdjust(R,<span class="number">1</span>,i<span class="number">-1</span>);<span class="comment">//对R[1]到R[i-1]重新建堆</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>算法分析</p><ul><li>初始化堆所需时间不超过O(n)</li><li>堆排序在最坏情况下，其时间复杂度也为$O(nlog_2 n)$，这是堆排序的最大优点。无论待排序列中的记录是正序还是逆序排列，都不会使堆排序处于&quot;最好”或&quot;最坏“的状态。</li></ul></li></ul><h3 id="8-5-归并排序">8.5 归并排序</h3><ul><li><p>基本思想：</p><ul><li>将两个或两个以上的有序子序列“<strong>归并</strong>”为一个有序序列</li><li>在内部排序中，通常采用的是<strong>2-路归并排序</strong>。</li></ul></li><li><p>排序示例：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231021151235.png" alt=""></p><ul><li>整个归并排序仅需[$log_2 n $]趟</li></ul></li><li><p>算法分析：</p><ul><li>时间效率：$O(nlog_2n)$</li><li>空间效率：$O(n)$、<ul><li>因为需要一个与原始序列同样大小的辅助序列。这正是此算法的缺点</li></ul></li><li>具有<strong>稳定</strong>性</li></ul></li></ul><h3 id="8-6-基数排序">8.6 基数排序</h3><ul><li><p>基本思想：</p><ul><li>分配+收集</li><li>也叫<strong>桶排序或箱排序</strong>: 设置若干个箱子，将关键字为k的记录放入第k个箱子，然后在按序号将非空的连接。</li></ul></li><li><p>算法示例：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231021152926.png" alt=""></p><ul><li>将这组数据第一趟按照个位排，然后收集回来</li><li>接下来按照十位来排，接下来收集回来</li><li>最后按照百位来排，收集回来时，我们可以发现已经有序了</li></ul></li><li><p>算法分析</p><ul><li>时间效率：O(k*(n+m))<ul><li>k：关键字个数</li><li>m：关键字取值范围位m个值</li></ul></li></ul><p>总结：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231021153750.png" alt=""></p></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第八章--王卓老师第十四周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第七章(下篇)</title>
    <link href="https://www.fomal.cc/posts/28267775.html"/>
    <id>https://www.fomal.cc/posts/28267775.html</id>
    <published>2023-10-22T15:44:34.000Z</published>
    <updated>2023-10-23T10:01:44.302Z</updated>
    
    <content type="html"><![CDATA[<h4 id="7-3-2-平衡二叉树">7.3.2 平衡二叉树</h4><ul><li><p>平衡二叉树的定义</p><ul><li>又称AVL树</li><li>一棵平衡二叉树或者是空树，或者是具有以下性质的二叉排序树：<ul><li><strong>左</strong>子树与<strong>右</strong>子树的<strong>高度之差的绝对值小于等于1</strong>；</li><li><strong>左</strong>子树和<strong>右</strong>子树也是平衡二叉排序树。</li></ul></li></ul></li><li><p>为了方便起见，给每个结点附加一个数字，给出<strong>该结点左子树与右子树的高度差</strong>。这个数字称为结点的<strong>平衡因子</strong></p><ul><li><p>根据平衡二叉树的定义，平衡二叉树上所有结点的平衡因子只能是-1，0，或1</p></li><li><p>对于一棵有n个结点的AVL树，其高度保持在$O(log_2n)$数量级，ASL也是保持在$O(log_2n)$量级</p></li><li><p>当我们在一个平衡二叉树上插入结点时，可能导致<strong>失衡</strong></p><ul><li><p>平衡调整的四种类型：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231018144342.png" alt=""></p><p>LR型和RL型左右要看B和A哪个大</p><ul><li>调整原则：1. 降低高度 2. 保持二叉排序树性质</li></ul></li></ul></li></ul></li></ul><h3 id="7-4-哈希表的查找">7.4 哈希表的查找</h3><h4 id="7-4-1-散列表的基本概念">7.4.1 散列表的基本概念</h4><ul><li>基本思想：记录的存储位置与关键字之间存在对应关系</li></ul><p>​对应关系——hash函数（hash:散列）</p><p>​Loc(i)=H(keyi)</p><ul><li>散列表的查找<ul><li>根据散列函数H(key)=k<ul><li>查找key=9，直接访问H(9)=9号地址，若内容为9则成功<br>若查不到，则返回一个特殊值，如空指针或空记录。</li></ul></li><li>优点：查找效率高<br>缺点：空间效率低</li></ul></li><li>散列函数和散列地址：在记录的存储位置p和其关键字key 之间建立一个确定的对应关系H, 使 p=H(key ), 称这个对应关系H为散列函数，p为散列地址。</li><li>散列表：一个有线连续的地址空间，用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组，散列地址是数组的下标</li><li>冲突：不同的关键码映射到同一个散列地址<ul><li>key1≠key2，但是H(key1)=H(key2)</li><li>冲突是不可避免的，但可以减少</li></ul></li><li>同义词：具有相同函数值的多个关键字</li></ul><h4 id="7-4-2-散列函数的构造方法">7.4.2 散列函数的构造方法</h4><ul><li><p>构造散列函数的考虑因素：</p><ul><li>散列表的长度</li><li>关键字的长度</li><li>关键字的分布情况</li><li>计算散列函数所需的时间</li><li>记录的查找频率</li></ul></li><li><p>构造号的散列函数要遵循以下两条原则</p><ul><li>函数计算要简单，每一个关键字只能有一个散列地址与之对应</li><li>函数的值域需在表长的范围内， 计算出的散列地址的分布应均匀，尽可能减少冲突。</li></ul></li><li><p>根据数据元素的集合特性构造</p><ul><li>要求一: n个数据原仅占用n个地址虽然散列查找是以空间换时间，但仍希望散列的地址空间尽量小</li><li>要求二:无论用什么方法存储，目的都是尽量均匀地存放元素，以避免冲突。</li></ul></li></ul><h5 id="7-4-2-1-直接定址法">7.4.2.1 直接定址法</h5><ul><li>Hash(key)=a.key+b</li><li>优点：以关键码key的某个线性函数值为散列地址，不会产生冲突<br>缺点：要占用连续的地址空间，空间效率低</li></ul><h5 id="7-4-2-2-除留余数法">7.4.2.2 除留余数法</h5><ul><li><p>Hash(key)=key mod p</p><ul><li><p>技巧：设：表长为m，取p&lt;=m且为质数</p><ul><li><p>例：{15,23,27,38,53,61,70}</p><p>散列函数：Hash(key)=key mod 7</p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th></tr></thead><tbody><tr><td>70</td><td>15</td><td>23</td><td>38</td><td>53</td><td>61</td><td>27</td></tr></tbody></table></li></ul></li></ul></li></ul><h4 id="7-4-3-处理冲突的方法">7.4.3 处理冲突的方法</h4><h5 id="7-4-3-1-开放地址法（开地址法）">7.4.3.1 开放地址法（开地址法）</h5><ul><li><p>基本思想：有冲突时就去寻找下一个空的散列地址，只要散列表足够大，空的散列地址总能找到，并将数据元素存入</p><ul><li>列入：除留余数法：$H_i=(Hash(key)+d)mod \ m$</li><li>常用方法：<ul><li>线性探测法      $d_i为1,2,……，m-1线性序列$</li><li>二次探测法      $d_i为1^2,-1^2,2^2,-2^2……，q^2二次序列$</li><li>伪随机探测法    $d_i为伪随机数序列$</li></ul></li></ul></li><li><p>例：关键码集为：{47，7，29，11，16，92，22，8，3}，散列表长m=11，散列函数为$Hash(key)=key \ mod \ 11$,拟用线性探测法处理冲突</p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th><th>10</th></tr></thead><tbody><tr><td>11</td><td>22</td><td></td><td>47</td><td>92</td><td>16</td><td>3</td><td>7</td><td>29</td><td>8</td><td></td></tr></tbody></table><ul><li>使用线性探测法解释：</li></ul><ol><li>47、7均是由散列函数得到的没有冲突的散列地址;</li><li>Hash(29)=7，散列地址有冲突，需寻找下一个空的散列地址由$H=(Hash(29)+1) mod \ 11=8$，散列地址8为空，因此将29存入。</li><li>11、16、92均是由散列函数得到的没有冲突的散列地址:</li><li>另外，22、8、3同样在散列地址上有冲突，也是由H,找到空的散列地址的。<ul><li>平均查找长度ASL=(1+2 +1 +1 +1 +4 +1 +2 +2)/9=1.67</li></ul></li></ol><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th><th>10</th></tr></thead><tbody><tr><td>11</td><td>22</td><td>3</td><td>47</td><td>92</td><td>16</td><td></td><td>7</td><td>29</td><td>8</td><td></td></tr></tbody></table><ul><li>使用二次探测法解释：<ul><li>Hash(3)=3散列地址有冲突，由于$H_1=(Hash(3)+1^2)mod \ 11=4$，仍然冲突；</li><li>$H_2=(Hash(3)-1^2)mod \ 11=2$找到空的散列地址，存入</li></ul></li></ul></li></ul><h5 id="7-4-3-2-链地址法（拉链法）">7.4.3.2 链地址法（拉链法）</h5><ul><li><p>基本思想：相同散列地址的记录链成一单链表</p></li><li><p>m个散列地址就设m个单链表，然后用一个数组将m个单链表的表头指针存储起来，形成一个动态结构。</p></li><li><p>链地址法建立散列表的步骤</p><ul><li>step1：取数据元素的关键字key，计算其散列函数值 (地址)。若该地址对应的链表为空，则将该元素插入此链表；否则执行Step2解决冲突。</li><li>step2：根据选择的冲突处理方法，计算关键字key的下一个存储地址若该地址对应的链表为不为空，则利用链表的前插法或后插法将该元素插入此链表</li></ul></li><li><p>链地址法的优点：</p><ul><li>非同义词不会冲突，无“聚集”现象<ul><li>链表上的结点空间动态申请，更适合于表长不确定的情况</li></ul></li></ul></li></ul><h4 id="7-4-4-散列表的查找">7.4.4 散列表的查找</h4><ul><li>给定值查找值k，查找过程：</li></ul><figure class="highlight plaintext"><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">graph TD</span><br><span class="line">A[给定k值]==&gt;B(计算H)</span><br><span class="line">B==&gt;C&#123;此地址为空&#125;</span><br><span class="line">C--&gt;|N|D&#123;关键字==k&#125;</span><br><span class="line">C--&gt;|Y|G[查找失败]</span><br><span class="line">D--&gt;|N|E[案处理冲突方法计算Hi]</span><br><span class="line">D--&gt;|Y|H[查找成功]</span><br><span class="line">E--&gt;C</span><br><span class="line">F[竖向流程图]</span><br></pre></td></tr></table></figure><ul><li><p>$ASL=(1<em>6+2+3</em>3+4+9)/12=2.5$</p></li><li><p>用连地址法处理冲突：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231018180909.png" alt=""></p></li><li><p>$ASL=(1<em>6+2</em>4+3+4)/12=1.75$</p></li><li><p>使用平均查找长度ASL来衡量查找算法，ASL取决于</p><ul><li>散列函数</li><li>处理冲突的方法</li><li>散列表的装填因子α<ul><li>$\Large α=\dfrac {表中填入的记录数}{哈希表的长度}$</li><li>α越大，表中的记录数越多，表越满，发生冲突的可能性就越大，查找时比较次数就越多</li></ul></li><li>无冲突时才能达到O(1)</li><li>$\large ASL≈1+\dfrac {α}{2}$</li><li>$\large ASL≈\dfrac {1}{2}(1+\dfrac{1}{1-α})$</li><li>$\large ASL≈-\dfrac{1}{α}ln(1-α)$</li></ul></li><li><p>结论</p><ul><li>链地址法优于开地址法</li><li>散列表技术具有很好的平均性能，优于一些传统的技术</li><li>除留余数法作散列函数优于其它类型函数</li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第七章(下篇)--王卓老师第十三周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第七章(汇总)</title>
    <link href="https://www.fomal.cc/posts/a518c5d2.html"/>
    <id>https://www.fomal.cc/posts/a518c5d2.html</id>
    <published>2023-10-22T15:44:34.000Z</published>
    <updated>2023-10-23T10:01:44.305Z</updated>
    
    <content type="html"><![CDATA[<h2 id="7-查找">7 查找</h2><h3 id="7-1-查找的基本概念">7.1 查找的基本概念</h3><ul><li><p><strong>查找表</strong>：是由同一类型的数据元素（或记录）构成的<strong>集合</strong>。由于“集合”中的数据元素之间存在着某种松散的关系，因此查找表是一种应用灵便的结构</p><ul><li>查找表分两类<ul><li>静态查找表：仅做“查询“{检索)操作的查找表</li><li>动态查找表：做“插入”和“删除”操作的查找表</li></ul></li></ul></li><li><p><strong>关键字</strong>：关键字是数据元素（或记录） 中某个数据项的值，用它可以标识一个数据元素（或记录）。</p><ul><li>主关键字：此关键字可以<strong>唯一地</strong>标识一个<strong>记录</strong>，则称此关键字为主关键字</li><li>次关键字：用以识别若千记录的关键字为次关键字。</li></ul></li><li><p><strong>查找</strong>：根据给定的某个值，在查找表中确定一个其关键字等于给定值的数据元素或（记录）</p><ul><li>若查找表中存在这样一个记录，则称 <strong>查找成功</strong><ul><li>查找结果给出整个记录的<strong>信息</strong>，或指示该记录在查找表中的<strong>位置</strong>；</li></ul></li><li>否则称 <strong>查找不成功</strong><ul><li>查找结果给出“空记录”或“空指针”</li></ul></li><li>查找经常进行的操作<ul><li><strong>查询</strong>某个**“特定的”**数据元素是否在查找表中；</li><li><strong>检索</strong>某个**“特定的”**数据元素的各种属性；</li><li>在查找表中<strong>插入</strong>一个数据元素；</li><li><strong>删除</strong>查找表中的某个数据元素；</li></ul></li></ul></li><li><p>查找算法的评价指标：</p><ul><li><p>关键字的平均比较次数，也称平均查找长度。</p><p>$\LARGE ASL=\sum\limits_{i=1}^np_ic_i$    (关键字比较次数的期望值)</p><ul><li>n：记录的个数</li><li>pi：查找第i个记录的概率（通常认为pi=1/n）</li><li>ci：找到第i个记录所需的比较次数</li></ul></li></ul></li></ul><h3 id="7-2-线性表的查找">7.2 线性表的查找</h3><h4 id="7-2-1-顺序查找">7.2.1 顺序查找</h4><ul><li><p>应用范围：</p><ul><li>顺序表或线性链表表示的静态查找表</li><li>表内元素之间无序</li></ul></li><li><p>数据元素类型定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    KeyType key;</span><br><span class="line">    ……</span><br><span class="line">&#125;ElemType;</span><br></pre></td></tr></table></figure></li><li><p>顺序表的定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//顺序结构表结构类型定义</span></span><br><span class="line">    ElemType *R;<span class="comment">//表基址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//表长</span></span><br><span class="line">&#125;SSTable;</span><br><span class="line">SSTable ST;<span class="comment">//定义顺序表ST</span></span><br></pre></td></tr></table></figure></li><li><p>顺序查找</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">Search_seq</span><span class="params">(SSTable st,keytype key)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=ST.length;i&gt;=<span class="number">1</span>;--i)</span><br><span class="line">        <span class="keyword">if</span> (ST.R[i].key==key)<span class="keyword">return</span> i;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>改进：把待查关键字key存入表头（”哨兵“）从后往前挨个比较，可免去查找过程中每一步都要检查是否查找完毕，加快速度</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">search_seq</span><span class="params">(SSTable ST,KeyType key)</span>&#123;</span><br><span class="line">    ST.R[<span class="number">0</span>].key=key;</span><br><span class="line">    <span class="keyword">for</span>(i=ST.length;ST.R[i].key!=key;--i);</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>比较次数与key位置有关：</p><ul><li>查找第i个元素，需要比较n-i+1次</li><li>查找失败，需要比较n+1次</li></ul></li><li><p>时间复杂度O(n)</p><ul><li><p>查找成功时的平均查找长度，设表中各记录查找概率相等</p><p>$ASL=（1+2+……+n）/n=\dfrac {n+1}{2}$</p></li></ul></li><li><p>空间复杂度：一个辅助空间——O(1)</p></li></ul></li></ul></li><li><p>顺序查找的特点</p><ul><li>优点：算法简单，逻辑次序无要求，且不同存储结构均适用</li><li>缺点：ASL太长，时间效率太低</li></ul></li></ul><h4 id="7-2-2-折半查找">7.2.2 折半查找</h4><ul><li><p>算法步骤</p><ol><li>设表长为n，low、high和mid分别指向待查元素所在区闻的上界，下界和中点，key为给定的要查找的值；</li><li>初始时，令low=1，high=n，</li><li>让k与mid指向的记录比较<ol><li>若key==R[mid].KEY,查找成功</li><li>若key&lt;R[mid].key,则high=mid-1</li><li>若key&gt;R[mid].key,则low=mid-1</li></ol></li></ol></li><li><p>折半查找</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">search_bIN</span><span class="params">(SSTable ST,KeyType key)</span>&#123;</span><br><span class="line">    low=<span class="number">1</span>;high=ST,length;</span><br><span class="line">    <span class="keyword">while</span>(low&lt;=high)&#123;</span><br><span class="line">        mid=(low+high)/<span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span>(ST.R[mid].key==KEY)<span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            <span class="keyword">if</span>(key&lt;ST.R[mid].key)</span><br><span class="line">                high=mid<span class="number">-1</span>;</span><br><span class="line">            <span class="keyword">else</span> low=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231016165250.png" alt=""></p></li><li><p>$\large ASL=1/11*(1<em>1+2</em>2+4<em>3+4</em>4)=33/11=3$</p></li><li><p>平均查找长度ASL（成功时）</p><ul><li><p>$设表长为n=2^h-1,则h=log_2(n+1)，且表中每个记录的查找概率相等：p_i=1/n$</p><p>$则\begin{align}<br>ASL_{bs}&amp;=\dfrac {1}{n} \sum\limits ^{n}_{j=1}j*2^{j-1}\<br>&amp;=\dfrac{n+1}{n}log_2(n+1)-1\<br>&amp;≈log_2(n+1)-1(n&gt;50)\end{align}$</p></li><li><p>折半查找的性能分析</p><ul><li>优点：效率比顺序查找高</li><li>缺点：只适用于有序表，且限于顺序存储结构(对线性链表无效)</li></ul></li></ul></li></ul></li></ul><h4 id="7-2-3-分块查找">7.2.3 分块查找</h4><ul><li><p>分块查找 (Blocking Search) 又称索引顺序查找，这是一种性能介于顺序查找和折半查找之间的一种查找方法。</p><ol><li>将表分成几块,且表或者有序，或者<strong>分块有序</strong></li><li>建立“索引表”（每个结点含有最大关键字域和指向本块第一个结点的指针，且按关键字有序）。</li></ol></li><li><p>查找过程：先确定待查记录所在块，再在块内查找</p></li><li><p>查找效率：$ASL=L_b+L_w$</p></li><li><p>$\large ALS_{bs}≈log_2(\dfrac{n}{s}+1)+\dfrac {s}{2}$   s为每块内部的记录个数，n/s即块内数目</p></li><li><p>分块查找的特点</p><ul><li>优点：插入和删除比较容易</li><li>缺点：要增加一个索引表的存储空间并对初始索引表进行排序运算</li><li>适用情况：线性表既要快速查找又要经常动态变化</li></ul></li></ul><table><thead><tr><th></th><th>顺序查找</th><th>折半查找</th><th>分块查找</th></tr></thead><tbody><tr><td>ASL</td><td>最大</td><td>最小</td><td>中间</td></tr><tr><td>表结构</td><td>有序表、无序表</td><td>有序表</td><td>分块有序</td></tr><tr><td>存储结构</td><td>顺序表、线性链表</td><td>顺序表</td><td>顺序表、线性链表</td></tr></tbody></table><h3 id="7-3-树表的查找">7.3 树表的查找</h3><ul><li>当表插入、删除操作频繁时，为了维护表的有序性，需要移动表中很多记录<ul><li><strong>改用动态查找表——几种特殊的树</strong></li><li>表结构在<strong>查找过程中动态生成</strong></li><li>对于给定值key，若表中存在，则成功返回；<strong>否则，插入关键字等于key的记录</strong><ul><li><strong>二叉排序树</strong><br><strong>平衡二叉树</strong><br>红黑树<br>B-树<br>B+树<br>键树</li></ul></li></ul></li></ul><h4 id="7-3-1-二叉排序树">7.3.1 二叉排序树</h4><ul><li><p>二叉排序树又称为二叉搜索树、二叉查找树</p><ul><li>二叉排序树或是空树，或是满足如下性质的二叉树；<ol><li>若其<strong>左子树非空</strong>，则左子树上所有结点的值均<strong>小于</strong>根结点的值；</li><li>若其<strong>右子树非空</strong>，则右子树上所有结点的值均<strong>大于</strong>根结点的值；</li><li>其左右子树本身又各是一棵二叉排序树</li></ol></li><li>二叉排序树的性质<ul><li>中序遍历非空的二又排序树所<strong>得到的数据元素序列是</strong>一个按关键字排列的<strong>递增有序序列</strong>。</li></ul></li></ul></li><li><p>二叉排列树的操作——查找</p><ul><li><p>若查找的关键字等于根结点，成功</p></li><li><p>否则</p><ul><li>小于根结点，查左子树</li><li>大于根结点，查右子树</li></ul></li><li><p>在左右子树上的操作类似</p></li></ul></li><li><p>算法描述</p><figure class="highlight c"><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">BSTree <span class="title function_">SearchBST</span> <span class="params">(BSTree_T,KeyType key)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> ((!T)||key==T-&gt;data.key) <span class="keyword">return</span> T;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (key&lt;T-&gt;data.key)</span><br><span class="line">        <span class="keyword">return</span> SearchBST(T-&gt;lchild,key);</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">return</span> SearchBST (T-&gt;rchild,key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>二叉排序树的查找分析</p><ul><li><p>含有n个结点的二叉排列树的平均查找长度和树的形态有关</p><ul><li><p>最好的情况：</p><p>二叉排列树的形态和折半查找的判定树相似:</p><p>$ASL=log_2(n+1)-1$<br>$O(log_2n)$</p></li><li><p>最坏情况</p><p>二叉排序树的形态为单支树，树的深度为n，</p><p>$ASL=\dfrac {n+1}{2}$</p><p>$O(n)$</p></li></ul></li></ul></li><li><p>二叉排序树的操作——插入</p><ul><li>若二叉排序树为空，则插入结点作为根结点插入到空树中</li><li>否则，继续在左右子树上查找<ul><li>树中已有，不再插入</li><li>树中没有<ul><li>查找直至某个叶子结点的左子树或右子树为空为止，则插入结点应为该叶子结点的左孩子或右孩子</li></ul></li></ul></li><li>插入的元素一定在叶子结点上</li></ul></li><li><p>二叉排序树的操作——生成</p><ul><li>一个无序序列可通过构造二叉排序树而变成一个有序序列。构造树的过程就是对无序序列进行排序的过程。</li><li>插入的结点均为叶子结点，故无序移动其他结点。相当于在有序序列上插入记录儿无需移动其他记录</li><li>不同的插入次序的序列生成不同形态的二叉排序树</li></ul></li><li><p>二叉排序树的操作——删除</p><ul><li>被删除的结点是叶子结点：直接删去该结点</li><li>被删除的结点只有左子树或右子树，用其左子树或右子树替换它</li><li>被删除的既有左子树，也有右子树，以其中序前驱值替换之（前驱是左子树中最大的结点），然后再删除该前驱结点<ul><li>也可以用其后继替换之（后继是右子树中最小结点），然后再删除该后继结点</li></ul></li></ul></li></ul><h4 id="7-3-2-平衡二叉树">7.3.2 平衡二叉树</h4><ul><li><p>平衡二叉树的定义</p><ul><li>又称AVL树</li><li>一棵平衡二叉树或者是空树，或者是具有以下性质的二叉排序树：<ul><li><strong>左</strong>子树与<strong>右</strong>子树的<strong>高度之差的绝对值小于等于1</strong>；</li><li><strong>左</strong>子树和<strong>右</strong>子树也是平衡二叉排序树。</li></ul></li></ul></li><li><p>为了方便起见，给每个结点附加一个数字，给出<strong>该结点左子树与右子树的高度差</strong>。这个数字称为结点的<strong>平衡因子</strong></p><ul><li><p>根据平衡二叉树的定义，平衡二叉树上所有结点的平衡因子只能是-1，0，或1</p></li><li><p>对于一棵有n个结点的AVL树，其高度保持在$O(log_2n)$数量级，ASL也是保持在$O(log_2n)$量级</p></li><li><p>当我们在一个平衡二叉树上插入结点时，可能导致<strong>失衡</strong></p><ul><li><p>平衡调整的四种类型：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231018144342.png" alt=""></p><p>LR型和RL型左右要看B和A哪个大</p><ul><li>调整原则：1. 降低高度 2. 保持二叉排序树性质</li></ul></li></ul></li></ul></li></ul><h3 id="7-4-哈希表的查找">7.4 哈希表的查找</h3><h4 id="7-4-1-散列表的基本概念">7.4.1 散列表的基本概念</h4><ul><li>基本思想：记录的存储位置与关键字之间存在对应关系</li></ul><p>​对应关系——hash函数（hash:散列）</p><p>​Loc(i)=H(keyi)</p><ul><li>散列表的查找<ul><li>根据散列函数H(key)=k<ul><li>查找key=9，直接访问H(9)=9号地址，若内容为9则成功<br>若查不到，则返回一个特殊值，如空指针或空记录。</li></ul></li><li>优点：查找效率高<br>缺点：空间效率低</li></ul></li><li>散列函数和散列地址：在记录的存储位置p和其关键字key 之间建立一个确定的对应关系H, 使 p=H(key ), 称这个对应关系H为散列函数，p为散列地址。</li><li>散列表：一个有线连续的地址空间，用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组，散列地址是数组的下标</li><li>冲突：不同的关键码映射到同一个散列地址<ul><li>key1≠key2，但是H(key1)=H(key2)</li><li>冲突是不可避免的，但可以减少</li></ul></li><li>同义词：具有相同函数值的多个关键字</li></ul><h4 id="7-4-2-散列函数的构造方法">7.4.2 散列函数的构造方法</h4><ul><li><p>构造散列函数的考虑因素：</p><ul><li>散列表的长度</li><li>关键字的长度</li><li>关键字的分布情况</li><li>计算散列函数所需的时间</li><li>记录的查找频率</li></ul></li><li><p>构造号的散列函数要遵循以下两条原则</p><ul><li>函数计算要简单，每一个关键字只能有一个散列地址与之对应</li><li>函数的值域需在表长的范围内， 计算出的散列地址的分布应均匀，尽可能减少冲突。</li></ul></li><li><p>根据数据元素的集合特性构造</p><ul><li>要求一: n个数据原仅占用n个地址虽然散列查找是以空间换时间，但仍希望散列的地址空间尽量小</li><li>要求二:无论用什么方法存储，目的都是尽量均匀地存放元素，以避免冲突。</li></ul></li></ul><h5 id="7-4-2-1-直接定址法">7.4.2.1 直接定址法</h5><ul><li>Hash(key)=a.key+b</li><li>优点：以关键码key的某个线性函数值为散列地址，不会产生冲突<br>缺点：要占用连续的地址空间，空间效率低</li></ul><h5 id="7-4-2-2-除留余数法">7.4.2.2 除留余数法</h5><ul><li><p>Hash(key)=key mod p</p><ul><li><p>技巧：设：表长为m，取p&lt;=m且为质数</p><ul><li><p>例：{15,23,27,38,53,61,70}</p><p>散列函数：Hash(key)=key mod 7</p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th></tr></thead><tbody><tr><td>70</td><td>15</td><td>23</td><td>38</td><td>53</td><td>61</td><td>27</td></tr></tbody></table></li></ul></li></ul></li></ul><h4 id="7-4-3-处理冲突的方法">7.4.3 处理冲突的方法</h4><h5 id="7-4-3-1-开放地址法（开地址法）">7.4.3.1 开放地址法（开地址法）</h5><ul><li><p>基本思想：有冲突时就去寻找下一个空的散列地址，只要散列表足够大，空的散列地址总能找到，并将数据元素存入</p><ul><li>列入：除留余数法：$H_i=(Hash(key)+d)mod \ m$</li><li>常用方法：<ul><li>线性探测法      $d_i为1,2,……，m-1线性序列$</li><li>二次探测法      $d_i为1^2,-1^2,2^2,-2^2……，q^2二次序列$</li><li>伪随机探测法    $d_i为伪随机数序列$</li></ul></li></ul></li><li><p>例：关键码集为：{47，7，29，11，16，92，22，8，3}，散列表长m=11，散列函数为$Hash(key)=key \ mod \ 11$,拟用线性探测法处理冲突</p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th><th>10</th></tr></thead><tbody><tr><td>11</td><td>22</td><td></td><td>47</td><td>92</td><td>16</td><td>3</td><td>7</td><td>29</td><td>8</td><td></td></tr></tbody></table><ul><li>使用线性探测法解释：</li></ul><ol><li>47、7均是由散列函数得到的没有冲突的散列地址;</li><li>Hash(29)=7，散列地址有冲突，需寻找下一个空的散列地址由$H=(Hash(29)+1) mod \ 11=8$，散列地址8为空，因此将29存入。</li><li>11、16、92均是由散列函数得到的没有冲突的散列地址:</li><li>另外，22、8、3同样在散列地址上有冲突，也是由H,找到空的散列地址的。<ul><li>平均查找长度ASL=(1+2 +1 +1 +1 +4 +1 +2 +2)/9=1.67</li></ul></li></ol><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th><th>10</th></tr></thead><tbody><tr><td>11</td><td>22</td><td>3</td><td>47</td><td>92</td><td>16</td><td></td><td>7</td><td>29</td><td>8</td><td></td></tr></tbody></table><ul><li>使用二次探测法解释：<ul><li>Hash(3)=3散列地址有冲突，由于$H_1=(Hash(3)+1^2)mod \ 11=4$，仍然冲突；</li><li>$H_2=(Hash(3)-1^2)mod \ 11=2$找到空的散列地址，存入</li></ul></li></ul></li></ul><h5 id="7-4-3-2-链地址法（拉链法）">7.4.3.2 链地址法（拉链法）</h5><ul><li><p>基本思想：相同散列地址的记录链成一单链表</p></li><li><p>m个散列地址就设m个单链表，然后用一个数组将m个单链表的表头指针存储起来，形成一个动态结构。</p></li><li><p>链地址法建立散列表的步骤</p><ul><li>step1：取数据元素的关键字key，计算其散列函数值 (地址)。若该地址对应的链表为空，则将该元素插入此链表；否则执行Step2解决冲突。</li><li>step2：根据选择的冲突处理方法，计算关键字key的下一个存储地址若该地址对应的链表为不为空，则利用链表的前插法或后插法将该元素插入此链表</li></ul></li><li><p>链地址法的优点：</p><ul><li>非同义词不会冲突，无“聚集”现象<ul><li>链表上的结点空间动态申请，更适合于表长不确定的情况</li></ul></li></ul></li></ul><h4 id="7-4-4-散列表的查找">7.4.4 散列表的查找</h4><ul><li>给定值查找值k，查找过程：</li></ul><figure class="highlight plaintext"><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">graph TD</span><br><span class="line">A[给定k值]==&gt;B(计算H)</span><br><span class="line">B==&gt;C&#123;此地址为空&#125;</span><br><span class="line">C--&gt;|N|D&#123;关键字==k&#125;</span><br><span class="line">C--&gt;|Y|G[查找失败]</span><br><span class="line">D--&gt;|N|E[案处理冲突方法计算Hi]</span><br><span class="line">D--&gt;|Y|H[查找成功]</span><br><span class="line">E--&gt;C</span><br><span class="line">F[竖向流程图]</span><br></pre></td></tr></table></figure><ul><li><p>$ASL=(1<em>6+2+3</em>3+4+9)/12=2.5$</p></li><li><p>用连地址法处理冲突：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231018180909.png" alt=""></p></li><li><p>$ASL=(1<em>6+2</em>4+3+4)/12=1.75$</p></li><li><p>使用平均查找长度ASL来衡量查找算法，ASL取决于</p><ul><li>散列函数</li><li>处理冲突的方法</li><li>散列表的装填因子α<ul><li>$\Large α=\dfrac {表中填入的记录数}{哈希表的长度}$</li><li>α越大，表中的记录数越多，表越满，发生冲突的可能性就越大，查找时比较次数就越多</li></ul></li><li>无冲突时才能达到O(1)</li><li>$\large ASL≈1+\dfrac {α}{2}$</li><li>$\large ASL≈\dfrac {1}{2}(1+\dfrac{1}{1-α})$</li><li>$\large ASL≈-\dfrac{1}{α}ln(1-α)$</li></ul></li><li><p>结论</p><ul><li>链地址法优于开地址法</li><li>散列表技术具有很好的平均性能，优于一些传统的技术</li><li>除留余数法作散列函数优于其它类型函数</li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第七章(汇总)--王卓老师两周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构(汇总)</title>
    <link href="https://www.fomal.cc/posts/603d4955.html"/>
    <id>https://www.fomal.cc/posts/603d4955.html</id>
    <published>2023-10-22T09:49:17.000Z</published>
    <updated>2023-10-25T05:36:42.637Z</updated>
    
    <content type="html"><![CDATA[<h1>数据结构与算法基础</h1><p><strong><u>“程序=数据结构+算法”</u></strong></p><p>​                                <em>——图灵奖获得者、Pascal语言之父Nicklaus Wirth</em></p><p>课程内容：<div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536497cc458853aef5cf2f8.webp"/></div></div></p><p>[TOC]</p><h2 id="1-绪论">1 绪论</h2><h3 id="1-1-数据结构研究内容">1.1 数据结构研究内容</h3><p><em><strong>“计算机主要用于数值计算时， 一般要经过如下几个步骤：首先从具体问题抽象出数学模型，然后设计一个解此数学模型的算法，最后编写程序，进行测试、调试，直到解决问题。”</strong></em></p><ul><li><p>实例1.线性表<div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496ec458853aef5cd3e9.png"/></div></div></p><ul><li><p>操作==<strong>对象</strong>==：每位学生的信息（姓名，性别……）。</p><p>操作==<strong>算法</strong>==：查询，插入，修改，删除等。</p></li></ul></li><li><p>实例2.树结构<div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536497cc458853aef5cf297.png"/></div></div></p><ul><li><p>操作==<strong>对象</strong>==：各种棋局状态，即描述棋盘的格局信息。</p><p>操作==<strong>算法</strong>==：走棋，使棋局状态发生变化。</p></li></ul></li><li><p>实例3.图结构</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536497cc458853aef5cf2cb.png"/></div></div><ul><li><p>操作==<strong>对象</strong>==：各地点及路的信息。</p><p>操作==<strong>算法</strong>==：设置信号灯，求出各个可同时通行的路的集合。</p></li></ul></li></ul><p><em><strong>“      从上面三个实例可以看出，非数值计算问题的数学模型不再是数学方程，而是诸如==线性表、树和图的数据结构==。因此，简单地说，数据结构是一门研究==非数值计算==程序设计中的操作对象，以及这些对象之间的==关系==和==操作==的学科。</strong></em>“</p><h3 id="1-2-基本概念和术语">1.2 基本概念和术语</h3><h4 id="1-2-1-数据、-数据元素、-数据项和数据对象">1.2.1 数据、 数据元素、 数据项和数据对象</h4><ul><li><p>数据：各种符号的集合</p><ul><li>数值型的数据：整数、实数等</li><li>非数值型的数据：文字、图像、图形、声音等</li></ul></li><li><p>数据元素<a href="%E4%B9%9F%E7%AE%80%E7%A7%B0%E4%B8%BA%E5%85%83%E7%B4%A0%E3%80%81%E8%AE%B0%E5%BD%95%E3%80%81%E7%BB%93%E7%82%B9%E6%88%96%E9%A1%B6%E7%82%B9%E3%80%82">^a</a> ：是数据的基本单位，在计算机程序中通常作为一个整体进行考虑和处理</p><table><thead><tr><th style="text-align:center">学号</th><th style="text-align:center">姓名</th><th style="text-align:center">性别</th><th style="text-align:center">出生日期</th><th style="text-align:center">政治面貌</th></tr></thead><tbody><tr><td style="text-align:center">0001</td><td style="text-align:center">陆宇</td><td style="text-align:center">男</td><td style="text-align:center">1986/09/02</td><td style="text-align:center">团员</td></tr><tr><td style="text-align:center"><strong>0002</strong></td><td style="text-align:center"><strong>李明</strong></td><td style="text-align:center"><strong>男</strong></td><td style="text-align:center"><strong>1985/12/25</strong></td><td style="text-align:center"><strong>党员</strong></td></tr><tr><td style="text-align:center">0003</td><td style="text-align:center">汤晓影</td><td style="text-align:center">女</td><td style="text-align:center">1986/03/26</td><td style="text-align:center">团员</td></tr></tbody></table><p>其中第二行整体为一个<em><strong>数据元素</strong></em><a href="%E4%B9%9F%E7%AE%80%E7%A7%B0%E4%B8%BA%E5%85%83%E7%B4%A0%E3%80%81%E8%AE%B0%E5%BD%95%E3%80%81%E7%BB%93%E7%82%B9%E6%88%96%E9%A1%B6%E7%82%B9%E3%80%82">^a</a> ,其中包含了五个数据项</p></li><li><p>数据项：构成数据元素的不可分割的<strong>最小单位</strong></p><ul><li>数据&gt;数据元素&gt;数据项<ul><li>例：学生表&gt;个人记录&gt;学号</li></ul></li></ul></li><li><p>数据对象：是性质相同的数据元素的集合，是数据的一个子集<a href="%E5%A6%82%E6%9E%9C%E9%9B%86%E5%90%88A%E7%9A%84**%E4%BB%BB%E6%84%8F%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0**%E9%83%BD%E6%98%AF%E9%9B%86%E5%90%88B%E7%9A%84%E5%85%83%E7%B4%A0%EF%BC%8C%E9%82%A3%E4%B9%88%E9%9B%86%E5%90%88A%E7%A7%B0%E4%B8%BA%E9%9B%86%E5%90%88B%E7%9A%84**%E5%AD%90%E9%9B%86**">^b</a></p><ul><li>例：字母字符数据对象是集合C= {<strong>‘A’,‘B’,</strong> …，<strong>‘Z’,‘a’,‘b’,</strong> …，<strong>‘z’}</strong></li></ul></li><li><p>数据元素和数据对象</p><ul><li><p><strong>数据元素</strong>与数据的关系：集合的<strong>个体</strong></p></li><li><p><strong>数据对象</strong>和数据的关系：集合的<strong>子集</strong></p></li></ul></li></ul><h4 id="1-2-2-数据结构">1.2.2 数据结构</h4><ul><li><p>数据结构</p><ol><li>数据元素之间的逻辑关系，也称为<strong>逻辑结构</strong></li><li>数据元素及其关系在计算机存储器中的表示（又称为映像），称为数据的物理结构或数据的<strong>存储结构</strong></li><li>数据的<strong>运算和实现</strong>，即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现</li></ol><ul><li><p>逻辑结构</p><ul><li>描述数据<strong>元素之间的逻辑关系</strong></li><li>与数据的<strong>存储无关</strong>，<strong>独立</strong>于计算机</li><li>是从具体问题抽象出来的数学模型</li></ul></li><li><p>物理结构（存储结构）</p><ul><li>数据元素及其关系在计算机存储器中的结构（存储方式）</li><li>是数据结构在计算机中的表示</li></ul></li><li><p>逻辑结构与存储结构的关系</p><ul><li>存储结构是逻辑关系的映象与元素本身的映象</li><li><strong>逻辑结构</strong>是数据结构的<strong>抽象</strong>，<strong>存储结构</strong>是数据结构的<strong>实现</strong></li><li><strong>两者综合</strong>起来建立了数据元素之间的结构关系</li></ul></li><li><p>逻辑结构的种类</p><ul><li><p>划分方法一——两类结构</p><ul><li><p>（1）==线性结构== ：有且仅有一个开始和一个终端节点，并且所有结点都最多只有一个直接前趋和一个直接后继</p><p>例如：线性表、栈、队列、串</p></li><li><p>（2）==<strong>非线性结构</strong>==：一个节点可能有多个直接前驱和直接后继</p><p>例如：树、图</p></li></ul></li><li><p>划分方法二——四类基本逻辑结构</p><ul><li><p>（1）==<strong>集合结构</strong>==：数据元素之间除了 <strong>属于同一集合</strong>的关系外，别无任何其他关系。</p><p>例如：确定一名学生是否为班级成员， 只需将班级看做一个集合结构。</p></li><li><p>（2）==<strong>线性结构</strong>===结构中的数据元素之间存在着<strong>一对一</strong>的<strong>线性</strong>关系。</p><p>例如：将学生信息数据按照其入学报到的时间先后顺序进行排列，将组成一个线性结构</p></li><li><p>（3）==<strong>树形结构</strong>==：<strong>结构中的数据元素之间存在着</strong>一对多<strong>的</strong>层次**关系。</p><p>例如：在班级的管理体系中，班长管理多个组长，每位组</p><p>长管理多名组员，从而构成树形结构。</p></li><li><p>（4）==<strong>图状结构或网状结构</strong>==：结构中的数据元素之间存在着<strong>多对多</strong>的<strong>任意</strong>关系。</p><p>例如：多位同学之间的朋友关系， 任何两位同学都可以是朋友，从而构成图状结构或网状结构。</p></li></ul></li></ul></li><li><p>存储结构的种类</p><ul><li><p>四种基本的存储结构：</p><ul><li><p><strong>顺序存储结构</strong>：用一组<strong>连续</strong>的存储单元<strong>依次</strong>存储数据元素，数据元素之间的逻辑关系由元素的<strong>存储位置</strong>来表示。</p><p>在C语言中用<strong>数组</strong>来实现。</p></li><li><p><strong>链式存储结构</strong>：用一组<strong>任意</strong>的存储单元存储数据元素，数据元素之间的逻辑关系用<strong>指针</strong>来表示。</p><p>在C语言中用<strong>指针</strong>来实现。<s>关于“指针”的痛苦回忆浮现出来力</s>。:cry:</p></li><li><p><strong>索引存储结构</strong>：存储结点信息的同时，还建立附加的<strong>索引表</strong></p><p>例如：通讯录</p></li><li><p><strong>散列存储结构</strong>：根据结点的关键字直接计算出该结点的存储地址</p></li></ul></li></ul></li></ul></li></ul><h4 id="1-2-3-数据类型和抽象数据类型">1.2.3 数据类型和抽象数据类型</h4><ul><li><p>数据类型：在使用高级程序设计语言编写程序时，必须对程序中出现的每个变量.常量或表达式，明确说明它们所属的<strong>数据类型</strong><a href="%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%98%AF%E4%B8%80%E7%BB%84%E6%80%A7%E8%B4%A8%E7%9B%B8%E5%90%8C%E7%9A%84%E5%80%BC%E7%9A%84%E9%9B%86%E5%90%88%E4%BB%A5%E5%8F%8A%E5%AE%9A%E4%B9%89%E4%BA%8E%E8%BF%99%E4%B8%AA%E5%80%BC%E9%9B%86%E5%90%88%E4%B8%8A%E7%9A%84%E4%B8%80%E7%BB%84%E6%93%8D%E4%BD%9C%E7%9A%84%E6%80%BB%E7%A7%B0">^c</a>。</p><ul><li>例如C语言中：<ul><li>提供int，char, float, double等基本数据类型</li><li>数组、结构、共用体、枚举 等构造数据类型</li><li>还有指针、空(void)类型</li><li>用户也可用typedef 自己定义数据类型</li></ul></li><li>一些最基本数据结构可以用数据类型来实现，如数组、字符串等;</li><li>而另一些常用的数据结构，如栈、队列、树、图等，不能直接用数据类型来表示。</li><li>数值类型的作用<ul><li>约束变量或常量的<strong>取值范围</strong></li><li>约束变量或常量的<strong>操作</strong></li></ul></li></ul></li><li><p>抽象数据类型:是指一个数学模型以及定义在此数学模型上的一组操作。</p><ul><li><p>由用户定义，从问题抽象出数据模型（逻辑结构）</p></li><li><p>还包括定义在数据模型上的一组抽象运算（相关操作）</p></li><li><p>不考虑计算机内的具体存储结构与运算的具体实现算法</p></li><li><p>抽象数据类型的形式定义：</p><ul><li><p>ADT抽象数据类型名{</p><p>数据对象：&lt;数据对象的定义&gt;</p><p>数据关系：&lt;数据关系的定义&gt;</p><p>基本操作：&lt;基本操作的定义&gt;</p><p>}ADT抽象数据类型名</p><ul><li>其中：</li></ul><ol><li><p>数据对象、数据关系的定义用伪代码描述</p></li><li><p>基本操作的定义格式为：</p><p>基本操作名（参数表）</p><p>初始条件：&lt;初始条件描述&gt;</p><p>操作结果：&lt;操作结果描述&gt;</p><p>基本操作定义格式说明：</p><p><strong>参数表</strong>：</p><ol><li>赋值参数 只为操作提供输入值</li><li>引用参数 以&amp;打头，除可提供输入值外，还将返回操作结果</li></ol><p><strong>初始条件</strong>：描述操作执行之前数据结构和参数应满足的条件，若不满足则操作失败，并返回相应出错信息。若初始条件为空，则省略之</p><p><strong>操作结果</strong>：说明操作正常完成后，数据结构的变化情况和应返回的结果。</p><p>ADT大致结构：</p></li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT 抽象数据名&#123;</span><br><span class="line">    Data</span><br><span class="line">        数据对象的定义</span><br><span class="line">        数据元素之间的逻辑关系的定义</span><br><span class="line">    Operation</span><br><span class="line">        操作<span class="number">1</span></span><br><span class="line">           初始条件</span><br><span class="line">           操作结果描述</span><br><span class="line">        操作<span class="number">2</span></span><br><span class="line">            ……</span><br><span class="line">        操作n</span><br><span class="line">&#125;ADT 抽象数据类型名</span><br></pre></td></tr></table></figure><ul><li>Circle的定义</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT Circle&#123;</span><br><span class="line">  数据对象：D=&#123;r,x,y|rxy均为实数&#125;</span><br><span class="line">  数据关系：R=&#123;&lt;r,x,y&gt;|r是半径，&lt;x,y&gt;是圆心坐标&#125;</span><br><span class="line">  基本操作：</span><br><span class="line">  Circle(&amp;C,r,x,y)</span><br><span class="line">         操作结果：构造一个圆</span><br><span class="line">  <span class="type">double</span> Area(C)</span><br><span class="line">         初始条件：圆已存在</span><br><span class="line">         操作结果：计算面积</span><br><span class="line">  <span class="type">double</span> Circumference(C)</span><br><span class="line">         初始条件：圆已存在</span><br><span class="line">         操作结果：计算周长</span><br><span class="line">   ……</span><br><span class="line">&#125;ADT Cicle</span><br></pre></td></tr></table></figure><ul><li>复数的定义</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT Complex&#123;</span><br><span class="line">    数据对象：D = &#123;r1,r2|r1,r2都是实数&#125;</span><br><span class="line">    数据关系：S = &#123; &lt;r1,r2&gt;|r1是实部，r2是虚部&#125;</span><br><span class="line">    基本操作：</span><br><span class="line">        assign (&amp;C,v1,v2)</span><br><span class="line">        操作结果：构造复数Z，其实部和虚部，分别赋以参数V1,V2值</span><br><span class="line">    destroy(&amp;C)</span><br><span class="line">        操作结果：复数Z被销毁</span><br><span class="line">    getreal(C,&amp;realPart)</span><br><span class="line">        初始条件：复数已存在</span><br><span class="line">        操作结果：用realpart返回复数Z的实部值。</span><br><span class="line">    getlmag(C,&amp;lmagPart)</span><br><span class="line">        初始条件：复数已存在</span><br><span class="line">        操作结果：用magPart返回复数Z的虚部值。</span><br><span class="line">    add(C1,C2,&amp;sum)</span><br><span class="line">        初始条件：Z1,Z2是复数</span><br><span class="line">        操作结果：sum返回两个复数Z1，Z2的和</span><br><span class="line">&#125;ADT Circle</span><br></pre></td></tr></table></figure></li><li><p>以上代码均为<strong>类C语言</strong>作为描述工具</p></li></ul></li></ul></li></ul><h3 id="1-3-抽象数据类型的表示与实现">1.3 抽象数据类型的表示与实现</h3><p>​以下给出一个具体的实现过程</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">complex</span>&#123;</span><span class="comment">//typedef起别名的，定义一个结构体变量struct[结构体名]结构体变量名：用[struct[结构体名]等价于int</span></span><br><span class="line">    <span class="type">float</span> realpart;<span class="comment">//实部</span></span><br><span class="line">    <span class="type">float</span> imagepart;<span class="comment">//虚部</span></span><br><span class="line">&#125;<span class="type">complex</span>;<span class="comment">//typedef关键字把struct complex 起了个名字叫complex]</span></span><br><span class="line"><span class="type">complex</span> <span class="title function_">assign</span><span class="params">(<span class="type">float</span> real,<span class="type">float</span> image)</span>&#123;<span class="comment">//构造复数</span></span><br><span class="line">    <span class="type">complex</span> c;</span><br><span class="line">    c.realpart=real;</span><br><span class="line">    c.imagepart=image;</span><br><span class="line">    <span class="keyword">return</span> c;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">float</span> <span class="title function_">getreal</span><span class="params">(<span class="type">complex</span> c)</span>&#123;<span class="comment">//取复数实部</span></span><br><span class="line">    <span class="keyword">return</span> c.realpart;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">float</span> <span class="title function_">getimage</span><span class="params">(<span class="type">complex</span> c)</span>&#123;<span class="comment">//取复数虚部</span></span><br><span class="line">    <span class="keyword">return</span> c.imagepart;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">complex</span> <span class="title function_">Add</span><span class="params">(<span class="type">complex</span> c1,<span class="type">complex</span> c2)</span>&#123;<span class="comment">//求两复数c1与c2的和sum</span></span><br><span class="line">    <span class="type">complex</span> sum;</span><br><span class="line">    sum.realpart=c1.realpart+c2.realpart;</span><br><span class="line">    sum.imagepart=c1.imagepart+c2.imagepart;</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span>&#123;<span class="comment">//主函数</span></span><br><span class="line">    <span class="type">complex</span> c1,c2,c3;</span><br><span class="line">    c1=assign(<span class="number">1.0</span>,<span class="number">2.0</span>);</span><br><span class="line">    c2=assign(<span class="number">3.0</span>,<span class="number">4.0</span>);</span><br><span class="line">    c3=Add(c1,c2);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;c1:%.2f+%.2fi\n&quot;</span>,c1.realpart,c1.imagepart);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;c1:%.2f+%.2fi\n&quot;</span>,c2.realpart,c2.imagepart);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;c1与c2的和为:%.2f+%.2fi\n&quot;</span>,Add(c1,c2).realpart,Add(c1,c2).imagepart);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536497cc458853aef5cf365.png"/></div></div><h3 id="1-4-算法和算法分析">1.4 算法和算法分析</h3><ul><li><p>算法；对特定问题<strong>求解方法和步骤</strong>的一种描述，它是<strong>指令</strong>的<strong>有限序列</strong>。其中<strong>每个指令</strong>表示一个或多个操作。</p></li><li><p>算法的描述：</p><ol><li>自然语言：英语、中文</li><li>流程图：传统流程图、NS流程图   // <em>软件工程没白学</em></li><li>伪代码：类语言：类C语言</li><li>程序代码：C语言程序，JAVA语言程序</li></ol></li><li><p>算法与程序：</p><ul><li><p>算法是解决问题的一种方法或一个过程，考虑如何将输入转换成输出，一个问题可以有多个算法。</p></li><li><p>程序是用某种程序设计语言对算法的具体实现。</p><p><strong>程序=数据结构+算法</strong></p></li></ul></li></ul><h4 id="1-4-1-算法特性">1.4.1 算法特性</h4><ol><li>有穷性：算法总在<strong>有穷步</strong>之后结束，且每一步在<strong>有穷时间</strong>内完成。</li><li>确定性：每个指令有确切的含义，没有二义性，在任何条件下，只有唯一的一条执行路径，即对于<strong>相同的输入只能得到相同的输出</strong>。</li><li>可行性：算法是<strong>可执行</strong>的，算法描述的操作可以通过已经实现的基本操作执行有限次来实现。</li><li>输入：一个算法有零个或多个输入。</li><li>输出：一个算法有一个或多个输出。</li></ol><h4 id="1-4-2-算法设计的要求">1.4.2 算法设计的要求</h4><ol><li><strong>正确性</strong>：<ol><li>程序中不含语法错误。</li><li>程序对于几组输入数据能够得出满足要求的结果。</li><li>程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果。</li><li>程序对于一切合法的输入数据都能得出满足要求的结。</li></ol></li><li><strong>可读性</strong>：主要为了人的阅读和交流，其次才是为计算机的执行，因此算法要易于人理解。</li><li><strong>健壮性</strong>：指当<strong>输入非法数据</strong>时，算法算法恰当的做出反应或进行相应处理，而不是产生莫名其妙的输出结果。</li><li><strong>高效性</strong>：花费尽量少的时间和尽量地的存储需求。</li></ol><h4 id="1-4-3-算法的时间复杂度">1.4.3 算法的时间复杂度</h4><ul><li><p>算法效率以下两个方面来考虑：</p><ol><li>时间效率：指的是算法所耗费的时间。</li><li>空间效率，值算法执行过程中所耗费的存储空间。<ul><li>时间效率和空间效率有时是矛盾的。</li></ul></li></ol></li><li><p>时间效率的度量：</p><ul><li><p>事后统计：将算法实现，测算其时间和空间开销。</p><ul><li>缺点：编写程序实现算法将花费较多时间与精力，所得实验结果依赖于计算机的软硬件等环境因素，掩盖算法本身的优劣。</li></ul></li><li><p>事前分析：对算法所消耗资源的一种估算方法。算法运行时间=一个简单操作*简单操作次数</p><ul><li>为了方便比较不同算法的时间效率，我们仅比较他们的数量级</li></ul></li></ul></li></ul><figure class="highlight c"><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"><span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;=n;i++)<span class="comment">//n+1次</span></span><br><span class="line">    <span class="keyword">for</span>(j+<span class="number">1</span>;j&lt;=n;j++)&#123;<span class="comment">//n(n+1)次</span></span><br><span class="line">        c[<span class="number">1</span>][j]=<span class="number">0</span>;<span class="comment">//n*n次</span></span><br><span class="line">        <span class="keyword">for</span>(k=<span class="number">0</span>:k&lt;n:k++)<span class="comment">//n*n*(n+1)次</span></span><br><span class="line">            c[i][j]=c[i][j]+a[i][k]*b[k][j];<span class="comment">//n*n*n次</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li><p>对于求解矩阵相乘问题，算法耗费时间：</p><p>$\large T(n)={2n^3}+{3n^2}+2n+1$</p><p>​$n→∞$时，$T(n)/n3→2$,这表示n充分大时，$T(n)与n^3$是同阶或同数量级，引入大“O”记号，则T(n)可记作：</p><ul><li><p>​$\large T(n)=O(n^3)$</p></li><li><p><strong>原公式</strong>：                                           $\large T(n)=O(f(n))$</p></li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364d01c458853aef653927.png"/></div></div><ul><li>分析算法时间复杂度的基本方法<ol><li>找出<strong>语句频度</strong>最大的那条语句作为<strong>基本语句</strong></li><li>计算<strong>基本语句</strong>的拼读得到问题规模$n$的某个函数$f(n)$</li><li>取其数量级用符号“$O$”表示</li></ol></li></ul><figure class="highlight c"><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">x=<span class="number">0</span>;y=<span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> k=<span class="number">0</span>;k&lt;n;k++)<span class="comment">//n+1次（这里是判断次数，所以要加一）</span></span><br><span class="line">    x++;<span class="comment">//n次(这里是执行次数，所以不用加一)</span></span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;n:i++)<span class="comment">//n+1次</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">0</span>;j&lt;n;j++)<span class="comment">//n*(n+1)次</span></span><br><span class="line">        y++;<span class="comment">//n*n次</span></span><br></pre></td></tr></table></figure><ul><li>$\large f(n)=n(n+1)$     $\large T(n)=O(n^2)$<ul><li>可以直接看执行次数最多的语句，比如嵌套最深的语句</li></ul></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(i=<span class="number">1</span>:i&lt;=n:i++)</span><br><span class="line">    <span class="keyword">for</span>(j+!;j&lt;+i:j++)</span><br><span class="line">        <span class="keyword">for</span>(k=<span class="number">1</span>;k&lt;=j:k++)</span><br><span class="line">            x=x+<span class="number">1</span></span><br></pre></td></tr></table></figure><ul><li><p>语句频度=$\large\sum\limits_{i=1}^n\sum\limits_{j=1}^n\sum\limits_{k=1}^n=\sum\limits_{i=1}^n\sum\limits_{j=1}^nj=\sum\limits_{i=1}^n\dfrac{i(i+1)}{2}=\dfrac{1}{2}(\sum\limits_{i=1}^ni^2+\sum\limits_{i=1}^ni)=v$</p><ul><li>公式第一步是第三层嵌套，执行$\large j$次（$\large j$个1的和）<ul><li>公式第二步是求和公式$\large j$看做一个变量，从$\large j=1$到$\large i$一个等差数列求和直接套公式<ul><li>公式第三步一个<strong>平方和累加公式</strong><a href="$%5Clarge%5Csum%5Climits_%7Bk=1%7D%5En*K%5E2=%5Cdfrac%7Bn(n+1)(2n+1)%7D%7B6%7D$%E2%80%8B%E2%80%8B%E2%80%8B">^d</a>一个<strong>累加公式</strong>[^e]，轻易可得出结果<ul><li>直接抓大放小，可得$T(N)=O(n^3)$</li></ul></li></ul></li></ul></li></ul></li><li><p>分析以下程序段的时间复杂度</p><figure class="highlight c"><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">i=<span class="number">1</span>;</span><br><span class="line"><span class="keyword">while</span>(i&lt;=n)</span><br><span class="line">    i=i*<span class="number">2</span>;</span><br></pre></td></tr></table></figure><p><strong>关键是找出来执行次数x与n的关系，并表示成n的函数</strong></p><p>假设<em><strong>语句2</strong></em>执行x次，由循环条件$i&lt;=n,所以2^x=n 所以x&lt;=log_2n$</p><p>即$f(n)&lt;=loglog_2n$,<strong>取最大值</strong>$f(n)=log_2n$</p></li></ul><h4 id="1-4-4-算法的空间复杂度">1.4.4 算法的空间复杂度</h4><ul><li>空间复杂度：算法所需存储空间的度量<ul><li>记作:$\large S(n)=O(f(n))$</li></ul></li></ul><p>算法一：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;=n/<span class="number">2</span>;i++)&#123;</span><br><span class="line">    t=a[i];</span><br><span class="line">    a[i]=a[n-i<span class="number">-1</span>];</span><br><span class="line">    a[n-i<span class="number">-1</span>]=t;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法二：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;=n;i++)</span><br><span class="line">    b[i]=a[n-i<span class="number">-1</span>];</span><br><span class="line"><span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;=n;i++)</span><br><span class="line">    a[i]=b[i];</span><br></pre></td></tr></table></figure><p>算法一：$\large S(n)=O(1)$ <strong>原地工作</strong></p><p>算法二：$\large S(n)=O(n)$</p><p>[^e]: $\large \sum\limits_{k=1}^n<em>K=\dfrac{n(n+1)}{2}$​​</em></p><h3 id="1-5-小结">1.5 小结</h3><ol><li><p>数据结构是一门研究<strong>非数值</strong>计算程序设计中<strong>操作对象</strong>， 以及这些<strong>对象之间的关系和操作</strong>。</p></li><li><p>数据结构包括两个方面的内容：数据的逻<strong>辑结构</strong>和<strong>存储结构</strong>。同一逻辑结构采用不同的存储方法可以得到不同的存储结构。</p><ol><li>逻辑结构是从具体问题抽象出来的数学模型，从逻辑关系上描述数据，它与数据的存储无关。根据数据元素之间关系的不同特性， 通常有<strong>四类基本逻辑结构</strong>：<strong>集合结构、线性结构、树形结构和图状结构</strong>。</li><li>存储结构是逻辑结构在计算机中的存储表示，有<strong>两类存储结构</strong>：<strong>顺序存储结构和链式存储结构。</strong></li></ol></li><li><p>抽象数据类型是指由<strong>用户定义</strong>的、表示应用问题的<strong>数学模型</strong> ， 以及定义在这个<strong>模型上的一组操作</strong>的总称， 具体包括三部分：<strong>数据对象、数据对象上关系的集合， 以及对数据对象的基本操作</strong>的集合。</p></li><li><p>算法是为了解决某类问题而规定的一个有限长的操作序列。算法具有五个特性：<strong>有穷性、确定性、可行性、输入和输出</strong>。一个算法的优劣应该从以下四方面来评价：<strong>正确性、可读性、健壮性和高效性。</strong></p></li><li><p>算法分析的两个主要方面是分析算法的<strong>时间复杂度和空间复杂度</strong>， 以考察算法的时间和空间效率。一般情况下， 鉴于运算空间较为充足， 故将算法的<strong>时间复杂度作为分析的重点</strong>。算法执行时间的<strong>数量级</strong>称为算法的<strong>渐近时间复杂度</strong>，$T(n) = 0(f(n) )$, 它表示随着问题规模n的增大，算法执行时间的增长率和.f(n)的增长率相同， 简称时间复杂度。</p></li></ol><h2 id="2-线性表">2 线性表</h2><p><em>“<strong>以下四章为线性结构相关的知识，同时本章是整个课程的重点与核心内容，也是其他后续章节的重要基础。</strong>”</em></p><h3 id="2-1-线性表的定义和特点">2.1 线性表的定义和特点</h3><p><strong>线性表</strong>是具有相同特性的数据元素的一个<strong>有限序列</strong><br>$\large (a1,a2,a3,……a_i,a_{i+1},……,a_n)$    <strong><s>也就是说数组就是线性表咯</s></strong></p><p>其中数据元素的个数n定义为表的长度。</p><ul><li><p>当n=0时称为空表。</p></li><li><p>将非空的线性表记作(a1,a2……，an)</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496ec458853aef5cd3e9.png"/></div></div></li></ul><p>可以看出线性表的逻辑特征是：</p><ol><li>有且仅有一个开始结点$a1$，他没有直接前驱，而仅有一个直接后继$a2$。</li><li>有且仅有一个终端结点$a_n$，他没有直接前驱，而仅有一个直接前驱$a{n-1}$。</li><li>其余内部结点都有且仅有一个直接前驱和一个直接后继。</li></ol><h3 id="2-2-案例引入">2.2 案例引入</h3><h4 id="2-2-1-一元多项式的运算">2.2.1  一元多项式的运算</h4><p>$\large P_n(x)=p_0+p_1x+p_2x^{2}+……+p_nx^n$       $\large Q_n(x)=q_0+q_1x+q_2x^{2}+……+q_nx^n$</p><ul><li>其中$\large p_x和\large q_x$是系数<ul><li>那么两个多项式相加的结果$R_n(x)=P_n(x)+Q_m(x)$可用线性表R表示：<ul><li>$\large R=(p_0+q_0,p1+q1,p2+q2+……+p_n+q_n)$</li></ul></li></ul></li></ul><h4 id="2-2-2-稀疏多项式的运算">2.2.2 稀疏多项式的运算</h4><p>$\large S(x)=1+3x^{10000}+2x^{20000}$</p><ul><li><strong>仅记录</strong>系数<strong>不为零</strong>的系数即可，这样可以大大节省空间。</li></ul><p>​线性表A：(7,0)、(3,1)、(9,8)、(5,7)</p><p>​线性表B：(8,1)、(22,7)、(-9,8)</p><ul><li>创建一个<strong>新数组C</strong></li><li>分别从头比较A、B的每一项     PS：<em><strong>就是相当于走一遍一元多项式运算的步骤</strong></em><ul><li>指数相同：对应系数相加，若其和不为零，则在C中增加一个新项</li><li>指数不相同：则将指数较小的项复制到C中</li><li>一个线性表添加完后，将另一个线性表剩余项依次复制到C中即可</li></ul></li></ul><table><thead><tr><th style="text-align:center">0</th><th style="text-align:center">1</th><th style="text-align:center">7</th><th style="text-align:center">8</th><th style="text-align:center">7</th></tr></thead><tbody><tr><td style="text-align:center">7</td><td style="text-align:center">11</td><td style="text-align:center">22</td><td style="text-align:center">0</td><td style="text-align:center">5</td></tr></tbody></table><ul><li>顺序结构存在的问题<ol><li>存储空间分配不灵活</li><li>运算的空间复杂度高</li></ol></li><li>由此引出了<strong>链式存储结构</strong></li><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364d01c458853aef653b3f.png"/></div></div><ul><li>不需要额外的操作空间</li></ul></li></ul><h4 id="2-2-3-图书信息管理系统">2.2.3 图书信息管理系统</h4><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496fc458853aef5cd5cf.png"/></div></div><ul><li>将图书表抽象为线性表</li><li>表中每本图书抽象线性表中数据元素</li><li>具体实现，学完这一章就会了，暂时按下不表</li></ul><p>总结：</p><ol><li><em>线性表中数据元素的类型可以为<strong>简单类型</strong>，也可以为<strong>复杂类型</strong>。</em></li><li><em>许多实际应用问题所涉及的<strong>基本操作有很大相似性</strong>，不应为每个具体应用单独编写一个程序。</em></li><li><em>从具体应用中抽象出共性的逻辑结构和基本操作（抽象数据类型），然后实现其<strong>存储结构和基本操作</strong>。</em></li></ol><h3 id="2-3-线性表的类型定义">2.3 线性表的类型定义</h3><ul><li>抽象数据类型线性表定义如下：</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496fc458853aef5cd508.png"/></div></div><p><em><strong>PS: List是线性表的英文名</strong></em></p><ul><li>基本操作：<ul><li>InitList(&amp;L) (Initialization List)<br>操作结果：构造一个空的线性表L。</li><li>DestroyList(&amp;L)<br>初始条件:线性表L已存在。<br>操作结果：将L重置为空表。</li><li>ListEmpty(L)<br>初始条件：线性表L已存在。<br>操作结果：若L为空表， 则返回true, 否则返回false。</li><li>ListLength(L)<br>初始条件：线性表L已存在。<br>操作结果：返回L中数据元素个数。</li><li>GetElem(L,i,&amp;e)<br>初始条件：线性表L巳存在，且1:,s;i:os;ListLength(L)。<br>操作结果：用e返回L中第1个数据元素的值。</li><li>LocateElem(L,e)<br>初始条件：线性表L已存在<br>操作结果：返回L中第1个 值与e相同的元素在 L中的位置 。若这样的数据元素不存在 ， 则返回值为0。</li><li>PriorElem(r,cur_e,&amp;pre_e)<br>初始条件：线性表L已存在。<br>操作结果：若cur_e是L的数据元素，且不是第一个，则用pre_e返回其前驱，否则操作失败，pre_e无定义。</li><li>NextElem(L,cur_e,&amp;next_e)<br>初始条件：线性表L已存在。<br>操作结果：若cur_e是L的数据元素，且不是最后一个，则用next_e返回其后继，否则操作失败，next_e无定义。</li><li>Listinsert(&amp;L,i,e)<br>初始条件：线性表L已存在，且1:,s;i:os;ListLength (L) +l。<br>操作结果：在 L中第1个位置之前插入新的数据元素 e, L的长度加1。</li><li>ListDelete(&amp;L,i)<br>初始条件：线性表L已存在且非空 ，且l:os;i:os;ListLength(L)。<br>操作结果：删除L的第1个数据元素，L的长度减1。</li><li>TraverseList(L)<br>初始条件：线性表L已存在<br>操作结果：对线性表L进行遍历，在遍历过程中对 L的每个结点访问一次。</li></ul></li></ul><h3 id="2-4-线性表的顺序表示和实现">2.4 线性表的顺序表示和实现</h3><h4 id="2-4-1-线性表的顺序存储表示">2.4.1 线性表的顺序存储表示</h4><ul><li>线性表的顺序表示又称为<strong>顺序存储结构或顺序映像</strong><ul><li><strong>顺序存储定义</strong>：把<strong>逻辑上相邻</strong>的数据元素存储在<strong>物理上相邻</strong>的存储单元中的存储结构</li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364d01c458853aef653b8a.png"/></div></div><p>线性表的第一个数据元素$a_1$的存储位置，称作线性表的<strong>起始位置或基地址</strong></p><ul><li><p><strong>顺序表的特点</strong>：<em>以物理位置相邻表示逻辑关系，任一元素均可随机存取</em>。（优点）</p><ul><li>PS：必须是<strong>占用一片连续的存储空间</strong>，中间不存在空的存储单元</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364d02c458853aef653c27.png"/></div></div></li><li><p><strong>线性表类型定义</strong>的模版：</p></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> LIST_INIT_SIZE 100<span class="comment">//线性表存储空间的初始分配量</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType elem[LIST_INIT_SIZE];<span class="comment">//ElemType改成需要的元素类型，把他当做未知数x,根据题目需求而改变</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//当前长度</span></span><br><span class="line">&#125;SqList;</span><br></pre></td></tr></table></figure><ul><li>例一：多项式的顺序存储结构类型定义</li></ul> <div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496ec458853aef5cd4b6.png"/></div></div><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAXSIZE 1000<span class="comment">//多项式可能达到的最大长度</span></span></span><br><span class="line"> <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//多项式非零项的定义</span></span><br><span class="line">     <span class="type">float</span> p;<span class="comment">//系数</span></span><br><span class="line">     <span class="type">int</span> e;<span class="comment">//指数</span></span><br><span class="line"> &#125;Ploynomial;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    polynomial *elem;<span class="comment">//存储空间的基地址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//多项式中当前项的个数</span></span><br><span class="line">&#125;SqList;<span class="comment">//多项式的顺序存储结构类型为SqList</span></span><br></pre></td></tr></table></figure><ul><li>例二：图书表的顺序存储结构类型定义</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496fc458853aef5cd5cf.png"/></div></div><figure class="highlight c"><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="meta">#<span class="keyword">define</span> MAXSIZE 10000</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//图书信息定义</span></span><br><span class="line">    <span class="type">char</span> no [<span class="number">20</span>];<span class="comment">//图书的ISBN</span></span><br><span class="line">    <span class="type">char</span> name[<span class="number">50</span>];</span><br><span class="line">    <span class="type">float</span> price;</span><br><span class="line">&#125;Book;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    Book *elem;<span class="comment">//存储空间的基地址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//图书表中当前图书个数</span></span><br><span class="line">&#125;SqList;<span class="comment">//图书表的顺序存储结构类型为SqList</span></span><br></pre></td></tr></table></figure><ul><li><p>补充：数组定义</p><ul><li><p>数组静态分配</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType data[MaxSize];</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;<span class="comment">//顺序表类型</span></span><br></pre></td></tr></table></figure></li><li><p>数组动态分配</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType *data;</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;<span class="comment">//顺序表类型</span></span><br></pre></td></tr></table></figure></li><li><p>C语言的内存动态分配</p><p>SqList L；</p><p>L.data=(<u>ElemType*</u><a href="%E8%BD%AC%E5%8C%96%E4%B8%BA%E8%AF%A5%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%8C%87%E9%92%88">^f</a>)<strong>malloc</strong>(<strong>sizeof</strong>(ElemType)*MaxSize);</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364d02c458853aef653cc3.png"/></div></div><p>需要加载头文件：&lt;stdlid.h&gt;</p></li></ul></li><li><p>传地址方式——指针变量做参数(<strong>c++</strong>)</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">swap</span><span class="params">(<span class="type">float</span>*m,<span class="type">float</span>*n)</span>&#123;</span><br><span class="line">    <span class="type">float</span> t:</span><br><span class="line">    t=*m;</span><br><span class="line">    *m=*n;</span><br><span class="line">    *n=t;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">float</span> a,b,*p1,*p2;</span><br><span class="line">    <span class="built_in">cin</span>&gt;&gt;a&gt;&gt;b;<span class="comment">//cin&gt;&gt;输入的意思</span></span><br><span class="line">    p1=&amp;a; p2=&amp;b;</span><br><span class="line">    swap(p1,p2);</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;a&lt;&lt;<span class="built_in">endl</span>&lt;&lt;b&lt;&lt;<span class="built_in">endl</span>;<span class="comment">//endl换行的意思</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>传地址方式——数组名作参数</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">sub</span><span class="params">(<span class="type">char</span> b[])</span>&#123;</span><br><span class="line">    b[]=<span class="string">&quot;world&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span> <span class="params">(<span class="type">void</span>)</span>&#123;</span><br><span class="line">    <span class="type">char</span> a[<span class="number">10</span>]=<span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    sub(a):</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;a&lt;&lt;<span class="built_in">endl</span>;<span class="comment">//结果为world</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>传地址方式——引用类型作参数</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> i=<span class="number">5</span>;</span><br><span class="line">    <span class="type">int</span> &amp;j=i;<span class="comment">//i是本名；j是小名</span></span><br><span class="line">    i=<span class="number">7</span>;<span class="comment">//i值改变j值也会跟着改变</span></span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;<span class="string">&quot;i=&quot;</span>&lt;&lt;i&lt;&lt;<span class="string">&quot;j=&quot;</span>&lt;&lt;j;<span class="comment">//输出i=7 j=7</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>形参变化实参也发生变化</li><li>占有同一片区域</li></ul></li><li><p>顺序表示意图</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAXSIZE 100</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType *elem;</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;<span class="comment">//定义顺序表类型</span></span><br></pre></td></tr></table></figure><ul><li>$\large SqList$   $\large L$；  //定义变量L，L是SqList这种类型的，L是个顺序表</li></ul></li></ul><h4 id="2-4-2-顺序表的基本操作的实现">2.4.2 顺序表的基本操作的实现</h4><ol><li><p>初始化线性表L</p><p><strong>算法2.1  顺序表的初始化</strong></p><ol><li>elem指向这段空间的基地址</li><li>将表的当前长度设为0</li></ol><figure class="highlight c"><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">Status <span class="title function_">lnitLIS_Sq</span><span class="params">(SqList &amp;L)</span>&#123;<span class="comment">//构造一个空的顺序表</span></span><br><span class="line">L.elem=new ElemType[MAXSIZE];<span class="comment">//为顺序表分配空间</span></span><br><span class="line"><span class="keyword">if</span>(!L.elem)<span class="built_in">exit</span>(OVERFLOW);<span class="comment">//存储分配失败</span></span><br><span class="line">L.length=<span class="number">0</span>;<span class="comment">//空表长度为0</span></span><br><span class="line">retunrn OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>销毁线性表L</p><figure class="highlight c"><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="type">void</span> <span class="title function_">DestroyList</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(L.elem)delete L.elem:<span class="comment">//释放存储空间</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>清空线性表L</p><figure class="highlight c"><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="type">void</span> <span class="title function_">ClearList</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    L.length=<span class="number">0</span>;<span class="comment">//将线性表长度置为0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>求线性表L的长度</p><figure class="highlight c"><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="type">int</span> <span class="title function_">GetLength</span><span class="params">(SqList L)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span>(L.length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>判断线性表L是否为空</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">IsEmpty</span><span class="params">(SqList L)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(L.length==<span class="number">0</span>)<span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>取值</p><p><strong>算法2.2  顺序表的取值</strong></p><ol><li>根据指定的位置序号$\large i$，获取顺序表中第$\large i$个数据元素的值。</li><li>若是$\large i$​值合理，则将将第 $\large i $个数据元素 $\large L.elem[i-1]$赋给参数$\large e$, 通过$\large e$返回第 1 个数据元素的传值。</li></ol><figure class="highlight c++"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">GetElem</span><span class="params">(SqList L,<span class="type">int</span> i,ElemType &amp;e)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(i&lt;<span class="number">1</span>||i&gt;L.length)<span class="keyword">return</span> ERROR: <span class="comment">//i是序号，所以不能小于1</span></span><br><span class="line">    e=L.elem[i<span class="number">-1</span>];</span><br><span class="line">    <span class="keyword">return</span> OK</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>查找</p><p><strong>算法2.3 顺序表的查找</strong></p><ol><li>从第一个元素开始，往后找，查找成功返回该元素的序号i+1</li><li>若找到最后还是没找到，则查找失败，返回0</li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">LocateELem</span><span class="params">(SqList L,ElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;L.length;i++)</span><br><span class="line">        jif(L.elem[i]==e)<span class="keyword">return</span> i+<span class="number">1</span>;  <span class="comment">//查找成功返回i+1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>； <span class="comment">//查找失败返回0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>平均查找长度ASL</strong> ：<em>在查找时，为确定元素在顺序表中的位置， 需和给定值进行比较的数据元素个数的期望值称为查找算法在查找成功时的平均查找长度</em></p><p><strong>$\large ASL=\sum\limits_{i=1}^nP_iC_i$</strong>          $P_i$是第i个记录被查找的概率          $C_i$是第i个记录需要比较的次数</p></li></ol><p>​$\large ASL=p_1+2p_2+3p_3+np_n=\dfrac{1}{n}(1+2+3+……+n)$         每一个记录被查找的概率都相等是$\large p_i=\dfrac{1}{n}$</p><p>​$ASL=\dfrac{1}{n}\dfrac{n(n+1)}{2}=\dfrac{n+1}{2}$</p><p>​<em>tips：这里和C语言的查找有着异曲同工之妙，还是要打好C语言的基础学这个就会轻松很多了</em></p><ol start="8"><li><p>插入</p><p><strong>算法2.4 顺序表的插入</strong></p><ol><li>看插入位置是否合法（1&lt;=i&lt;=n+1）,不合法则返回error</li><li>看顺序表存储空间是否已满，已满返回error</li><li>将第n干活到第i个位置元素一次后移一个位置</li><li>将e放入第i个位置</li><li>表长+1</li></ol><figure class="highlight c"><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">status <span class="title function_">ListInsert</span><span class="params">(SqList &amp;L,<span class="type">int</span> i,ElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(i&lt;=<span class="number">1</span>||i&gt;L.length+<span class="number">1</span>)<span class="keyword">return</span> ERROR;</span><br><span class="line">    <span class="keyword">if</span>(L.length==MAXSIZE)<span class="keyword">return</span> ERROR;</span><br><span class="line">    <span class="keyword">for</span>(j=L.length<span class="number">-1</span>;j&gt;=i<span class="number">-1</span>;j--)</span><br><span class="line">        L,elem[j+<span class="number">1</span>]=L.elem[j];<span class="comment">//元素后移</span></span><br><span class="line">    L.elem[i<span class="number">-1</span>]=e;<span class="comment">//赋值</span></span><br><span class="line">    L.length++;<span class="comment">//表长加一</span></span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>顺序表插入算法分析</strong>：$\large E_{ins}=\dfrac{1}{n+1}\sum\limits_{i=1}^{n+1}(n-i+1)=\dfrac{n}{2}$</p><p>i是第几个位置，x是移动次数，第1个移动n次，第2个移动n-1次最后一个移动0次，可发现$\large i+x=n+1$</p></li></ol><p>​所以顺序表插入算法的平均复杂度为$\large O(n)$    (前面最高次项的系数二分一是常数，删除了)</p><ol start="9"><li><p>删除</p><p><strong>算法2.5 顺序表的删除</strong></p><ol><li>判断位置i是否合法</li><li>将位置i后的元素一次前移</li><li>表长-1，删除成功返回OK、</li></ol><figure class="highlight c"><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">status <span class="title function_">ListInsert</span><span class="params">(SqList &amp;L,<span class="type">int</span> i,ElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(i&lt;=<span class="number">1</span>||i&gt;L.length+<span class="number">1</span>)<span class="keyword">return</span> ERROR;</span><br><span class="line">    <span class="keyword">for</span>(j=i.j&lt;length<span class="number">-1</span>;j++</span><br><span class="line">         L,elem[j<span class="number">-1</span>]=L.elem[j];<span class="comment">//元素前移</span></span><br><span class="line">        L，length--;</span><br><span class="line">        ruturn OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>顺序表删除算法分析</strong>：$\large E_{del}=\dfrac{1}{n}\sum\limits_{i=1}^{n}(n-i)=\dfrac{n-1}{2}$</p></li></ol><p>​i是第一个移动n-1次，i是2移动n-2次,i是第n个移动0次，可发现$\large x=n-i$</p><h3 id="2-5-线性表的链式表示和实现">2.5 线性表的链式表示和实现</h3><p>​<em>通过上一小节的学习，我们不难发现顺序表的优点是<strong>任一元素均可随机存取</strong>。但他的缺点也很明显，<strong>在进行插入和删除操作时，需移动大量元素，存储空间不灵活</strong></em></p><p>​<em>所有这些问题，都可以通过线性表的另一种表示方法——链式存储结构来解决。</em></p><ul><li><em><strong>再开始本章的学习之前，我们先了解一下什么是链表：</strong></em><ul><li>用一组<strong>物理位置任意</strong>的存储单元来存放线性表的数据元素。</li><li>这组存储单元既可以是连续的也可以是<strong>不连续的</strong>，甚至是<strong>零散分布</strong>在存储中任意位置上的。</li><li>链表中的<strong>元素</strong>的<strong>逻辑次序和物理次序</strong>不一定相同</li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536496ec458853aef5cd48a.png"/></div></div><p>其中姓名列为<strong>数据域</strong>，数据域是用来存储元素数值数据</p><p>后面的四位编码为<strong>指针域</strong>，用来存储直接后继结点的存储位置</p><p>我们可以通过<strong>头指针来命名</strong>单链表</p><ul><li>与链式存储相关的术语<ol><li>结点：数据元素的存储映像。有数据域和指针域两部分组成</li><li>链表：n个结点由指针链组成一个链表。它是线性表的链式存储映像，称为线性表的链式存储结构。</li></ol></li><li>单链表，双链表，循环链表<ul><li>节点只有一个指针域的链表称为单链表或线性链表</li><li>结点有两个指针域的链表，称为双链表</li><li>首位相连的链表称为循环链表</li></ul></li><li>头指针，头结点，首元结点（头指针&gt;头结点&gt;首元结点）<ul><li>头指针：指向链表中第一个结点的指针</li><li>首元结点：存储第一个元素的结点</li><li>头结点：首元结点前附设的一个结点<ul><li>带头结点</li><li>不带头结点</li></ul></li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364e5bc458853aef689403.png"/></div></div><ul><li><p>空表如何表示？</p><p>无头结点时，头指针为空</p><p>有头结点时，头结点指针域为空</p></li><li><p>多设置一个头结点有什么好处？</p><ul><li><p><strong>便于首元结点的处理</strong></p><p>​        结点是由结构体构造的，没有头结点的话，第一个数据元素的地址储存在头指针里，就没有储存在结构体里，操作上就和其他数据元素不同，而有了头结点，在链表里第一个位置的操作和其他位置一致，无须进行特殊处理</p></li><li><p><strong>便于空表和非空表的统一处理</strong></p><p>​        结点是由结构体构造的，没有头结点的话，第一个数据元素的地址储存在头指针里，就没有储存在结构体里，操作上就和其他数据元素不同</p></li><li><p>头结点的数据域内装什么</p><p>可以空着也可以存放线性表长度等附加信息，但该结点<strong>不能计入链表长度值</strong></p></li></ul><p>顺序表是随机存取法：找到要取的元素直接找他的位置就可以了</p><p>链表是顺序存取：只能从头指针进入链表，然后顺序扫描其余结点。</p></li></ul><h4 id="2-5-1-单链表的定义和表示">2.5.1 单链表的定义和表示</h4><ul><li><p>单链表的存储结构为： 数据域|指针域     =》   【data|next】</p></li><li><p>其中data什么类型取决于<strong>数据元素</strong>什么类型，如果处理的是学生表这种，比较复杂的数据类型，则通常定义为ElemType类型</p></li><li><p>next的类型取决于他<strong>存放的地址的数据元素是什么类型</strong>  比如存放的是int a=5,那么就是int *P</p></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span>&#123;</span><span class="comment">//声明结点的类型和指向结点的指针类型</span></span><br><span class="line">    ElemType data;<span class="comment">//结点的数据域</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span> *<span class="title">nmet</span>;</span><span class="comment">//结点的指针域</span></span><br><span class="line">&#125;Lnode,*LinkList<span class="comment">//LinkList为指向结构体Lnode的指针类型</span></span><br></pre></td></tr></table></figure><p>定义链表L：LinkList L；</p><p>定义结点指针P：LNode *P；</p><ul><li>例如：有一个存储学生学号、姓名、成绩的单链表结点类型定义如下：</li></ul><figure class="highlight c"><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"><span class="keyword">typedef</span> Struct student&#123;</span><br><span class="line">    <span class="type">char</span> num[<span class="number">8</span>];</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>];</span><br><span class="line">    <span class="type">int</span> score;<span class="comment">//以上三个数据域</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">student</span> *<span class="title">next</span>;</span><span class="comment">//指针域</span></span><br><span class="line">&#125;Lnode,*LinkList;</span><br></pre></td></tr></table></figure><p>​这样写不常用，不方便不统一，通常用一下格式</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> Struct&#123;</span><br><span class="line">    <span class="type">char</span> num[<span class="number">8</span>];</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>];</span><br><span class="line">    <span class="type">int</span> score;</span><br><span class="line">&#125;ElemType;<span class="comment">//将存储的多个数据项定义为一个结构类型</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span>&#123;</span></span><br><span class="line">    ElemType data;<span class="comment">//用这个结构类型定义data</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;Lnode,*LinkList</span><br></pre></td></tr></table></figure><h4 id="2-5-2-单链表基本操作和实现-（重点）">2.5.2 单链表基本操作和实现    （重点）</h4><h5 id="2-5-2-1-初始化">2.5.2.1 初始化</h5><p><strong>算法2.6 单链表的初始化</strong></p><ol><li><strong>生成新结点作为头结点，用头指针L 指向头结点。</strong></li><li><strong>头结点的指针域置空。</strong>     =》  【  |^】</li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">lnitList_L</span><span class="params">(LinkList &amp;L)</span>&#123;</span><br><span class="line">    L=new LNode；   <span class="comment">//或L=(LinkList) malloc (sizeof (LNode))</span></span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>补充单链表的几个常用简单算法：</p><ol><li><p><strong>判断链表是否为空</strong></p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">ListEmpty</span><span class="params">(LinkList L)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(L-&gt;next)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span></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></li><li><p><strong>单链表的销毁</strong></p><ul><li><p>从头指针开始，依次释放所有结点</p><figure class="highlight c"><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">Status <span class="title function_">DestroyList_L</span><span class="params">(LinkList &amp;L)</span>&#123;<span class="comment">//销毁单链表P</span></span><br><span class="line">    Lnode *p;<span class="comment">//或LinkLIst p</span></span><br><span class="line">    <span class="keyword">while</span>(L)&#123;<span class="comment">//L指向空停止循环</span></span><br><span class="line">        p=L;<span class="comment">//把L的地址给p</span></span><br><span class="line">        L=L-&gt;next;<span class="comment">//L指向下一个地址</span></span><br><span class="line">        delete p;<span class="comment">//删除p所指的结点</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>清空链表</strong></p><ul><li><p>依次释放所有结点，并将头结点的指针域设置为空</p><figure class="highlight c"><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">Status <span class="title function_">clearList_L</span><span class="params">(LinkList &amp;L)</span>&#123;<span class="comment">//将L重置为空表</span></span><br><span class="line">    Lnode *p,*q;</span><br><span class="line">    p=L-&gt;next;</span><br><span class="line">    <span class="keyword">while</span>(p)&#123;<span class="comment">//没到表尾</span></span><br><span class="line">        g=p-&gt;next;</span><br><span class="line">        delete p;</span><br><span class="line">        p=q;</span><br><span class="line">    &#125;</span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>; <span class="comment">//头结点指针域为空</span></span><br><span class="line">    <span class="keyword">return</span> OK:</span><br><span class="line">     </span><br><span class="line">&#125;</span><br><span class="line">     </span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>求链表表长</strong></p><ul><li><p>从首元结点开始，依次计数所有结点</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">ListLength_L</span><span class="params">(LinkList L)</span>&#123;<span class="comment">//LinkLIst定义的变量就是指针型的</span></span><br><span class="line">    LinkList p:</span><br><span class="line">    P=L-&gt;next;</span><br><span class="line">    i=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(p)&#123;</span><br><span class="line">        i++;</span><br><span class="line">        p=p-&gt;next</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ol><p>重要知识点重温：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364e5bc458853aef68943e.png"/></div></div><h5 id="2-5-2-2-取值">2.5.2.2 取值</h5><p><strong>算法2.7 取值</strong> ——取链表中第i个元素的内容</p><ol><li><p>从头开始，顺着链域往下搜索，指针后移且计数器x++直到x=i.</p><figure class="highlight c"><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">Status <span class="title function_">GetElem_L</span><span class="params">(LinkList L,<span class="type">int</span> i, ElemType &amp;e)</span>&#123;</span><br><span class="line">    p=L-&gt;next;i=<span class="number">1</span>;<span class="comment">//初始化</span></span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;j&lt;i)&#123;<span class="comment">//向后扫，知道p指向第i个元素或p为空</span></span><br><span class="line">        p=p-&gt;next;++j;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(!p||j&gt;i)<span class="keyword">return</span> ERROR;<span class="comment">//第i个元素不存在</span></span><br><span class="line">    e=p-&gt;data;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;<span class="comment">//GetElem_L</span></span><br></pre></td></tr></table></figure></li></ol><h5 id="2-5-2-3-查找">2.5.2.3 查找</h5><p><strong>算法2.8 按值查找</strong>——根据指定数据获取改数据所在位置</p><ol><li><p>从第一个结点开，依次和e比较</p></li><li><p>如果找到了与e值相等的数据元素，则返回其地址</p></li><li><p>没找到则返回0或NULL</p><figure class="highlight c"><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">Lnode *<span class="title function_">LocateELem_L</span> <span class="params">(LinkList L, Elemtype e)</span> &#123;</span><br><span class="line">    p=L-&gt;next;</span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;p-&gt;data!=e)</span><br><span class="line">        p=p-&gt;next;</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">LocateELem_L</span><span class="params">(LinkList L, Elemtype e)</span>&#123;</span><br><span class="line">    p=L-&gt;next;i=<span class="number">1</span>:</span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;p-&gt;data!=e)</span><br><span class="line">        &#123;p=p-&gt;next;j++;&#125;</span><br><span class="line">    <span class="keyword">if</span>(p)<span class="keyword">return</span> j;<span class="comment">//没找到的话p指向空</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h5 id="2-5-2-4-插入">2.5.2.4 插入</h5><p><strong>算法2.9 插入——在第i个结点前插入值为e的新结点</strong></p><ol><li><p>找到结点i-1</p></li><li><p>建构新结点s</p></li><li><p>s-&gt;next=p-&gt;next；p-&gt;next=s；</p><figure class="highlight c"><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">Status <span class="title function_">Listlnsert_L</span><span class="params">(lLinkList &amp;L,<span class="type">int</span> i,ElemType e)</span>&#123;</span><br><span class="line">    p=L;j=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;j&lt;i<span class="number">-1</span>)&#123;p=p-&gt;next;++j;&#125;<span class="comment">//找节点</span></span><br><span class="line">    <span class="keyword">if</span>(!p||j&gt;i<span class="number">-1</span>)<span class="keyword">return</span> ERROR;<span class="comment">//判断位置是否合法（是否小于一或大于表长加一）</span></span><br><span class="line">    s=new LNode; s-&gt;data=e;<span class="comment">//生成新结点s,数据域为e</span></span><br><span class="line">    s-&gt;next=p-&gt;next;<span class="comment">//插入新结点</span></span><br><span class="line">    p-&gt;next=s;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;<span class="comment">//Listlnsert_L</span></span><br></pre></td></tr></table></figure></li></ol><h5 id="2-5-2-5-删除">2.5.2.5 删除</h5><p><strong>算法2.10 删除——删除第i个结点</strong></p><ol><li><p>找到结点i-1</p></li><li><p>p-&gt;next=p-&gt;next-&gt;next</p></li><li><p>释放结点i的空间</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">ListDelet_L</span><span class="params">(LinkList &amp;L,<span class="type">int</span> i,ElemType &amp;e)</span>&#123;</span><br><span class="line">    p=L;j=<span class="number">0</span>;q;i;</span><br><span class="line">    <span class="keyword">while</span>(p-&gt;next&amp;&amp;j&lt;i<span class="number">-1</span>)&#123;p=p-next;++j;&#125;   <span class="comment">//找到i-1</span></span><br><span class="line">    <span class="keyword">if</span>(!(p-&gt;next)||j&gt;i<span class="number">-1</span>)<span class="keyword">return</span> ERROR;<span class="comment">//删除位置不合理</span></span><br><span class="line">    q=p-&gt;next;<span class="comment">//q指向要删除的结点</span></span><br><span class="line">    p-&gt;next=q-&gt;next;<span class="comment">//是p指针next域指向删除域的下一个结点</span></span><br><span class="line">    e=q-&gt;data;<span class="comment">//保存删除结点的数据域</span></span><br><span class="line">    delete q;<span class="comment">//释放空间</span></span><br><span class="line">    retun OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p><em><strong>分析单链表查找，插入，删除算法的时间效率分析：</strong></em></p><ol><li>查找：应为只能顺序存取，即要从头指针开始找起，查找时间复杂度为$\large O(n)$</li><li>插入和删除：因为线性链表不需要移动元素，只用修改至臻，一般情况下的时间复杂度为$\large O(1)$</li></ol><h5 id="2-5-2-6-创建单链表">2.5.2.6 创建单链表</h5><h6 id="2-5-2-6-1-头插法——插到链表头部">2.5.2.6.1 头插法——插到链表头部</h6><ol><li><p>L=new LNode;</p><p>L=（LinkList）malloc(sizeof(LNode));//C语言//生成一个结点</p><p>p-&gt;data=$a_n$//数据域赋值</p></li><li><p>p-&gt;next=L-&gt;next;   L-&gt;next=p;                        //L指针域的空赋给p的指针域,L指针域指向p</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreateList_H</span><span class="params">(LinkList &amp;L,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    L=new LNode;</span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>;<span class="comment">//建立带头结点的单链表</span></span><br><span class="line">    <span class="keyword">for</span>(i=n;i&gt;<span class="number">0</span>;--i)&#123;</span><br><span class="line">        p=（LNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>（LNode));<span class="comment">//生成新结点</span></span><br><span class="line">        <span class="built_in">scanf</span>(&amp;p-&gt;data);</span><br><span class="line">        p-&gt;next=L-&gt;next;</span><br><span class="line">        L-&gt;next=P:</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h6 id="2-5-2-6-2-尾插法——插到链表尾部">2.5.2.6.2 尾插法——插到链表尾部</h6><ol><li><p>从空表L开始，将新结点逐个插入到链表尾部，尾指针r指向链表的尾节点</p></li><li><p>初始时，r同L均指向头结点。每读入一个数据元素则申请一个新结点，将新结点插入到尾结点后，r指向新结点。</p><figure class="highlight c"><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="type">void</span> <span class="title function_">CreatList_R</span><span class="params">(LinkList &amp;L,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    L=new LNode;</span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line">    r=L;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;n;++i)&#123;</span><br><span class="line">         p=（LNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>（LNode));<span class="comment">//生成新结点</span></span><br><span class="line">         <span class="built_in">scanf</span>(&amp;p-&gt;data);</span><br><span class="line">        r-&gt;next=p;</span><br><span class="line">        r=p;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h4 id="2-5-3-循环链表">2.5.3 循环链表</h4><ul><li><p>循环链表：头尾相接的链表</p><ul><li>优点：从表中任意结点出发均可找到表中其他结点</li><li>注意：终止条件为判断他们是否等于头指针</li><li>如果是<strong>头指针表示</strong>的单循环链表<ul><li>找$a_1$的时间复杂度：O(1)</li><li>找$a_n$的时间复杂度：O(n)</li></ul></li><li>若是<strong>尾指针表示</strong>的单循环链表<ul><li>找$a_1$的存储是：R-&gt;next-&gt;next    时间复杂度：O(1)</li><li>找$a_n$的存储是：R                           时间复杂度：O(1)</li></ul></li></ul></li><li><p>带尾指针循环链表的合并</p><ul><li><p>p存表头结点  将Tb表头连接到Ta表尾    释放Tb表头结点    修改指针</p></li><li><p>p=Ta-&gt;next ; Ta-&gt;next=Tb-&gt;next-&gt;next ; delete Tb-&gt;next ; Tb-&gt;next=p;</p></li></ul><figure class="highlight c"><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">LinkList <span class="title function_">Connect</span><span class="params">(LinkList Ta,LinkList Tb)</span>&#123;</span><br><span class="line">    p=Ta-&gt;next;<span class="comment">//  an指向b1,没毛病，但是bn指向a头结点p就找不到了因为第一步你覆盖了an，所以an指向b1前，要利用an找到a头结点，bn指向bn的next</span></span><br><span class="line">    Ta-&gt;next=Tb-&gt;next-&gt;next:</span><br><span class="line">    delete Tb-&gt;next:</span><br><span class="line">    Tb-&gt;next=p;</span><br><span class="line">    <span class="keyword">return</span> Tb;</span><br><span class="line">&#125;<span class="comment">//时间复杂度O(1)</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="2-5-4-双向链表">2.5.4 双向链表</h4><ul><li><p>双向链表的结构可以定义如下：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">DuLNode</span>&#123;</span><span class="comment">//前面的Du是double双向的意思</span></span><br><span class="line">    Elemtype         data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">DuLNode</span>   *<span class="title">prior</span>,*<span class="title">next</span>;</span>          <span class="comment">//一个指向前驱，一个指向后驱</span></span><br><span class="line">&#125;DuLNode,*DuLinkList;</span><br></pre></td></tr></table></figure><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364e5cc458853aef6894ba.png"/></div></div></li><li><p>双向循环列表</p><ul><li>让头结点的前驱指针指向链表的最后一个结点</li><li>让最后一个结点的后继指针指向头结点</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364e5cc458853aef689496.png"/></div></div></li><li><p>双向链表结构的对称性：</p><ul><li>$\large p-&gt;prior-&gt;next=p=p-&gt;next-&gt;prior$</li></ul></li></ul><p><strong>算法2.13 双向链表的插入</strong></p><ol><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f0dc458853aef6a3b05.png"/></div></div></li><li><pre><code class="language-c">void Listlnsert_DuL(DuLinkList &amp;L, lnt i, ElemType e)&#123;    //双向链表头结点指针L，第i个位置，值为e的元素    if(!(p=GetElemP_DuL(L,i)))return ERROR;//确定第i个元素的位置指针P    S=new DuLNode;s-&gt;date=e;    s-&gt;prior=p-&gt;prior;P-&gt;prior-&gt;next=s;    s-&gt;next=p;p-&gt;prior=s;    return OK;&#125;//Listlnsert_DuL<figure class="highlight xl"><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><br><span class="line">**算法<span class="number">2.14</span> 双向链表的删除**</span><br><span class="line"></span><br><span class="line">&#123;% image https:<span class="comment">//pic.imgdb.cn/item/65364f0dc458853aef6a3b51.png %&#125;</span></span><br><span class="line"></span><br><span class="line">```c</span><br><span class="line">void ListDelete_DuL(DuLinkList &amp;L, lnt i, ElemType &amp;e)&#123;</span><br><span class="line">    <span class="comment">//用e返回</span></span><br><span class="line">    <span class="keyword">if</span>(!(p=GetElemP_DuL(L,i)))return ERROR;<span class="comment">//确定第i个元素的位置指针P，顺着链表查找时间复杂度为O(n)</span></span><br><span class="line">    <span class="function"><span class="title">e</span>=p-&gt;</span><span class="keyword">data</span>;</span><br><span class="line">    <span class="function"><span class="title">p</span>-&gt;</span><span class="function"><span class="title">prior</span>-&gt;</span><span class="function"><span class="title">next</span>=p-&gt;</span>next;</span><br><span class="line">    <span class="function"><span class="title">p</span>-&gt;</span><span class="function"><span class="title">next</span>-&gt;</span><span class="function"><span class="title">prior</span>=p-&gt;</span>prior;</span><br><span class="line">    free(p);</span><br><span class="line">    return OK;</span><br><span class="line">&#125;<span class="comment">//ListDelete_DuL</span></span><br></pre></td></tr></table></figure></code></pre></li></ol><ul><li><p>单链表、循环链表和双向链表的时间效率比较</p><table><thead><tr><th style="text-align:left"></th><th style="text-align:left">查找表头结点（首元结点）</th><th>查找表尾结点</th><th>查找结点*p的前驱结点</th></tr></thead><tbody><tr><td style="text-align:left">带头结点的单链表L</td><td style="text-align:left">L-&gt;next<br />时间复杂度O(1)</td><td>L-&gt;next依次向后遍历<br />时间复杂度O(n)</td><td>通过p-&gt;next无法找到其前驱</td></tr><tr><td style="text-align:left">带头结点仅设头指针L的循环单链表</td><td style="text-align:left">L-&gt;next<br />时间复杂度O(1)</td><td>L-&gt;next依次向后遍历<br />时间复杂度O(n)</td><td>通过p-&gt;next可以找到其前驱<br />时间复杂度O(n)</td></tr><tr><td style="text-align:left">带头结点仅设尾指针R的循环单链表</td><td style="text-align:left">R-&gt;next<br />时间复杂度O(1)</td><td>R<br />时间复杂度O(1)</td><td>通过p-&gt;next可以找到其前驱<br />时间复杂度O(n)</td></tr><tr><td style="text-align:left">带头结点的双向循环链表L</td><td style="text-align:left">L-&gt;next<br />时间复杂度O(1)</td><td>L-》prior<br />时间复杂度O(1)</td><td>p-&gt;prior<br />时间复杂度O(1)</td></tr></tbody></table></li></ul><h3 id="2-6顺序表和链表的比较">2.6顺序表和链表的比较</h3><ul><li><p>链式存储结构的优点：</p><ul><li><p><strong>结点空间</strong>可以<strong>动态申请和释放</strong></p></li><li><p>数据元素的逻辑次序靠结点的指针来指示，插入和删除时不需要移动数据元素</p></li></ul></li><li><p>链式存储结构的缺点：</p><ul><li><p><strong><u>存储密度小</u></strong>，每个结点的<strong>指针域需要额外占用存储空间</strong>。当每个结点的数据域所占字节不多时，指针域所占存储空间的比重显得很大。</p><ul><li><p>$\large 存储密度=\dfrac {结点数据本身占用的空间}{结点占用的空间总量}$</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f0dc458853aef6a3b99.png"/></div></div></li><li><p>链式存储结构是<strong>非随机存储</strong>结构。对任一结点的操作都要从头遍历，这增加了算法的复杂度</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f0dc458853aef6a3c43.png"/></div></div></li></ul></li></ul></li></ul><h3 id="2-7-线性表的应用">2.7 线性表的应用</h3><h4 id="2-7-1-线性表的合并">2.7.1 线性表的合并</h4><ul><li><p>线性表的合并</p><ul><li><p>问题描述：</p><p>假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=AUB</p><p>La=(7,5,3, 11) Lb=(2, 6, 3)     ====》  La=(7, 5, 3, 11, 2, 6)</p></li><li><p>算法步骤：</p><p>从Lb中的每个元素,执行以下操作：</p><ol><li>在La中查找该元素</li><li>如果找不到，则将其插入到La的最后</li></ol></li><li><p>代码实现:</p><figure class="highlight c"><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="type">void</span> <span class="title function_">union</span><span class="params">(List &amp;La, List Lb)</span>&#123;</span><br><span class="line">    La_len=ListLength(La);</span><br><span class="line">    Lb_len=ListLength(Lb);</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;=Lb_len;i++)&#123;</span><br><span class="line">        GetElem(Lb,i,e);</span><br><span class="line">        <span class="keyword">if</span>(!LocateElem(La,e))  Listlnsert(&amp;La,++La_len,e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度为La的长度乘以Lb的长度</p></li></ul></li></ul><h4 id="2-7-2-有序表的合并">2.7.2 有序表的合并</h4><ul><li><p>有序表的合并</p><ul><li><p>问题描述：</p><p>已知线性表La 和Lb中的数据元素按值非递减有序排列，现要求将La和Lb归并为一个新的线性表Lc，且Lc中的数据元素仍按值非递减有序排列。</p></li><li><p>算法步骤：</p><ol><li>创建一个空表Lc</li><li>依次从 La 或 Lb 中“摘取”元素值较小的结点插入到 Lc 表的最后，直至其中一个表变空为止</li><li>继续将La或Lb其中一个表的剩余结点插入在Lc表的最后</li></ol></li><li><p>用顺序表实现合并的代码实现：</p><figure class="highlight c"><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="type">void</span> <span class="title function_">MergeList_Sq</span><span class="params">(SqList LA,SqList LB,SqList &amp;LC)</span>&#123;</span><br><span class="line">    pa=LA.elem;</span><br><span class="line">    pb=LB.elem;<span class="comment">//指针pa和pb的初值分别指向两个表的第一个元素      </span></span><br><span class="line">    LC.length=LA.length+LB.length;<span class="comment">//新表的长度等于两表长之和</span></span><br><span class="line">    LC.elem=new ElemType[LC.length];<span class="comment">//为合并后的新表分配一个数组空间</span></span><br><span class="line">    pc=LC.elem;<span class="comment">//指针pc指向新表第一个元素</span></span><br><span class="line">    pa_last=LA.elem+LA.length<span class="number">-1</span>;<span class="comment">//指针pa_last指向LA表的最后一个元素（基地址加上长度减一）</span></span><br><span class="line">    pa_last=LB.elem+LB.length<span class="number">-1</span>;<span class="comment">//指针pa_last指向LB表的最后一个元素（基地址加上长度减一）</span></span><br><span class="line"><span class="keyword">while</span>(pa&lt;pa_last&amp;&amp;pb&lt;=pb_last)&#123;<span class="comment">//两个表都非空</span></span><br><span class="line">    <span class="keyword">if</span>(*pa&lt;=*pb)*pc++=*pa++;<span class="comment">//依次“摘取”两表中值较小的结点</span></span><br><span class="line">    <span class="keyword">else</span> *pc++=*pb++;</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">while</span>(pa&lt;=pa_last) *pc++=*pa++;<span class="comment">//LB表已经到达表位，将LA中剩余元素加入LC</span></span><br><span class="line">    <span class="keyword">while</span>(pb&lt;=pb_last) *pc++=*pb++;<span class="comment">//LA表已经到达表位，将LB中剩余元素加入LC</span></span><br><span class="line">&#125;<span class="comment">//MergeList_Sq</span></span><br></pre></td></tr></table></figure></li><li><p>用链表实现合并的代码实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">MergeList_L</span><span class="params">(LinkList &amp;La,LinkList &amp;Lb,LinkList &amp;Lc)</span>&#123;</span><br><span class="line">    pa=La-&gt;next; pb=Lb-&gt;next; </span><br><span class="line">    pc=Lc=La;<span class="comment">//用La的头结点作为Lc的头结点</span></span><br><span class="line">    <span class="keyword">while</span>( pa &amp;&amp; pb)&#123;</span><br><span class="line">        <span class="keyword">if</span>(pa-&gt;data&lt;=pb-&gt;data) &#123;pc-&gt;next=pa; pc=pa; pa=pa-&gt;next;&#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;pc-&gt;next=pb; pc=pb; pb=pb-&gt;next;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    pc-&gt;next=pa?pa:pb; <span class="comment">//插入剩余段delete Lb;</span></span><br><span class="line">    delete LB;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法的时间复杂度是：O(ListLength(La)+ListLength(Lb)）</p></li></ul></li></ul><h3 id="2-8案例分析与实现">2.8案例分析与实现</h3><p><strong>案例2.1 一元多项式的运算：实现两个多项式加、减、乘、除运算</strong>   <em>线性表</em></p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f0ec458853aef6a3cb6.png"/></div></div><ul><li>实现两个多项式相加运算</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f83c458853aef6b7948.png"/></div></div><p><strong>案例2.2：稀疏多项式的运算</strong>   <em>链表</em></p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f83c458853aef6b79b6.png"/></div></div><ul><li>创建一个新数组C</li><li>分别从头遍历比较A和B的每一项<ul><li><strong>指数相同</strong>，系数相加，和若不为零，则在C中新加一项</li><li><strong>指数不同</strong>，则将指数较小的项复制到C中</li></ul></li><li>一个多项式已遍历完毕时，将另一个剩余项依次复制到C中即可</li></ul><p>用顺序存储结构的话，存储空间分配不灵活，运算的空间复杂度高，所以我们常用链式存储结构</p><ol><li>创建一个只有头结点的空链表。</li><li>根据多项式的项的个数n，循环n次执行以下操作:<ol><li>生成一个新结点*s;</li><li>输入多项式当前项的系数和指数赋给新结点*s的数据域：</li><li>设置一前驱指针pre，用于指向待找到的第一个大于输入项指数的结点的前驱pre初值指向头结点;</li><li>指针q初始化，指向首元结点;</li><li>循链向下逐个比较链表中当前结点与输入项指数，找到第一个大于输入项指数的结点*q;</li><li>将输入项结点<em>s插入到结点</em>q之前。</li></ol></li></ol><ul><li>算法步骤：<ol><li>指针p1和p2初始化，分别指向Pa和Pb的首元结点</li><li>p3指向和多项式的当前结点，初值为Pa的头结点</li><li>当指针p1和p2均未到达相应表尾时，则循环比较p1和p2所指结点对应的指数值(p1-&gt;expn与p2-&gt;expn) 有下列3种情况:<ol><li>当p1-&gt;expn==p2-&gt;expn时，则将两个结点中的系数相加<ol><li>若和不为零，则修改p1所指结点的系数值，同时删除p2所指结点</li><li>若和为零，则删除p1和p2所指结点;</li></ol></li><li>当p1-&gt;expn<p2->expn时，则应摘取p1所指结点插入到“和多项式”链表中去</li><li>当p1-&gt;expn&gt;p2-&gt;expn时，则应摘取p2所指结点插入到“和多项式”链表中去</li></ol></li><li>将非空多项式的剩余段插入到p3所指结点之后</li><li>释放Pb的头结点</li></ol></li></ul><p><strong>案例2.3 图书信息管理系统</strong>      <em>线性表或链表</em></p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f83c458853aef6b7a0b.png"/></div></div><h2 id="3-栈和队列">3 栈和队列</h2><p>​<strong>栈和队列是两种重要的线性结构。从数据结构角度看，栈和队列也是线性表，其特殊性在千栈和队列的基本操作是线性表操作的子集，它们是操作受限的线性表， 因此，可称为限定性的数据结构。但从数据类型角度看，它们是和线性表不相同的两类重要的抽象数据类型。</strong></p><h3 id="3-1-栈和队列的定义和特点">3.1 栈和队列的定义和特点</h3><ul><li><p><strong>栈和队列</strong>是两种常用的、重要的数据结构</p></li><li><p>栈和队列是<strong>限定插入和删除</strong>只能在表的<strong>端点</strong>进行的<strong>线性表</strong></p><ul><li>线性表可以删除任意位置</li><li><strong>栈</strong>只能在<strong>表尾</strong>插入或删除（<em>类似弹夹，后面对应名词压入弹出都能十分契合</em>）</li><li><strong>队列</strong>只能在<strong>表尾插入</strong>或<strong>表头删除</strong>（<em>类似于排队，队头先走，人来了在队尾</em>）<ul><li>特性：<strong>栈——后进先出</strong></li></ul></li></ul></li><li><p>由于栈的操作具有后进先出的固有特性，使得栈成为程序设计中的有用工具</p><ul><li><p><strong>数制转换</strong><strong>表达式求值</strong><strong>括号匹配检验</strong><strong>八皇后问题</strong></p><p><strong>行编辑程序</strong><strong>函数调用</strong><strong>迷宫求解</strong><strong>递归调用的实现</strong></p></li></ul></li><li><p>由于队列具有先进先出的特性，使得队列可以解决类似排位问题的有用工具</p><ul><li><p><strong>脱机打印输出</strong>：按申请的先后顺序依次输出</p><p><strong>多用户系统中</strong>，多个用户排成队，分时地循环使用CPU和主存，按用户的优先级排成多个队，每个优先级一个队列</p><p><strong>实时控制系统中</strong>，信号按接收的先后顺序依次处理</p><p><strong>网络电文传输</strong>，按到达的时间先后顺序依次进行</p></li></ul></li></ul><h4 id="3-1-1-栈的定义和特点">3.1.1 栈的定义和特点</h4><ul><li>栈的定义：是一种特殊的线性表，是限定仅在一端（通常是表尾）进行插入和删除操作的线性表<ul><li>又称为<strong>后进先出的线性表</strong>，简称LIFO结构（Last in First Out）</li><li><strong>表尾</strong>称为<strong>栈顶</strong>，<strong>表头</strong>称为<strong>栈底</strong>，插入 元素到栈顶的操作，称为入栈（压入、进栈、压栈），从栈顶删除最后一个元素的操作，称为出栈（弹出、弹栈）</li></ul></li><li>栈的逻辑结构：<strong>与线性表相同</strong>，仍为一对一关系。</li><li>栈的存储结构：用<strong>顺序栈或链栈存储</strong>均可，但以顺序栈更常见</li><li>栈的运算规则：只能在栈顶运算，且访问结点时依照<strong>后进先出</strong>（LIFO）的原则</li><li>栈的实现方式：关键是<strong>编写入栈和出栈函数</strong>，具体实现依顺序栈或链栈的不同而不同</li></ul><h4 id="3-1-2-队列的定义和特点">3.1.2 队列的定义和特点</h4><ul><li><p>队列的定义：是一种先进先出的线性表，简称FIFO结构</p><ul><li>只能插入到队尾，从队头删除</li></ul></li><li><p>队列的逻辑结构：<strong>同线性表</strong>，仍为一对一关系。</p></li><li><p>队列的存储结构：<strong>顺序队或链队</strong>，以循环顺序队列更常见</p></li><li><p>队列的运算规则：只能在队首和队尾运算，且访问结点时依照<strong>先进先出</strong>（FIFO）的原则</p></li><li><p>队列的实现方式：关键是掌握<strong>入队和出队</strong>操作，具体实现依顺序队或链队的不同而不同</p></li></ul><h3 id="3-2-案例引入">3.2 案例引入</h3><p><strong>案例3.1：进制转换</strong></p><ul><li><p>十进制整数N向其他进制数d（二、八、十六）的转换是计算机实现计算的基本问题。</p><p><strong>转换法则：除以d倒取余</strong></p><p>该转换法则对应于一个简单算法原理：</p><p>​*<em>n=(n div d)<em>d+n mod d</em></em></p><p>其中：div为整除运算，mod为求余运算</p></li><li><p>例：十进制把159转换成八进制</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f83c458853aef6b7a7c.png"/></div></div><p><em><strong>运用到栈的后进先出的特性</strong></em></p></li></ul><h4 id="3-2-1：括号匹配的检验">3.2.1：括号匹配的检验</h4><ul><li><p>假设表达式中允许包含两种括号：圆括号和方括号</p></li><li><p>检验（（）】）是否匹配</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364f83c458853aef6b7b00.png"/></div></div><p>后进的括号与前面括号进行匹配，如果为<strong>相同括号的两边</strong>，则栈顶的左括号弹出，也符合<em><strong>后进先出的特性</strong></em></p><ul><li>若遇到一下集中情况之一，说明括号不匹配<ol><li>遇到某一个右括号时，栈已空，说明到目前为止，<strong>右括号多于左括号；</strong></li><li>当栈中弹出的左括号与当前检验的右括号类型不同，则说明出现了<strong>括号交叉</strong>情况；</li><li>表达式输入完毕后，但栈中还有没匹配的左括号，说明<strong>左括号多于右括号</strong>。</li></ol></li></ul></li></ul><h4 id="3-3-2：表达式求值">3.3.2：表达式求值</h4><ul><li><p>表达式求值是程序设计语言编译中一个最基本的问题，他的实现也需要运用栈</p></li><li><p>这里介绍的算法是由运算符优先级确定运算顺序的对表达式求值算法</p><p>——<strong>算符优先算法</strong></p><ul><li><p>表达式的组成</p><ul><li><strong>操作数</strong>：常数、变量。</li><li><strong>运算符</strong>：算术运算符、关系运算符和逻辑运算符。</li><li><strong>界限符</strong>：左右括弧和表达式结束符。</li></ul></li><li><p>任何一个算术表达式都由<strong>操作数、算术运算符和界限符</strong>组层。<strong>后两者</strong>统称为<strong>算符</strong></p><ul><li><p>例如：#3*（7-2）#<br>为了实现表达式求值。需要设置两个栈</p><ul><li>一个是算符栈OPTR，用于寄存运算符</li><li>另一个称为操作数栈OPND，用于寄存运算数和运算结果</li></ul><p>求值的处理过程是自左至右扫描表达式的每一个字符</p><ul><li>当扫描到的是运算数，则将其压入栈OPND</li><li>当扫描到的是运算符时<ul><li>若这个运算符比OPTR栈顶运算符的优先级高，则入栈OPTR，继续向后处理</li><li>若这个运算符比OPTR栈顶运算符优先级低，则从OPND栈中弹出两个运算数，从栈OPTR中弹出栈顶运算符进行运算，并将运算结果压入栈OPND。</li><li>继续处理当前字符，直到遇到结束符为止。</li></ul></li></ul></li></ul></li></ul></li></ul><h4 id="3-2-3：舞伴问题">3.2.3：舞伴问题</h4><ul><li>舞会上男女各排一队，舞会开始从队头各出一人配成舞伴，如果两队初始人数不同，则较长那一队未配对者等待下一轮舞曲。</li><li>该问题具有典型的<strong>先进先出</strong>特性，可以用<strong>队列</strong>作为是算法的数据结构<ul><li>首先构造两个队列</li><li>依次将队头元素出队配成舞伴</li><li>某队为空，则另外一队等待者为下一舞曲第一个可获得舞伴的人。</li></ul></li></ul><h3 id="3-3-栈的表示和操作的实现">3.3 栈的表示和操作的实现</h3><h4 id="3-3-1-栈的抽象数据类型和类型的定义">3.3.1 栈的抽象数据类型和类型的定义</h4><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364ff2c458853aef6c8901.png"/></div></div><p><strong>基本操作：</strong></p><p>​InitStack(&amp;S)</p><p>​<strong>操作结果：构造一个空栈s。</strong></p><p>​DestroyStack(&amp;S)</p><p>​<strong>初始条件：栈s巳存在。</strong></p><p>​<strong>操作结果：栈S被销毁。</strong></p><p>​ClearStack(&amp;S)</p><p>​<strong>初始条件：栈S已存在。</strong></p><p>​<strong>操作结果：将S清为空栈。</strong></p><p>​StackEmpty(S)</p><p>​<strong>初始条件：栈S巳存在。</strong></p><p>​<strong>操作结果：若栈</strong> s <strong>为空栈， 则返回</strong> true, <strong>否则返回</strong> false**。**</p><p>​StackLength (S)</p><p>​<strong>初始条件：栈S已存在。</strong></p><p>​<strong>操作结果：返回s的元素个数， 即栈的长度。</strong></p><p>​GetTop(S)</p><p>​<strong>初始条件：栈S已存在且非空。</strong></p><p>​<strong>操作结果：返回s的栈顶元素， 不修改栈顶指针。</strong></p><p>​Push(&amp;S,e)</p><p>​<strong>初始条件：栈S已存在。</strong></p><p>​<strong>操作结果：插入元素e为新的栈顶元素。</strong></p><p>​Pop(&amp;S,&amp;e)</p><p>​<strong>初始条件：栈s已存在且非空。</strong></p><p>​<strong>操作结果：删除S的栈顶元素，并用e返回其值。</strong></p><p>​StackTraverse(S)</p><p>​<strong>初始条件：栈S已存在且非空。</strong></p><p>​<strong>操作结果：从栈底到栈顶依次对S的每个数据元素进行访问。</strong></p><h4 id="3-3-2-顺序栈的表示和实现">3.3.2 顺序栈的表示和实现</h4><ul><li>有与栈本身就是线性表，于是栈也有顺序存储和链式存储两种实现方式。<ul><li>栈的顺序存储——顺序栈</li><li>栈的链式存储——链栈</li></ul></li></ul><p>​存储方式：同一般线性表的顺序存储结构完全相同，利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。</p><p>​设<strong>top</strong>指针，指栈顶元素在顺序栈中的位置</p><p>​另设<strong>base</strong>指针，指示栈底元素在顺序栈中的位置</p><ul><li><p><em>但是为了方便操作，通常<strong>top</strong>指示真正的栈顶<strong>元素之上的下标地址</strong></em></p><p><em>另外用<strong>stacksize</strong>表示栈可使用的最大容量</em></p><p><strong>空栈</strong>：base==top是栈空的标志</p><p><strong>栈满</strong>：top-base==stacksize</p><p>​栈满时的处理方法：</p><ol><li><strong>报错</strong>，返回操作系统。</li><li><strong>分配更大的空间</strong></li></ol></li><li><p>使用数组作为顺序栈存储方式的特点：</p><ul><li>简单、方便、但容易产生溢出<ul><li><strong>上溢</strong>：栈已满，又要压入元素</li><li><strong>下溢</strong>：栈已空，还要弹出元素</li></ul></li><li>一般<strong>上溢是错误</strong>，<strong>下溢是</strong>一种<strong>结束条件</strong>，即问题处理已结束</li></ul></li><li><p>顺序栈的表示</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXSIZE 100</span></span><br><span class="line">typeldef <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    SElemType *base;</span><br><span class="line">    SElemType *top;</span><br><span class="line">    <span class="type">int</span> stacksize;</span><br><span class="line">&#125;SqStack;</span><br></pre></td></tr></table></figure></li></ul><h5 id="3-3-2-1-顺序栈的初始化">3.3.2.1 顺序栈的初始化</h5><figure class="highlight c"><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">Status <span class="title function_">lnitStack</span><span class="params">(SqStack &amp;S)</span>&#123;<span class="comment">//构造空栈</span></span><br><span class="line">S.base=new SElemType[MAXSIZE]; </span><br><span class="line">    <span class="comment">//或S.base=(SElemType*)malloc(MAXSIZE*sizeof（SElemType));</span></span><br><span class="line"><span class="keyword">if</span> (!S.base)<span class="built_in">exit</span> (OVERFLOW);<span class="comment">//存储分配失败</span></span><br><span class="line">S.top=S.base;<span class="comment">//栈顶指针等于栈底指针</span></span><br><span class="line">S.stacksize=MAXSIZE;</span><br><span class="line">    <span class="keyword">return</span> OK；</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：顺序栈判断栈是否为空</strong></p><figure class="highlight c"><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">Status StackEmpty（SqStack S)&#123;</span><br><span class="line">    <span class="comment">//若栈为空，返回TRUE否则返回FALSE</span></span><br><span class="line">    <span class="keyword">if</span>(S.top==S.base)</span><br><span class="line">        <span class="keyword">return</span> TRUE;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> FALSE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：求顺序栈长度</strong></p><figure class="highlight c"><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="type">int</span> <span class="title function_">StackLength</span><span class="params">(SqStack S)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> S.top-S.base;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：清空顺序栈</strong></p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">ClearStack</span><span class="params">(SqStack S)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (S.base)S.top=S.base;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：销毁顺序栈</strong></p><figure class="highlight c"><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">Status <span class="title function_">DestroyStack</span><span class="params">(SqStack &amp;S)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(S.base)&#123;</span><br><span class="line">        delete S.base;</span><br><span class="line">        S.stacksize=<span class="number">0</span>:</span><br><span class="line">        S.base=S.top=<span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-2-2-顺序栈的入栈">3.3.2.2 顺序栈的入栈</h5><figure class="highlight c"><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">Status <span class="title function_">Push</span><span class="params">(SqStack &amp;S.SElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>（S.top-S.base==S.stacksize)</span><br><span class="line">        <span class="keyword">return</span> ERROR;</span><br><span class="line">    *S.top++=e:</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-2-3-顺序栈的出栈">3.3.2.3 顺序栈的出栈</h5><figure class="highlight c"><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">Status <span class="title function_">Pop</span><span class="params">(SqStack &amp;S,SElemType &amp;e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(S.top==S.base)</span><br><span class="line">        <span class="keyword">return</span> ERROR;</span><br><span class="line">    e=*--S.top;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-3-3-链栈的表示和实现">3.3.3 链栈的表示和实现</h4><ul><li>链栈是<strong>运算受限</strong>的单链表，只能在<strong>链表头部</strong>进行操作</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">StackNode</span>&#123;</span></span><br><span class="line">    SElemType data；</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">StackNode</span> *<span class="title">next</span>；  </span></span><br><span class="line"><span class="class">&#125;<span class="title">StackNode</span>,*<span class="title">LinkStack</span>;</span></span><br><span class="line">LinkStack S;</span><br></pre></td></tr></table></figure><ol><li>链表头结点就是栈顶</li><li>不需要头结点</li><li>基本不存在栈满的情况</li><li>空栈相当于头指针指向空</li><li>插入和删除仅在栈顶处执行</li></ol><h5 id="3-3-3-1-链栈的初始化">3.3.3.1 链栈的初始化</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> lnit <span class="title function_">Stack</span><span class="params">(LinkStack &amp;S)</span>&#123;</span><br><span class="line">    S=NuLL;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法补充：判断链栈是否为空</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">StackEmptyl</span><span class="params">(LinkStack S)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(S==<span class="literal">NULL</span>)<span class="keyword">return</span> TRUE;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">return</span> FALSE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-3-2-链栈的入栈">3.3.3.2 链栈的入栈</h5><figure class="highlight c"><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">Status <span class="title function_">Push</span><span class="params">(LinkStack &amp;S,SElemType e)</span>&#123;</span><br><span class="line">    p=new StackNode;<span class="comment">//生成新结点p</span></span><br><span class="line">    p-&gt;data=e;<span class="comment">//将新结点数据域置为e</span></span><br><span class="line">    p-&gt;nextg=S;<span class="comment">//将新结点插入栈顶</span></span><br><span class="line">    S=p;<span class="comment">//修改栈顶指针</span></span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>相当于头插法</em></p><h5 id="3-3-3-3-链栈的出栈">3.3.3.3 链栈的出栈</h5><figure class="highlight c"><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">Status Pop（LinkStack &amp;S,SElemType &amp;e)&#123;</span><br><span class="line">    <span class="keyword">if</span>(S==<span class="literal">NULL</span>)<span class="keyword">return</span> ERROR;</span><br><span class="line">    e=S-&gt;data;</span><br><span class="line">    p=S;</span><br><span class="line">    S=S-&gt;next;</span><br><span class="line">    delete p;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-3-4-取栈顶元素">3.3.3.4 取栈顶元素</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">SElemType <span class="title function_">GetTop</span><span class="params">(LinkStack S)</span>&#123;</span><br><span class="line"><span class="keyword">if</span>(S!=<span class="literal">NULL</span>)</span><br><span class="line">    <span class="keyword">return</span> S-&gt;data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4栈与递归">3.4栈与递归</h3><ul><li><p>递归的定义</p><ul><li><p>若一个对象<strong>部分地包含它自己</strong>，或用他<strong>自己给自己定义</strong>，则称这个对象是递归的</p></li><li><p>若一个过程<strong>直接地或间接地调用自己</strong>，则称这个过程是递归的过程。</p><ul><li><p>例如：递归求n的阶乘</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="title function_">Fact</span><span class="params">(<span class="type">long</span> n)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(n==<span class="number">0</span>)<span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> n*Fact(n<span class="number">-1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li><li><p>以下三种情况尝尝用到递归方法</p><ul><li>递归定义的数学函数<ul><li>阶乘函数</li><li>二阶fibonaci数列</li></ul></li><li>具有递归特性的数据结构<ul><li>二叉树</li><li>广义表</li></ul></li><li>可递归求解的问题<ul><li>迷宫问题</li><li>hanoi塔问题</li></ul></li></ul></li><li><p>递归问题——用分治法求解</p><ul><li>分治法：对于一个较为复杂的问题，能够分解成几个相对简单的且解法相同或类似的子问题来求解</li><li>必备的三个条件<ol><li>能够将一个问题转变成一个新的问题，而新的问题与原问题解法相同或类同，不同的仅是处理的对象，且这些处理对象是变化有规律的</li><li>可以通过上述转化而使问题简化</li><li>必须有一个明确的递归出口或递归的边 界</li></ol></li></ul></li></ul></li></ul><p>分治法求解递归问题算法的一般形式：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364ff2c458853aef6c8948.png"/></div></div><ul><li><p>函数调用过程</p><p>调用前，系统完成：</p><ol><li>将<strong>实参，返回地址</strong>等传递给被调用函数</li><li>为被调用函数的<strong>局部变量</strong>分配存储区</li><li>将控制转移到被调用函数的<strong>入口</strong></li></ol><p>调用后，系统完成：</p><ol><li>保存被调用函数的计算<strong>结果</strong></li><li>释放被调用函数的<strong>数据区</strong></li><li>依照被调用函数保存的<strong>返回地址</strong>将控制转移到调用函数</li></ol></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364ff2c458853aef6c89c3.png"/></div></div><ul><li><p>递归的优缺点</p><ul><li><p>优点：结构清晰，程序易读</p></li><li><p>缺点：每次调用都要生成工作记录，保存状态信息，入栈；返回时要出栈，恢复状态信息。时间开销大</p><p>递归-&gt;非递归</p><ol><li>尾递归、单项递归-&gt;循环结构</li><li>自用栈模拟系统的运行时栈</li></ol></li></ul></li></ul><h3 id="3-5-队列的表示和操作的实现">3.5 队列的表示和操作的实现</h3><h4 id="3-5-1-队列的抽象数据类型定义">3.5.1 队列的抽象数据类型定义</h4><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364ff2c458853aef6c8a37.png"/></div></div><h4 id="3-5-2-队列的顺序表示和实现">3.5.2 队列的顺序表示和实现</h4><ul><li><p>队列的物理存储可以用顺序存储结构，也可以用链式存储结构。相应的队列的存储方式也分为两种，即<strong>顺序队列</strong>和<strong>链式队列</strong></p></li><li><p><strong>队列的顺序表示</strong>——用一维数组base[MAXQSIZE]</p></li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXQSIZE 100<span class="comment">//最大队列长度</span></span></span><br><span class="line">Typedef <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    QElemType *base;<span class="comment">//初始化的动态分配存储空间</span></span><br><span class="line">    <span class="type">int</span> front;<span class="comment">//头指针</span></span><br><span class="line">    <span class="type">int</span> rear;<span class="comment">//尾指针</span></span><br><span class="line">&#125;SqQueue;</span><br></pre></td></tr></table></figure><ul><li><p>若front=0且rear=MAXQSIZE时，再入队——真溢出</p></li><li><p>若front不为0，rear=MAXQSIZE时，再入队——假溢出，此时队列中还有空间可以存放</p><ul><li><p>解决假溢出可以吧队的空间想象成一个循环的表</p><ul><li><p>引入循环队列</p><ol><li><p>实现方法：利用%运算</p></li><li><p>插入元素：Q.base[Q.rear]=x;</p><p>​                   Q.rear=（Q.rear+1)%MAXQSIZE;</p></li><li><p>删除元素：x=Q.base[s.front]</p><p>​                   Q.front=(Q.front+1)%MAXQSIZE</p></li><li><p>循环队列:循环使用为队列分配的存储空间。</p></li></ol></li></ul></li></ul></li><li><p>因为队空队满都是：front==rear</p><ul><li><p>所以我们常常另设一个标志来区别队空队满、另设一个变量，记录元素个数或者少用一个元素空间。</p></li><li><p>队满时——少用一个元素空间</p><ul><li>队空：front==rear</li><li>队满：（rear+1)%MAXQSIZE</li></ul></li></ul></li></ul><p>3.5.2.1 队列的初始化</p><figure class="highlight c"><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">Status <span class="title function_">InitQueue</span><span class="params">(SqQueue &amp;Q)</span>&#123;</span><br><span class="line">    Q.base=(QElemType*)</span><br><span class="line">    <span class="built_in">malloc</span>(MAXQSIZE*<span class="keyword">sizeof</span>（QElemType)）；</span><br><span class="line">    <span class="keyword">if</span>（!Q.base)<span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">    Q.fornt=Q.rear=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.2.2 求队列的长度</p><figure class="highlight c"><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="type">int</span> <span class="title function_">QueueLength</span><span class="params">(SqQueue Q)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span>(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.2.3 循环队列入队</p><figure class="highlight c"><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">Status <span class="title function_">EnQueue</span><span class="params">(SqQueue &amp;Q，QElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>((Q.rear+<span class="number">1</span>)%MAXQSIZE==Q.front) <span class="keyword">return</span> ERROR;</span><br><span class="line">    Q.base[Q.rear]=e;</span><br><span class="line">    Q.rear=(Q.rear+<span class="number">1</span>)%MAXQSIZE;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.2.4 循环队列出队</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">DeQueue</span><span class="params">(SqQueue &amp;Q,QElemType &amp;e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.rear==Q.front) <span class="keyword">return</span> ERROR;</span><br><span class="line">    e=Q.base[Q.front];</span><br><span class="line">    Q.front=(Q.front+<span class="number">1</span>)%MAXQSIZE;</span><br><span class="line">    <span class="keyword">return</span> OK；</span><br></pre></td></tr></table></figure><p>3.5.2.5 取队头元素</p><figure class="highlight c"><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">Status <span class="title function_">GetHead</span><span class="params">(SqQueue Q)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.front!==Q.rear);</span><br><span class="line">    <span class="keyword">return</span> Q.base[Q.front];<span class="comment">//返回队头指针元素的值，队头指针不变</span></span><br></pre></td></tr></table></figure><p>3.5.3 链队——队列的链式表示和实现</p><p>若用户无法估计所用队列长度，则宜采用链队列</p><ul><li>链队的类型定义</li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXQSIZE 100 </span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Qnode</span>&#123;</span></span><br><span class="line">    QElemType data;</span><br><span class="line">    stuct Qnode *next;</span><br><span class="line">&#125;QNode,*QueuePtr<span class="comment">//ptr是pointer的缩写</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    QueuePtr front;</span><br><span class="line">    QueuePtr rear;</span><br><span class="line">&#125;LinkQueue;</span><br></pre></td></tr></table></figure><p>3.5.3.1 链队的初始化</p><figure class="highlight c"><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">Status <span class="title function_">InitQueue</span> <span class="params">(LinkQueue &amp;Q)</span>&#123;</span><br><span class="line">    Q.front=Q.rear=new QNode;</span><br><span class="line">    <span class="keyword">if</span>(!Q.front)<span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">    Q.front-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.2 链队列的销毁</p><figure class="highlight c"><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">Status <span class="title function_">DestroyQueue</span><span class="params">(LinkQueue &amp;Q)</span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(Q.front)&#123;</span><br><span class="line">        p=Q.front-&gt;next;</span><br><span class="line">        ferr(Q.front);</span><br><span class="line">        Q.front=p;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.3 链队列的入队</p><figure class="highlight c"><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">Status <span class="title function_">EnQueue</span> <span class="params">(LinkQueue &amp;Q, QElemType e)</span>&#123;</span><br><span class="line">    p=new QNode;</span><br><span class="line">    p-&gt;data=e;</span><br><span class="line">    p-&gt;next=<span class="literal">NULL</span>; Q. rear-&gt;next=p;</span><br><span class="line">    Q.rear=p;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.4 链队列的出队</p><figure class="highlight c"><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">Status <span class="title function_">DeQueue</span><span class="params">(LinkQueue &amp;Q,QElemType &amp;e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.front==Q.rear) <span class="keyword">return</span> ERROR;</span><br><span class="line">    p=Q.front-&gt;next;</span><br><span class="line">    e=p-&gt;data;</span><br><span class="line">    Q.front-&gt;next=p-&gt;next;</span><br><span class="line">    <span class="keyword">if</span>(Q.rear==p) Q.rear=Q.front;</span><br><span class="line">    delete p;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.5 链队列取队头</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">SElemType GetHead&#123;LinkQueue Q)&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.front!=Q.rear)</span><br><span class="line">        <span class="keyword">return</span> Q.front-&gt;next-&gt;data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-串、数组和广义表">4 串、数组和广义表</h2><h3 id="4-1-串的定义——几个术语">4.1 串的定义——几个术语</h3><ul><li>串(String)——由零个或多个任意<strong>字符组成的有限序列</strong></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65364ff2c458853aef6c8a66.png"/></div></div><ul><li><p>子串：一个串中任意个连续字符组成的子序列（含空串）称为该串的子串</p><ul><li>真子串，<strong>不包含自身</strong>的所有子串</li></ul></li><li><p>字符位置：字符在序列中的序号为该字符在串中的位置</p></li><li><p>子串位置：子串第一个字符在主串中的位置</p></li><li><p>空格串：由一个或多个空格组成的串，与空串不同</p><ul><li>计算他们的长度时，要包括空格</li></ul></li><li><p>串相等：当且仅当两个串的长度相等并且<strong>各个对应位置上的字符都相同</strong>时，两个串才是<strong>相等</strong>的。</p></li></ul><h3 id="4-2-案例引入">4.2 案例引入</h3><p>​“<em>串的应用非常广泛，计算机上的非数值处理的对象大部分是字符串数据，例如：文字编辑、符号处理、各种信息处理系统等等。</em>”</p><h4 id="4-2-1-病毒感染检测">4.2.1 病毒感染检测</h4><p>研究者将人的DNA和病毒DNA均表示成由一些字母组成的字符串序列 （字符串的匹配）</p><h3 id="4-3-串的类型定义、存储结构及运算">4.3 串的类型定义、存储结构及运算</h3><h4 id="4-3-1-串的抽象类型定义">4.3.1 串的抽象类型定义</h4><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536544ac458853aef78dcca.png"/></div></div><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536544bc458853aef78dddb.png"/></div></div><h4 id="4-3-2-串的存储结构">4.3.2 串的存储结构</h4><h5 id="4-3-2-1-串的顺序存储结构">4.3.2.1 串的顺序存储结构</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAXLEN 255</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">char</span> ch[MAXLEN+<span class="number">1</span>];</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SString;</span><br></pre></td></tr></table></figure><p><em>顺序存储结构用的更多一些</em></p><h5 id="4-3-2-2-串的链式存储结构">4.3.2.2 串的链式存储结构</h5><p>如果是普通的链式存储的话，虽然方便操作，但是存储密度较低，所以在这里，我们将多个字符存放在一个结点中，以克服其缺点。</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536544bc458853aef78de1f.png"/></div></div><ul><li><strong>我们称之为——块链结构</strong></li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> CHUNKSIZE BO <span class="comment">//可由用户定义的块大小</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Chunk</span>&#123;</span></span><br><span class="line">    <span class="type">char</span> ch [CHUNKSIZE];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Chunk</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;)Chunk;</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span> </span><br><span class="line">Chunk *head,*tail; <span class="comment">//串的头指针和尾指针</span></span><br><span class="line"><span class="type">int</span> length; <span class="comment">//串的当前长度</span></span><br><span class="line">) LString;<span class="comment">//字符串的块链结构</span></span><br></pre></td></tr></table></figure><h4 id="4-3-3-串的模式匹配算法">4.3.3 串的模式匹配算法</h4><ul><li>算法目的：<ul><li>确定主串中所含子串（模式串）第一次出现的位置（定位）</li></ul></li><li>算法应用：<ul><li>搜索引擎、拼写检查、语言翻译、数据压缩</li></ul></li><li>算法种类：<ul><li>BF算法（暴力破解，朴素的、穷举的）</li><li>KMP算法（速度快）</li></ul></li></ul><h5 id="4-3-3-1-BF算法">4.3.3.1 BF算法</h5><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536544bc458853aef78deac.png"/></div></div><ul><li><strong>匹配失败：</strong><ul><li><strong>$\large i=(i-j+1)+1=i-j+2$</strong>(<em><u><strong>i和j走的路程是一样的，i-j表示退回原位，而模式串是从下标1开始计算的，则要+1，这个时候才是真正退回了原位，再+1，就是原位的下一位了</strong></u></em>)</li><li><strong>$\large j=i$</strong></li></ul></li><li><strong>匹配成功：</strong><ul><li><strong>$\large i=7$</strong></li><li><strong>$\large j=5$</strong></li><li><strong>返回$\large i-t.length=3$</strong></li></ul></li><li>index(S,P,pos)<ul><li>将主串的第pos个字符和模式串的第一个字符比较。</li><li>若相等，继续这个比较后续字符</li><li>若不等，就从主串的下一字符起，重新逐个比较</li><li>直到发现一个连续子串序列与模式串相等，返回值为S中与T匹配的子序列第一个字符的序号即匹配成功</li><li>否则匹配失败，返回0</li></ul></li></ul><figure class="highlight c"><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="type">int</span> <span class="title function_">Index_BF</span><span class="params">(SString S,SString T)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i=<span class="number">1</span>,j=<span class="number">1</span>:</span><br><span class="line">    <span class="keyword">while</span>(i&lt;=S.length &amp;&amp; j&lt;=T.length)&#123;</span><br><span class="line">        <span class="keyword">if</span>(s.ch[i]==t.ch[j])&#123;++i.++j:&#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;i=i-j+<span class="number">2</span>;j=<span class="number">1</span>;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (j&gt;=T.length)<span class="keyword">return</span> i-T.length<span class="comment">//返回匹配的第一个字符的下标</span></span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><ul><li>BF算法的时间复杂度：</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536544bc458853aef78df4d.png"/></div></div><h5 id="4-3-3-2-KMP算法">4.3.3.2 KMP算法</h5><p>​这种改进算法是由 <strong>K</strong>nuth 、 <strong>M</strong>orris 和 <strong>P</strong>ratt 同时设计实现的， 因此简称 <strong>KMP</strong> 算法。</p><ul><li><p>利用已经<strong>部分匹配</strong>的结果而加快模式串的滑动速度</p></li><li><p>且主串S的指针<strong>i不必回溯</strong>！可提速到<strong>O(N+M</strong>)！</p><p>为此，定义next[j]函数，表明当模式中第j个字符与主串中相应字符“失配”时，在模式中需重新和主串中该字符进行比较的字符的位置</p></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653654edc458853aef7af6ba.png"/></div></div><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653654edc458853aef7af717.png"/></div></div><p><em><strong>第四个这里是1因为比较的时候不包括末尾元素但包括首元素</strong></em></p><p>$\large k-1=最大公共前后缀的长度$</p><figure class="highlight c"><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="type">int</span> <span class="title function_">Index_KMP</span><span class="params">(SString S,SString T,<span class="type">int</span> pos)</span>&#123;</span><br><span class="line">    i=pos;j=l;</span><br><span class="line">    <span class="keyword">while</span> (i&lt;S.length &amp;&amp; j&lt;T.length) &#123;</span><br><span class="line">        <span class="keyword">if</span> </span><br><span class="line">            &lt;j==o||s.ch[i]==T.ch[j])&#123; ++i;++j;&#125;<span class="comment">//继续比较后继字符</span></span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            j=next[i]; <span class="comment">//i不变，j后退</span></span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">if</span> </span><br><span class="line">        (j&gt;T.length)<span class="keyword">return</span> i-T.length;<span class="comment">//匹配成功</span></span><br><span class="line"><span class="keyword">else</span> </span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>; <span class="comment">//匹配失败</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>根据next值求nextval值的方法</strong></p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653654edc458853aef7af801.png"/></div></div><h3 id="4-4-数组">4.4 数组</h3><p><em><strong>栈和队列是操作受限的线性表。串是内容受限的线性表。数组和广义表是对线性表的推广。</strong></em></p><h4 id="4-4-1-数组的类型定义">4.4.1 数组的类型定义</h4><ul><li><p>数组：按一定格式排列起来，具有<strong>相同类型</strong>的数据元素的集合</p></li><li><p>一维数组：若线性表中的数据元素为非结构的简单元素，一维数组</p><ul><li><p>一维数组的逻辑结构：线性结构。定长的线性表。</p></li><li><p>声明格式： 数据类型 变量名称[长度]</p><ul><li>例：int num[5]={0,1,2,3,4};</li></ul></li></ul></li><li><p>二维数组：若一维数组中的数据元素又是一维数组结构，则称为二维数组。</p><ul><li><p>二维数组的逻辑结构：</p><ul><li>非线性结构：每一个数据元素即在一个行表中，又在一个行列中</li><li>线性结构：该线性表的每个数据元素也是一个定长的线性表</li></ul></li><li><p>声明格式：数据类型 变量名称[行数][列数];</p><ul><li>例：int num[5] [8]</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653654eec458853aef7af85e.png"/></div></div></li></ul></li><li><p>n维数组：若n-1维数组中的元素又是一个一维数组结构，则称作n维数组。</p></li><li><p><em><strong>结论：线性表结构是数组结构的一个特例，二数组结构又是线性表结构的拓展</strong></em></p></li><li><p>数组特点：<strong>结构固定</strong>——定义后，维数和维界不再改变。</p></li><li><p>数组基本操作：除了结构的<strong>初始化</strong>和<strong>销毁</strong>之外，只有<strong>取元素</strong>和<strong>修改元素值</strong>的操作。</p></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653654eec458853aef7af949.png"/></div></div><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536554ec458853aef7bf400.png"/></div></div><h4 id="4-4-2-数组的顺序存储">4.4.2 数组的顺序存储</h4><ul><li>二维数组可有两种存储方式<ul><li>以行序为主序</li><li>以列序为主序</li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536554ec458853aef7bf456.png"/></div></div><h4 id="4-4-3-特殊矩阵的压缩存储">4.4.3 特殊矩阵的压缩存储</h4><p>矩阵：一个由M*N个元素排成的m行n列的表。</p><p>$\large \begin{pmatrix}a_{11}&amp;a_{12}&amp;a_{13}&amp;……&amp;a_{1n}\a_{21}&amp;a_{22}&amp;a_{23}&amp;……&amp;a_{2n}\……&amp;……&amp;……&amp;……&amp;……&amp;\a_{m1}&amp;a_{m2}&amp;a_{m3}&amp;……&amp;a_{mn}\end{pmatrix}$</p><p>矩阵的常规存储：</p><ol><li>将矩阵描述为一个二维数组。</li></ol><p>矩阵的常规存储的特点：</p><ol><li><p>可以对其元素进行随机存取 ；</p></li><li><p>矩阵运算非常简单；存储密度为1</p></li></ol><p>不适宜常规存储的矩阵：</p><ol><li>值相同的元素很多且呈某种规律分布；</li><li>零元素多</li></ol><p><strong>矩阵的压缩存储:</strong></p><ol><li><p>为多个相同的非零元素只分配一个存储空间；</p></li><li><p>对零元素不分配空间</p></li><li><p>一些特殊的矩阵可以压缩，如：<strong>对称矩阵，对角矩阵，三角矩阵，稀疏矩阵</strong>(矩阵中非零元素的个数较少，不到百分之五)等。</p><ol><li><p>对称矩阵：在N*N的矩阵中满足**$\large a_{ij}=a_{ji}$<strong>,存储方法，<strong>只存储上（或下）三角的数据元素</strong>，共占用</strong>N(N+1)/2**个元素空间</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536554ec458853aef7bf4e2.png"/></div></div><p>下标k的求法为$(i-1)+(j-1)$,其中$a_{n-1}$前面行前面有$n-1$个数求和之后得到$\dfrac {n(n-1)}{2}$，列前面没有</p></li><li><p>三角矩阵：对角线一下或以上的数据元素全为常数C</p><ol><li><p>存储方法：重复元素c共享一个元素存储空间，共占用n（n+1)/2+1个元素空间：sa[1…n（n+1)/2+1]</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536554ec458853aef7bf549.png"/></div></div></li></ol></li><li><p>对角矩阵（带状矩阵）</p><ol><li>特点：在n*n的方阵中，所有的<strong>非零元素</strong>都集中在以<strong>主对角线</strong>为中心的带状区域中，区域外的值全为零 ，则称为对角矩阵，常见的有三对角矩阵，五对角矩阵，起对角矩阵。（几条对角线有数值就是几对角矩阵）</li><li>存储方法：以对角线的顺序存储</li></ol></li><li><p>稀疏矩阵</p><ol><li>特点：设在m*n的矩阵中又t个非零元素，且t所占总体百分比小于五时称为稀疏矩阵</li><li>压缩存储原则：存各非零元素的值，行列位置和矩阵的行列数<ol><li>例： $\large （i,j,a_{ij}）$</li><li>三元组顺序表又称有序的双下标<ol><li>优点：便于进行依行顺序处理的矩阵运算。</li><li>缺点：不能随机存取，若按行号存取需要从头开始进行查找</li></ol></li></ol></li></ol></li></ol></li></ol><h4 id="4-5-广义表">4.5 广义表</h4><ul><li>广义表：又称列表Lists是n&gt;=0个元素$a_0,a_1,……，a_{n-1}$的有限序列，其中没一个$a_i$或者是<strong>原子</strong>，或者是一个<strong>广义表</strong></li><li>广义表通常记作：LS=$a_0,a_1,……，a_{n}$<ul><li>其中LS为表名，n为表长，$a_i$为表的元素</li><li>通常用<strong>大写字母</strong>表示<strong>广义表</strong>，<strong>小写字母</strong>表示<strong>原子</strong></li><li><strong>表头</strong>：若LS非空，则其<strong>第一个</strong>元素$a_1$就是表头<ul><li>记作 $head（LS）=a_1$</li></ul></li><li><strong>表尾</strong>，除了表头的<strong>其他元素</strong>组成的表<ul><li>记作$tail（LS）=（a_2,……，a_{n}）$</li></ul></li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536554ec458853aef7bf60b.png"/></div></div><ol><li><p>广义表中的数据元素有相对次序；一个直接前驱和一个直接后继</p></li><li><p>广义表的<strong>长度</strong>定义为<strong>最外层</strong>所包含元素的个数</p></li><li><p>广义表的<strong>深度</strong>定义为该广义表<strong>展开后所含括号的重数</strong></p></li><li><p>广义表可以为其他广义表共享</p></li><li><p>广义表可以是一个递归的表</p></li><li><p>广义表是<strong>多层次</strong>结构，广义表的元素可以是单元素，也饿可以是子表，而子表的元素还可以是子表类似<strong>二叉树</strong>。</p></li></ol><p><em><strong>广义表可以看做线性表的推广，线性表是广义表的特例</strong></em></p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653655fcc458853aef7dcba2.png"/></div></div><p><em>函数本身带括号，所以最后的c应该外面加一个括号</em></p><h2 id="5-树和二叉树">5 树和二叉树</h2><p>​ <em>树形结构与线性结构的不同就是，<strong>线性结构</strong>的前驱和后继是<strong>一对一</strong>的，树状结构属于<strong>非线性结构</strong>，有多个后继。前驱与后继是<strong>一对n</strong>的</em></p><h3 id="5-1-树和二叉树的定义">5.1 树和二叉树的定义</h3><h4 id="5-1-1-树的定义">5.1.1 树的定义</h4><ul><li>树是个n个结点的<strong>有限集</strong>：<ul><li>若n=0，称为<strong>空树</strong>；</li><li>若n&gt;0，则他满足如下两个条件：<ol><li>有且仅有一个特定的称为<strong>根</strong>的结点</li><li>其余结点可分为吗（m&gt;=0）个互不相交的有限集T1,T2,T3。。</li></ol></li><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653655fcc458853aef7dcc56.png"/></div></div></li></ul></li></ul><h4 id="5-1-2-树的基本术语">5.1.2 树的基本术语</h4><ul><li><strong>根结点</strong>：非空树中无前驱结点的结点。</li><li>结点的<strong>度</strong>：结点拥有的子树数。</li><li>树的度：树内各结点的度的最大值。</li><li>结点的子树的根称为该结点的<strong>孩子</strong>，该结点称为该孩子的<strong>双亲</strong>，还有结点的<strong>祖先</strong>，结点的<strong>子孙</strong>就不展开描述了。</li><li>有序树：树中结点的各子树从左至右有次序（最左边的为第一个孩子）。</li><li>无序树：树中结点的各子树无次序。</li><li>森林：是m棵互不相交的树的集合<ul><li>把根结点删除树就变成了森林</li><li>一棵树可以看成是一个特殊的森林，<em><strong>树一定是森林，森林不一定是树</strong></em></li><li>给森林中各子树加上一个双亲节点，森林就变成了树</li></ul></li></ul><h4 id="5-1-3-二叉树的定义">5.1.3 二叉树的定义</h4><p>二叉树的结构最简单，规律性最强，可以证明所有树都能转化为<strong>唯一对应的二叉树</strong>，不失一般性。</p><ul><li><p>二叉树：是n个结点的有限集，他或者是空集或者是由<strong>一个根结点</strong>及两棵<strong>互不相交</strong>的分别称作这个根的<strong>左子树</strong>和<strong>右子树</strong>的二叉树组成</p><ul><li><p>特点：每个节点最多两个孩子</p></li><li><p>子树有左右之分(即使只有一棵子树也进行区分)，<strong>次序不能颠倒</strong></p></li><li><p>二叉树可以是空集合，根可以有空的左子树或空的右子树。</p></li><li><p>具有三个结点的树可能有几种形态？</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653655fcc458853aef7dccf7.png"/></div></div></li></ul></li></ul><h3 id="5-2-案例引入">5.2 案例引入</h3><h4 id="5-2-1-数据压缩问题">5.2.1 数据压缩问题</h4><p>将数据文件转换成由0、1组成的二进制串，称之为编码</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653655fcc458853aef7dcd94.png"/></div></div><p><em>具体的方法到哈夫曼树和哈弗曼编码那里学习，这里暂且按下不表</em></p><h4 id="5-2-2-利用二叉树求解表达式的值">5.2.2 利用二叉树求解表达式的值</h4><ul><li>以二叉树表示表达式的递归定义如下：<ol><li>若表达式为数或简单变量，则相应二叉树中仅有一个根结点，其数据域存放该表达式信息；</li><li>若表达式为 “ 第一操作数 运算符 第二操作数” 的形式， 则相应的二叉树中以<strong>左子树表示第一操作数</strong>，<strong>右子树表示第二操作数</strong>，根结点存放<strong>运算符</strong>，其中错作数本身又为表达式。</li></ol></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653655fcc458853aef7dcb63.png"/></div></div><p>具体实现，我们会在[5.8 案例分析与实现](####5.8 案例分析与实现)进行讲解</p><h3 id="5-3-树和二叉树的抽象数据类型定义">5.3 树和二叉树的抽象数据类型定义</h3> <div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536567ec458853aef7f3efc.png"/></div></div> <div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536567ec458853aef7f3f6c.png"/></div></div><h3 id="5-4-二叉树的性质和存储结构">5.4 二叉树的性质和存储结构</h3><ol><li><p>在二叉树的第i层上之多有$\large 2^{i-1}$个结点</p></li><li><p>深度为k的二叉树<strong>至多</strong>有$\large 2^k-1$个结点</p></li><li><p>对任何一棵二叉树T，如果其叶子树为$\large n_0$,度为2的结点数为$\large n_2$，则$\large n_0=n_2+1$</p><ul><li>总边数$\large B=n-1   =   B=n_2<em>2+n_1</em>1$</li><li>总结点数为$\large n=n_2<em>2+n_1</em>1+1$  又   $n=n_2+n_1+n_0$</li><li>推导出$\large n_0=n_2+1$</li></ul></li><li><p>具有n个结点的完全二叉树的深度为[log_2n]（向下取整）+1</p><p>性质4表明了完全二叉树<strong>结点数n</strong>与完全二叉树<strong>深度k</strong>之间的关系</p></li><li><p>如果对一棵有 n个结点的完全二叉树，其结点按层序编号（从第 1 层到第[log2n]+ 1 层， 每层从左到右）， 则对任一结点(n=&gt;i&gt;=1), 有如果i=1，无双亲，如果i&gt;1.则其双亲是结点[$\large i/2$]。</p><ol><li>性质5表明了完全二叉树中<strong>双亲结点</strong>编号与<strong>孩子结点</strong>编号之间的关系</li></ol></li></ol><ul><li>两种特殊形式的二叉树<ul><li>满二叉树：一颗深度为k且有$\large 2^k-1$个结点的二叉树称为<strong>满二叉树</strong></li><li>特点：<ul><li>每一层上的结点数</li><li>编号从上到下，从左到右</li></ul></li><li>完全二叉树：深度为K的， 有n个结点的二叉树， 当且仅当其每一个结点都与深度为K的<strong>满二叉树中编号</strong>从1至n的结点<strong>一一对应</strong>时， 称之为<strong>完全二叉树</strong>。<ul><li>在满二叉树中，从<strong>最后一个结点开始</strong>，<strong>连续</strong>去掉<strong>任意</strong>个结点，都是一棵完全二叉树</li><li>叶子只能分布在最大的两层上</li><li>对任意一结点，如果其右子树的最大层次为i，则其左子树的最大层次必为i或i-1</li></ul></li></ul></li></ul><h3 id="5-5-遍历二叉树和线索二叉树">5.5 遍历二叉树和线索二叉树</h3><h4 id="5-5-1-遍历二叉树">5.5.1 遍历二叉树</h4><ul><li><p>遍历的定义——顺着某一条搜索路径巡访二叉树中的结点，使得每个结点均被访问一次，而且仅被访问一次</p><ul><li>“访问”可以看做对结点作各种处理</li></ul></li><li><p>遍历的目的——得到树中所有结点的一个线性排列</p></li><li><p>遍历的用途——它是树结构插入、删除、修改、查找和排序运算的前提，是二叉树一切运算的基础和核心</p></li><li><p>遍历的算法</p><ul><li><p>DLR——先（根）序遍历</p></li><li><p>LDR——中（根）序遍历 【从最左边 开始左根右】，可以吧空序都先画出来，然后再开始遍历</p></li><li><p>LRD——后（根）序遍历</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/6536567ec458853aef7f410d.png"/></div></div></li><li><p>例题——已知中序序列和后续序列求二叉树</p></li><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365703c458853aef80dfdd.png"/></div></div></li><li><p><em><strong>前后确定根，中序辨左右</strong></em>      重点</p></li></ul></li><li><p>二叉树先序遍历算法</p><figure class="highlight c"><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">Status PreOrderTiraverse（BiTree T）&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>)<span class="keyword">return</span> OK;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        visit(T);<span class="comment">//访问根结点</span></span><br><span class="line">        PreOrderTiraverse(T-&gt;lchild);<span class="comment">//递归遍历左子树，递归调用</span></span><br><span class="line">        PreOrderTiraverse(T-&gt;rchild);<span class="comment">//递归遍历右子树</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365703c458853aef80e13c.png"/></div></div><p><strong>其中涉及到了递归调用的逐层返回</strong></p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365703c458853aef80e1d4.png"/></div></div></li></ul></li></ul><p>如果去掉输出语句，从递归的角度看，三种三发事完全相同的，只是访问的十几不同</p><p>时间复杂度是3n，其中3是常数可以去掉，所以O(n)</p><h5 id="5-5-1-1-遍历二叉树的非递归算法">5.5.1.1 遍历二叉树的非递归算法</h5><ul><li><p>二叉树<strong>中序遍历的非递归算法的关键</strong>：在中序遍历过某结点的整个左子树后，如何找到该结点的<strong>根</strong>以及<strong>右子树</strong></p></li><li><p>基本思想</p><ol><li><p>建立一个<strong>栈</strong></p></li><li><p>根结点<strong>进栈</strong>，遍历<strong>左子树</strong></p></li><li><p>根结点<strong>出栈</strong>，输出根结点，遍历<strong>右子树</strong></p></li><li><pre><code class="language-c">Status InOrderTraverse(BiTree T)&#123;    BiTree p;InitStack(S);P=T;    while(p||!StackEmpty(S))&#123;//StackEmpty（S），S空返回true,非空返回false        if(p)&#123;Push (S,p); p=p-&gt;lchild;&#125;//push（S，p）是把p值入栈S        else &#123;Pop(S,q); printf(&quot;%c&quot;, q-&gt;data);//pop（S，q）是出栈值给q             p=q-&gt;rchild;&#125;    &#125;//while&#125;<figure class="highlight clean"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">     ***走到D时，左右子树都为空返回时，q会指向A应为栈内一开始就存放了A，然后再访问A的右子树***</span><br><span class="line"></span><br><span class="line">##### <span class="number">5.5</span><span class="number">.1</span><span class="number">.2</span> 二叉树的层次遍历</span><br><span class="line"></span><br><span class="line">- 队列类型定义</span><br><span class="line"></span><br><span class="line">  ```c</span><br><span class="line">  typedef struct&#123;</span><br><span class="line">      BTNode data[MaxSize];</span><br><span class="line">      int front,rear;</span><br><span class="line">  &#125;SqQueue;</span><br></pre></td></tr></table></figure></code></pre></li></ol><p>二叉树层次遍历算法：</p><figure class="highlight c"><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="type">void</span> <span class="title function_">LevelOrder</span><span class="params">(BTNode*b)</span>&#123;</span><br><span class="line">    BTNode *p;   SqQueue *qu;</span><br><span class="line">    InitQueue(qu);<span class="comment">//初始化队列</span></span><br><span class="line">    enQueue(qu, b);<span class="comment">//根结点指针进入队列</span></span><br><span class="line">    <span class="keyword">while</span>(!QueueEmpty（qu)）&#123;</span><br><span class="line">        deQueue(qu,p);<span class="comment">//出队结点p</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%c&quot;</span>,p-&gt;data);<span class="comment">//访问结点p</span></span><br><span class="line">        <span class="keyword">if</span>(p-&gt;lchild!=<span class="literal">NULL</span>)enQueue(qu,p-&gt;lchild);<span class="comment">//有左孩子时将其进队</span></span><br><span class="line">        <span class="keyword">if</span>(p-&gt;rchild!=<span class="literal">NULL</span>)enQueue(qu,p-&gt;rchild);<span class="comment">//有右孩子时将其进队</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-5-1-3-遍历算法的应用——二叉树的建立">5.5.1.3 遍历算法的应用——二叉树的建立</h5><ul><li>算法描述：</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Statys <span class="title function_">CreatBiTree</span><span class="params">(BiTree &amp;T)</span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(&amp;ch);</span><br><span class="line">    <span class="keyword">if</span>(ch==<span class="string">&quot;#&quot;</span>)T=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(!(BiTNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(BiTNode)))<span class="comment">//分配一块儿结点空间</span></span><br><span class="line">            <span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">        T-&gt;data=ch;<span class="comment">//生成根结点</span></span><br><span class="line">        CreateBiTree(T-&gt;lchild);</span><br><span class="line">        CreateBiTree(T-&gt;rchild);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-4-复制二叉树">5.5.1.4 复制二叉树</h5><figure class="highlight c"><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="type">int</span> <span class="title function_">Copy</span><span class="params">(BiTree T,BiTree &amp;NewT)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>)&#123;</span><br><span class="line">        NewT==<span class="literal">NULL</span>;<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        NewT=new BiTNode;</span><br><span class="line">        NewT-&gt;data=T-&gt;data;</span><br><span class="line">        copy(T-&gt;lchild, NewT-&gt;lchild);</span><br><span class="line">        copy(T-&gt;rchild, NewT-&gt;rchild);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-5-计算二叉树的深度">5.5.1.5 计算二叉树的深度</h5><ul><li>如果是空树，则深度为0</li></ul><p>​否则，递归计算左子树深度记为m，右子树深度为n，二叉树深度则为n和m的较大者加1</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">Depth</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        m=Depth &#123;T-&gt;lchild); </span><br><span class="line">n=Depth &#123;T-&gt;rchild);</span><br><span class="line"><span class="keyword">if</span>&#123;m&gt;n) <span class="keyword">return</span>&#123;m+l);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">return</span>(n+l);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-6-计算二叉树的结点总数">5.5.1.6 计算二叉树的结点总数</h5><ul><li>如果是空树，则结点个数为0</li></ul><p>​否则，结点个数为左子树的结点个数+右子树的结点个数再加1</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">NodeCount</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (T==<span class="literal">NULL</span>) </span><br><span class="line">        <span class="keyword">return</span> O;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> NodeCount (T-&gt;lchild) +Node Count (T-&gt;rchild) + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-7-计算二叉树叶子结点数">5.5.1.7 计算二叉树叶子结点数</h5><figure class="highlight c"><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="type">int</span> <span class="title function_">leafcount</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T=<span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>:</span><br><span class="line">    <span class="keyword">if</span>(T-&gt;lchild==<span class="literal">NULL</span>&amp;&amp;T-&gt;rchild==<span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> LeafCount(T-&gt;lchild)+LeafCount(T-&gt;rchild;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5-5-2-线索二叉树">5.5.2 线索二叉树</h4><ul><li><p>利用二叉链表中的空指针域：</p><p>如果某个节点的左孩子为空，则将空的<strong>左孩子</strong>指针域改为<strong>指向其前驱</strong>；如果<strong>右孩子</strong>为空，则其指针域改为<strong>指向其后继</strong>————将<strong>改变指向的指针</strong>称为**“线索”<strong>，加上了线索的二叉树称为</strong>线索二叉树**</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365704c458853aef80e25f.png"/></div></div></li><li><p>为了区分是指向孩子的指针还是指向前驱或者后继的指针，对二叉链表中每个结点增设两个标志域ltag和rtag，这样结点的结构就为【lchild | ltag | data | rtag | rchild 】并约定：</p><ul><li><p>ltag=0/rtag=0  指针指向孩子</p></li><li><p>ltag=1/rtag=1  指针指向前驱/后继</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365704c458853aef80e2d8.png"/></div></div></li></ul></li></ul><h3 id="5-6-树和森林">5.6 树和森林</h3><p><em><strong>本节将讨论树的表示及其遍历操作，并建立森林与二叉树的对应关系。</strong></em></p><h4 id="5-6-1-树的存储结构">5.6.1 树的存储结构</h4><h5 id="5-6-6-1-双亲表示法">5,6,6,1 双亲表示法</h5><p>实现：定义结构数组存放树的结点，每个结点含两个域；</p><ul><li><p>数据域：存放结点本身信息</p></li><li><p>双亲域：指示本结点的双亲结点在数组中的位置</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365806c458853aef83b774.png"/></div></div></li><li><p>C语言的类型描述：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">PTNode</span>&#123;</span></span><br><span class="line">    TElemType data;</span><br><span class="line">    <span class="type">int</span> parent;</span><br><span class="line">&#125;PTNode;</span><br></pre></td></tr></table></figure></li><li><p>树结构：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAX_TREE_SIZE 100</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    PTNode nodes[MAX_TREE_SIZE];</span><br><span class="line">    <span class="type">int</span> r,n;<span class="comment">//根结点的位置和结点个数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-6-1-2-孩子链表">5.6.1.2 孩子链表</h5><ul><li><p>把每个结点的孩子结点排列起来。看成一个线性表，用单链表存储</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365806c458853aef83b7d2.png"/></div></div></li><li><p>C语言描述：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">CTNode</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> child;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">CTNode</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;*Childptr;</span><br></pre></td></tr></table></figure></li><li><p>树结构</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    CTBox nodes[MAX_TREE_SIZE];</span><br><span class="line">    <span class="type">int</span> n,r;</span><br><span class="line">&#125;CTree;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-6-1-3-孩子兄弟表示法">5.6.1.3 孩子兄弟表示法</h5><ul><li><p>C语言描述</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">CSNode</span>&#123;</span></span><br><span class="line">    ElemType data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">CSNode</span> *<span class="title">firstchild</span>,*<span class="title">nextsibling</span>;</span></span><br><span class="line">&#125;CSNode,*CSTree;</span><br></pre></td></tr></table></figure><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365806c458853aef83b832.png"/></div></div></li></ul><h4 id="5-6-2-树与二叉树的转换">5.6.2 树与二叉树的转换</h4><ul><li><p>将树转换为二叉树进行处理，利用二叉树的算法来实现对树的操作。</p></li><li><p>由于树和二叉树都可以用二叉链表做存储结构，则以二叉链表做媒介可以导出树与二叉树之间的一个对应关系。</p></li><li><p>给定一棵树可以找到唯一的一颗二叉树与之对应</p><ul><li><p>加线：在兄弟之间加一条线，</p></li><li><p>抹线：对每个结点，除了其左孩子外，去除其余孩子之间的关系</p></li><li><p>旋转：以树根结点为轴心，将整树顺时针旋转45°</p><p><strong>树变二叉树：兄弟相连留长子。<em>其中根结点的油茶树一定为空</em></strong></p></li></ul></li><li><p>反之可以把二叉树转换成树</p><ul><li><p>加线：若p结点是双亲节点的左孩子，则将p的右孩子，右孩子的右孩子……沿分支找到所有右孩子，都与p的双亲用线连起来</p></li><li><p>抹线：抹掉原二叉树中双亲与右孩子之间的连线</p></li><li><p>调整：将结点按层次排列，形成树结构。</p><p><strong>二叉树变树：左孩右右连双亲，去掉原来右孩线</strong></p></li></ul></li></ul><h4 id="5-6-3-森林与二叉树的转换">5.6.3 森林与二叉树的转换</h4><ul><li><p>森林转换成二叉树（二叉树与多棵树之间的关系）</p><ul><li><p>将个棵树分别转换成二叉树</p></li><li><p>将每棵树的根结点用线相连</p></li><li><p>以第一课树根结点为二叉树的根，再以根结点为轴心，顺时针旋转，构成二叉树型结构</p><p><strong>森林变二叉树：树变二叉根相连</strong></p></li></ul></li><li><p>二叉树转换成森林</p><ul><li><p>抹线：将二叉树中根结点与其右孩子连线，及沿有分支搜索到的所有右孩子之间的连线抹掉，使之变成孤立的二叉树</p></li><li><p>还原：将孤立的二叉树还原成树</p><p><strong>二叉树变森林：去掉全部右孩线，孤立二叉再还原</strong></p></li></ul></li></ul><h4 id="5-6-4-树和森林的遍历">5.6.4 树和森林的遍历</h4><h5 id="5-6-4-1树的遍历">5.6.4.1树的遍历</h5><ul><li>先根遍历：若树不为空，则先访问根结点，然后一次先根遍历各棵子树。</li><li>后根遍历：若树不为空，则先依次后根遍历各棵子树，然后访问根结点。</li><li>层次遍历：若树不为空，则自上而下自左至右访问树种每个结点。</li></ul><h5 id="5-6-4-2-森林的遍历">5.6.4.2 森林的遍历</h5><ul><li><p>将森林看作由三部分构成：</p><ol><li>森林中第一棵树的根结点；</li><li>森林中第一棵树的子树森林；</li><li>森林中其他树构成的森林。</li></ol></li><li><p>先序遍历：若森林不为空，则依次从左至右对森林中的每一棵树进行先根遍历</p></li><li><p>中序遍历：若森林不为空，则</p><ol><li><p>中序遍历森林中第一棵树的子树森林；</p></li><li><p>访问森林中第一棵树的根结点；</p></li><li><p>中序遍历森林中（除第一棵树之外）其余树构成的森林。</p><p>即：依次从左至右对森林中每一个棵树进行后根遍历</p></li></ol></li></ul><h3 id="5-7哈夫曼树的基本概念">5.7哈夫曼树的基本概念</h3><h4 id="5-7-1-哈夫曼树的基本概念">5.7.1 哈夫曼树的基本概念</h4><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365807c458853aef83b94d.png"/></div></div><p><em><strong>效率最高的判别树，就是哈夫曼树（也称最优二叉树）</strong></em></p><ul><li><p><strong>路径</strong>：从树中一个结点到另一个结点之间的<strong>分支</strong>构成这两个结点间的路径</p></li><li><p><strong>结点的路径长度</strong>：两结点间路径上的<strong>分支数</strong>。</p></li><li><p><strong>树的路径长度</strong>：从<strong>树根</strong>到没一个结点的<strong>路径长度之和</strong>。<strong>记作：TL</strong></p><ul><li>结点数目相同的二叉树中，完全二叉树是路径长度最短的二叉树，但路径长度最短的不一定是完全二叉树</li></ul></li><li><p><strong>权</strong>(weight)：将树中结点赋给一个有着某种含义的数值，则这个数值称为该<strong>结点的权。</strong></p></li><li><p><strong>结点的带权路径长度</strong>：从<strong>根</strong>结点到该结点之间的<strong>路径长度</strong>与该结点的<strong>权</strong>的<strong>乘积</strong>。</p></li><li><p><strong>树的带权路径长度</strong>：树中所有<strong>叶子</strong>结点的<strong>带权路径长度之和</strong>。记作：$\large WPL=\sum\limits_{k=1}^nw_kl_K$(weighted path length)</p></li><li><p>哈夫曼树：<strong>最优树</strong>/<strong>最优二叉树</strong>（<strong>带权路径长度最短的树</strong>）</p><ul><li>因为构造这种树的算法是由哈夫曼教授于<strong>1952</strong>年提出的，所以被称为<strong>哈夫曼树</strong>，相应的算法称为<strong>哈夫曼算法</strong></li><li>满二叉树不一定是哈夫曼树</li><li>哈夫曼树中全越大的叶子离根越近</li><li>具有相同带权结点的哈夫曼树不唯一</li></ul></li><li><p>贪心算法：构造哈夫曼树时首先选择权值小的叶子结点</p></li></ul><h4 id="5-7-2-哈夫曼树的构造算法">5.7.2 哈夫曼树的构造算法</h4><ul><li><p>构造过程</p><ol><li><p>根据给定的n个权值{$w_1.w_2.w_3,……,w_n$}，构造n棵只有根结点的二叉树，这n棵二叉树构成一个森林F。</p></li><li><p>在森林 F 中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树，且置新的二叉树的根结点的权值为其左 、右子树上根结点的权值之和</p></li><li><p>在森林F中删除这两棵树，同时将新得到的二叉树加入森林中。</p></li><li><p>重复2和3的步骤，直到森林中只有一棵树为止，这棵树即为哈夫曼树。</p></li></ol></li><li><p>口诀</p><ol><li>构造森林全是根</li><li>选用两小造新树</li><li>删除两小添新人</li><li>重复二三剩单根</li></ol><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365807c458853aef83b9d8.png"/></div></div><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365893c458853aef852cda.png"/></div></div></li><li><p>总结</p><ol><li><p>在哈夫曼树算法中，初始时有n棵二叉树，要经过n-1次合并最终形成哈夫曼树</p></li><li><p>经过n_1次合并产生n-1个新结点，且这n-1个新结点都是具有两个孩子的分支节点</p><p>可见：哈夫曼树中共有n+n-1=2n-1个结点，且其所有的分支结点的度均不为1.</p></li></ol></li><li><p>哈夫曼树构造算法的实现</p></li><li><p>采用顺序存储结构——一维结构数组 HuffmanTree H;</p></li><li><p>结点类型定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> weight;</span><br><span class="line">    <span class="type">int</span> parent,lch,rch;</span><br><span class="line">&#125;HTNode,*HuffmanTree;</span><br></pre></td></tr></table></figure><table><thead><tr><th>哈夫曼树中结点下标i</th><th>weight</th><th>parent</th><th>lch</th><th>rch</th></tr></thead><tbody><tr><td>1</td><td></td><td></td><td></td><td></td></tr><tr><td>2</td><td></td><td></td><td></td><td></td></tr><tr><td>3</td><td></td><td></td><td></td><td></td></tr><tr><td>4</td><td></td><td></td><td></td><td></td></tr><tr><td>……</td><td></td><td></td><td></td><td></td></tr><tr><td>2n_1</td><td></td><td></td><td></td><td></td></tr></tbody></table></li><li><p>构造哈夫曼树</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreateHuffmanTree</span><span class="params">(HuffmanTree &amp;HT,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(n&lt;=l) <span class="keyword">return</span>;</span><br><span class="line">m=<span class="number">2</span>*n-l;<span class="comment">//数组一共2n-1个元素</span></span><br><span class="line">HT=new HTNode[m+l);<span class="comment">//0号单元未用，HT[m]表示根结点</span></span><br><span class="line">    <span class="keyword">for</span>(i=l;i&lt;=m;++i)&#123;<span class="comment">//将2n-1个元素的lch/rch/parent置为0</span></span><br><span class="line">        HT[i].parent=O;HT[i].lchild=O;HT[i].rchild=O;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(i=l;i&lt;=n;++i&#125;<span class="built_in">cin</span>&gt;&gt;HT[i] .weight;<span class="comment">//输入前n个元素weight的值，初始化结束</span></span><br><span class="line">    <span class="keyword">for</span> (i=n+l;i&lt;=m;++i)</span><br><span class="line">        Select (HT,i<span class="number">-1</span>,sl,s2);</span><br><span class="line">        HT[sl] .parent=i;HT[s2] .parent=i;</span><br><span class="line">        HT[i] .lchild=sl;HT [i]. rchild=s2;</span><br><span class="line">        HT[i] .weight=HT[sl] .weight+HT[s2] .weight;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>例题：</p><p>巳知 w = (5,29,7,8,14,23,3,11), 利用算法 5.10 试构造一棵哈夫曼树， 计算树的带权路径长度， 并给出其构造过程中存储结构HT的初始状态和终结状态。</p><table><thead><tr><th>哈夫曼树中结点下标i</th><th>weight</th><th>parent</th><th>lch</th><th>rch</th></tr></thead><tbody><tr><td>1</td><td><strong>5</strong></td><td>9</td><td>0</td><td>0</td></tr><tr><td>2</td><td><strong>29</strong></td><td>14</td><td>0</td><td>0</td></tr><tr><td>3</td><td><strong>7</strong></td><td>10</td><td>0</td><td>0</td></tr><tr><td>4</td><td><strong>8</strong></td><td>10</td><td>0</td><td>0</td></tr><tr><td>5</td><td><strong>14</strong></td><td>12</td><td>0</td><td>0</td></tr><tr><td>6</td><td><strong>23</strong></td><td>13</td><td>0</td><td>0</td></tr><tr><td>7</td><td><strong>3</strong></td><td>9</td><td>0</td><td>0</td></tr><tr><td>8</td><td><strong>11</strong></td><td>11</td><td>0</td><td>0</td></tr><tr><td>9</td><td><strong>8</strong></td><td>11</td><td>1</td><td>7</td></tr><tr><td>10</td><td><strong>15</strong></td><td>12</td><td>3</td><td>4</td></tr><tr><td>11</td><td><strong>19</strong></td><td>13</td><td>8</td><td>9</td></tr><tr><td>12</td><td><strong>29</strong></td><td>14</td><td>5</td><td>10</td></tr><tr><td>13</td><td><strong>42</strong></td><td>15</td><td>6</td><td>11</td></tr><tr><td>14</td><td><strong>58</strong></td><td>15</td><td>2</td><td>12</td></tr><tr><td>15</td><td><strong>100</strong></td><td>0</td><td>13</td><td>14</td></tr></tbody></table></li></ul><h4 id="5-7-3-哈夫曼编码">5.7.3 哈夫曼编码</h4><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreatHuffmanCode</span><span class="params">(HuffmanTree HT,HuffmanCode &amp;HC,<span class="type">int</span> n)</span>&#123;<span class="comment">//从叶子到根逆向求每个字符的哈夫曼编码，存储在编码表HC中</span></span><br><span class="line">    HC=nuw <span class="type">char</span>*[n+<span class="number">1</span>];<span class="comment">//分配n个字符编码的头指针矢量</span></span><br><span class="line">    cd=new <span class="type">char</span>[n];<span class="comment">//分配临时存放编码的动态数组空间</span></span><br><span class="line">    cd[n<span class="number">-1</span>]=<span class="string">&#x27;\0&#x27;</span>;<span class="comment">//编码结束符</span></span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;=n;++i)&#123;<span class="comment">//逐个字符求哈夫曼编码</span></span><br><span class="line">        start=n<span class="number">-1</span>;c=i;f=HT[i].parent;</span><br><span class="line">        <span class="keyword">while</span>(f!=<span class="number">0</span>)&#123;<span class="comment">//从叶子结点开始向上回溯，直到根节点</span></span><br><span class="line">            --start;<span class="comment">//回溯一次start向前指一个位置</span></span><br><span class="line">            <span class="keyword">if</span></span><br><span class="line">                (HT[f].lchild==c)cd[start]=<span class="string">&#x27;0&#x27;</span>;<span class="comment">//结点c是f的左孩子，则生成代码0</span></span><br><span class="line">            <span class="keyword">else</span> cd[start]=<span class="string">&#x27;1&#x27;</span>;<span class="comment">//结点c是f的右孩子，则生成代码1</span></span><br><span class="line">                c=f;f=HT[f].parent;<span class="comment">//继续向上回溯</span></span><br><span class="line">        &#125;<span class="comment">//求出第i个字符的编码</span></span><br><span class="line">        HC[i]=new <span class="type">char</span>[n-start];<span class="comment">//为第i个字符串编码分配空间</span></span><br><span class="line">        <span class="built_in">strcpy</span>(HC[i],&amp;cd[start]);<span class="comment">//将求得的编码从临时空间cd复制到HC的当前行中</span></span><br><span class="line">    &#125;</span><br><span class="line">   delete cd;<span class="comment">//释放临时空间</span></span><br><span class="line">&#125;<span class="comment">//creatHuffmanCode</span></span><br></pre></td></tr></table></figure><ol><li>编码：<ol><li>输入各字符及其权值</li><li>构造哈夫曼树——HT[i]  n+n-1个</li><li>进行哈夫曼编码——HC[i]</li><li>查HC[i]，得到各字符的哈夫曼编码</li></ol></li><li>解码：<ol><li>构造哈夫曼树</li><li>依次读入二进制码</li><li>读入0，则走向左孩子，读入1，则走向右孩子</li><li>一旦到达某叶子时，即可译出字符</li><li>然后再从根出发继续译码，直到结束</li></ol></li></ol><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365893c458853aef852d84.png"/></div></div><h2 id="6-图">6 图</h2><p><em><strong>图是一种比线性表和树更为复杂的数据结构。</strong></em></p><h3 id="6-1-图的定义和基本术语">6.1 图的定义和基本术语</h3><h4 id="6-1-1-图的定义">6.1.1 图的定义</h4><ul><li><p>图：G=（V.E）</p><ul><li>V：顶点（数据元素）的<strong>有穷非空</strong>集合；</li><li>E：边的<strong>有穷</strong>集合</li></ul></li><li><p>无向图：每条边都是无方向的</p></li><li><p>有向图：每条边都是有方向的</p></li><li><p>完全图：任意两个点都有一条边相连</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365894c458853aef852dbd.png"/></div></div></li><li><p>稀疏图：有很少边或弧的图（$e&lt;nlog_2n$）</p></li><li><p>稠密图：有较多边或弧的图</p></li></ul><h4 id="6-1-2-图的基本术语">6.1.2 图的基本术语</h4><ul><li><p>网，边/弧带权的图</p></li><li><p>邻接：有边/弧相连的两个顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）,则称为$v_i和v_j$互为<strong>邻接点</strong></li><li>存在&lt;$v_i,v_j$&gt;，则称$v_i$邻接到$v_j$,$v_j$邻接于$v_i$,</li></ul></li><li><p>关联（依附）：边/弧与顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）/&lt;$v_i,v_j$&gt;，则称该边/弧关联于$v_i$和$v_j$</li></ul></li><li><p>顶点的度：与该顶点相关联的边的数目，记为TD（v）</p><ul><li><p>在<strong>有向图</strong>中，顶点的度等于该顶点的<strong>入度</strong>和<strong>出度</strong>之和。</p></li><li><p>顶点<strong>v的入度</strong>是以v为终点的有向边的条数，记作ID（v）</p></li><li><p>顶点<strong>v的出度</strong>是以v为始点的有向边的条数，记作OD（v）</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365894c458853aef852e6d.png"/></div></div></li></ul></li><li><p>路径：连续的边构成的顶点序列</p></li><li><p>路径长度：路径上边或弧的数目/权值之和</p></li><li><p>回路（环）：第一个顶点和最后一个顶点相同的路径</p></li><li><p>简单路径：除路径起点和终点可以相同外，其余顶点均不相同的路径</p></li><li><p>简单回路（环）：除路径起点和终点相同外，其余顶点均不相同的路径</p></li><li><p>连通图（强连通图）：在无（有）向图G=(V,{E})中，若对任意两个顶点v、u都存在从v到u的路径，则称G是<strong>连通图</strong>（强连通图）</p></li><li><p>权与网：</p><ul><li>图中边或弧所具有的相关数称为<strong>权</strong>，表明从一个顶点到另一个顶点的距离或耗费。</li><li>带权的图称为<strong>网</strong></li></ul></li><li><p>子图：设有两个图$G=（V，{E}）、G_1=(V_1,{E_1})$,若$V_1属于V，E_1属于E$则称$G_1是G$的子图</p></li><li><p>连通分量（强连通分量）</p><ul><li><p>无向图G的<strong>极大联通子图</strong>称为G的<strong>连通分量</strong></p><ul><li>极大连通子图意思是：该子图是G联通子图，将G的任何不在该子图汇总的顶点加入，子图不再连通</li></ul></li><li><p>有向图G的<strong>极大连通子图</strong>称为G的<strong>强连通分量</strong></p><ul><li>极大强连通子图意思是: 该子图是G的强连通子图，将D的任何不在该子图中的顶点加入，子图不再是强连通的。</li></ul></li><li><p>极小联通子图：该子图是G的连通子图，在该子图中删除任何一条边，联通子图不再连通</p></li><li><p>生成树:包含无向图G所有顶点的极小连通子图</p></li><li><p>生成森林:对非连通图，由各个连通分量的生成树的集合</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365894c458853aef852f10.png"/></div></div></li></ul></li></ul><h3 id="6-2-案例引入">6.2 案例引入</h3><h4 id="6-2-1-六度空间理论">6.2.1 六度空间理论</h4><p><strong>理论又称作六度分隔论 (Six Degrees of Separation)。六度空间理论是20世纪60年代由美国的心理学家米格兰姆(Stanley Milgram)提出的，理论指出：“<em>你和任何一个陌生人之间所间隔的人不会超过六个</em>”</strong></p><h3 id="6-3-图的类型定义">6.3 图的类型定义</h3><p>图的抽象数据类型定义如下：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT Graph&#123;</span><br><span class="line">    数据对象V：具有相同特性的数据元素的集合，称为顶点集。</span><br><span class="line">    数据关系R：R&#123;VR&#125;</span><br><span class="line">    VR=&#123;&lt;V,W&gt;|&lt;V,W&gt;|V,W属于V^p(v,w)&#125;， &lt;v, w&gt;表示从v到w的弧，P (v, w)定义了弧&lt;v, w&gt;的信息</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>基本操作：</p><ul><li><p>CreateGraph{&amp;G,V,VR}</p><p>初始条件：V是图的顶点集，VR是图中弧的集合。</p><p>操作结果：按V和VR的定义<strong>构造图G</strong>。</p></li><li><p>DFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行<strong>深度优先遍历</strong>，在遍历过程中对每个顶点访问一次。</p></li><li><p>BFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行广度优先遍历，在遍历过程中对每个顶点访问一次.</p></li></ul></li></ul><h3 id="6-4-图的存储结构">6.4 图的存储结构</h3><ul><li>图的逻辑结构：多对多<ul><li>图没有顺序存储结构，可以借助二维数组来表示元素间的关系</li></ul></li><li>数组表示法（邻接矩阵）</li><li>链式存储结构：<ul><li>多重链表<ul><li>邻接表</li><li>邻接多重表</li><li>十字链表</li></ul></li></ul></li><li>重点介绍：<ul><li><strong>邻接矩阵</strong>（数组）表示法</li><li><strong>邻接表</strong>（链式）表示法</li></ul></li></ul><h4 id="6-4-1-邻接矩阵">6.4.1 邻接矩阵</h4><ul><li><p>数组（邻接矩阵）表示法</p><ul><li><p>建立一个顶点表（记录各个顶点信息）和一个邻接矩阵（表示各个顶点之间关系）。</p><ul><li><p>设图A=（V，E)有n个顶点，则</p></li><li><p>顶点表Vexs[n]</p><table><thead><tr><th>i</th><th>1</th><th>2</th><th>3</th><th>……</th><th>n-1</th></tr></thead><tbody><tr><td>Vexs[n]</td><td>$V_1$</td><td>$V_2$</td><td>$V_3$</td><td>……</td><td>$V_n$</td></tr></tbody></table></li><li><p>图的邻接矩阵是一个二维数组$A.arcs[n][n]$,定义为：</p><h6 id="A-arcs-i-j-quad-begin-cases-1-如果-v-i-v-j-或-v-i-v-j-in-E-0，反之-end-cases">$A.arcs[i][j]=\quad\begin{cases}1,如果&lt;v_i,v_j&gt;或(v_i,v_j)\in E\0，反之\end{cases}$</h6></li><li><p>无向图的邻接矩阵表示法：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653658f3c458853aef862e73.png"/></div></div><ol><li>无向图的邻接矩阵是<strong>对称</strong>的</li><li>顶点i的度=第i行（列）中1的个数；</li><li>完全图的邻接矩阵中，对角元素为0，其余1.</li></ol></li><li><p>有向图的邻接矩阵表示法：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653658f3c458853aef862eb2.png"/></div></div><ol><li>有向图的邻接矩阵<strong>可能是不对称</strong>的</li><li>顶点的<strong>出度=第i行元素之和</strong></li><li>顶点的<strong>入度=第i列元素之和</strong></li><li>顶点的<strong>度=第i行和列的元素之和</strong></li></ol></li><li><p>网（即有权图）的邻接矩阵表示法</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653658f3c458853aef862f13.png"/></div></div></li><li><p>邻接矩阵的存储表示：用<strong>两个数组</strong>分别存储<strong>顶点表和邻接矩阵</strong></p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> Maxint 32767<span class="comment">//表示极大值</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MVNum 100 <span class="comment">//最大顶点数</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">char</span> VerTexType;<span class="comment">//设顶点的数据类型为字符型</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> ArcType;<span class="comment">//假设边的权值类型为整形</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>｛ </span></span><br><span class="line"><span class="class">    <span class="title">VerTexType</span> <span class="title">vexs</span> [<span class="title">MVNum</span>];</span><span class="comment">//顶点表</span></span><br><span class="line">ArcType arcs[MVNum) [MVNum];<span class="comment">//邻接矩阵</span></span><br><span class="line">    <span class="type">int</span> vexnum,arcnum;<span class="comment">//图的当前点数和边数</span></span><br><span class="line">&#125;AMGraph;</span><br></pre></td></tr></table></figure></li></ul></li><li><p>采用邻接矩阵表示法创建无向网</p><ol><li>输入总顶点数和总边数。</li><li>依次输入点的信息存入顶点表中。</li><li>初始化邻接矩阵， 使每个权值初始化为极大值。</li><li>构造邻接矩阵。</li></ol><figure class="highlight c"><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">Status <span class="title function_">CreateUDN</span><span class="params">(AMGraph &amp;G)</span>&#123;<span class="comment">//创建无向网G</span></span><br><span class="line">    <span class="built_in">cin</span>&gt;&gt;G.vexnum&gt;&gt;G.arcnum;<span class="comment">//输入总顶点数，总边数</span></span><br><span class="line">    <span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)</span><br><span class="line"><span class="built_in">cin</span>&gt;&gt;G.vexs[i];<span class="comment">//依次输入点的信息</span></span><br><span class="line"><span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)<span class="comment">//初始化邻接矩阵呢</span></span><br><span class="line"><span class="keyword">for</span> (j=<span class="number">0</span>; j&lt;G.vexnum;++j) </span><br><span class="line">G.arcs[i][j]=Maxint;<span class="comment">//变得权值均置为最大</span></span><br><span class="line"><span class="keyword">for</span>(k=O;k&lt;G.arcnum;++k)&#123;<span class="comment">//构造邻接矩阵</span></span><br><span class="line">        <span class="built_in">cin</span>&gt;&gt;vl&gt;&gt;v2&gt;&gt;w;<span class="comment">//输入一条边所依附的顶点及边的权值</span></span><br><span class="line">        i=LocateVex(G,vl);j=LocateVex(G,v2);<span class="comment">//确定V1和V2在G中的位置</span></span><br><span class="line">        G.arcs[i][j]=w;<span class="comment">//边&lt;v1,v2&gt;的权值置为w</span></span><br><span class="line">        G.arcs[j][i]=G.arcs[i][j];<span class="comment">//置&lt;v1,v2&gt;的对称边&lt;v2,v1&gt;的权值为w</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>补充算法：在图中查找顶点：</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">LocateVex</span><span class="params">(AMGraph G,VertexType u)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;G.vexnum;++i)</span><br><span class="line">        <span class="keyword">if</span>(u==G.vexs[i]) <span class="keyword">return</span> i;</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></li></ul></li><li><p>邻接矩阵的优点</p><ul><li>直观简单好理解</li><li>方便检查任意一对顶点间是否存在边</li><li>方便找任一顶点的所有“邻接点”</li><li>方便计算任一顶点的“度”</li></ul></li><li><p>邻接矩阵的缺点</p><ul><li>不便于增加和删除顶点</li><li>浪费空间——存稀疏图有大量无效元素<ul><li>对稠密图来说还是很合算的</li></ul></li><li>浪费时间——统计稠密图中一共有多少条边</li></ul></li></ul><h4 id="6-4-2-邻接表">6.4.2 邻接表</h4><ol><li><p>无向图邻接表表示法（链式）</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653658f4c458853aef862f60.png"/></div></div><ul><li><strong>顶点</strong>：按编号顺序将顶点数据存储在一维数组中；</li><li><strong>关联同一顶点的边</strong>（以顶点为尾的弧）：<ul><li>用线性<strong>链表</strong>存储</li></ul></li><li>特点：<ul><li>邻接表不唯一</li><li>若无向图中有n个顶点、e条边，则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图</li><li>无向图中顶点$V_i$的度为第i个单链表中的结点数</li></ul></li></ul></li><li><p>有向图邻接表表示法</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/653658f4c458853aef862f8a.png"/></div></div><ul><li>特点：<ul><li>顶点$V_i$的<strong>出度</strong>为第i个单链表中的结点个数</li><li>顶点的入度为整个链表中领接点域值是i-1的结点个数</li></ul></li></ul></li><li><p>图的邻接表存储表示：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">VNode</span>&#123;</span></span><br><span class="line">    VerTexType data;<span class="comment">//顶点信息</span></span><br><span class="line">    ArcNode *firstarc;<span class="comment">//指向第一条依附该顶点的边的指针</span></span><br><span class="line">&#125;VNode,AdjList[MVNum]<span class="comment">//AdjList表示邻接表类型</span></span><br></pre></td></tr></table></figure><p>弧（边）的结点结构    adjvex | nextarc | info</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MVNum 100<span class="comment">//最大顶点数</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">ArcNode</span>&#123;</span><span class="comment">//边结点</span></span><br><span class="line">    <span class="type">int</span> adjvex;<span class="comment">//该边所指向的顶点的位置</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ArcNode</span> * <span class="title">nextarc</span>;</span><span class="comment">//指向下一条边的指针</span></span><br><span class="line">    OtherInfo info;<span class="comment">//和边相关的信息</span></span><br><span class="line">&#125;ARcNode;</span><br></pre></td></tr></table></figure><p>图的结构定义：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    AdjList vertices;<span class="comment">//vertices——vertex的复数</span></span><br><span class="line">    <span class="type">int</span> vexnum,arcnum;<span class="comment">//图的当前顶点数和弧数</span></span><br><span class="line">&#125;ALGraph;</span><br></pre></td></tr></table></figure><p>邻接表操作举例说明：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365a1bc458853aef89c4c6.png"/></div></div></li><li><p>无向图的邻接表表示 p118</p></li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">CreateUDG</span><span class="params">(ALGraph &amp;G)</span> &#123;<span class="comment">//采用邻接表表示法， 创建无向图 G</span></span><br><span class="line"><span class="built_in">cin</span>&gt;&gt;G.vexnum&gt;&gt;G.arcnum;<span class="comment">//输入总顶点数，总边数</span></span><br><span class="line"><span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)&#123;<span class="comment">//输入各点，构造表头结点表</span></span><br><span class="line"><span class="built_in">cin</span>»G.vertices[i) .data;<span class="comment">//输入顶点值</span></span><br><span class="line">G.vertices[i) .firstarc=<span class="literal">NULL</span>;&#125;<span class="comment">//初始化表头结点的指针域</span></span><br><span class="line"><span class="keyword">for</span>(k=O;k&lt;G.arcnum;++k)&#123;<span class="comment">//输入各边，构造邻接表</span></span><br><span class="line">        <span class="built_in">cin</span>&gt;&gt;vl&gt;&gt;v2;<span class="comment">//输入一条边依附的两个顶点</span></span><br><span class="line">        i=LocateVex(G,vl);</span><br><span class="line">        j=LocateVex(G,v2);</span><br><span class="line">        pl=new ArcNode;<span class="comment">//生成一个新的边结点*p1</span></span><br><span class="line">        pl-&gt;adjvex=j;<span class="comment">//邻接点序号为j</span></span><br><span class="line">        pl-&gt;nextarc=G.vertices[i].firstarc;</span><br><span class="line">        G.vertices[i].firstarc=pl;<span class="comment">//将新结点*p1插入顶点vi的边表头部（头插法）</span></span><br><span class="line">        p2=new ArcNode;<span class="comment">//生成另一个对称的新的边结点*p2</span></span><br><span class="line">        p2-&gt;adjvex=i;<span class="comment">//邻接点序号为i</span></span><br><span class="line">        p2-&gt;nextarc=G.vertices[j].firstarc;</span><br><span class="line">        G.vertices[j].firstarc=p2;<span class="comment">//将心结点*p2插入顶点vj的边表头部</span></span><br><span class="line">    &#125;</span><br><span class="line">     <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>无向图邻接表特点：</p><ol><li>方便找任一顶点的所有“邻接点”</li><li>节约稀疏图的空间<ol><li>需要n个头指针和+2e个结点（每个结点至少2个域）</li></ol></li><li>方便计算任一顶点的“度”<ol><li>对无向图：是的</li><li>对有向图：只能计算“出度”需要构造“逆邻接表”来方便计算“入度”</li></ol></li><li>不方便检查任意一对顶点间是否存在边</li></ol></li><li><p>邻接矩阵与邻接表表示法的关系</p><ol><li>联系：邻接表中每个链表对应于接短阵中的一行，链表中结点个数等于一行中非零元素的个数。</li><li>区别：<ol><li>对于任一确定的无向图，邻接矩阵是唯一的 (行列号与顶点编号一致)，但邻接表不唯一 (链接次序与顶点编号无关)</li><li>邻接矩阵的空间复杂度为$O（n^2)$,而邻接表的空间复杂度为$O(n+e)$。(e是出于$0到n^2$之间的复杂变量)</li></ol></li><li>用途：邻接矩阵多用于稠密图；而邻接表多用于稀疏图</li></ol></li></ul><h4 id="6-4-3-十字链表">6.4.3 十字链表</h4><p>有向图：</p> <div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365950c458853aef872e88.png"/></div></div><ul><li>顶点结点：</li></ul><table><thead><tr><th>data</th><th>firstin</th><th>firstout</th></tr></thead><tbody><tr><td>存与顶点相关的信息</td><td>入度边</td><td>出度边</td></tr></tbody></table><ul><li>弧结点：</li></ul><table><thead><tr><th>tailvex</th><th>headvex</th><th>hlink</th><th>tlink</th></tr></thead><tbody><tr><td>弧尾位置</td><td>弧头位置</td><td>弧头相同的下一条弧</td><td>弧尾相同的下一条弧</td></tr></tbody></table><p><em><strong>弧头是箭头，弧尾是箭尾</strong></em></p><h4 id="6-4-4-邻接多重表">6.4.4 邻接多重表</h4><p>边结点：</p><table><thead><tr><th>mark</th><th>ivex</th><th>ilink</th><th>jvex</th><th>jlink</th><th>info</th></tr></thead><tbody><tr><td>标志域，标记此边是否被搜索过</td><td>该边依附的两个顶点在表头数组中的位置</td><td>指向依附于ivex的下一条边</td><td>该边依附的两个顶点在表头数组中的位置</td><td>指向依附于jvex的下一条边</td><td></td></tr></tbody></table><p>顶点节点</p><table><thead><tr><th>data</th><th>firstedge</th></tr></thead><tbody><tr><td>存与顶点有关的信息</td><td>指向第一条依附于该顶点的边</td></tr></tbody></table><h3 id="6-5-图的遍历">6.5 图的遍历</h3><ul><li><p>遍历的实质：找每个顶点的邻接点的过程</p></li><li><p>图常用的遍历：</p><ul><li>深度优先搜索(DFS)</li><li>广度优先搜索(BFS)</li></ul></li></ul><h4 id="6-5-1-深度优先搜索">6.5.1 深度优先搜索</h4><p>甚多有限搜索遍历过程：</p><ol><li>从图中某个顶点v出发， 访问v。</li><li>找出刚访问过的顶点的第一个未被访问的邻接点， 访问该顶点。 以该顶点为新顶点，重复此步骤， 直至刚访问过的顶点没有未被访问的邻接点为止。</li><li>返回前一个访问过的且仍有未被访问的邻接点的顶点，找出该顶点的下一个未被访问的邻接点， 访问该顶点。</li><li>重复步骤 (2)和(3), 直至图中所有顶点都被访问过，搜索结束。</li><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365950c458853aef872f05.png"/></div></div></li></ol><h5 id="6-5-1-1-采用邻接矩阵表示图的深度优先搜索遍历">6.5.1.1 采用邻接矩阵表示图的深度优先搜索遍历</h5><figure class="highlight c"><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"><span class="type">void</span> <span class="title function_">DFS</span><span class="params">(AMGraph G,<span class="type">int</span> v)</span>&#123;<span class="comment">//图G为邻接矩阵类型</span></span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;v;visited[v]=<span class="literal">true</span>;<span class="comment">//访问第v个顶点</span></span><br><span class="line">    <span class="keyword">for</span>(w=<span class="number">0</span>;w&lt;G.vexnum;w++)<span class="comment">//依次检查邻接矩阵v所在行</span></span><br><span class="line">        <span class="keyword">if</span>((G.arcs[v][w]!=<span class="number">0</span>)&amp;&amp;(!visited[w]))</span><br><span class="line">            DFS(G,w);<span class="comment">//w是v邻接点，如果w未访问，则递归调用DFS</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>DFS算法效率分析<ul><li>用邻接矩阵来表示图，遍历图中每一个顶点都要<strong>从头扫描</strong>该顶点所在行，时间复杂度为$O(n^2)$</li><li>用邻接表来表示图，虽然有<strong>2e</strong>个表结点，但只需扫描 <strong>e</strong> 个结点即可完成遍历，加上访问 <strong>n</strong>个头结点的时间，时间复杂度为$O(n+e)$</li></ul></li><li>结论<ul><li>稠密图适合在邻接矩阵上进行深度遍历</li><li>稀疏图适合在邻接表上进行深度遍历</li></ul></li></ul><h4 id="6-5-2-广度优先搜索">6.5.2 广度优先搜索</h4><ul><li>用队列实现广度优先遍历，累次层次遍历那样</li></ul><h5 id="6-5-2-1-按广度优先非递归遍历连通图G">6.5.2.1 按广度优先非递归遍历连通图G</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">BFS</span> <span class="params">(Graph G,<span class="type">int</span> v)</span>&#123;</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;v;visited[v]=<span class="literal">true</span>;<span class="comment">//访问第v个顶点</span></span><br><span class="line">    initQueue(Q);<span class="comment">//辅助队列Q初始化，置空</span></span><br><span class="line">    EnQueue(Q,v);<span class="comment">//v进队</span></span><br><span class="line">    <span class="keyword">while</span>(!QueueEmpty(Q))&#123;<span class="comment">//队列非空</span></span><br><span class="line">        DeQueue(Q,u);<span class="comment">//队头元素出队并置为u</span></span><br><span class="line">        <span class="keyword">for</span>(W=FirstAdjVex(G,u);w&gt;=<span class="number">0</span>;w=NextAdjVex(G,u,w))</span><br><span class="line">            <span class="keyword">if</span>(!visited[w])&#123;<span class="comment">//w为u的尚未访问的邻接顶点</span></span><br><span class="line">                <span class="built_in">cout</span>&lt;&lt;w;visited[w]=<span class="literal">true</span>; EnQueue(Q,w);<span class="comment">//w进队</span></span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>BFS算法效率分析<ul><li>如果邻接矩阵，则BFS对于每一个背访问到的顶点，都要循环检测矩阵中的整整一行（n个元素），总的时间代价为$O(n^2)$</li><li>用邻接表来表示图，虽然有 2e 个表结点，但只需扫描 e 个结点即可完成遍历，加上访问 n个头结点的时间，时间复杂度为$O(n+e)$</li></ul></li><li>DFS和BFS算法效率比较<ul><li>空间复杂度相同，都是O(n)（借用了堆栈或队列）；</li><li>时间复杂度只与存储结构（邻接矩阵或邻接表）有关，而与搜索路径无关。</li></ul></li></ul><h3 id="6-6-图的应用">6.6 图的应用</h3><h4 id="6-6-1-最小生成树">6.6.1 最小生成树</h4><ul><li><p>生成树：所有顶点均由边连接在一起，但不存在回路的图</p><ul><li><p>一个图可以有许多棵不同的生成树</p></li><li><p>所有生成树具有以下共同特点</p><ul><li>生成树的顶点个数与图的<strong>顶点个数相同</strong></li><li>生成树是图的<strong>极小连通子图</strong>，去掉一条边则非连通</li><li>一个有<strong>n</strong>个顶点的连通图的生成树有<strong>n-1</strong>条边</li><li><strong>在生成树中再加一条边必然形成回路</strong></li><li>生成树中任意两个顶点间的<strong>路径是唯一</strong>的</li></ul></li></ul></li><li><p>无向图的生成树：</p><ul><li>设图G=(V,E)是个连通图，当从图任一顶点出发遍历图G时，将边集E(G)分成两个集合T(G) 和B(G)。其中 T(G)是遍历图时所经过的边的集合，B(G)是遍历图时未经过的边的集合。显然，G1(V,T)是图G的极小连通子图。即子图G1 是连通图G的生成树</li></ul></li><li><p>最小生成树</p><ul><li><p>给定一个无向网络，在该网络的所有生成树中，使得<strong>各边权值之和最小</strong>的那棵生成树称为该网的<strong>最小生成树</strong>，也叫<strong>最小代价生成树</strong>。</p></li><li><p>最小生成树的典型用途</p><ul><li>要在n个城市间建立通信网，则n个城市应铺设n-1条路，每条路也有对应的经济成本</li><li>建立数学模型：<ul><li>顶点代表城市有n个</li><li>边代表线路有n-1条</li><li>边的权值  表示线路的经济代价</li><li>连通网    表示n个城市间的通信网</li></ul></li></ul></li></ul></li><li><p>构造最小生成树</p><ul><li>构造最小生成树的算法很多，其中多数算法都利用了<strong>MST的性质</strong></li><li>MST性质：设N=（V,E) 是一个连通网，U是顶点集V的一个非空子集。若边(u,v) 是一条具有最小权值的边，其中$u\in U，v \in V-U$则必存在一棵包含边(u,v)的最小生成树</li><li>在生成树的构造过程中，图中n个顶点分属两个集合：<ul><li>已落在生成树上的顶点集: U</li><li>尚未落在生成树上的顶点集: V-U</li></ul></li><li>接下来则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365950c458853aef872f05.png"/></div></div></li></ul><h5 id="6-6-1-1-普利姆-prim-算法">6.6.1.1 普利姆(prim)算法</h5><ul><li><p>算法思想：</p><ol><li><p>设N=（V,E)是连通网，TE是N上最小生成树 中边的集合</p></li><li><p>初始令$U={u_0}，(u_0 \in V),TE=${}</p></li><li><p>在所有$u\in U，v \in V-U$的边$（u,v）\in E$中找一条代价最小的边$(u_0,v_0)$.</p></li><li><p>将$(u_0,v_0)$并入集合TE，同时$v_o$并入U</p></li><li><p>重复上述操作直至U=V为止，则T=（V，TE）为N的最小生成树</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365950c458853aef87302c.png"/></div></div></li></ol></li></ul><h5 id="6-6-1-2-克鲁斯卡尔-Kruskal-算法">6.6.1.2 克鲁斯卡尔(Kruskal)算法</h5><ul><li>算法思想：<ol><li>设连通网 N=(VE)，令最小生成树初始状态为<strong>只有</strong> n个<strong>顶点</strong>而<strong>无边</strong>的非连通图T=（V，{}),每个顶点自成一个连通分量。</li><li>在E中选取代价最小的边，若该边依附的顶点落在T中不同的连通分量上(即:<strong>不能形成环</strong>），则将此边加入到 T中;否则，舍去此边，选取下一条代价最小的边。</li><li>依此类推，直至T中所有顶点都在同一连通分量上为止。</li></ol></li><li><strong>最小生成树可能不唯一</strong></li></ul><table><thead><tr><th>算法名</th><th>普里姆算法</th><th>克鲁斯卡尔算法</th></tr></thead><tbody><tr><td>算法思想</td><td>选择点</td><td>选择边</td></tr><tr><td>时间复杂度</td><td>O（n^2)(n为顶点数)</td><td>O(eloge)（e为边数）</td></tr><tr><td>适应范围</td><td>稠密图</td><td>稀疏图</td></tr></tbody></table><h4 id="6-6-2-最短路径">6.6.2 最短路径</h4><ul><li>两种最常见的最短路径问题：<ol><li>单源最短路径——用Dijkstra（迪杰斯特拉）算法</li><li>所有顶点间的最短路径——用Floyd——（弗洛伊德）算法</li></ol></li></ul><h5 id="6-6-2-1-迪杰斯特拉算法">6.6.2.1  迪杰斯特拉算法</h5><ol><li>初始化：从源点$v_0$到各终点$v_k$的直达路径 $（v_o,v_k）$，即通过一条弧到达的路径。</li><li>选择：从这些路径中找出一条长度最短的路径$（v_0,u）$.</li><li>更新：然后对其余各条路径进行适当调整：<ol><li>若在图中存在弧$(u，v_k)$,且$(v_0,u)+（u，v_k）&lt;(v_0,v_k),则以路径（v_0,u,v_k）代替（v_0，v_k)$</li></ol></li><li>在调整后的各条路径中，再找长度最短的路径，依此类推</li></ol><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365a1bc458853aef89c5cf.png"/></div></div><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365a1bc458853aef89c5cf.png"/></div></div><p>时间复杂度为$O(n^3)$</p><h5 id="6-6-2-2-弗洛伊德算法">6.6.2.2 弗洛伊德算法</h5><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365a1cc458853aef89c625.png"/></div></div><h4 id="6-6-3-拓扑排序">6.6.3 拓扑排序</h4><ul><li><p>有向无环图：无环的有向图，简称DAG图</p><ul><li>有向无环图常用来描述一个工程或系统的进行过程。</li><li>一个工程可以分为若干个子工程，只要完成了这些子工程，就可以导致整个工程的完成</li></ul></li><li><p><strong>AOV网    拓扑排序</strong></p><ul><li>用一个有向图表示一个工程的各子工程及其相只制约的关系，其中以<strong>顶点表示活动</strong>，<strong>弧表示活动之间的优先制约关系</strong>，称这种有向图为<strong>顶点表示活动的网</strong>，简称 <strong>AOV网</strong>(Activity On Vertex network)。</li></ul></li><li><p><strong>AOE网    关键路径</strong></p><ul><li>有向图表示一个工程的各子工程及其相互制约的关系，以<strong>弧表示活动</strong>，以<strong>顶点表示活动的开始或结束事件</strong>，称这种有向图为<strong>边表示活动的网</strong>，简称为<strong>AOE网</strong> (Activity On Edge)</li></ul></li><li><p>AOV网的特点：</p><ol><li>若从i到j有一条有向路径，则i是j的前驱j是i的后继</li><li>若&lt;i,j&gt;是网中有向边，则i是j的直接前驱;j是i的直接后继</li><li>AOV网中不允许有回路，因为如果有回路存在，则表明某项活动以自己为先决条件，显然这是荒谬的。</li></ol></li><li><p>拓扑排序</p><ul><li>在AOV 网没有回路的前提下，我们将全部活动排列成一个线性序列，使得若 AOV 网中有弧 &lt;i,j&gt;存在，则在这个序列中，i一定排在的前面，具有这种性质的线性序列称为<strong>拓扑有序序列</strong>，相应的拓扑有序排序的算法称为<strong>拓扑排序</strong></li></ul></li><li><p>拓扑排序方法：</p><ol><li>在有向图中选一个没有前驱的顶点且输出</li><li>在图中删除该顶点和所有以他为尾的弧</li><li>重复1和2直到全部顶点均已输出；当图中不存在无前驱的顶点位置<ul><li>一个AOV网的拓扑序列不是唯一的</li></ul></li></ol></li><li><p>拓扑排序的一个重要应用：</p><ul><li>检测AOV网中<strong>是否存在环</strong>的方法：<ul><li>对有向图构造其顶点的拓扑有序序列，若网中所有顶点都在它的拓扑有序序列中，则该 AOV 网必定不存在环。</li></ul></li></ul></li><li><p>关键路径</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365a1cc458853aef89c6b4.png"/></div></div></li><li><p>假设一个工程有11项活动，9个事件</p><ul><li>事件V1——表示整个工程开始（源点：入度为0的顶点）</li><li>事件v9——表示整个工程结束（汇点：出度为0的顶点）</li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365a1cc458853aef89c701.png"/></div></div><ul><li>关键路径——路径长度最长的路径</li><li>路径长度——路径上各活动持续时间之和<ul><li>ve(vj)——表示事件vj的最早发生时间</li><li>vl(vj)——表示事件vj的最迟发生时间</li><li>e(i)——表示活动ai的最早开始时间</li><li>l(i)——表示活动ai的最迟开始时间</li><li>l(i)-e(i)——表示完成活动ai的时间余量</li><li>关键活动——关键路径上的活动，即l(i)==e(i)的活动（即没有时间余量的活动）</li></ul></li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365b00c458853aef8c3542.png"/></div></div><ul><li>其中√的为关键活动，而包含关键活动的路径为关键路径<ul><li>需要加快同时在几条关键路径上的关键活动<ul><li>如：a11,a10,a8,a7</li></ul></li><li>如果一个活动处于所有关键路径上，那么提高这个活动的速度，就能缩短整个工程的完成时间<ul><li>如：a1,a4</li></ul></li><li>处于所有关键路径上的活动完成时间不能缩短太多，否则会使原来的关键路径变成不是关键路径。这时必须重新寻找关键路径</li></ul></li></ul><h2 id="7-查找">7 查找</h2><h3 id="7-1-查找的基本概念">7.1 查找的基本概念</h3><ul><li><p><strong>查找表</strong>：是由同一类型的数据元素（或记录）构成的<strong>集合</strong>。由于“集合”中的数据元素之间存在着某种松散的关系，因此查找表是一种应用灵便的结构</p><ul><li>查找表分两类<ul><li>静态查找表：仅做“查询“{检索)操作的查找表</li><li>动态查找表：做“插入”和“删除”操作的查找表</li></ul></li></ul></li><li><p><strong>关键字</strong>：关键字是数据元素（或记录） 中某个数据项的值，用它可以标识一个数据元素（或记录）。</p><ul><li>主关键字：此关键字可以<strong>唯一地</strong>标识一个<strong>记录</strong>，则称此关键字为主关键字</li><li>次关键字：用以识别若千记录的关键字为次关键字。</li></ul></li><li><p><strong>查找</strong>：根据给定的某个值，在查找表中确定一个其关键字等于给定值的数据元素或（记录）</p><ul><li>若查找表中存在这样一个记录，则称 <strong>查找成功</strong><ul><li>查找结果给出整个记录的<strong>信息</strong>，或指示该记录在查找表中的<strong>位置</strong>；</li></ul></li><li>否则称 <strong>查找不成功</strong><ul><li>查找结果给出“空记录”或“空指针”</li></ul></li><li>查找经常进行的操作<ul><li><strong>查询</strong>某个**“特定的”**数据元素是否在查找表中；</li><li><strong>检索</strong>某个**“特定的”**数据元素的各种属性；</li><li>在查找表中<strong>插入</strong>一个数据元素；</li><li><strong>删除</strong>查找表中的某个数据元素；</li></ul></li></ul></li><li><p>查找算法的评价指标：</p><ul><li><p>关键字的平均比较次数，也称平均查找长度。</p><p>$\LARGE ASL=\sum\limits_{i=1}^np_ic_i$    (关键字比较次数的期望值)</p><ul><li>n：记录的个数</li><li>pi：查找第i个记录的概率（通常认为pi=1/n）</li><li>ci：找到第i个记录所需的比较次数</li></ul></li></ul></li></ul><h3 id="7-2-线性表的查找">7.2 线性表的查找</h3><h4 id="7-2-1-顺序查找">7.2.1 顺序查找</h4><ul><li><p>应用范围：</p><ul><li>顺序表或线性链表表示的静态查找表</li><li>表内元素之间无序</li></ul></li><li><p>数据元素类型定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    KeyType key;</span><br><span class="line">    ……</span><br><span class="line">&#125;ElemType;</span><br></pre></td></tr></table></figure></li><li><p>顺序表的定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//顺序结构表结构类型定义</span></span><br><span class="line">    ElemType *R;<span class="comment">//表基址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//表长</span></span><br><span class="line">&#125;SSTable;</span><br><span class="line">SSTable ST;<span class="comment">//定义顺序表ST</span></span><br></pre></td></tr></table></figure></li><li><p>顺序查找</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">Search_seq</span><span class="params">(SSTable st,keytype key)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=ST.length;i&gt;=<span class="number">1</span>;--i)</span><br><span class="line">        <span class="keyword">if</span> (ST.R[i].key==key)<span class="keyword">return</span> i;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>改进：把待查关键字key存入表头（”哨兵“）从后往前挨个比较，可免去查找过程中每一步都要检查是否查找完毕，加快速度</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">search_seq</span><span class="params">(SSTable ST,KeyType key)</span>&#123;</span><br><span class="line">    ST.R[<span class="number">0</span>].key=key;</span><br><span class="line">    <span class="keyword">for</span>(i=ST.length;ST.R[i].key!=key;--i);</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>比较次数与key位置有关：</p><ul><li>查找第i个元素，需要比较n-i+1次</li><li>查找失败，需要比较n+1次</li></ul></li><li><p>时间复杂度O(n)</p><ul><li><p>查找成功时的平均查找长度，设表中各记录查找概率相等</p><p>$ASL=（1+2+……+n）/n=\dfrac {n+1}{2}$</p></li></ul></li><li><p>空间复杂度：一个辅助空间——O(1)</p></li></ul></li></ul></li><li><p>顺序查找的特点</p><ul><li>优点：算法简单，逻辑次序无要求，且不同存储结构均适用</li><li>缺点：ASL太长，时间效率太低</li></ul></li></ul><h4 id="7-2-2-折半查找">7.2.2 折半查找</h4><ul><li><p>算法步骤</p><ol><li>设表长为n，low、high和mid分别指向待查元素所在区闻的上界，下界和中点，key为给定的要查找的值；</li><li>初始时，令low=1，high=n，</li><li>让k与mid指向的记录比较<ol><li>若key==R[mid].KEY,查找成功</li><li>若key&lt;R[mid].key,则high=mid-1</li><li>若key&gt;R[mid].key,则low=mid-1</li></ol></li></ol></li><li><p>折半查找</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">search_bIN</span><span class="params">(SSTable ST,KeyType key)</span>&#123;</span><br><span class="line">    low=<span class="number">1</span>;high=ST,length;</span><br><span class="line">    <span class="keyword">while</span>(low&lt;=high)&#123;</span><br><span class="line">        mid=(low+high)/<span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span>(ST.R[mid].key==KEY)<span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            <span class="keyword">if</span>(key&lt;ST.R[mid].key)</span><br><span class="line">                high=mid<span class="number">-1</span>;</span><br><span class="line">            <span class="keyword">else</span> low=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365b00c458853aef8c35d4.png"/></div></div></li><li><p>$\large ASL=1/11*(1<em>1+2</em>2+4<em>3+4</em>4)=33/11=3$</p></li><li><p>平均查找长度ASL（成功时）</p><ul><li><p>$设表长为n=2^h-1,则h=log_2(n+1)，且表中每个记录的查找概率相等：p_i=1/n$</p><p>$则\begin{align}<br>ASL_{bs}&amp;=\dfrac {1}{n} \sum\limits ^{n}_{j=1}j*2^{j-1}\<br>&amp;=\dfrac{n+1}{n}log_2(n+1)-1\<br>&amp;≈log_2(n+1)-1(n&gt;50)\end{align}$</p></li><li><p>折半查找的性能分析</p><ul><li>优点：效率比顺序查找高</li><li>缺点：只适用于有序表，且限于顺序存储结构(对线性链表无效)</li></ul></li></ul></li></ul></li></ul><h4 id="7-2-3-分块查找">7.2.3 分块查找</h4><ul><li><p>分块查找 (Blocking Search) 又称索引顺序查找，这是一种性能介于顺序查找和折半查找之间的一种查找方法。</p><ol><li>将表分成几块,且表或者有序，或者<strong>分块有序</strong></li><li>建立“索引表”（每个结点含有最大关键字域和指向本块第一个结点的指针，且按关键字有序）。</li></ol></li><li><p>查找过程：先确定待查记录所在块，再在块内查找</p></li><li><p>查找效率：$ASL=L_b+L_w$</p></li><li><p>$\large ALS_{bs}≈log_2(\dfrac{n}{s}+1)+\dfrac {s}{2}$   s为每块内部的记录个数，n/s即块内数目</p></li><li><p>分块查找的特点</p><ul><li>优点：插入和删除比较容易</li><li>缺点：要增加一个索引表的存储空间并对初始索引表进行排序运算</li><li>适用情况：线性表既要快速查找又要经常动态变化</li></ul></li></ul><table><thead><tr><th></th><th>顺序查找</th><th>折半查找</th><th>分块查找</th></tr></thead><tbody><tr><td>ASL</td><td>最大</td><td>最小</td><td>中间</td></tr><tr><td>表结构</td><td>有序表、无序表</td><td>有序表</td><td>分块有序</td></tr><tr><td>存储结构</td><td>顺序表、线性链表</td><td>顺序表</td><td>顺序表、线性链表</td></tr></tbody></table><h3 id="7-3-树表的查找">7.3 树表的查找</h3><ul><li>当表插入、删除操作频繁时，为了维护表的有序性，需要移动表中很多记录<ul><li><strong>改用动态查找表——几种特殊的树</strong></li><li>表结构在<strong>查找过程中动态生成</strong></li><li>对于给定值key，若表中存在，则成功返回；<strong>否则，插入关键字等于key的记录</strong><ul><li><strong>二叉排序树</strong><br><strong>平衡二叉树</strong><br>红黑树<br>B-树<br>B+树<br>键树</li></ul></li></ul></li></ul><h4 id="7-3-1-二叉排序树">7.3.1 二叉排序树</h4><ul><li><p>二叉排序树又称为二叉搜索树、二叉查找树</p><ul><li>二叉排序树或是空树，或是满足如下性质的二叉树；<ol><li>若其<strong>左子树非空</strong>，则左子树上所有结点的值均<strong>小于</strong>根结点的值；</li><li>若其<strong>右子树非空</strong>，则右子树上所有结点的值均<strong>大于</strong>根结点的值；</li><li>其左右子树本身又各是一棵二叉排序树</li></ol></li><li>二叉排序树的性质<ul><li>中序遍历非空的二又排序树所<strong>得到的数据元素序列是</strong>一个按关键字排列的<strong>递增有序序列</strong>。</li></ul></li></ul></li><li><p>二叉排列树的操作——查找</p><ul><li><p>若查找的关键字等于根结点，成功</p></li><li><p>否则</p><ul><li>小于根结点，查左子树</li><li>大于根结点，查右子树</li></ul></li><li><p>在左右子树上的操作类似</p></li></ul></li><li><p>算法描述</p><figure class="highlight c"><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">BSTree <span class="title function_">SearchBST</span> <span class="params">(BSTree_T,KeyType key)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> ((!T)||key==T-&gt;data.key) <span class="keyword">return</span> T;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (key&lt;T-&gt;data.key)</span><br><span class="line">        <span class="keyword">return</span> SearchBST(T-&gt;lchild,key);</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">return</span> SearchBST (T-&gt;rchild,key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>二叉排序树的查找分析</p><ul><li><p>含有n个结点的二叉排列树的平均查找长度和树的形态有关</p><ul><li><p>最好的情况：</p><p>二叉排列树的形态和折半查找的判定树相似:</p><p>$ASL=log_2(n+1)-1$<br>$O(log_2n)$</p></li><li><p>最坏情况</p><p>二叉排序树的形态为单支树，树的深度为n，</p><p>$ASL=\dfrac {n+1}{2}$</p><p>$O(n)$</p></li></ul></li></ul></li><li><p>二叉排序树的操作——插入</p><ul><li>若二叉排序树为空，则插入结点作为根结点插入到空树中</li><li>否则，继续在左右子树上查找<ul><li>树中已有，不再插入</li><li>树中没有<ul><li>查找直至某个叶子结点的左子树或右子树为空为止，则插入结点应为该叶子结点的左孩子或右孩子</li></ul></li></ul></li><li>插入的元素一定在叶子结点上</li></ul></li><li><p>二叉排序树的操作——生成</p><ul><li>一个无序序列可通过构造二叉排序树而变成一个有序序列。构造树的过程就是对无序序列进行排序的过程。</li><li>插入的结点均为叶子结点，故无序移动其他结点。相当于在有序序列上插入记录儿无需移动其他记录</li><li>不同的插入次序的序列生成不同形态的二叉排序树</li></ul></li><li><p>二叉排序树的操作——删除</p><ul><li>被删除的结点是叶子结点：直接删去该结点</li><li>被删除的结点只有左子树或右子树，用其左子树或右子树替换它</li><li>被删除的既有左子树，也有右子树，以其中序前驱值替换之（前驱是左子树中最大的结点），然后再删除该前驱结点<ul><li>也可以用其后继替换之（后继是右子树中最小结点），然后再删除该后继结点</li></ul></li></ul></li></ul><h4 id="7-3-2-平衡二叉树">7.3.2 平衡二叉树</h4><ul><li><p>平衡二叉树的定义</p><ul><li>又称AVL树</li><li>一棵平衡二叉树或者是空树，或者是具有以下性质的二叉排序树：<ul><li><strong>左</strong>子树与<strong>右</strong>子树的<strong>高度之差的绝对值小于等于1</strong>；</li><li><strong>左</strong>子树和<strong>右</strong>子树也是平衡二叉排序树。</li></ul></li></ul></li><li><p>为了方便起见，给每个结点附加一个数字，给出<strong>该结点左子树与右子树的高度差</strong>。这个数字称为结点的<strong>平衡因子</strong></p><ul><li><p>根据平衡二叉树的定义，平衡二叉树上所有结点的平衡因子只能是-1，0，或1</p></li><li><p>对于一棵有n个结点的AVL树，其高度保持在$O(log_2n)$数量级，ASL也是保持在$O(log_2n)$量级</p></li><li><p>当我们在一个平衡二叉树上插入结点时，可能导致<strong>失衡</strong></p><ul><li><p>平衡调整的四种类型：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365b01c458853aef8c3647.png"/></div></div><p>LR型和RL型左右要看B和A哪个大</p><ul><li>调整原则：1. 降低高度 2. 保持二叉排序树性质</li></ul></li></ul></li></ul></li></ul><h3 id="7-4-哈希表的查找">7.4 哈希表的查找</h3><h4 id="7-4-1-散列表的基本概念">7.4.1 散列表的基本概念</h4><ul><li>基本思想：记录的存储位置与关键字之间存在对应关系</li></ul><p>​对应关系——hash函数（hash:散列）</p><p>​Loc(i)=H(keyi)</p><ul><li>散列表的查找<ul><li>根据散列函数H(key)=k<ul><li>查找key=9，直接访问H(9)=9号地址，若内容为9则成功<br>若查不到，则返回一个特殊值，如空指针或空记录。</li></ul></li><li>优点：查找效率高<br>缺点：空间效率低</li></ul></li><li>散列函数和散列地址：在记录的存储位置p和其关键字key 之间建立一个确定的对应关系H, 使 p=H(key ), 称这个对应关系H为散列函数，p为散列地址。</li><li>散列表：一个有线连续的地址空间，用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组，散列地址是数组的下标</li><li>冲突：不同的关键码映射到同一个散列地址<ul><li>key1≠key2，但是H(key1)=H(key2)</li><li>冲突是不可避免的，但可以减少</li></ul></li><li>同义词：具有相同函数值的多个关键字</li></ul><h4 id="7-4-2-散列函数的构造方法">7.4.2 散列函数的构造方法</h4><ul><li><p>构造散列函数的考虑因素：</p><ul><li>散列表的长度</li><li>关键字的长度</li><li>关键字的分布情况</li><li>计算散列函数所需的时间</li><li>记录的查找频率</li></ul></li><li><p>构造号的散列函数要遵循以下两条原则</p><ul><li>函数计算要简单，每一个关键字只能有一个散列地址与之对应</li><li>函数的值域需在表长的范围内， 计算出的散列地址的分布应均匀，尽可能减少冲突。</li></ul></li><li><p>根据数据元素的集合特性构造</p><ul><li>要求一: n个数据原仅占用n个地址虽然散列查找是以空间换时间，但仍希望散列的地址空间尽量小</li><li>要求二:无论用什么方法存储，目的都是尽量均匀地存放元素，以避免冲突。</li></ul></li></ul><h5 id="7-4-2-1-直接定址法">7.4.2.1 直接定址法</h5><ul><li>Hash(key)=a.key+b</li><li>优点：以关键码key的某个线性函数值为散列地址，不会产生冲突<br>缺点：要占用连续的地址空间，空间效率低</li></ul><h5 id="7-4-2-2-除留余数法">7.4.2.2 除留余数法</h5><ul><li><p>Hash(key)=key mod p</p><ul><li><p>技巧：设：表长为m，取p&lt;=m且为质数</p><ul><li><p>例：{15,23,27,38,53,61,70}</p><p>散列函数：Hash(key)=key mod 7</p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th></tr></thead><tbody><tr><td>70</td><td>15</td><td>23</td><td>38</td><td>53</td><td>61</td><td>27</td></tr></tbody></table></li></ul></li></ul></li></ul><h4 id="7-4-3-处理冲突的方法">7.4.3 处理冲突的方法</h4><h5 id="7-4-3-1-开放地址法（开地址法）">7.4.3.1 开放地址法（开地址法）</h5><ul><li><p>基本思想：有冲突时就去寻找下一个空的散列地址，只要散列表足够大，空的散列地址总能找到，并将数据元素存入</p><ul><li>列入：除留余数法：$H_i=(Hash(key)+d)mod \ m$</li><li>常用方法：<ul><li>线性探测法      $d_i为1,2,……，m-1线性序列$</li><li>二次探测法      $d_i为1^2,-1^2,2^2,-2^2……，q^2二次序列$</li><li>伪随机探测法    $d_i为伪随机数序列$</li></ul></li></ul></li><li><p>例：关键码集为：{47，7，29，11，16，92，22，8，3}，散列表长m=11，散列函数为$Hash(key)=key \ mod \ 11$,拟用线性探测法处理冲突</p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th><th>10</th></tr></thead><tbody><tr><td>11</td><td>22</td><td></td><td>47</td><td>92</td><td>16</td><td>3</td><td>7</td><td>29</td><td>8</td><td></td></tr></tbody></table><ul><li>使用线性探测法解释：</li></ul><ol><li>47、7均是由散列函数得到的没有冲突的散列地址;</li><li>Hash(29)=7，散列地址有冲突，需寻找下一个空的散列地址由$H=(Hash(29)+1) mod \ 11=8$，散列地址8为空，因此将29存入。</li><li>11、16、92均是由散列函数得到的没有冲突的散列地址:</li><li>另外，22、8、3同样在散列地址上有冲突，也是由H,找到空的散列地址的。<ul><li>平均查找长度ASL=(1+2 +1 +1 +1 +4 +1 +2 +2)/9=1.67</li></ul></li></ol><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th><th>10</th></tr></thead><tbody><tr><td>11</td><td>22</td><td>3</td><td>47</td><td>92</td><td>16</td><td></td><td>7</td><td>29</td><td>8</td><td></td></tr></tbody></table><ul><li>使用二次探测法解释：<ul><li>Hash(3)=3散列地址有冲突，由于$H_1=(Hash(3)+1^2)mod \ 11=4$，仍然冲突；</li><li>$H_2=(Hash(3)-1^2)mod \ 11=2$找到空的散列地址，存入</li></ul></li></ul></li></ul><h5 id="7-4-3-2-链地址法（拉链法）">7.4.3.2 链地址法（拉链法）</h5><ul><li><p>基本思想：相同散列地址的记录链成一单链表</p></li><li><p>m个散列地址就设m个单链表，然后用一个数组将m个单链表的表头指针存储起来，形成一个动态结构。</p></li><li><p>链地址法建立散列表的步骤</p><ul><li>step1：取数据元素的关键字key，计算其散列函数值 (地址)。若该地址对应的链表为空，则将该元素插入此链表；否则执行Step2解决冲突。</li><li>step2：根据选择的冲突处理方法，计算关键字key的下一个存储地址若该地址对应的链表为不为空，则利用链表的前插法或后插法将该元素插入此链表</li></ul></li><li><p>链地址法的优点：</p><ul><li>非同义词不会冲突，无“聚集”现象<ul><li>链表上的结点空间动态申请，更适合于表长不确定的情况</li></ul></li></ul></li></ul><h4 id="7-4-4-散列表的查找">7.4.4 散列表的查找</h4><ul><li>给定值查找值k，查找过程：</li></ul><figure class="highlight plaintext"><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">graph TD</span><br><span class="line">A[给定k值]==&gt;B(计算H)</span><br><span class="line">B==&gt;C&#123;此地址为空&#125;</span><br><span class="line">C--&gt;|N|D&#123;关键字==k&#125;</span><br><span class="line">C--&gt;|Y|G[查找失败]</span><br><span class="line">D--&gt;|N|E[案处理冲突方法计算Hi]</span><br><span class="line">D--&gt;|Y|H[查找成功]</span><br><span class="line">E--&gt;C</span><br><span class="line">F[竖向流程图]</span><br></pre></td></tr></table></figure><ul><li><p>$ASL=(1<em>6+2+3</em>3+4+9)/12=2.5$</p></li><li><p>用连地址法处理冲突：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365b01c458853aef8c369d.png"/></div></div></li><li><p>$ASL=(1<em>6+2</em>4+3+4)/12=1.75$</p></li><li><p>使用平均查找长度ASL来衡量查找算法，ASL取决于</p><ul><li>散列函数</li><li>处理冲突的方法</li><li>散列表的装填因子α<ul><li>$\Large α=\dfrac {表中填入的记录数}{哈希表的长度}$</li><li>α越大，表中的记录数越多，表越满，发生冲突的可能性就越大，查找时比较次数就越多</li></ul></li><li>无冲突时才能达到O(1)</li><li>$\large ASL≈1+\dfrac {α}{2}$</li><li>$\large ASL≈\dfrac {1}{2}(1+\dfrac{1}{1-α})$</li><li>$\large ASL≈-\dfrac{1}{α}ln(1-α)$</li></ul></li><li><p>结论</p><ul><li>链地址法优于开地址法</li><li>散列表技术具有很好的平均性能，优于一些传统的技术</li><li>除留余数法作散列函数优于其它类型函数</li></ul></li></ul><h2 id="8-排序">8 排序</h2><h3 id="8-1-基本概念和排序方法概述">8.1 基本概念和排序方法概述</h3><h4 id="8-1-1-排序的基本概念">8.1.1 排序的基本概念</h4><ul><li><p>排序：将一组杂乱无章的数据按照一定规律顺次排列起来，即，将无需数列排成一个有序序列（由小到大或由大到小）的运算。</p><ul><li>如果参加排序的数据结点包含多个数据域，那么排序往往是针对其中某个域而言</li><li>排序方法的分类：<ul><li>按<strong>数据存储介质</strong>：内部排序和外部排序<ul><li>内部排序：数据量不大，数据在外存，无需内外存交换数据</li><li>外部排序：数据量较大，数据在外存（文件排序）<ul><li>外部排序时，要将数据分批调入内存来排序，中间结果还要及时放入外存，显然外部排序要复杂得多</li></ul></li></ul></li><li>按<strong>比价器个数</strong>：串行排序和并行排序<ul><li>串行排序：单处理机（同一时刻比较一对元素）</li><li>并行排序：多处理机（同一时刻比较多对元素）</li></ul></li><li>按<strong>主要操作</strong>：比较排序和基数排序<ul><li>比较排序：用比较的方法<ul><li>插入排序、交换排序、选择排序、归并排序</li></ul></li><li>基数排序：不比较元素的大小，仅仅根据元素本身的取值确定其有序位置</li></ul></li><li>按<strong>辅助空间</strong>：原地排序和非原地排序<ul><li>原地排序：辅助空间用量为O(1)的排序方法（所占用的辅助存储空间与参与排序的数据量大小无关）</li><li>非原地排序：辅助空间用量超过O(1)的排序方法</li></ul></li><li>按<strong>稳定性</strong>：稳定排序和非稳定排序<ul><li>稳定排序：能够使任何数值相等的元素，排序以后相对次序不变。</li><li>非稳定排序：不是稳定排序的方法</li></ul></li><li>按<strong>自然性</strong>：自然排序和非自然排序<ul><li>自然排序：输入数据越有序，排序的速度越快的排序方法</li><li>非自然排序：不是自然排序的方法</li></ul></li></ul></li></ul></li><li><p>接下来的学习内容：</p></li><li><p>按排序依据原则：</p><ul><li>插入排序：直接插入排序、折半插入排序、希尔排序</li><li>交换排序：冒泡排序、快速排序</li><li>选择排序：简单选择排序、堆排序</li><li>归并排序:：2-路归并排序</li><li>基数排序</li></ul></li><li><p>按排序所需工作量</p><ul><li>简单的排序方法：$ T(n)=O(n^2)$</li><li>基数排序：$ T(n)=O(d.n)$</li><li>先进的排序方法：$ T(n)=O(nlogn)$</li></ul></li><li><p>存储结构——记录序列以顺序表存储</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXSIZE 20<span class="comment">//设记录不超过20个</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> KeyType;<span class="comment">//设关键字为int型</span></span><br></pre></td></tr></table></figure><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Typedef <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    KeyType key;<span class="comment">//关键字</span></span><br><span class="line">    infoType otherinfo;<span class="comment">//其他数据项</span></span><br><span class="line">&#125;RedType;</span><br></pre></td></tr></table></figure><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Typedef <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//定义顺序表的结构</span></span><br><span class="line">    RedType r[MAXSIZE +<span class="number">1</span>];<span class="comment">//存储顺序表的向量</span></span><br><span class="line">    <span class="comment">//r[0]一般作哨兵或缓冲区</span></span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-2-插入排序">8.2 插入排序</h3><ul><li>基本思想：<ul><li>在有序序列中插入一个元素，保持序列有序，有序长度不断增加</li></ul></li><li>插入排序的种类<ul><li>顺序法定位插入位置——直接插入排序</li><li>二分法定位插入位置——二分插入排序</li><li>缩小增量多遍插入排序——希尔排序</li></ul></li></ul><h4 id="8-2-1-直接插入排序">8.2.1 直接插入排序</h4><ul><li><p>直接插入排序——采用顺序查找法查找插入位置</p><ol><li><p>复制插入元素</p></li><li><p>记录后移，查找插入位置</p><figure class="highlight c"><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"><span class="keyword">for</span>(j=i<span class="number">-1</span>;j&gt;=<span class="number">0</span>&amp;&amp;x&lt;a[j]；j--)</span><br><span class="line">    a[j+<span class="number">1</span>]=a[j];</span><br></pre></td></tr></table></figure></li><li><p>插入到正确位置</p></li></ol></li><li><p>直接插入排序，使用“哨兵”</p><ol><li>复制为哨兵   L.r[0]=L.r[i];</li><li>记录后移，查找插入位置</li><li>插入到正确位置</li></ol></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">InserSort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i,j;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">2</span>;i&lt;=L.length;++i)&#123;</span><br><span class="line">        <span class="keyword">if</span>(L.r[i].key&lt;L.r[i<span class="number">-1</span>].key)&#123;</span><br><span class="line">            L.r[<span class="number">0</span>]=L.r[i];</span><br><span class="line">            <span class="keyword">for</span>(j=i<span class="number">-1</span>;L.r[<span class="number">0</span>].key&lt;L.r[j].key;--j)&#123;</span><br><span class="line">                L.r[j+<span class="number">1</span>]=L.r[j];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        L.r[j+<span class="number">1</span>]=L.r[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>平均的情况：<ul><li>比较次数$\sum\limits^{n-1}_{i=1} \dfrac{i+1}{2}=\dfrac{1}{4}(n+2)(n-1) $</li><li>移动次数$\sum\limits^{n-1}_{i=1} （\dfrac{i+1}{2}+1）=\dfrac{1}{4}(n+6)(n-1) $</li></ul></li><li>时间复杂度结论<ul><li>原始数据越接近有序，排序速度越快</li><li>最坏情况下（输入数据是逆有序的） $Tw(n)=O(n^2)$</li><li>平均情况下，耗时差不多是最坏情况的一半 $Te(n)=O(n^2)$</li><li>要提高查找速度<ul><li>减少元素的比较次数</li><li>减少元素的移动次数</li></ul></li></ul></li></ul><h4 id="8-2-2-折半插入排序">8.2.2 折半插入排序</h4><figure class="highlight c"><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="type">void</span> <span class="title function_">BinsertSort</span><span class="params">(SqList &amp;L)</span>｛   <span class="comment">//对顺序表L做折半插入排序</span></span><br><span class="line"><span class="title function_">for</span> <span class="params">(i=<span class="number">2</span>;i&lt;=L.length;++i)</span>&#123;</span><br><span class="line">    L.r[O]=L.r[i];</span><br><span class="line">low=l;high=i<span class="number">-1</span>;</span><br><span class="line"><span class="keyword">while</span>(low&lt;=high)&#123;</span><br><span class="line">        m=(low+high)/<span class="number">2</span>;</span><br><span class="line"><span class="keyword">if</span>(L.r[O].key&lt;L.r[mid].key) high=mid<span class="number">-1</span>;<span class="comment">//插入点在前一子表</span></span><br><span class="line"><span class="keyword">else</span> low=mid+<span class="number">1</span>;<span class="comment">//插入点在后一子表</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (j=i一l;j&gt;=high+l; --j) L.r[j+l]=L.r(j]; <span class="comment">//记录后移</span></span><br><span class="line">L.r[high+l]=L.r[O]; <span class="comment">//将r[O]即原r[i], 插入到正确位置</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>算法特点：<ul><li>稳定排序</li><li>应为要进行折半查找，所以只能用于顺序结构，不能用于链式结构</li><li>适合初始记录无序，n较大时的情况</li></ul></li><li>算法分析：<ul><li>时间复杂度为$O(n^2)$</li><li>空间复杂度为$O(1)$</li></ul></li></ul><h4 id="8-2-3-希尔排序">8.2.3 希尔排序</h4><ul><li><p>基本思想：</p><ul><li>先将整个待排记录序列分割成<strong>若干子序列</strong>，分别进行<strong>直接插入排序</strong>待整个序列中的记录“<strong>基本有序</strong>”时，再对全体记录进行一次直接插入排序。</li></ul></li><li><p>算法特点：</p><ul><li>一次移动，移动位置较大，跳跃式地接近排序后的最终位置</li><li>最后一次只需要少量移动</li><li>增量序列必须是递减的，最后一个必须是1</li><li>增量序列应该是互质的</li></ul></li><li><p>算法举例：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365b01c458853aef8c3764.png"/></div></div></li><li><p>算法代码：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">shellsort</span><span class="params">(Sqlist &amp;L,<span class="type">int</span> dlta[],<span class="type">int</span> t)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(k=<span class="number">0</span>;k&lt;t;++k)</span><br><span class="line">        shellinsert(L,dlta[k]);<span class="comment">//一趟增量为dlta[k]的插入排序</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c"><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="type">void</span> shellinert（SqList &amp;L，<span class="type">int</span> dk)</span><br><span class="line">    <span class="keyword">for</span>(i=dk+<span class="number">1</span>;i&lt;=L.length;++i)</span><br><span class="line">        <span class="keyword">if</span>(r[i].key&lt;r[i-dk].key)&#123;</span><br><span class="line">            r[<span class="number">0</span>]=r[i];</span><br><span class="line">            <span class="keyword">for</span>(j=i-dk;j&gt;<span class="number">0</span> &amp;&amp;（r[<span class="number">0</span>].key&lt;r[j].key);j=j-dk)</span><br><span class="line">                r[j+dk]=r[j];</span><br><span class="line">            r[j+dk]=r[<span class="number">0</span>]</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure></li><li><p>算法分析：</p><ul><li>时间复杂度是n和d的函数：<ul><li>$O(N^{1.25})到O(1.6n^{1.25})——经验公式 $</li><li>时间复杂度为$O(1)$</li><li>是一种<strong>不稳定</strong>的排序方法</li></ul></li></ul></li></ul><h3 id="8-3-交换排序">8.3 交换排序</h3><h4 id="8-3-1-冒泡排序">8.3.1 冒泡排序</h4><ul><li>基本思想：每趟不断将记录两两比较，并按“前小后大”规则交换</li></ul><figure class="highlight c"><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="type">void</span> <span class="title function_">bubble_sort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="type">int</span> m,j,i; RedType x;</span><br><span class="line">    <span class="keyword">for</span>(m=<span class="number">1</span>;m&lt;=n<span class="number">-1</span>;m++)&#123;</span><br><span class="line">        <span class="keyword">for</span>(j=<span class="number">1</span>j=n-m;j++)</span><br><span class="line">            <span class="keyword">if</span>(L.r[j].key&gt;L.r[j+<span class="number">1</span>].key)&#123;</span><br><span class="line">                x=L.r[j];</span><br><span class="line">                L.r[j]=L.r[j+<span class="number">1</span>];</span><br><span class="line">                r[j+<span class="number">1</span>]=x</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>改进的冒泡排序算法</p><figure class="highlight c"><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="type">void</span> <span class="title function_">bubble_sort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="type">int</span> m,j,i,flag=<span class="number">1</span>; RedType x; </span><br><span class="line">    <span class="keyword">for</span>(m=<span class="number">1</span>;m&lt;=n<span class="number">-1</span>&amp;&amp;flag==<span class="number">1</span>;m++)&#123;<span class="comment">//用flag作为是否有交换的标记</span></span><br><span class="line">        flag=<span class="number">0</span>；</span><br><span class="line">        <span class="keyword">for</span>(j=<span class="number">1</span>j=n-m;j++)</span><br><span class="line">            <span class="keyword">if</span>(L.r[j].key&gt;L.r[j+<span class="number">1</span>].key)&#123;</span><br><span class="line">           flag=<span class="number">1</span> <span class="comment">//若发生交换，flag置为1</span></span><br><span class="line">                x=L.r[j];</span><br><span class="line">                L.r[j]=L.r[j+<span class="number">1</span>];</span><br><span class="line">                r[j+<span class="number">1</span>]=x</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>冒泡排序算法评价</p><ul><li>冒泡排序最好时间复杂度是$O(n)$</li><li>冒泡排序最坏时间复杂度为$(n^2)$</li><li>冒泡排序平均时间复杂度为$O(n^2)$</li><li>冒泡排序算法中增加一个辅助空间temp，辅助空间为$S(n)=O(1)$</li><li>冒泡排序是<strong>稳定的</strong></li></ul></li></ul><h4 id="8-3-2-快速排序">8.3.2 快速排序</h4><ul><li><p>基本思想</p><ul><li>任取一个元素<strong>为中心</strong></li><li>所有比他<strong>小的元素一律前放</strong>，比他<strong>大的后放</strong>，形成<strong>左右两个子表</strong></li><li>对各子表重新选择中心元素并<strong>依此规则调整</strong></li><li>知道每个子表的元素<strong>只剩一个</strong></li></ul></li><li><p>快速排序演示</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365be2c458853aef8ea9c3.png"/></div></div></li><li><p>算法图解：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365be2c458853aef8eaa4a.png"/></div></div></li><li><p>排序算法：</p><figure class="highlight c"><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="type">int</span> <span class="title function_">partition</span><span class="params">(SqList &amp;L,<span class="type">int</span> low,<span class="type">int</span> high)</span>&#123;</span><br><span class="line">    L.r[<span class="number">0</span>]=L.r[low];<span class="comment">//用子表的第一个记录做枢轴记录</span></span><br><span class="line">    pivotkey=L.r[low].key;<span class="comment">//枢轴记录关键字保存在pivotkey中</span></span><br><span class="line">    <span class="keyword">while</span>(low&lt;high)&#123;<span class="comment">//从表的两端交替地向中间扫描</span></span><br><span class="line">        <span class="keyword">while</span>(low&lt;high&amp;&amp;L.[high].key&gt;=pivotkey)--high;</span><br><span class="line">        L.r[low]=L.r[high];<span class="comment">//将比枢轴记录小的移动到低端</span></span><br><span class="line">        <span class="keyword">while</span>(low&lt;high&amp;&amp;L.[low].key&gt;=pivotkey)++low;</span><br><span class="line">        L.r[high]=L.r[low];<span class="comment">//将比枢轴记录大的移动到高端</span></span><br><span class="line">    &#125;</span><br><span class="line">    L.r[low]=L.r[<span class="number">0</span>];<span class="comment">//枢轴记录到位</span></span><br><span class="line">    <span class="keyword">return</span> low;<span class="comment">//返回枢轴记录</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">QSort</span><span class="params">(SqList &amp;L,<span class="type">int</span> low,<span class="type">int</span> high)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(low&lt;high)&#123;</span><br><span class="line">        pivotloc=Partition(L,low,high);<span class="comment">//将L.r[low,high]一分为二。pivotloc是枢轴记录</span></span><br><span class="line">        QSort(L,low,pivotloc<span class="number">-1</span>);<span class="comment">//对左子表递归排序</span></span><br><span class="line">        QSort(L,pivotloc+<span class="number">1</span>,high);<span class="comment">//对右子表递归排序</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">QuickSort</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    QSort(L,<span class="number">1</span>,L.length);<span class="comment">//对顺序表L做快速排序</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>快速排序算法分析：</p><ul><li><p>时间复杂度</p><ul><li>平均计算时间是$O(nlog_2n)$</li><li>实验表明快速排序是我们所讨论的所有内排序方法中最好的一个。</li></ul></li><li><p>空间复杂度</p><ul><li>快速排序不是原地排序</li><li>平均情况下：需要$O(logn)$的栈空间</li><li>最坏情况下：栈空间可达$O(n)$</li></ul></li><li><p>快速排序不适于队原本有序或基本有序的记录序列进行排序</p></li></ul></li></ul><h3 id="8-4-选择排序">8.4 选择排序</h3><h4 id="8-4-1-简单选择排序">8.4.1 简单选择排序</h4><ul><li><p>基本思想：</p><ul><li>在待排序的数据中选出最大(小)的元素放在其最终的位置。</li></ul><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365be3c458853aef8eaac8.png"/></div></div></li><li><p>算法代码</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">SelectSort</span><span class="params">(SqList &amp;K)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;L.length;++i)&#123;</span><br><span class="line">        k=i;</span><br><span class="line">        <span class="keyword">for</span>(j=i+<span class="number">1</span>;j&lt;=L.length;j++)</span><br><span class="line">            <span class="keyword">if</span>(L.r[j].key&lt;L.r[k].key)k=j;<span class="comment">//记录最小值位置</span></span><br><span class="line">        <span class="keyword">if</span>(k!=i)&#123;</span><br><span class="line">            x=L.r[i];</span><br><span class="line">            L.r[i]=L.r[k];</span><br><span class="line">            L.r[k]=x;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>算法分析</p><ul><li>时间复杂度：$O(n^2)$</li><li>空间复杂度：$O(1)$</li></ul></li><li><p>简单选择排序是<strong>不稳定</strong>排序</p></li></ul><h4 id="8-2-2-堆排序">8.2.2 堆排序</h4><ul><li><p>若有n个元素${a_1,a_2,……,a_n}$满足<br>$$<br>\left{<br>\begin{array}{l}<br>a_i&lt;=a_{2i}\<br>a_i&lt;=a_{2i+1}\<br>\end{array}<br>\right.<br>\quad\text{或者}\quad<br>\begin{cases}<br>a_i&gt;=a_{2i}\<br>a_i&gt;=a_{2i+1}\<br>\end{cases}<br>$$</p><ul><li><p>则分别称该序列为小根堆和大根堆。</p></li><li><p>从堆 定义可以看出，堆实质是满足如下性质的完全二叉树：二叉树中任一非叶子结点均小于(大于)它的孩子结点</p></li></ul></li><li><p>若在输出<strong>堆顶</strong>的最小值 (最大值) 后，使得剩余n-1个元素的序列重又建成一个堆，则得到n个元素的次小值 (次大值) …如此反复，便能得到一个有序序列，这个过程称之为<strong>堆排序</strong>。</p></li><li><p>堆的调整</p><ul><li>小根堆<ol><li>输出堆顶元素之后，以堆中<strong>最后一个元素替代之</strong></li><li>.然后将根结点值与左、右子树的根结点值进行比较，并与其中<strong>小者进行交换</strong>，</li><li>重复上述操作，直至叶子结点，将得到新的堆，称这个从堆顶至叶子的调整过程为“<strong>筛选</strong>“</li></ol></li></ul></li><li><p>算法描述</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">HeapAdjust</span><span class="params">(elem R[],<span class="type">int</span> s, <span class="type">int</span> m)</span>&#123;</span><br><span class="line">    rc=R[s];</span><br><span class="line">    <span class="keyword">for</span>(j=<span class="number">2</span>*S;J&lt;=M;J*=<span class="number">2</span>)&#123;<span class="comment">//沿key较大的孩子结点向下筛选</span></span><br><span class="line">        <span class="keyword">if</span>(j&lt;m&amp;&amp;R[j]&lt;R[j+<span class="number">1</span>])++j;<span class="comment">//j为key较大的记录的下标</span></span><br><span class="line">        <span class="keyword">if</span>(rc&gt;=R[j])<span class="keyword">break</span>;</span><br><span class="line">        R[s]=R[j];</span><br><span class="line">        s=j;<span class="comment">//rc应插入在位置s上</span></span><br><span class="line">    &#125;</span><br><span class="line">    R[s]=rc;<span class="comment">//插入</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>堆的建立</p><ul><li><p>单结点的二又树是堆;</p></li><li><p>在完全二叉树中所有以叶子结点 (序号i &gt; n/2) 为根的子树是堆这样，我们只需依次将以序号为$n/2，n/2-1，…,1$的结点为根的子树均调整为堆即可。</p><p>从最后一个非叶子结点开始，以此向前调整</p><ol><li>调整从第$n/2$个元素开始，将以该元素为根的二叉树调整为堆</li><li>将以序号为$n/2 - 1$的结点为根的二叉树调整为堆</li><li>再将以序号为$n/2 - 2$的结点为根的二又树调整为堆</li><li>再将以序号为$n/2 - 3$的结点为根的二又树调整为堆</li></ol></li><li><p>通过以上分析可知：</p><ul><li>若对一个无序序列建堆，然后输出根；重复该过程就可以由一个无序序列输出有序序列。</li><li>实质上，堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的</li></ul></li></ul></li><li><p>堆排序算法</p><figure class="highlight c"><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"><span class="type">void</span> HeapSort（elem R[])&#123;<span class="comment">//对R[1]到R[n]进行堆排序</span></span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span>(i=n/<span class="number">2</span>;i&gt;=<span class="number">1</span>;i--)</span><br><span class="line">        HeapAdjust(R,i,n);<span class="comment">//建立初始堆</span></span><br><span class="line">    <span class="keyword">for</span>(i=n;i&gt;<span class="number">1</span>;i--)&#123;<span class="comment">//进行n-1趟排序</span></span><br><span class="line">        Swap(R[<span class="number">1</span>],R[i]);<span class="comment">//根与最后一个元素交换</span></span><br><span class="line">        HeapAdjust(R,<span class="number">1</span>,i<span class="number">-1</span>);<span class="comment">//对R[1]到R[i-1]重新建堆</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>算法分析</p><ul><li>初始化堆所需时间不超过O(n)</li><li>堆排序在最坏情况下，其时间复杂度也为$O(nlog_2 n)$，这是堆排序的最大优点。无论待排序列中的记录是正序还是逆序排列，都不会使堆排序处于&quot;最好”或&quot;最坏“的状态。</li></ul></li></ul><h3 id="8-5-归并排序">8.5 归并排序</h3><ul><li><p>基本思想：</p><ul><li>将两个或两个以上的有序子序列“<strong>归并</strong>”为一个有序序列</li><li>在内部排序中，通常采用的是<strong>2-路归并排序</strong>。</li></ul></li><li><p>排序示例：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365be3c458853aef8eab1a.png"/></div></div><ul><li>整个归并排序仅需[$log_2 n $]趟</li></ul></li><li><p>算法分析：</p><ul><li>时间效率：$O(nlog_2n)$</li><li>空间效率：$O(n)$、<ul><li>因为需要一个与原始序列同样大小的辅助序列。这正是此算法的缺点</li></ul></li><li>具有<strong>稳定</strong>性</li></ul></li></ul><h3 id="8-6-基数排序">8.6 基数排序</h3><ul><li><p>基本思想：</p><ul><li>分配+收集</li><li>也叫<strong>桶排序或箱排序</strong>: 设置若干个箱子，将关键字为k的记录放入第k个箱子，然后在按序号将非空的连接。</li></ul></li><li><p>算法示例：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365be3c458853aef8eac19.png"/></div></div><ul><li>将这组数据第一趟按照个位排，然后收集回来</li><li>接下来按照十位来排，接下来收集回来</li><li>最后按照百位来排，收集回来时，我们可以发现已经有序了</li></ul></li><li><p>算法分析</p><ul><li>时间效率：O(k*(n+m))<ul><li>k：关键字个数</li><li>m：关键字取值范围位m个值</li></ul></li></ul><p>总结：</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://pic.imgdb.cn/item/65365c3bc458853aef8fa7fa.png"/></div></div></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础--王卓老师十四周全部课程记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第七章(上篇)</title>
    <link href="https://www.fomal.cc/posts/15465ec5.html"/>
    <id>https://www.fomal.cc/posts/15465ec5.html</id>
    <published>2023-10-20T12:46:12.000Z</published>
    <updated>2023-10-23T10:01:44.307Z</updated>
    
    <content type="html"><![CDATA[<h2 id="7-查找">7 查找</h2><h3 id="7-1-查找的基本概念">7.1 查找的基本概念</h3><ul><li><p><strong>查找表</strong>：是由同一类型的数据元素（或记录）构成的<strong>集合</strong>。由于“集合”中的数据元素之间存在着某种松散的关系，因此查找表是一种应用灵便的结构</p><ul><li>查找表分两类<ul><li>静态查找表：仅做“查询“{检索)操作的查找表</li><li>动态查找表：做“插入”和“删除”操作的查找表</li></ul></li></ul></li><li><p><strong>关键字</strong>：关键字是数据元素（或记录） 中某个数据项的值，用它可以标识一个数据元素（或记录）。</p><ul><li>主关键字：此关键字可以<strong>唯一地</strong>标识一个<strong>记录</strong>，则称此关键字为主关键字</li><li>次关键字：用以识别若千记录的关键字为次关键字。</li></ul></li><li><p><strong>查找</strong>：根据给定的某个值，在查找表中确定一个其关键字等于给定值的数据元素或（记录）</p><ul><li>若查找表中存在这样一个记录，则称 <strong>查找成功</strong><ul><li>查找结果给出整个记录的<strong>信息</strong>，或指示该记录在查找表中的<strong>位置</strong>；</li></ul></li><li>否则称 <strong>查找不成功</strong><ul><li>查找结果给出“空记录”或“空指针”</li></ul></li><li>查找经常进行的操作<ul><li><strong>查询</strong>某个**“特定的”**数据元素是否在查找表中；</li><li><strong>检索</strong>某个**“特定的”**数据元素的各种属性；</li><li>在查找表中<strong>插入</strong>一个数据元素；</li><li><strong>删除</strong>查找表中的某个数据元素；</li></ul></li></ul></li><li><p>查找算法的评价指标：</p><ul><li><p>关键字的平均比较次数，也称平均查找长度。</p><p>$\LARGE ASL=\sum\limits_{i=1}^np_ic_i$    (关键字比较次数的期望值)</p><ul><li>n：记录的个数</li><li>pi：查找第i个记录的概率（通常认为pi=1/n）</li><li>ci：找到第i个记录所需的比较次数</li></ul></li></ul></li></ul><h3 id="7-2-线性表的查找">7.2 线性表的查找</h3><h4 id="7-2-1-顺序查找">7.2.1 顺序查找</h4><ul><li><p>应用范围：</p><ul><li>顺序表或线性链表表示的静态查找表</li><li>表内元素之间无序</li></ul></li><li><p>数据元素类型定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    KeyType key;</span><br><span class="line">    ……</span><br><span class="line">&#125;ElemType;</span><br></pre></td></tr></table></figure></li><li><p>顺序表的定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//顺序结构表结构类型定义</span></span><br><span class="line">    ElemType *R;<span class="comment">//表基址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//表长</span></span><br><span class="line">&#125;SSTable;</span><br><span class="line">SSTable ST;<span class="comment">//定义顺序表ST</span></span><br></pre></td></tr></table></figure></li><li><p>顺序查找</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">Search_seq</span><span class="params">(SSTable st,keytype key)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=ST.length;i&gt;=<span class="number">1</span>;--i)</span><br><span class="line">        <span class="keyword">if</span> (ST.R[i].key==key)<span class="keyword">return</span> i;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>改进：把待查关键字key存入表头（”哨兵“）从后往前挨个比较，可免去查找过程中每一步都要检查是否查找完毕，加快速度</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">search_seq</span><span class="params">(SSTable ST,KeyType key)</span>&#123;</span><br><span class="line">    ST.R[<span class="number">0</span>].key=key;</span><br><span class="line">    <span class="keyword">for</span>(i=ST.length;ST.R[i].key!=key;--i);</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>比较次数与key位置有关：</p><ul><li>查找第i个元素，需要比较n-i+1次</li><li>查找失败，需要比较n+1次</li></ul></li><li><p>时间复杂度O(n)</p><ul><li><p>查找成功时的平均查找长度，设表中各记录查找概率相等</p><p>$ASL=（1+2+……+n）/n=\dfrac {n+1}{2}$</p></li></ul></li><li><p>空间复杂度：一个辅助空间——O(1)</p></li></ul></li></ul></li><li><p>顺序查找的特点</p><ul><li>优点：算法简单，逻辑次序无要求，且不同存储结构均适用</li><li>缺点：ASL太长，时间效率太低</li></ul></li></ul><h4 id="7-2-2-折半查找">7.2.2 折半查找</h4><ul><li><p>算法步骤</p><ol><li>设表长为n，low、high和mid分别指向待查元素所在区闻的上界，下界和中点，key为给定的要查找的值；</li><li>初始时，令low=1，high=n，</li><li>让k与mid指向的记录比较<ol><li>若key==R[mid].KEY,查找成功</li><li>若key&lt;R[mid].key,则high=mid-1</li><li>若key&gt;R[mid].key,则low=mid-1</li></ol></li></ol></li><li><p>折半查找</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">search_bIN</span><span class="params">(SSTable ST,KeyType key)</span>&#123;</span><br><span class="line">    low=<span class="number">1</span>;high=ST,length;</span><br><span class="line">    <span class="keyword">while</span>(low&lt;=high)&#123;</span><br><span class="line">        mid=(low+high)/<span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span>(ST.R[mid].key==KEY)<span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            <span class="keyword">if</span>(key&lt;ST.R[mid].key)</span><br><span class="line">                high=mid<span class="number">-1</span>;</span><br><span class="line">            <span class="keyword">else</span> low=mid+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231016165250.png" alt=""></p></li><li><p>$\large ASL=1/11*(1<em>1+2</em>2+4<em>3+4</em>4)=33/11=3$</p></li><li><p>平均查找长度ASL（成功时）</p><ul><li><p>$设表长为n=2^h-1,则h=log_2(n+1)，且表中每个记录的查找概率相等：p_i=1/n$</p><p>$则\begin{align}<br>ASL_{bs}&amp;=\dfrac {1}{n} \sum\limits ^{n}_{j=1}j*2^{j-1}\<br>&amp;=\dfrac{n+1}{n}log_2(n+1)-1\<br>&amp;≈log_2(n+1)-1(n&gt;50)\end{align}$</p></li><li><p>折半查找的性能分析</p><ul><li>优点：效率比顺序查找高</li><li>缺点：只适用于有序表，且限于顺序存储结构(对线性链表无效)</li></ul></li></ul></li></ul></li></ul><h4 id="7-2-3-分块查找">7.2.3 分块查找</h4><ul><li><p>分块查找 (Blocking Search) 又称索引顺序查找，这是一种性能介于顺序查找和折半查找之间的一种查找方法。</p><ol><li>将表分成几块,且表或者有序，或者<strong>分块有序</strong></li><li>建立“索引表”（每个结点含有最大关键字域和指向本块第一个结点的指针，且按关键字有序）。</li></ol></li><li><p>查找过程：先确定待查记录所在块，再在块内查找</p></li><li><p>查找效率：$ASL=L_b+L_w$</p></li><li><p>$\large ALS_{bs}≈log_2(\dfrac{n}{s}+1)+\dfrac {s}{2}$   s为每块内部的记录个数，n/s即块内数目</p></li><li><p>分块查找的特点</p><ul><li>优点：插入和删除比较容易</li><li>缺点：要增加一个索引表的存储空间并对初始索引表进行排序运算</li><li>适用情况：线性表既要快速查找又要经常动态变化</li></ul></li></ul><table><thead><tr><th></th><th>顺序查找</th><th>折半查找</th><th>分块查找</th></tr></thead><tbody><tr><td>ASL</td><td>最大</td><td>最小</td><td>中间</td></tr><tr><td>表结构</td><td>有序表、无序表</td><td>有序表</td><td>分块有序</td></tr><tr><td>存储结构</td><td>顺序表、线性链表</td><td>顺序表</td><td>顺序表、线性链表</td></tr></tbody></table><h3 id="7-3-树表的查找">7.3 树表的查找</h3><ul><li>当表插入、删除操作频繁时，为了维护表的有序性，需要移动表中很多记录<ul><li><strong>改用动态查找表——几种特殊的树</strong></li><li>表结构在<strong>查找过程中动态生成</strong></li><li>对于给定值key，若表中存在，则成功返回；<strong>否则，插入关键字等于key的记录</strong><ul><li><strong>二叉排序树</strong><br><strong>平衡二叉树</strong><br>红黑树<br>B-树<br>B+树<br>键树</li></ul></li></ul></li></ul><h4 id="7-3-1-二叉排序树">7.3.1 二叉排序树</h4><ul><li><p>二叉排序树又称为二叉搜索树、二叉查找树</p><ul><li>二叉排序树或是空树，或是满足如下性质的二叉树；<ol><li>若其<strong>左子树非空</strong>，则左子树上所有结点的值均<strong>小于</strong>根结点的值；</li><li>若其<strong>右子树非空</strong>，则右子树上所有结点的值均<strong>大于</strong>根结点的值；</li><li>其左右子树本身又各是一棵二叉排序树</li></ol></li><li>二叉排序树的性质<ul><li>中序遍历非空的二又排序树所<strong>得到的数据元素序列是</strong>一个按关键字排列的<strong>递增有序序列</strong>。</li></ul></li></ul></li><li><p>二叉排列树的操作——查找</p><ul><li><p>若查找的关键字等于根结点，成功</p></li><li><p>否则</p><ul><li>小于根结点，查左子树</li><li>大于根结点，查右子树</li></ul></li><li><p>在左右子树上的操作类似</p></li></ul></li><li><p>算法描述</p><figure class="highlight c"><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">BSTree <span class="title function_">SearchBST</span> <span class="params">(BSTree_T,KeyType key)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> ((!T)||key==T-&gt;data.key) <span class="keyword">return</span> T;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (key&lt;T-&gt;data.key)</span><br><span class="line">        <span class="keyword">return</span> SearchBST(T-&gt;lchild,key);</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">return</span> SearchBST (T-&gt;rchild,key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>二叉排序树的查找分析</p><ul><li><p>含有n个结点的二叉排列树的平均查找长度和树的形态有关</p><ul><li><p>最好的情况：</p><p>二叉排列树的形态和折半查找的判定树相似:</p><p>$ASL=log_2(n+1)-1$<br>$O(log_2n)$</p></li><li><p>最坏情况</p><p>二叉排序树的形态为单支树，树的深度为n，</p><p>$ASL=\dfrac {n+1}{2}$</p><p>$O(n)$</p></li></ul></li></ul></li><li><p>二叉排序树的操作——插入</p><ul><li>若二叉排序树为空，则插入结点作为根结点插入到空树中</li><li>否则，继续在左右子树上查找<ul><li>树中已有，不再插入</li><li>树中没有<ul><li>查找直至某个叶子结点的左子树或右子树为空为止，则插入结点应为该叶子结点的左孩子或右孩子</li></ul></li></ul></li><li>插入的元素一定在叶子结点上</li></ul></li><li><p>二叉排序树的操作——生成</p><ul><li>一个无序序列可通过构造二叉排序树而变成一个有序序列。构造树的过程就是对无序序列进行排序的过程。</li><li>插入的结点均为叶子结点，故无序移动其他结点。相当于在有序序列上插入记录儿无需移动其他记录</li><li>不同的插入次序的序列生成不同形态的二叉排序树</li></ul></li><li><p>二叉排序树的操作——删除</p><ul><li>被删除的结点是叶子结点：直接删去该结点</li><li>被删除的结点只有左子树或右子树，用其左子树或右子树替换它</li><li>被删除的既有左子树，也有右子树，以其中序前驱值替换之（前驱是左子树中最大的结点），然后再删除该前驱结点<ul><li>也可以用其后继替换之（后继是右子树中最小结点），然后再删除该后继结点</li></ul></li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第七章(上篇)--王卓老师第十二周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://www.fomal.cc/posts/0.html"/>
    <id>https://www.fomal.cc/posts/0.html</id>
    <published>2023-10-18T13:12:16.000Z</published>
    <updated>2023-10-23T10:01:44.317Z</updated>
    
    <content type="html"><![CDATA[<h2 id="6-图">6 图</h2><p><em><strong>图是一种比线性表和树更为复杂的数据结构。</strong></em></p><h3 id="6-1-图的定义和基本术语">6.1 图的定义和基本术语</h3><h4 id="6-1-1-图的定义">6.1.1 图的定义</h4><ul><li><p>图：G=（V.E）</p><ul><li>V：顶点（数据元素）的<strong>有穷非空</strong>集合；</li><li>E：边的<strong>有穷</strong>集合</li></ul></li><li><p>无向图：每条边都是无方向的</p></li><li><p>有向图：每条边都是有方向的</p></li><li><p>完全图：任意两个点都有一条边相连</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012174252.png" alt=""></p></li><li><p>稀疏图：有很少边或弧的图（$e&lt;nlog_2n$）</p></li><li><p>稠密图：有较多边或弧的图</p></li></ul><h4 id="6-1-2-图的基本术语">6.1.2 图的基本术语</h4><ul><li><p>网，边/弧带权的图</p></li><li><p>邻接：有边/弧相连的两个顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）,则称为$v_i和v_j$互为<strong>邻接点</strong></li><li>存在&lt;$v_i,v_j$&gt;，则称$v_i$邻接到$v_j$,$v_j$邻接于$v_i$,</li></ul></li><li><p>关联（依附）：边/弧与顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）/&lt;$v_i,v_j$&gt;，则称该边/弧关联于$v_i$和$v_j$</li></ul></li><li><p>顶点的度：与该顶点相关联的边的数目，记为TD（v）</p><ul><li><p>在<strong>有向图</strong>中，顶点的度等于该顶点的<strong>入度</strong>和<strong>出度</strong>之和。</p></li><li><p>顶点<strong>v的入度</strong>是以v为终点的有向边的条数，记作ID（v）</p></li><li><p>顶点<strong>v的出度</strong>是以v为始点的有向边的条数，记作OD（v）</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012175741.png" alt=""></p></li></ul></li><li><p>路径：连续的边构成的顶点序列</p></li><li><p>路径长度：路径上边或弧的数目/权值之和</p></li><li><p>回路（环）：第一个顶点和最后一个顶点相同的路径</p></li><li><p>简单路径：除路径起点和终点可以相同外，其余顶点均不相同的路径</p></li><li><p>简单回路（环）：除路径起点和终点相同外，其余顶点均不相同的路径</p></li><li><p>连通图（强连通图）：在无（有）向图G=(V,{E})中，若对任意两个顶点v、u都存在从v到u的路径，则称G是<strong>连通图</strong>（强连通图）</p></li><li><p>权与网：</p><ul><li>图中边或弧所具有的相关数称为<strong>权</strong>，表明从一个顶点到另一个顶点的距离或耗费。</li><li>带权的图称为<strong>网</strong></li></ul></li><li><p>子图：设有两个图$G=（V，{E}）、G_1=(V_1,{E_1})$,若$V_1属于V，E_1属于E$则称$G_1是G$的子图</p></li><li><p>连通分量（强连通分量）</p><ul><li><p>无向图G的<strong>极大联通子图</strong>称为G的<strong>连通分量</strong></p><ul><li>极大连通子图意思是：该子图是G联通子图，将G的任何不在该子图汇总的顶点加入，子图不再连通</li></ul></li><li><p>有向图G的<strong>极大连通子图</strong>称为G的<strong>强连通分量</strong></p><ul><li>极大强连通子图意思是: 该子图是G的强连通子图，将D的任何不在该子图中的顶点加入，子图不再是强连通的。</li></ul></li><li><p>极小联通子图：该子图是G的连通子图，在该子图中删除任何一条边，联通子图不再连通</p></li><li><p>生成树:包含无向图G所有顶点的极小连通子图</p></li><li><p>生成森林:对非连通图，由各个连通分量的生成树的集合</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012201656.png" alt=""></p></li></ul></li></ul><h3 id="6-2-案例引入">6.2 案例引入</h3><h4 id="6-2-1-六度空间理论">6.2.1 六度空间理论</h4><p><strong>理论又称作六度分隔论 (Six Degrees of Separation)。六度空间理论是20世纪60年代由美国的心理学家米格兰姆(Stanley Milgram)提出的，理论指出：“<em>你和任何一个陌生人之间所间隔的人不会超过六个</em>”</strong></p><h3 id="6-3-图的类型定义">6.3 图的类型定义</h3><p>图的抽象数据类型定义如下：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT Graph&#123;</span><br><span class="line">    数据对象V：具有相同特性的数据元素的集合，称为顶点集。</span><br><span class="line">    数据关系R：R&#123;VR&#125;</span><br><span class="line">    VR=&#123;&lt;V,W&gt;|&lt;V,W&gt;|V,W属于V^p(v,w)&#125;， &lt;v, w&gt;表示从v到w的弧，P (v, w)定义了弧&lt;v, w&gt;的信息</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>基本操作：</p><ul><li><p>CreateGraph{&amp;G,V,VR}</p><p>初始条件：V是图的顶点集，VR是图中弧的集合。</p><p>操作结果：按V和VR的定义<strong>构造图G</strong>。</p></li><li><p>DFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行<strong>深度优先遍历</strong>，在遍历过程中对每个顶点访问一次。</p></li><li><p>BFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行广度优先遍历，在遍历过程中对每个顶点访问一次.</p></li></ul></li></ul><h3 id="6-4-图的存储结构">6.4 图的存储结构</h3><ul><li>图的逻辑结构：多对多<ul><li>图没有顺序存储结构，可以借助二维数组来表示元素间的关系</li></ul></li><li>数组表示法（邻接矩阵）</li><li>链式存储结构：<ul><li>多重链表<ul><li>邻接表</li><li>邻接多重表</li><li>十字链表</li></ul></li></ul></li><li>重点介绍：<ul><li><strong>邻接矩阵</strong>（数组）表示法</li><li><strong>邻接表</strong>（链式）表示法</li></ul></li></ul><h4 id="6-4-1-邻接矩阵">6.4.1 邻接矩阵</h4><ul><li><p>数组（邻接矩阵）表示法</p><ul><li><p>建立一个顶点表（记录各个顶点信息）和一个邻接矩阵（表示各个顶点之间关系）。</p><ul><li><p>设图A=（V，E)有n个顶点，则</p></li><li><p>顶点表Vexs[n]</p><table><thead><tr><th>i</th><th>1</th><th>2</th><th>3</th><th>……</th><th>n-1</th></tr></thead><tbody><tr><td>Vexs[n]</td><td>$V_1$</td><td>$V_2$</td><td>$V_3$</td><td>……</td><td>$V_n$</td></tr></tbody></table></li><li><p>图的邻接矩阵是一个二维数组$A.arcs[n][n]$,定义为：</p><ul><li><h6 id="A-arcs-i-j-quad-begin-cases-1-如果-v-i-v-j-或-v-i-v-j-in-E-0，反之-end-cases">$A.arcs[i][j]=\quad\begin{cases}1,如果&lt;v_i,v_j&gt;或(v_i,v_j)\in E\0，反之\end{cases}$</h6></li></ul></li><li><p>无向图的邻接矩阵表示法：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013142744.png" alt=""></p><ol><li>无向图的邻接矩阵是<strong>对称</strong>的</li><li>顶点i的度=第i行（列）中1的个数；</li><li>完全图的邻接矩阵中，对角元素为0，其余1.</li></ol></li><li><p>有向图的邻接矩阵表示法：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013143343.png" alt=""></p><ol><li>有向图的邻接矩阵<strong>可能是不对称</strong>的</li><li>顶点的<strong>出度=第i行元素之和</strong></li><li>顶点的<strong>入度=第i列元素之和</strong></li><li>顶点的<strong>度=第i行和列的元素之和</strong></li></ol></li><li><p>网（即有权图）的邻接矩阵表示法</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013145659.png" alt=""></p></li><li><p>邻接矩阵的存储表示：用<strong>两个数组</strong>分别存储<strong>顶点表和邻接矩阵</strong></p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> Maxint 32767<span class="comment">//表示极大值</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MVNum 100 <span class="comment">//最大顶点数</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">char</span> VerTexType;<span class="comment">//设顶点的数据类型为字符型</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> ArcType;<span class="comment">//假设边的权值类型为整形</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>｛ </span></span><br><span class="line"><span class="class">    <span class="title">VerTexType</span> <span class="title">vexs</span> [<span class="title">MVNum</span>];</span><span class="comment">//顶点表</span></span><br><span class="line">ArcType arcs[MVNum) [MVNum];<span class="comment">//邻接矩阵</span></span><br><span class="line">    <span class="type">int</span> vexnum,arcnum;<span class="comment">//图的当前点数和边数</span></span><br><span class="line">&#125;AMGraph;</span><br></pre></td></tr></table></figure></li></ul></li><li><p>采用邻接矩阵表示法创建无向网</p><ol><li>输入总顶点数和总边数。</li><li>依次输入点的信息存入顶点表中。</li><li>初始化邻接矩阵， 使每个权值初始化为极大值。</li><li>构造邻接矩阵。</li></ol><figure class="highlight c"><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">Status <span class="title function_">CreateUDN</span><span class="params">(AMGraph &amp;G)</span>&#123;<span class="comment">//创建无向网G</span></span><br><span class="line">    <span class="built_in">cin</span>&gt;&gt;G.vexnum&gt;&gt;G.arcnum;<span class="comment">//输入总顶点数，总边数</span></span><br><span class="line">    <span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)</span><br><span class="line"><span class="built_in">cin</span>&gt;&gt;G.vexs[i];<span class="comment">//依次输入点的信息</span></span><br><span class="line"><span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)<span class="comment">//初始化邻接矩阵呢</span></span><br><span class="line"><span class="keyword">for</span> (j=<span class="number">0</span>; j&lt;G.vexnum;++j) </span><br><span class="line">G.arcs[i][j]=Maxint;<span class="comment">//变得权值均置为最大</span></span><br><span class="line"><span class="keyword">for</span>(k=O;k&lt;G.arcnum;++k)&#123;<span class="comment">//构造邻接矩阵</span></span><br><span class="line">        <span class="built_in">cin</span>&gt;&gt;vl&gt;&gt;v2&gt;&gt;w;<span class="comment">//输入一条边所依附的顶点及边的权值</span></span><br><span class="line">        i=LocateVex(G,vl);j=LocateVex(G,v2);<span class="comment">//确定V1和V2在G中的位置</span></span><br><span class="line">        G.arcs[i][j]=w;<span class="comment">//边&lt;v1,v2&gt;的权值置为w</span></span><br><span class="line">        G.arcs[j][i]=G.arcs[i][j];<span class="comment">//置&lt;v1,v2&gt;的对称边&lt;v2,v1&gt;的权值为w</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>补充算法：在图中查找顶点：</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">LocateVex</span><span class="params">(AMGraph G,VertexType u)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;G.vexnum;++i)</span><br><span class="line">        <span class="keyword">if</span>(u==G.vexs[i]) <span class="keyword">return</span> i;</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></li></ul></li><li><p>邻接矩阵的优点</p><ul><li>直观简单好理解</li><li>方便检查任意一对顶点间是否存在边</li><li>方便找任一顶点的所有“邻接点”</li><li>方便计算任一顶点的“度”</li></ul></li><li><p>邻接矩阵的缺点</p><ul><li>不便于增加和删除顶点</li><li>浪费空间——存稀疏图有大量无效元素<ul><li>对稠密图来说还是很合算的</li></ul></li><li>浪费时间——统计稠密图中一共有多少条边</li></ul></li></ul><h4 id="6-4-2-邻接表">6.4.2 邻接表</h4><ol><li><p>无向图邻接表表示法（链式）</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013154441.png" alt=""></p><ul><li><strong>顶点</strong>：按编号顺序将顶点数据存储在一维数组中；</li><li><strong>关联同一顶点的边</strong>（以顶点为尾的弧）：<ul><li>用线性<strong>链表</strong>存储</li></ul></li><li>特点：<ul><li>邻接表不唯一</li><li>若无向图中有n个顶点、e条边，则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图</li><li>无向图中顶点$V_i$的度为第i个单链表中的结点数</li></ul></li></ul></li><li><p>有向图邻接表表示法</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013155951.png" alt=" "></p><ul><li>特点：<ul><li>顶点$V_i$的<strong>出度</strong>为第i个单链表中的结点个数</li><li>顶点的入度为整个链表中领接点域值是i-1的结点个数</li></ul></li></ul></li><li><p>图的邻接表存储表示：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">VNode</span>&#123;</span></span><br><span class="line">    VerTexType data;<span class="comment">//顶点信息</span></span><br><span class="line">    ArcNode *firstarc;<span class="comment">//指向第一条依附该顶点的边的指针</span></span><br><span class="line">&#125;VNode,AdjList[MVNum]<span class="comment">//AdjList表示邻接表类型</span></span><br></pre></td></tr></table></figure><p>弧（边）的结点结构    adjvex | nextarc | info</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MVNum 100<span class="comment">//最大顶点数</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">ArcNode</span>&#123;</span><span class="comment">//边结点</span></span><br><span class="line">    <span class="type">int</span> adjvex;<span class="comment">//该边所指向的顶点的位置</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ArcNode</span> * <span class="title">nextarc</span>;</span><span class="comment">//指向下一条边的指针</span></span><br><span class="line">    OtherInfo info;<span class="comment">//和边相关的信息</span></span><br><span class="line">&#125;ARcNode;</span><br></pre></td></tr></table></figure><p>图的结构定义：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    AdjList vertices;<span class="comment">//vertices——vertex的复数</span></span><br><span class="line">    <span class="type">int</span> vexnum,arcnum;<span class="comment">//图的当前顶点数和弧数</span></span><br><span class="line">&#125;ALGraph;</span><br></pre></td></tr></table></figure><p>邻接表操作举例说明：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013161315.png" alt=""></p></li><li><p>无向图的邻接表表示 p118</p></li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">CreateUDG</span><span class="params">(ALGraph &amp;G)</span> &#123;<span class="comment">//采用邻接表表示法， 创建无向图 G</span></span><br><span class="line"><span class="built_in">cin</span>&gt;&gt;G.vexnum&gt;&gt;G.arcnum;<span class="comment">//输入总顶点数，总边数</span></span><br><span class="line"><span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)&#123;<span class="comment">//输入各点，构造表头结点表</span></span><br><span class="line"><span class="built_in">cin</span>»G.vertices[i) .data;<span class="comment">//输入顶点值</span></span><br><span class="line">G.vertices[i) .firstarc=<span class="literal">NULL</span>;&#125;<span class="comment">//初始化表头结点的指针域</span></span><br><span class="line"><span class="keyword">for</span>(k=O;k&lt;G.arcnum;++k)&#123;<span class="comment">//输入各边，构造邻接表</span></span><br><span class="line">        <span class="built_in">cin</span>&gt;&gt;vl&gt;&gt;v2;<span class="comment">//输入一条边依附的两个顶点</span></span><br><span class="line">        i=LocateVex(G,vl);</span><br><span class="line">        j=LocateVex(G,v2);</span><br><span class="line">        pl=new ArcNode;<span class="comment">//生成一个新的边结点*p1</span></span><br><span class="line">        pl-&gt;adjvex=j;<span class="comment">//邻接点序号为j</span></span><br><span class="line">        pl-&gt;nextarc=G.vertices[i].firstarc;</span><br><span class="line">        G.vertices[i].firstarc=pl;<span class="comment">//将新结点*p1插入顶点vi的边表头部（头插法）</span></span><br><span class="line">        p2=new ArcNode;<span class="comment">//生成另一个对称的新的边结点*p2</span></span><br><span class="line">        p2-&gt;adjvex=i;<span class="comment">//邻接点序号为i</span></span><br><span class="line">        p2-&gt;nextarc=G.vertices[j].firstarc;</span><br><span class="line">        G.vertices[j].firstarc=p2;<span class="comment">//将心结点*p2插入顶点vj的边表头部</span></span><br><span class="line">    &#125;</span><br><span class="line">     <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>无向图邻接表特点：</p><ol><li>方便找任一顶点的所有“邻接点”</li><li>节约稀疏图的空间<ol><li>需要n个头指针和+2e个结点（每个结点至少2个域）</li></ol></li><li>方便计算任一顶点的“度”<ol><li>对无向图：是的</li><li>对有向图：只能计算“出度”需要构造“逆邻接表”来方便计算“入度”</li></ol></li><li>不方便检查任意一对顶点间是否存在边</li></ol></li><li><p>邻接矩阵与邻接表表示法的关系</p><ol><li>联系：邻接表中每个链表对应于接短阵中的一行，链表中结点个数等于一行中非零元素的个数。</li><li>区别：<ol><li>对于任一确定的无向图，邻接矩阵是唯一的 (行列号与顶点编号一致)，但邻接表不唯一 (链接次序与顶点编号无关)</li><li>邻接矩阵的空间复杂度为$O（n^2)$,而邻接表的空间复杂度为$O(n+e)$。(e是出于$0到n^2$之间的复杂变量)</li></ol></li><li>用途：邻接矩阵多用于稠密图；而邻接表多用于稀疏图</li></ol></li></ul><h4 id="6-4-3-十字链表">6.4.3 十字链表</h4><p>有向图：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014103949.png" alt=""></p><ul><li>顶点结点：</li></ul><table><thead><tr><th>data</th><th>firstin</th><th>firstout</th></tr></thead><tbody><tr><td>存与顶点相关的信息</td><td>入度边</td><td>出度边</td></tr></tbody></table><ul><li>弧结点：</li></ul><table><thead><tr><th>tailvex</th><th>headvex</th><th>hlink</th><th>tlink</th></tr></thead><tbody><tr><td>弧尾位置</td><td>弧头位置</td><td>弧头相同的下一条弧</td><td>弧尾相同的下一条弧</td></tr></tbody></table><p><em><strong>弧头是箭头，弧尾是箭尾</strong></em></p><h4 id="6-4-4-邻接多重表">6.4.4 邻接多重表</h4><p>边结点：</p><table><thead><tr><th>mark</th><th>ivex</th><th>ilink</th><th>jvex</th><th>jlink</th><th>info</th></tr></thead><tbody><tr><td>标志域，标记此边是否被搜索过</td><td>该边依附的两个顶点在表头数组中的位置</td><td>指向依附于ivex的下一条边</td><td>该边依附的两个顶点在表头数组中的位置</td><td>指向依附于jvex的下一条边</td><td></td></tr></tbody></table><p>顶点节点</p><table><thead><tr><th>data</th><th>firstedge</th></tr></thead><tbody><tr><td>存与顶点有关的信息</td><td>指向第一条依附于该顶点的边</td></tr></tbody></table><h3 id="6-5-图的遍历">6.5 图的遍历</h3><ul><li><p>遍历的实质：找每个顶点的邻接点的过程</p></li><li><p>图常用的遍历：</p><ul><li>深度优先搜索(DFS)</li><li>广度优先搜索(BFS)</li></ul></li></ul><h4 id="6-5-1-深度优先搜索">6.5.1 深度优先搜索</h4><p>甚多有限搜索遍历过程：</p><ol><li>从图中某个顶点v出发， 访问v。</li><li>找出刚访问过的顶点的第一个未被访问的邻接点， 访问该顶点。 以该顶点为新顶点，重复此步骤， 直至刚访问过的顶点没有未被访问的邻接点为止。</li><li>返回前一个访问过的且仍有未被访问的邻接点的顶点，找出该顶点的下一个未被访问的邻接点， 访问该顶点。</li><li>重复步骤 (2)和(3), 直至图中所有顶点都被访问过，搜索结束。</li><li><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014115016.png" alt=""></li></ol><h5 id="6-5-1-1-采用邻接矩阵表示图的深度优先搜索遍历">6.5.1.1 采用邻接矩阵表示图的深度优先搜索遍历</h5><figure class="highlight c"><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"><span class="type">void</span> <span class="title function_">DFS</span><span class="params">(AMGraph G,<span class="type">int</span> v)</span>&#123;<span class="comment">//图G为邻接矩阵类型</span></span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;v;visited[v]=<span class="literal">true</span>;<span class="comment">//访问第v个顶点</span></span><br><span class="line">    <span class="keyword">for</span>(w=<span class="number">0</span>;w&lt;G.vexnum;w++)<span class="comment">//依次检查邻接矩阵v所在行</span></span><br><span class="line">        <span class="keyword">if</span>((G.arcs[v][w]!=<span class="number">0</span>)&amp;&amp;(!visited[w]))</span><br><span class="line">            DFS(G,w);<span class="comment">//w是v邻接点，如果w未访问，则递归调用DFS</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>DFS算法效率分析<ul><li>用邻接矩阵来表示图，遍历图中每一个顶点都要<strong>从头扫描</strong>该顶点所在行，时间复杂度为$O(n^2)$</li><li>用邻接表来表示图，虽然有<strong>2e</strong>个表结点，但只需扫描 <strong>e</strong> 个结点即可完成遍历，加上访问 <strong>n</strong>个头结点的时间，时间复杂度为$O(n+e)$</li></ul></li><li>结论<ul><li>稠密图适合在邻接矩阵上进行深度遍历</li><li>稀疏图适合在邻接表上进行深度遍历</li></ul></li></ul><h4 id="6-5-2-广度优先搜索">6.5.2 广度优先搜索</h4><ul><li>用队列实现广度优先遍历，累次层次遍历那样</li></ul><h5 id="6-5-2-1-按广度优先非递归遍历连通图G">6.5.2.1 按广度优先非递归遍历连通图G</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">BFS</span> <span class="params">(Graph G,<span class="type">int</span> v)</span>&#123;</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;v;visited[v]=<span class="literal">true</span>;<span class="comment">//访问第v个顶点</span></span><br><span class="line">    initQueue(Q);<span class="comment">//辅助队列Q初始化，置空</span></span><br><span class="line">    EnQueue(Q,v);<span class="comment">//v进队</span></span><br><span class="line">    <span class="keyword">while</span>(!QueueEmpty(Q))&#123;<span class="comment">//队列非空</span></span><br><span class="line">        DeQueue(Q,u);<span class="comment">//队头元素出队并置为u</span></span><br><span class="line">        <span class="keyword">for</span>(W=FirstAdjVex(G,u);w&gt;=<span class="number">0</span>;w=NextAdjVex(G,u,w))</span><br><span class="line">            <span class="keyword">if</span>(!visited[w])&#123;<span class="comment">//w为u的尚未访问的邻接顶点</span></span><br><span class="line">                <span class="built_in">cout</span>&lt;&lt;w;visited[w]=<span class="literal">true</span>; EnQueue(Q,w);<span class="comment">//w进队</span></span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>BFS算法效率分析<ul><li>如果邻接矩阵，则BFS对于每一个背访问到的顶点，都要循环检测矩阵中的整整一行（n个元素），总的时间代价为$O(n^2)$</li><li>用邻接表来表示图，虽然有 2e 个表结点，但只需扫描 e 个结点即可完成遍历，加上访问 n个头结点的时间，时间复杂度为$O(n+e)$</li></ul></li><li>DFS和BFS算法效率比较<ul><li>空间复杂度相同，都是O(n)（借用了堆栈或队列）；</li><li>时间复杂度只与存储结构（邻接矩阵或邻接表）有关，而与搜索路径无关。</li></ul></li></ul><h3 id="6-6-图的应用">6.6 图的应用</h3><h4 id="6-6-1-最小生成树">6.6.1 最小生成树</h4><ul><li><p>生成树：所有顶点均由边连接在一起，但不存在回路的图</p><ul><li><p>一个图可以有许多棵不同的生成树</p></li><li><p>所有生成树具有以下共同特点</p><ul><li>生成树的顶点个数与图的<strong>顶点个数相同</strong></li><li>生成树是图的<strong>极小连通子图</strong>，去掉一条边则非连通</li><li>一个有<strong>n</strong>个顶点的连通图的生成树有<strong>n-1</strong>条边</li><li><strong>在生成树中再加一条边必然形成回路</strong></li><li>生成树中任意两个顶点间的<strong>路径是唯一</strong>的</li></ul></li></ul></li><li><p>无向图的生成树：</p><ul><li>设图G=(V,E)是个连通图，当从图任一顶点出发遍历图G时，将边集E(G)分成两个集合T(G) 和B(G)。其中 T(G)是遍历图时所经过的边的集合，B(G)是遍历图时未经过的边的集合。显然，G1(V,T)是图G的极小连通子图。即子图G1 是连通图G的生成树</li></ul></li><li><p>最小生成树</p><ul><li><p>给定一个无向网络，在该网络的所有生成树中，使得<strong>各边权值之和最小</strong>的那棵生成树称为该网的<strong>最小生成树</strong>，也叫<strong>最小代价生成树</strong>。</p></li><li><p>最小生成树的典型用途</p><ul><li>要在n个城市间建立通信网，则n个城市应铺设n-1条路，每条路也有对应的经济成本</li><li>建立数学模型：<ul><li>顶点代表城市有n个</li><li>边代表线路有n-1条</li><li>边的权值  表示线路的经济代价</li><li>连通网    表示n个城市间的通信网</li></ul></li></ul></li></ul></li><li><p>构造最小生成树</p><ul><li>构造最小生成树的算法很多，其中多数算法都利用了<strong>MST的性质</strong></li><li>MST性质：设N=（V,E) 是一个连通网，U是顶点集V的一个非空子集。若边(u,v) 是一条具有最小权值的边，其中$u\in U，v \in V-U$则必存在一棵包含边(u,v)的最小生成树</li><li>在生成树的构造过程中，图中n个顶点分属两个集合：<ul><li>已落在生成树上的顶点集: U</li><li>尚未落在生成树上的顶点集: V-U</li></ul></li><li>接下来则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014180317.png" alt=""></p></li></ul><h5 id="6-6-1-1-普利姆-prim-算法">6.6.1.1 普利姆(prim)算法</h5><ul><li><p>算法思想：</p><ol><li><p>设N=（V,E)是连通网，TE是N上最小生成树 中边的集合</p></li><li><p>初始令$U={u_0}，(u_0 \in V),TE=${}</p></li><li><p>在所有$u\in U，v \in V-U$的边$（u,v）\in E$中找一条代价最小的边$(u_0,v_0)$.</p></li><li><p>将$(u_0,v_0)$并入集合TE，同时$v_o$并入U</p></li><li><p>重复上述操作直至U=V为止，则T=（V，TE）为N的最小生成树</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014185917.png" alt=""></p></li></ol></li></ul><h5 id="6-6-1-2-克鲁斯卡尔-Kruskal-算法">6.6.1.2 克鲁斯卡尔(Kruskal)算法</h5><ul><li>算法思想：<ol><li>设连通网 N=(VE)，令最小生成树初始状态为<strong>只有</strong> n个<strong>顶点</strong>而<strong>无边</strong>的非连通图T=（V，{}),每个顶点自成一个连通分量。</li><li>在E中选取代价最小的边，若该边依附的顶点落在T中不同的连通分量上(即:<strong>不能形成环</strong>），则将此边加入到 T中;否则，舍去此边，选取下一条代价最小的边。</li><li>依此类推，直至T中所有顶点都在同一连通分量上为止。</li></ol></li><li><strong>最小生成树可能不唯一</strong></li></ul><table><thead><tr><th>算法名</th><th>普里姆算法</th><th>克鲁斯卡尔算法</th></tr></thead><tbody><tr><td>算法思想</td><td>选择点</td><td>选择边</td></tr><tr><td>时间复杂度</td><td>O（n^2)(n为顶点数)</td><td>O(eloge)（e为边数）</td></tr><tr><td>适应范围</td><td>稠密图</td><td>稀疏图</td></tr></tbody></table><h4 id="6-6-2-最短路径">6.6.2 最短路径</h4><ul><li>两种最常见的最短路径问题：<ol><li>单源最短路径——用Dijkstra（迪杰斯特拉）算法</li><li>所有顶点间的最短路径——用Floyd——（弗洛伊德）算法</li></ol></li></ul><h5 id="6-6-2-1-迪杰斯特拉算法">6.6.2.1  迪杰斯特拉算法</h5><ol><li>初始化：从源点$v_0$到各终点$v_k$的直达路径 $（v_o,v_k）$，即通过一条弧到达的路径。</li><li>选择：从这些路径中找出一条长度最短的路径$（v_0,u）$.</li><li>更新：然后对其余各条路径进行适当调整：<ol><li>若在图中存在弧$(u，v_k)$,且$(v_0,u)+（u，v_k）&lt;(v_0,v_k),则以路径（v_0,u,v_k）代替（v_0，v_k)$</li></ol></li><li>在调整后的各条路径中，再找长度最短的路径，依此类推</li></ol><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015160725.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015160837.png" alt=""></p><p>时间复杂度为$O(n^3)$</p><h5 id="6-6-2-2-弗洛伊德算法">6.6.2.2 弗洛伊德算法</h5><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015161627.png" alt=""></p><h4 id="6-6-3-拓扑排序">6.6.3 拓扑排序</h4><ul><li><p>有向无环图：无环的有向图，简称DAG图</p><ul><li>有向无环图常用来描述一个工程或系统的进行过程。</li><li>一个工程可以分为若干个子工程，只要完成了这些子工程，就可以导致整个工程的完成</li></ul></li><li><p><strong>AOV网    拓扑排序</strong></p><ul><li>用一个有向图表示一个工程的各子工程及其相只制约的关系，其中以<strong>顶点表示活动</strong>，<strong>弧表示活动之间的优先制约关系</strong>，称这种有向图为<strong>顶点表示活动的网</strong>，简称 <strong>AOV网</strong>(Activity On Vertex network)。</li></ul></li><li><p><strong>AOE网    关键路径</strong></p><ul><li>有向图表示一个工程的各子工程及其相互制约的关系，以<strong>弧表示活动</strong>，以<strong>顶点表示活动的开始或结束事件</strong>，称这种有向图为<strong>边表示活动的网</strong>，简称为<strong>AOE网</strong> (Activity On Edge)</li></ul></li><li><p>AOV网的特点：</p><ol><li>若从i到j有一条有向路径，则i是j的前驱j是i的后继</li><li>若&lt;i,j&gt;是网中有向边，则i是j的直接前驱;j是i的直接后继</li><li>AOV网中不允许有回路，因为如果有回路存在，则表明某项活动以自己为先决条件，显然这是荒谬的。</li></ol></li><li><p>拓扑排序</p><ul><li>在AOV 网没有回路的前提下，我们将全部活动排列成一个线性序列，使得若 AOV 网中有弧 &lt;i,j&gt;存在，则在这个序列中，i一定排在的前面，具有这种性质的线性序列称为<strong>拓扑有序序列</strong>，相应的拓扑有序排序的算法称为<strong>拓扑排序</strong></li></ul></li><li><p>拓扑排序方法：</p><ol><li>在有向图中选一个没有前驱的顶点且输出</li><li>在图中删除该顶点和所有以他为尾的弧</li><li>重复1和2直到全部顶点均已输出；当图中不存在无前驱的顶点位置<ul><li>一个AOV网的拓扑序列不是唯一的</li></ul></li></ol></li><li><p>拓扑排序的一个重要应用：</p><ul><li>检测AOV网中<strong>是否存在环</strong>的方法：<ul><li>对有向图构造其顶点的拓扑有序序列，若网中所有顶点都在它的拓扑有序序列中，则该 AOV 网必定不存在环。</li></ul></li></ul></li><li><p>关键路径</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015165107.png" alt=""></p></li><li><p>假设一个工程有11项活动，9个事件</p><ul><li>事件V1——表示整个工程开始（源点：入度为0的顶点）</li><li>事件v9——表示整个工程结束（汇点：出度为0的顶点）</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015183957.png" alt=""></p><ul><li>关键路径——路径长度最长的路径</li><li>路径长度——路径上各活动持续时间之和<ul><li>ve(vj)——表示事件vj的最早发生时间</li><li>vl(vj)——表示事件vj的最迟发生时间</li><li>e(i)——表示活动ai的最早开始时间</li><li>l(i)——表示活动ai的最迟开始时间</li><li>l(i)-e(i)——表示完成活动ai的时间余量</li><li>关键活动——关键路径上的活动，即l(i)==e(i)的活动（即没有时间余量的活动）</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231016135304.png" alt=""></p><ul><li>其中√的为关键活动，而包含关键活动的路径为关键路径<ul><li>需要加快同时在几条关键路径上的关键活动<ul><li>如：a11,a10,a8,a7</li></ul></li><li>如果一个活动处于所有关键路径上，那么提高这个活动的速度，就能缩短整个工程的完成时间<ul><li>如：a1,a4</li></ul></li><li>处于所有关键路径上的活动完成时间不能缩短太多，否则会使原来的关键路径变成不是关键路径。这时必须重新寻找关键路径</li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第六章(汇总)--王卓老师两周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第六章(下篇)</title>
    <link href="https://www.fomal.cc/posts/47c13b4.html"/>
    <id>https://www.fomal.cc/posts/47c13b4.html</id>
    <published>2023-10-18T12:12:16.000Z</published>
    <updated>2023-10-23T10:01:44.315Z</updated>
    
    <content type="html"><![CDATA[<h3 id="6-6-图的应用">6.6 图的应用</h3><h4 id="6-6-1-最小生成树">6.6.1 最小生成树</h4><ul><li><p>生成树：所有顶点均由边连接在一起，但不存在回路的图</p><ul><li><p>一个图可以有许多棵不同的生成树</p></li><li><p>所有生成树具有以下共同特点</p><ul><li>生成树的顶点个数与图的<strong>顶点个数相同</strong></li><li>生成树是图的<strong>极小连通子图</strong>，去掉一条边则非连通</li><li>一个有<strong>n</strong>个顶点的连通图的生成树有<strong>n-1</strong>条边</li><li><strong>在生成树中再加一条边必然形成回路</strong></li><li>生成树中任意两个顶点间的<strong>路径是唯一</strong>的</li></ul></li></ul></li><li><p>无向图的生成树：</p><ul><li>设图G=(V,E)是个连通图，当从图任一顶点出发遍历图G时，将边集E(G)分成两个集合T(G) 和B(G)。其中 T(G)是遍历图时所经过的边的集合，B(G)是遍历图时未经过的边的集合。显然，G1(V,T)是图G的极小连通子图。即子图G1 是连通图G的生成树</li></ul></li><li><p>最小生成树</p><ul><li><p>给定一个无向网络，在该网络的所有生成树中，使得<strong>各边权值之和最小</strong>的那棵生成树称为该网的<strong>最小生成树</strong>，也叫<strong>最小代价生成树</strong>。</p></li><li><p>最小生成树的典型用途</p><ul><li>要在n个城市间建立通信网，则n个城市应铺设n-1条路，每条路也有对应的经济成本</li><li>建立数学模型：<ul><li>顶点代表城市有n个</li><li>边代表线路有n-1条</li><li>边的权值  表示线路的经济代价</li><li>连通网    表示n个城市间的通信网</li></ul></li></ul></li></ul></li><li><p>构造最小生成树</p><ul><li>构造最小生成树的算法很多，其中多数算法都利用了<strong>MST的性质</strong></li><li>MST性质：设N=（V,E) 是一个连通网，U是顶点集V的一个非空子集。若边(u,v) 是一条具有最小权值的边，其中$u\in U，v \in V-U$则必存在一棵包含边(u,v)的最小生成树</li><li>在生成树的构造过程中，图中n个顶点分属两个集合：<ul><li>已落在生成树上的顶点集: U</li><li>尚未落在生成树上的顶点集: V-U</li></ul></li><li>接下来则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014180317.png" alt=""></p></li></ul><h5 id="6-6-1-1-普利姆-prim-算法">6.6.1.1 普利姆(prim)算法</h5><ul><li><p>算法思想：</p><ol><li><p>设N=（V,E)是连通网，TE是N上最小生成树 中边的集合</p></li><li><p>初始令$U={u_0}，(u_0 \in V),TE=${}</p></li><li><p>在所有$u\in U，v \in V-U$的边$（u,v）\in E$中找一条代价最小的边$(u_0,v_0)$.</p></li><li><p>将$(u_0,v_0)$并入集合TE，同时$v_o$并入U</p></li><li><p>重复上述操作直至U=V为止，则T=（V，TE）为N的最小生成树</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014185917.png" alt=""></p></li></ol></li></ul><h5 id="6-6-1-2-克鲁斯卡尔-Kruskal-算法">6.6.1.2 克鲁斯卡尔(Kruskal)算法</h5><ul><li>算法思想：<ol><li>设连通网 N=(VE)，令最小生成树初始状态为<strong>只有</strong> n个<strong>顶点</strong>而<strong>无边</strong>的非连通图T=（V，{}),每个顶点自成一个连通分量。</li><li>在E中选取代价最小的边，若该边依附的顶点落在T中不同的连通分量上(即:<strong>不能形成环</strong>），则将此边加入到 T中;否则，舍去此边，选取下一条代价最小的边。</li><li>依此类推，直至T中所有顶点都在同一连通分量上为止。</li></ol></li><li><strong>最小生成树可能不唯一</strong></li></ul><table><thead><tr><th>算法名</th><th>普里姆算法</th><th>克鲁斯卡尔算法</th></tr></thead><tbody><tr><td>算法思想</td><td>选择点</td><td>选择边</td></tr><tr><td>时间复杂度</td><td>O（n^2)(n为顶点数)</td><td>O(eloge)（e为边数）</td></tr><tr><td>适应范围</td><td>稠密图</td><td>稀疏图</td></tr></tbody></table><h4 id="6-6-2-最短路径">6.6.2 最短路径</h4><ul><li>两种最常见的最短路径问题：<ol><li>单源最短路径——用Dijkstra（迪杰斯特拉）算法</li><li>所有顶点间的最短路径——用Floyd——（弗洛伊德）算法</li></ol></li></ul><h5 id="6-6-2-1-迪杰斯特拉算法">6.6.2.1  迪杰斯特拉算法</h5><ol><li>初始化：从源点$v_0$到各终点$v_k$的直达路径 $（v_o,v_k）$，即通过一条弧到达的路径。</li><li>选择：从这些路径中找出一条长度最短的路径$（v_0,u）$.</li><li>更新：然后对其余各条路径进行适当调整：<ol><li>若在图中存在弧$(u，v_k)$,且$(v_0,u)+（u，v_k）&lt;(v_0,v_k),则以路径（v_0,u,v_k）代替（v_0，v_k)$</li></ol></li><li>在调整后的各条路径中，再找长度最短的路径，依此类推</li></ol><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015160725.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015160837.png" alt=""></p><p>时间复杂度为$O(n^3)$</p><h5 id="6-6-2-2-弗洛伊德算法">6.6.2.2 弗洛伊德算法</h5><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015161627.png" alt=""></p><h4 id="6-6-3-拓扑排序">6.6.3 拓扑排序</h4><ul><li><p>有向无环图：无环的有向图，简称DAG图</p><ul><li>有向无环图常用来描述一个工程或系统的进行过程。</li><li>一个工程可以分为若干个子工程，只要完成了这些子工程，就可以导致整个工程的完成</li></ul></li><li><p><strong>AOV网    拓扑排序</strong></p><ul><li>用一个有向图表示一个工程的各子工程及其相只制约的关系，其中以<strong>顶点表示活动</strong>，<strong>弧表示活动之间的优先制约关系</strong>，称这种有向图为<strong>顶点表示活动的网</strong>，简称 <strong>AOV网</strong>(Activity On Vertex network)。</li></ul></li><li><p><strong>AOE网    关键路径</strong></p><ul><li>有向图表示一个工程的各子工程及其相互制约的关系，以<strong>弧表示活动</strong>，以<strong>顶点表示活动的开始或结束事件</strong>，称这种有向图为<strong>边表示活动的网</strong>，简称为<strong>AOE网</strong> (Activity On Edge)</li></ul></li><li><p>AOV网的特点：</p><ol><li>若从i到j有一条有向路径，则i是j的前驱j是i的后继</li><li>若&lt;i,j&gt;是网中有向边，则i是j的直接前驱;j是i的直接后继</li><li>AOV网中不允许有回路，因为如果有回路存在，则表明某项活动以自己为先决条件，显然这是荒谬的。</li></ol></li><li><p>拓扑排序</p><ul><li>在AOV 网没有回路的前提下，我们将全部活动排列成一个线性序列，使得若 AOV 网中有弧 &lt;i,j&gt;存在，则在这个序列中，i一定排在的前面，具有这种性质的线性序列称为<strong>拓扑有序序列</strong>，相应的拓扑有序排序的算法称为<strong>拓扑排序</strong></li></ul></li><li><p>拓扑排序方法：</p><ol><li>在有向图中选一个没有前驱的顶点且输出</li><li>在图中删除该顶点和所有以他为尾的弧</li><li>重复1和2直到全部顶点均已输出；当图中不存在无前驱的顶点位置<ul><li>一个AOV网的拓扑序列不是唯一的</li></ul></li></ol></li><li><p>拓扑排序的一个重要应用：</p><ul><li>检测AOV网中<strong>是否存在环</strong>的方法：<ul><li>对有向图构造其顶点的拓扑有序序列，若网中所有顶点都在它的拓扑有序序列中，则该 AOV 网必定不存在环。</li></ul></li></ul></li><li><p>关键路径</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015165107.png" alt=""></p></li><li><p>假设一个工程有11项活动，9个事件</p><ul><li>事件V1——表示整个工程开始（源点：入度为0的顶点）</li><li>事件v9——表示整个工程结束（汇点：出度为0的顶点）</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231015183957.png" alt=""></p><ul><li>关键路径——路径长度最长的路径</li><li>路径长度——路径上各活动持续时间之和<ul><li>ve(vj)——表示事件vj的最早发生时间</li><li>vl(vj)——表示事件vj的最迟发生时间</li><li>e(i)——表示活动ai的最早开始时间</li><li>l(i)——表示活动ai的最迟开始时间</li><li>l(i)-e(i)——表示完成活动ai的时间余量</li><li>关键活动——关键路径上的活动，即l(i)==e(i)的活动（即没有时间余量的活动）</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231016135304.png" alt=""></p><ul><li>其中√的为关键活动，而包含关键活动的路径为关键路径<ul><li>需要加快同时在几条关键路径上的关键活动<ul><li>如：a11,a10,a8,a7</li></ul></li><li>如果一个活动处于所有关键路径上，那么提高这个活动的速度，就能缩短整个工程的完成时间<ul><li>如：a1,a4</li></ul></li><li>处于所有关键路径上的活动完成时间不能缩短太多，否则会使原来的关键路径变成不是关键路径。这时必须重新寻找关键路径</li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第六章(下篇)--王卓老师第十一周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第六章(上篇)</title>
    <link href="https://www.fomal.cc/posts/391c3a04.html"/>
    <id>https://www.fomal.cc/posts/391c3a04.html</id>
    <published>2023-10-16T11:22:24.000Z</published>
    <updated>2023-10-23T10:01:44.314Z</updated>
    
    <content type="html"><![CDATA[<h2 id="6-图">6 图</h2><p><em><strong>图是一种比线性表和树更为复杂的数据结构。</strong></em></p><h3 id="6-1-图的定义和基本术语">6.1 图的定义和基本术语</h3><h4 id="6-1-1-图的定义">6.1.1 图的定义</h4><ul><li><p>图：G=（V.E）</p><ul><li>V：顶点（数据元素）的<strong>有穷非空</strong>集合；</li><li>E：边的<strong>有穷</strong>集合</li></ul></li><li><p>无向图：每条边都是无方向的</p></li><li><p>有向图：每条边都是有方向的</p></li><li><p>完全图：任意两个点都有一条边相连</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012174252.png" alt=""></p></li><li><p>稀疏图：有很少边或弧的图（$e&lt;nlog_2n$）</p></li><li><p>稠密图：有较多边或弧的图</p></li></ul><h4 id="6-1-2-图的基本术语">6.1.2 图的基本术语</h4><ul><li><p>网，边/弧带权的图</p></li><li><p>邻接：有边/弧相连的两个顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）,则称为$v_i和v_j$互为<strong>邻接点</strong></li><li>存在&lt;$v_i,v_j$&gt;，则称$v_i$邻接到$v_j$,$v_j$邻接于$v_i$,</li></ul></li><li><p>关联（依附）：边/弧与顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）/&lt;$v_i,v_j$&gt;，则称该边/弧关联于$v_i$和$v_j$</li></ul></li><li><p>顶点的度：与该顶点相关联的边的数目，记为TD（v）</p><ul><li><p>在<strong>有向图</strong>中，顶点的度等于该顶点的<strong>入度</strong>和<strong>出度</strong>之和。</p></li><li><p>顶点<strong>v的入度</strong>是以v为终点的有向边的条数，记作ID（v）</p></li><li><p>顶点<strong>v的出度</strong>是以v为始点的有向边的条数，记作OD（v）</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012175741.png" alt=""></p></li></ul></li><li><p>路径：连续的边构成的顶点序列</p></li><li><p>路径长度：路径上边或弧的数目/权值之和</p></li><li><p>回路（环）：第一个顶点和最后一个顶点相同的路径</p></li><li><p>简单路径：除路径起点和终点可以相同外，其余顶点均不相同的路径</p></li><li><p>简单回路（环）：除路径起点和终点相同外，其余顶点均不相同的路径</p></li><li><p>连通图（强连通图）：在无（有）向图G=(V,{E})中，若对任意两个顶点v、u都存在从v到u的路径，则称G是<strong>连通图</strong>（强连通图）</p></li><li><p>权与网：</p><ul><li>图中边或弧所具有的相关数称为<strong>权</strong>，表明从一个顶点到另一个顶点的距离或耗费。</li><li>带权的图称为<strong>网</strong></li></ul></li><li><p>子图：设有两个图$G=（V，{E}）、G_1=(V_1,{E_1})$,若$V_1属于V，E_1属于E$则称$G_1是G$的子图</p></li><li><p>连通分量（强连通分量）</p><ul><li><p>无向图G的<strong>极大联通子图</strong>称为G的<strong>连通分量</strong></p><ul><li>极大连通子图意思是：该子图是G联通子图，将G的任何不在该子图汇总的顶点加入，子图不再连通</li></ul></li><li><p>有向图G的<strong>极大连通子图</strong>称为G的<strong>强连通分量</strong></p><ul><li>极大强连通子图意思是: 该子图是G的强连通子图，将D的任何不在该子图中的顶点加入，子图不再是强连通的。</li></ul></li><li><p>极小联通子图：该子图是G的连通子图，在该子图中删除任何一条边，联通子图不再连通</p></li><li><p>生成树:包含无向图G所有顶点的极小连通子图</p></li><li><p>生成森林:对非连通图，由各个连通分量的生成树的集合</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012201656.png" alt=""></p></li></ul></li></ul><h3 id="6-2-案例引入">6.2 案例引入</h3><h4 id="6-2-1-六度空间理论">6.2.1 六度空间理论</h4><p><strong>理论又称作六度分隔论 (Six Degrees of Separation)。六度空间理论是20世纪60年代由美国的心理学家米格兰姆(Stanley Milgram)提出的，理论指出：“<em>你和任何一个陌生人之间所间隔的人不会超过六个</em>”</strong></p><h3 id="6-3-图的类型定义">6.3 图的类型定义</h3><p>图的抽象数据类型定义如下：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT Graph&#123;</span><br><span class="line">    数据对象V：具有相同特性的数据元素的集合，称为顶点集。</span><br><span class="line">    数据关系R：R&#123;VR&#125;</span><br><span class="line">    VR=&#123;&lt;V,W&gt;|&lt;V,W&gt;|V,W属于V^p(v,w)&#125;， &lt;v, w&gt;表示从v到w的弧，P (v, w)定义了弧&lt;v, w&gt;的信息</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>基本操作：</p><ul><li><p>CreateGraph{&amp;G,V,VR}</p><p>初始条件：V是图的顶点集，VR是图中弧的集合。</p><p>操作结果：按V和VR的定义<strong>构造图G</strong>。</p></li><li><p>DFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行<strong>深度优先遍历</strong>，在遍历过程中对每个顶点访问一次。</p></li><li><p>BFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行广度优先遍历，在遍历过程中对每个顶点访问一次.</p></li></ul></li></ul><h3 id="6-4-图的存储结构">6.4 图的存储结构</h3><ul><li>图的逻辑结构：多对多<ul><li>图没有顺序存储结构，可以借助二维数组来表示元素间的关系</li></ul></li><li>数组表示法（邻接矩阵）</li><li>链式存储结构：<ul><li>多重链表<ul><li>邻接表</li><li>邻接多重表</li><li>十字链表</li></ul></li></ul></li><li>重点介绍：<ul><li><strong>邻接矩阵</strong>（数组）表示法</li><li><strong>邻接表</strong>（链式）表示法</li></ul></li></ul><h4 id="6-4-1-邻接矩阵">6.4.1 邻接矩阵</h4><ul><li><p>数组（邻接矩阵）表示法</p><ul><li><p>建立一个顶点表（记录各个顶点信息）和一个邻接矩阵（表示各个顶点之间关系）。</p><ul><li><p>设图A=（V，E)有n个顶点，则</p></li><li><p>顶点表Vexs[n]</p><table><thead><tr><th>i</th><th>1</th><th>2</th><th>3</th><th>……</th><th>n-1</th></tr></thead><tbody><tr><td>Vexs[n]</td><td>$V_1$</td><td>$V_2$</td><td>$V_3$</td><td>……</td><td>$V_n$</td></tr></tbody></table></li><li><p>图的邻接矩阵是一个二维数组$A.arcs[n][n]$,定义为：</p><ul><li><h6 id="A-arcs-i-j-quad-begin-cases-1-如果-v-i-v-j-或-v-i-v-j-in-E-0，反之-end-cases">$A.arcs[i][j]=\quad\begin{cases}1,如果&lt;v_i,v_j&gt;或(v_i,v_j)\in E\0，反之\end{cases}$</h6></li></ul></li><li><p>无向图的邻接矩阵表示法：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013142744.png" alt=""></p><ol><li>无向图的邻接矩阵是<strong>对称</strong>的</li><li>顶点i的度=第i行（列）中1的个数；</li><li>完全图的邻接矩阵中，对角元素为0，其余1.</li></ol></li><li><p>有向图的邻接矩阵表示法：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013143343.png" alt=""></p><ol><li>有向图的邻接矩阵<strong>可能是不对称</strong>的</li><li>顶点的<strong>出度=第i行元素之和</strong></li><li>顶点的<strong>入度=第i列元素之和</strong></li><li>顶点的<strong>度=第i行和列的元素之和</strong></li></ol></li><li><p>网（即有权图）的邻接矩阵表示法</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013145659.png" alt=""></p></li><li><p>邻接矩阵的存储表示：用<strong>两个数组</strong>分别存储<strong>顶点表和邻接矩阵</strong></p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> Maxint 32767<span class="comment">//表示极大值</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MVNum 100 <span class="comment">//最大顶点数</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">char</span> VerTexType;<span class="comment">//设顶点的数据类型为字符型</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> ArcType;<span class="comment">//假设边的权值类型为整形</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>｛ </span></span><br><span class="line"><span class="class">    <span class="title">VerTexType</span> <span class="title">vexs</span> [<span class="title">MVNum</span>];</span><span class="comment">//顶点表</span></span><br><span class="line">ArcType arcs[MVNum) [MVNum];<span class="comment">//邻接矩阵</span></span><br><span class="line">    <span class="type">int</span> vexnum,arcnum;<span class="comment">//图的当前点数和边数</span></span><br><span class="line">&#125;AMGraph;</span><br></pre></td></tr></table></figure></li></ul></li><li><p>采用邻接矩阵表示法创建无向网</p><ol><li>输入总顶点数和总边数。</li><li>依次输入点的信息存入顶点表中。</li><li>初始化邻接矩阵， 使每个权值初始化为极大值。</li><li>构造邻接矩阵。</li></ol><figure class="highlight c"><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">Status <span class="title function_">CreateUDN</span><span class="params">(AMGraph &amp;G)</span>&#123;<span class="comment">//创建无向网G</span></span><br><span class="line">    <span class="built_in">cin</span>&gt;&gt;G.vexnum&gt;&gt;G.arcnum;<span class="comment">//输入总顶点数，总边数</span></span><br><span class="line">    <span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)</span><br><span class="line"><span class="built_in">cin</span>&gt;&gt;G.vexs[i];<span class="comment">//依次输入点的信息</span></span><br><span class="line"><span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)<span class="comment">//初始化邻接矩阵呢</span></span><br><span class="line"><span class="keyword">for</span> (j=<span class="number">0</span>; j&lt;G.vexnum;++j) </span><br><span class="line">G.arcs[i][j]=Maxint;<span class="comment">//变得权值均置为最大</span></span><br><span class="line"><span class="keyword">for</span>(k=O;k&lt;G.arcnum;++k)&#123;<span class="comment">//构造邻接矩阵</span></span><br><span class="line">        <span class="built_in">cin</span>&gt;&gt;vl&gt;&gt;v2&gt;&gt;w;<span class="comment">//输入一条边所依附的顶点及边的权值</span></span><br><span class="line">        i=LocateVex(G,vl);j=LocateVex(G,v2);<span class="comment">//确定V1和V2在G中的位置</span></span><br><span class="line">        G.arcs[i][j]=w;<span class="comment">//边&lt;v1,v2&gt;的权值置为w</span></span><br><span class="line">        G.arcs[j][i]=G.arcs[i][j];<span class="comment">//置&lt;v1,v2&gt;的对称边&lt;v2,v1&gt;的权值为w</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>补充算法：在图中查找顶点：</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">LocateVex</span><span class="params">(AMGraph G,VertexType u)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;G.vexnum;++i)</span><br><span class="line">        <span class="keyword">if</span>(u==G.vexs[i]) <span class="keyword">return</span> i;</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></li></ul></li><li><p>邻接矩阵的优点</p><ul><li>直观简单好理解</li><li>方便检查任意一对顶点间是否存在边</li><li>方便找任一顶点的所有“邻接点”</li><li>方便计算任一顶点的“度”</li></ul></li><li><p>邻接矩阵的缺点</p><ul><li>不便于增加和删除顶点</li><li>浪费空间——存稀疏图有大量无效元素<ul><li>对稠密图来说还是很合算的</li></ul></li><li>浪费时间——统计稠密图中一共有多少条边</li></ul></li></ul><h4 id="6-4-2-邻接表">6.4.2 邻接表</h4><ol><li><p>无向图邻接表表示法（链式）</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013154441.png" alt=""></p><ul><li><strong>顶点</strong>：按编号顺序将顶点数据存储在一维数组中；</li><li><strong>关联同一顶点的边</strong>（以顶点为尾的弧）：<ul><li>用线性<strong>链表</strong>存储</li></ul></li><li>特点：<ul><li>邻接表不唯一</li><li>若无向图中有n个顶点、e条边，则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图</li><li>无向图中顶点$V_i$的度为第i个单链表中的结点数</li></ul></li></ul></li><li><p>有向图邻接表表示法</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013155951.png" alt=" "></p><ul><li>特点：<ul><li>顶点$V_i$的<strong>出度</strong>为第i个单链表中的结点个数</li><li>顶点的入度为整个链表中领接点域值是i-1的结点个数</li></ul></li></ul></li><li><p>图的邻接表存储表示：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">VNode</span>&#123;</span></span><br><span class="line">    VerTexType data;<span class="comment">//顶点信息</span></span><br><span class="line">    ArcNode *firstarc;<span class="comment">//指向第一条依附该顶点的边的指针</span></span><br><span class="line">&#125;VNode,AdjList[MVNum]<span class="comment">//AdjList表示邻接表类型</span></span><br></pre></td></tr></table></figure><p>弧（边）的结点结构    adjvex | nextarc | info</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MVNum 100<span class="comment">//最大顶点数</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">ArcNode</span>&#123;</span><span class="comment">//边结点</span></span><br><span class="line">    <span class="type">int</span> adjvex;<span class="comment">//该边所指向的顶点的位置</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ArcNode</span> * <span class="title">nextarc</span>;</span><span class="comment">//指向下一条边的指针</span></span><br><span class="line">    OtherInfo info;<span class="comment">//和边相关的信息</span></span><br><span class="line">&#125;ARcNode;</span><br></pre></td></tr></table></figure><p>图的结构定义：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    AdjList vertices;<span class="comment">//vertices——vertex的复数</span></span><br><span class="line">    <span class="type">int</span> vexnum,arcnum;<span class="comment">//图的当前顶点数和弧数</span></span><br><span class="line">&#125;ALGraph;</span><br></pre></td></tr></table></figure><p>邻接表操作举例说明：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231013161315.png" alt=""></p></li><li><p>无向图的邻接表表示 p118</p></li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">CreateUDG</span><span class="params">(ALGraph &amp;G)</span> &#123;<span class="comment">//采用邻接表表示法， 创建无向图 G</span></span><br><span class="line"><span class="built_in">cin</span>&gt;&gt;G.vexnum&gt;&gt;G.arcnum;<span class="comment">//输入总顶点数，总边数</span></span><br><span class="line"><span class="keyword">for</span>(i=O;i&lt;G.vexnum;++i)&#123;<span class="comment">//输入各点，构造表头结点表</span></span><br><span class="line"><span class="built_in">cin</span>»G.vertices[i) .data;<span class="comment">//输入顶点值</span></span><br><span class="line">G.vertices[i) .firstarc=<span class="literal">NULL</span>;&#125;<span class="comment">//初始化表头结点的指针域</span></span><br><span class="line"><span class="keyword">for</span>(k=O;k&lt;G.arcnum;++k)&#123;<span class="comment">//输入各边，构造邻接表</span></span><br><span class="line">        <span class="built_in">cin</span>&gt;&gt;vl&gt;&gt;v2;<span class="comment">//输入一条边依附的两个顶点</span></span><br><span class="line">        i=LocateVex(G,vl);</span><br><span class="line">        j=LocateVex(G,v2);</span><br><span class="line">        pl=new ArcNode;<span class="comment">//生成一个新的边结点*p1</span></span><br><span class="line">        pl-&gt;adjvex=j;<span class="comment">//邻接点序号为j</span></span><br><span class="line">        pl-&gt;nextarc=G.vertices[i].firstarc;</span><br><span class="line">        G.vertices[i].firstarc=pl;<span class="comment">//将新结点*p1插入顶点vi的边表头部（头插法）</span></span><br><span class="line">        p2=new ArcNode;<span class="comment">//生成另一个对称的新的边结点*p2</span></span><br><span class="line">        p2-&gt;adjvex=i;<span class="comment">//邻接点序号为i</span></span><br><span class="line">        p2-&gt;nextarc=G.vertices[j].firstarc;</span><br><span class="line">        G.vertices[j].firstarc=p2;<span class="comment">//将心结点*p2插入顶点vj的边表头部</span></span><br><span class="line">    &#125;</span><br><span class="line">     <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>无向图邻接表特点：</p><ol><li>方便找任一顶点的所有“邻接点”</li><li>节约稀疏图的空间<ol><li>需要n个头指针和+2e个结点（每个结点至少2个域）</li></ol></li><li>方便计算任一顶点的“度”<ol><li>对无向图：是的</li><li>对有向图：只能计算“出度”需要构造“逆邻接表”来方便计算“入度”</li></ol></li><li>不方便检查任意一对顶点间是否存在边</li></ol></li><li><p>邻接矩阵与邻接表表示法的关系</p><ol><li>联系：邻接表中每个链表对应于接短阵中的一行，链表中结点个数等于一行中非零元素的个数。</li><li>区别：<ol><li>对于任一确定的无向图，邻接矩阵是唯一的 (行列号与顶点编号一致)，但邻接表不唯一 (链接次序与顶点编号无关)</li><li>邻接矩阵的空间复杂度为$O（n^2)$,而邻接表的空间复杂度为$O(n+e)$。(e是出于$0到n^2$之间的复杂变量)</li></ol></li><li>用途：邻接矩阵多用于稠密图；而邻接表多用于稀疏图</li></ol></li></ul><h4 id="6-4-3-十字链表">6.4.3 十字链表</h4><p>有向图：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014103949.png" alt=""></p><ul><li>顶点结点：</li></ul><table><thead><tr><th>data</th><th>firstin</th><th>firstout</th></tr></thead><tbody><tr><td>存与顶点相关的信息</td><td>入度边</td><td>出度边</td></tr></tbody></table><ul><li>弧结点：</li></ul><table><thead><tr><th>tailvex</th><th>headvex</th><th>hlink</th><th>tlink</th></tr></thead><tbody><tr><td>弧尾位置</td><td>弧头位置</td><td>弧头相同的下一条弧</td><td>弧尾相同的下一条弧</td></tr></tbody></table><p><em><strong>弧头是箭头，弧尾是箭尾</strong></em></p><h4 id="6-4-4-邻接多重表">6.4.4 邻接多重表</h4><p>边结点：</p><table><thead><tr><th>mark</th><th>ivex</th><th>ilink</th><th>jvex</th><th>jlink</th><th>info</th></tr></thead><tbody><tr><td>标志域，标记此边是否被搜索过</td><td>该边依附的两个顶点在表头数组中的位置</td><td>指向依附于ivex的下一条边</td><td>该边依附的两个顶点在表头数组中的位置</td><td>指向依附于jvex的下一条边</td><td></td></tr></tbody></table><p>顶点节点</p><table><thead><tr><th>data</th><th>firstedge</th></tr></thead><tbody><tr><td>存与顶点有关的信息</td><td>指向第一条依附于该顶点的边</td></tr></tbody></table><h3 id="6-5-图的遍历">6.5 图的遍历</h3><ul><li><p>遍历的实质：找每个顶点的邻接点的过程</p></li><li><p>图常用的遍历：</p><ul><li>深度优先搜索(DFS)</li><li>广度优先搜索(BFS)</li></ul></li></ul><h4 id="6-5-1-深度优先搜索">6.5.1 深度优先搜索</h4><p>甚多有限搜索遍历过程：</p><ol><li>从图中某个顶点v出发， 访问v。</li><li>找出刚访问过的顶点的第一个未被访问的邻接点， 访问该顶点。 以该顶点为新顶点，重复此步骤， 直至刚访问过的顶点没有未被访问的邻接点为止。</li><li>返回前一个访问过的且仍有未被访问的邻接点的顶点，找出该顶点的下一个未被访问的邻接点， 访问该顶点。</li><li>重复步骤 (2)和(3), 直至图中所有顶点都被访问过，搜索结束。</li><li><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231014115016.png" alt=""></li></ol><h5 id="6-5-1-1-采用邻接矩阵表示图的深度优先搜索遍历">6.5.1.1 采用邻接矩阵表示图的深度优先搜索遍历</h5><figure class="highlight c"><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"><span class="type">void</span> <span class="title function_">DFS</span><span class="params">(AMGraph G,<span class="type">int</span> v)</span>&#123;<span class="comment">//图G为邻接矩阵类型</span></span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;v;visited[v]=<span class="literal">true</span>;<span class="comment">//访问第v个顶点</span></span><br><span class="line">    <span class="keyword">for</span>(w=<span class="number">0</span>;w&lt;G.vexnum;w++)<span class="comment">//依次检查邻接矩阵v所在行</span></span><br><span class="line">        <span class="keyword">if</span>((G.arcs[v][w]!=<span class="number">0</span>)&amp;&amp;(!visited[w]))</span><br><span class="line">            DFS(G,w);<span class="comment">//w是v邻接点，如果w未访问，则递归调用DFS</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>DFS算法效率分析<ul><li>用邻接矩阵来表示图，遍历图中每一个顶点都要<strong>从头扫描</strong>该顶点所在行，时间复杂度为$O(n^2)$</li><li>用邻接表来表示图，虽然有<strong>2e</strong>个表结点，但只需扫描 <strong>e</strong> 个结点即可完成遍历，加上访问 <strong>n</strong>个头结点的时间，时间复杂度为$O(n+e)$</li></ul></li><li>结论<ul><li>稠密图适合在邻接矩阵上进行深度遍历</li><li>稀疏图适合在邻接表上进行深度遍历</li></ul></li></ul><h4 id="6-5-2-广度优先搜索">6.5.2 广度优先搜索</h4><ul><li>用队列实现广度优先遍历，累次层次遍历那样</li></ul><h5 id="6-5-2-1-按广度优先非递归遍历连通图G">6.5.2.1 按广度优先非递归遍历连通图G</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">BFS</span> <span class="params">(Graph G,<span class="type">int</span> v)</span>&#123;</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;v;visited[v]=<span class="literal">true</span>;<span class="comment">//访问第v个顶点</span></span><br><span class="line">    initQueue(Q);<span class="comment">//辅助队列Q初始化，置空</span></span><br><span class="line">    EnQueue(Q,v);<span class="comment">//v进队</span></span><br><span class="line">    <span class="keyword">while</span>(!QueueEmpty(Q))&#123;<span class="comment">//队列非空</span></span><br><span class="line">        DeQueue(Q,u);<span class="comment">//队头元素出队并置为u</span></span><br><span class="line">        <span class="keyword">for</span>(W=FirstAdjVex(G,u);w&gt;=<span class="number">0</span>;w=NextAdjVex(G,u,w))</span><br><span class="line">            <span class="keyword">if</span>(!visited[w])&#123;<span class="comment">//w为u的尚未访问的邻接顶点</span></span><br><span class="line">                <span class="built_in">cout</span>&lt;&lt;w;visited[w]=<span class="literal">true</span>; EnQueue(Q,w);<span class="comment">//w进队</span></span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>BFS算法效率分析<ul><li>如果邻接矩阵，则BFS对于每一个背访问到的顶点，都要循环检测矩阵中的整整一行（n个元素），总的时间代价为$O(n^2)$</li><li>用邻接表来表示图，虽然有 2e 个表结点，但只需扫描 e 个结点即可完成遍历，加上访问 n个头结点的时间，时间复杂度为$O(n+e)$</li></ul></li><li>DFS和BFS算法效率比较<ul><li>空间复杂度相同，都是O(n)（借用了堆栈或队列）；</li><li>时间复杂度只与存储结构（邻接矩阵或邻接表）有关，而与搜索路径无关。</li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第六章(上篇)--王卓老师第十周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第五章(汇总)</title>
    <link href="https://www.fomal.cc/posts/bdc474ea.html"/>
    <id>https://www.fomal.cc/posts/bdc474ea.html</id>
    <published>2023-10-12T12:33:51.000Z</published>
    <updated>2023-10-12T12:38:21.282Z</updated>
    
    <content type="html"><![CDATA[<h2 id="5-树和二叉树">5 树和二叉树</h2><p>​ <em>树形结构与线性结构的不同就是，<strong>线性结构</strong>的前驱和后继是<strong>一对一</strong>的，树状结构属于<strong>非线性结构</strong>，有多个后继。前驱与后继是<strong>一对n</strong>的</em></p><h3 id="5-1-树和二叉树的定义">5.1 树和二叉树的定义</h3><h4 id="5-1-1-树的定义">5.1.1 树的定义</h4><ul><li>树是个n个结点的<strong>有限集</strong>：<ul><li>若n=0，称为<strong>空树</strong>；</li><li>若n&gt;0，则他满足如下两个条件：<ol><li>有且仅有一个特定的称为<strong>根</strong>的结点</li><li>其余结点可分为吗（m&gt;=0）个互不相交的有限集T1,T2,T3。。</li></ol></li><li><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008135419.png" alt=""></li></ul></li></ul><h4 id="5-1-2-树的基本术语">5.1.2 树的基本术语</h4><ul><li><strong>根结点</strong>：非空树中无前驱结点的结点。</li><li>结点的<strong>度</strong>：结点拥有的子树数。</li><li>树的度：树内各结点的度的最大值。</li><li>结点的子树的根称为该结点的<strong>孩子</strong>，该结点称为该孩子的<strong>双亲</strong>，还有结点的<strong>祖先</strong>，结点的<strong>子孙</strong>就不展开描述了。</li><li>有序树：树中结点的各子树从左至右有次序（最左边的为第一个孩子）。</li><li>无序树：树中结点的各子树无次序。</li><li>森林：是m棵互不相交的树的集合<ul><li>把根结点删除树就变成了森林</li><li>一棵树可以看成是一个特殊的森林，<em><strong>树一定是森林，森林不一定是树</strong></em></li><li>给森林中各子树加上一个双亲节点，森林就变成了树</li></ul></li></ul><h4 id="5-1-3-二叉树的定义">5.1.3 二叉树的定义</h4><p>二叉树的结构最简单，规律性最强，可以证明所有树都能转化为<strong>唯一对应的二叉树</strong>，不失一般性。</p><ul><li><p>二叉树：是n个结点的有限集，他或者是空集或者是由<strong>一个根结点</strong>及两棵<strong>互不相交</strong>的分别称作这个根的<strong>左子树</strong>和<strong>右子树</strong>的二叉树组成</p><ul><li><p>特点：每个节点最多两个孩子</p></li><li><p>子树有左右之分(即使只有一棵子树也进行区分)，<strong>次序不能颠倒</strong></p></li><li><p>二叉树可以是空集合，根可以有空的左子树或空的右子树。</p></li><li><p>具有三个结点的树可能有几种形态？</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008151232.png" alt=""></p></li></ul></li></ul><h3 id="5-2-案例引入">5.2 案例引入</h3><h4 id="5-2-1-数据压缩问题">5.2.1 数据压缩问题</h4><p>将数据文件转换成由0、1组成的二进制串，称之为编码</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008152034.png" alt=""></p><p><em>具体的方法到哈夫曼树和哈弗曼编码那里学习，这里暂且按下不表</em></p><h4 id="5-2-2-利用二叉树求解表达式的值">5.2.2 利用二叉树求解表达式的值</h4><ul><li>以二叉树表示表达式的递归定义如下：<ol><li>若表达式为数或简单变量，则相应二叉树中仅有一个根结点，其数据域存放该表达式信息；</li><li>若表达式为 “ 第一操作数 运算符 第二操作数” 的形式， 则相应的二叉树中以<strong>左子树表示第一操作数</strong>，<strong>右子树表示第二操作数</strong>，根结点存放<strong>运算符</strong>，其中错作数本身又为表达式。</li></ol></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008152737.png" alt=""></p><p>具体实现，我们会在[5.8 案例分析与实现](####5.8 案例分析与实现)进行讲解</p><h3 id="5-3-树和二叉树的抽象数据类型定义">5.3 树和二叉树的抽象数据类型定义</h3><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008155701.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008155916.png" alt=""></p><h3 id="5-4-二叉树的性质和存储结构">5.4 二叉树的性质和存储结构</h3><ol><li><p>在二叉树的第i层上之多有$\large 2^{i-1}$个结点</p></li><li><p>深度为k的二叉树<strong>至多</strong>有$\large 2^k-1$个结点</p></li><li><p>对任何一棵二叉树T，如果其叶子树为$\large n_0$,度为2的结点数为$\large n_2$，则$\large n_0=n_2+1$</p><ul><li>总边数$\large B=n-1   =   B=n_2<em>2+n_1</em>1$</li><li>总结点数为$\large n=n_2<em>2+n_1</em>1+1$  又   $n=n_2+n_1+n_0$</li><li>推导出$\large n_0=n_2+1$</li></ul></li><li><p>具有n个结点的完全二叉树的深度为[log_2n]（向下取整）+1</p><p>性质4表明了完全二叉树<strong>结点数n</strong>与完全二叉树<strong>深度k</strong>之间的关系</p></li><li><p>如果对一棵有 n个结点的完全二叉树，其结点按层序编号（从第 1 层到第[log2n]+ 1 层， 每层从左到右）， 则对任一结点(n=&gt;i&gt;=1), 有如果i=1，无双亲，如果i&gt;1.则其双亲是结点[$\large i/2$]。</p><ol><li>性质5表明了完全二叉树中<strong>双亲结点</strong>编号与<strong>孩子结点</strong>编号之间的关系</li></ol></li></ol><ul><li>两种特殊形式的二叉树<ul><li>满二叉树：一颗深度为k且有$\large 2^k-1$个结点的二叉树称为<strong>满二叉树</strong></li><li>特点：<ul><li>每一层上的结点数</li><li>编号从上到下，从左到右</li></ul></li><li>完全二叉树：深度为K的， 有n个结点的二叉树， 当且仅当其每一个结点都与深度为K的<strong>满二叉树中编号</strong>从1至n的结点<strong>一一对应</strong>时， 称之为<strong>完全二叉树</strong>。<ul><li>在满二叉树中，从<strong>最后一个结点开始</strong>，<strong>连续</strong>去掉<strong>任意</strong>个结点，都是一棵完全二叉树</li><li>叶子只能分布在最大的两层上</li><li>对任意一结点，如果其右子树的最大层次为i，则其左子树的最大层次必为i或i-1</li></ul></li></ul></li></ul><h3 id="5-5-遍历二叉树和线索二叉树">5.5 遍历二叉树和线索二叉树</h3><h4 id="5-5-1-遍历二叉树">5.5.1 遍历二叉树</h4><ul><li><p>遍历的定义——顺着某一条搜索路径巡访二叉树中的结点，使得每个结点均被访问一次，而且仅被访问一次</p><ul><li>“访问”可以看做对结点作各种处理</li></ul></li><li><p>遍历的目的——得到树中所有结点的一个线性排列</p></li><li><p>遍历的用途——它是树结构插入、删除、修改、查找和排序运算的前提，是二叉树一切运算的基础和核心</p></li><li><p>遍历的算法</p><ul><li><p>DLR——先（根）序遍历</p></li><li><p>LDR——中（根）序遍历 【从最左边 开始左根右】，可以吧空序都先画出来，然后再开始遍历</p></li><li><p>LRD——后（根）序遍历</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231009184307.png" alt=""></p></li><li><p>例题——已知中序序列和后续序列求二叉树</p></li><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231009185844.png" alt=""></p></li><li><p><em><strong>前后确定根，中序辨左右</strong></em>      重点</p></li></ul></li><li><p>二叉树先序遍历算法</p> <figure class="highlight c"><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">Status PreOrderTiraverse（BiTree T）&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>)<span class="keyword">return</span> OK;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        visit(T);<span class="comment">//访问根结点</span></span><br><span class="line">        PreOrderTiraverse(T-&gt;lchild);<span class="comment">//递归遍历左子树，递归调用</span></span><br><span class="line">        PreOrderTiraverse(T-&gt;rchild);<span class="comment">//递归遍历右子树</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010124050.png" alt=""></p><p><strong>其中涉及到了递归调用的逐层返回</strong></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010143819.png" alt=""></p></li></ul></li></ul><p>如果去掉输出语句，从递归的角度看，三种三发事完全相同的，只是访问的十几不同</p><p>时间复杂度是3n，其中3是常数可以去掉，所以O(n)</p><h5 id="5-5-1-1-遍历二叉树的非递归算法">5.5.1.1 遍历二叉树的非递归算法</h5><ul><li><p>二叉树<strong>中序遍历的非递归算法的关键</strong>：在中序遍历过某结点的整个左子树后，如何找到该结点的<strong>根</strong>以及<strong>右子树</strong></p></li><li><p>基本思想</p><ol><li><p>建立一个<strong>栈</strong></p></li><li><p>根结点<strong>进栈</strong>，遍历<strong>左子树</strong></p></li><li><p>根结点<strong>出栈</strong>，输出根结点，遍历<strong>右子树</strong></p></li><li><pre><code class="language-c">Status InOrderTraverse(BiTree T)&#123;    BiTree p;InitStack(S);P=T;    while(p||!StackEmpty(S))&#123;//StackEmpty（S），S空返回true,非空返回false        if(p)&#123;Push (S,p); p=p-&gt;lchild;&#125;//push（S，p）是把p值入栈S        else &#123;Pop(S,q); printf(&quot;%c&quot;, q-&gt;data);//pop（S，q）是出栈值给q             p=q-&gt;rchild;&#125;    &#125;//while&#125;<figure class="highlight clean"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">     ***走到D时，左右子树都为空返回时，q会指向A应为栈内一开始就存放了A，然后再访问A的右子树***</span><br><span class="line"></span><br><span class="line">##### <span class="number">5.5</span><span class="number">.1</span><span class="number">.2</span> 二叉树的层次遍历</span><br><span class="line"></span><br><span class="line">- 队列类型定义</span><br><span class="line"></span><br><span class="line">  ```c</span><br><span class="line">  typedef struct&#123;</span><br><span class="line">      BTNode data[MaxSize];</span><br><span class="line">      int front,rear;</span><br><span class="line">  &#125;SqQueue;</span><br></pre></td></tr></table></figure></code></pre></li></ol><p>二叉树层次遍历算法：</p><figure class="highlight c"><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="type">void</span> <span class="title function_">LevelOrder</span><span class="params">(BTNode*b)</span>&#123;</span><br><span class="line">    BTNode *p;   SqQueue *qu;</span><br><span class="line">    InitQueue(qu);<span class="comment">//初始化队列</span></span><br><span class="line">    enQueue(qu, b);<span class="comment">//根结点指针进入队列</span></span><br><span class="line">    <span class="keyword">while</span>(!QueueEmpty（qu)）&#123;</span><br><span class="line">        deQueue(qu,p);<span class="comment">//出队结点p</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%c&quot;</span>,p-&gt;data);<span class="comment">//访问结点p</span></span><br><span class="line">        <span class="keyword">if</span>(p-&gt;lchild!=<span class="literal">NULL</span>)enQueue(qu,p-&gt;lchild);<span class="comment">//有左孩子时将其进队</span></span><br><span class="line">        <span class="keyword">if</span>(p-&gt;rchild!=<span class="literal">NULL</span>)enQueue(qu,p-&gt;rchild);<span class="comment">//有右孩子时将其进队</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-5-1-3-遍历算法的应用——二叉树的建立">5.5.1.3 遍历算法的应用——二叉树的建立</h5><ul><li>算法描述：</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Statys <span class="title function_">CreatBiTree</span><span class="params">(BiTree &amp;T)</span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(&amp;ch);</span><br><span class="line">    <span class="keyword">if</span>(ch==<span class="string">&quot;#&quot;</span>)T=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(!(BiTNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(BiTNode)))<span class="comment">//分配一块儿结点空间</span></span><br><span class="line">            <span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">        T-&gt;data=ch;<span class="comment">//生成根结点</span></span><br><span class="line">        CreateBiTree(T-&gt;lchild);</span><br><span class="line">        CreateBiTree(T-&gt;rchild);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-4-复制二叉树">5.5.1.4 复制二叉树</h5><figure class="highlight c"><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="type">int</span> <span class="title function_">Copy</span><span class="params">(BiTree T,BiTree &amp;NewT)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>)&#123;</span><br><span class="line">        NewT==<span class="literal">NULL</span>;<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        NewT=new BiTNode;</span><br><span class="line">        NewT-&gt;data=T-&gt;data;</span><br><span class="line">        copy(T-&gt;lchild, NewT-&gt;lchild);</span><br><span class="line">        copy(T-&gt;rchild, NewT-&gt;rchild);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-5-计算二叉树的深度">5.5.1.5 计算二叉树的深度</h5><ul><li>如果是空树，则深度为0</li></ul><p>​否则，递归计算左子树深度记为m，右子树深度为n，二叉树深度则为n和m的较大者加1</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">Depth</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        m=Depth &#123;T-&gt;lchild); </span><br><span class="line">n=Depth &#123;T-&gt;rchild);</span><br><span class="line"><span class="keyword">if</span>&#123;m&gt;n) <span class="keyword">return</span>&#123;m+l);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">return</span>(n+l);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-6-计算二叉树的结点总数">5.5.1.6 计算二叉树的结点总数</h5><ul><li>如果是空树，则结点个数为0</li></ul><p>​否则，结点个数为左子树的结点个数+右子树的结点个数再加1</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">NodeCount</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (T==<span class="literal">NULL</span>) </span><br><span class="line">        <span class="keyword">return</span> O;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> NodeCount (T-&gt;lchild) +Node Count (T-&gt;rchild) + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-7-计算二叉树叶子结点数">5.5.1.7 计算二叉树叶子结点数</h5><figure class="highlight c"><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="type">int</span> <span class="title function_">leafcount</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T=<span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>:</span><br><span class="line">    <span class="keyword">if</span>(T-&gt;lchild==<span class="literal">NULL</span>&amp;&amp;T-&gt;rchild==<span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> LeafCount(T-&gt;lchild)+LeafCount(T-&gt;rchild;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5-5-2-线索二叉树">5.5.2 线索二叉树</h4><ul><li><p>利用二叉链表中的空指针域：</p><p>如果某个节点的左孩子为空，则将空的<strong>左孩子</strong>指针域改为<strong>指向其前驱</strong>；如果<strong>右孩子</strong>为空，则其指针域改为<strong>指向其后继</strong>————将<strong>改变指向的指针</strong>称为**“线索”<strong>，加上了线索的二叉树称为</strong>线索二叉树**</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010195613.png" alt=""></p></li><li><p>为了区分是指向孩子的指针还是指向前驱或者后继的指针，对二叉链表中每个结点增设两个标志域ltag和rtag，这样结点的结构就为【lchild | ltag | data | rtag | rchild 】并约定：</p><ul><li><p>ltag=0/rtag=0  指针指向孩子</p></li><li><p>ltag=1/rtag=1  指针指向前驱/后继</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010201924.png" alt=" "></p></li></ul></li></ul><h3 id="5-6-树和森林">5.6 树和森林</h3><p><em><strong>本节将讨论树的表示及其遍历操作，并建立森林与二叉树的对应关系。</strong></em></p><h4 id="5-6-1-树的存储结构">5.6.1 树的存储结构</h4><h5 id="5-6-6-1-双亲表示法">5,6,6,1 双亲表示法</h5><p>实现：定义结构数组存放树的结点，每个结点含两个域；</p><ul><li><p>数据域：存放结点本身信息</p></li><li><p>双亲域：指示本结点的双亲结点在数组中的位置<br><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011113024.png" alt=""></p></li><li><p>C语言的类型描述：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">PTNode</span>&#123;</span></span><br><span class="line">    TElemType data;</span><br><span class="line">    <span class="type">int</span> parent;</span><br><span class="line">&#125;PTNode;</span><br></pre></td></tr></table></figure></li><li><p>树结构：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAX_TREE_SIZE 100</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    PTNode nodes[MAX_TREE_SIZE];</span><br><span class="line">    <span class="type">int</span> r,n;<span class="comment">//根结点的位置和结点个数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-6-1-2-孩子链表">5.6.1.2 孩子链表</h5><ul><li><p>把每个结点的孩子结点排列起来。看成一个线性表，用单链表存储</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011114137.png" alt=""></p></li><li><p>C语言描述：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">CTNode</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> child;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">CTNode</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;*Childptr;</span><br></pre></td></tr></table></figure></li><li><p>树结构</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    CTBox nodes[MAX_TREE_SIZE];</span><br><span class="line">    <span class="type">int</span> n,r;</span><br><span class="line">&#125;CTree;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-6-1-3-孩子兄弟表示法">5.6.1.3 孩子兄弟表示法</h5><ul><li><p>C语言描述</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">CSNode</span>&#123;</span></span><br><span class="line">    ElemType data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">CSNode</span> *<span class="title">firstchild</span>,*<span class="title">nextsibling</span>;</span></span><br><span class="line">&#125;CSNode,*CSTree;</span><br></pre></td></tr></table></figure><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011115045.png" alt=""></p></li></ul><h4 id="5-6-2-树与二叉树的转换">5.6.2 树与二叉树的转换</h4><ul><li><p>将树转换为二叉树进行处理，利用二叉树的算法来实现对树的操作。</p></li><li><p>由于树和二叉树都可以用二叉链表做存储结构，则以二叉链表做媒介可以导出树与二叉树之间的一个对应关系。</p></li><li><p>给定一棵树可以找到唯一的一颗二叉树与之对应</p><ul><li><p>加线：在兄弟之间加一条线，</p></li><li><p>抹线：对每个结点，除了其左孩子外，去除其余孩子之间的关系</p></li><li><p>旋转：以树根结点为轴心，将整树顺时针旋转45°</p><p><strong>树变二叉树：兄弟相连留长子。<em>其中根结点的油茶树一定为空</em></strong></p></li></ul></li><li><p>反之可以把二叉树转换成树</p><ul><li><p>加线：若p结点是双亲节点的左孩子，则将p的右孩子，右孩子的右孩子……沿分支找到所有右孩子，都与p的双亲用线连起来</p></li><li><p>抹线：抹掉原二叉树中双亲与右孩子之间的连线</p></li><li><p>调整：将结点按层次排列，形成树结构。</p><p><strong>二叉树变树：左孩右右连双亲，去掉原来右孩线</strong></p></li></ul></li></ul><h4 id="5-6-3-森林与二叉树的转换">5.6.3 森林与二叉树的转换</h4><ul><li><p>森林转换成二叉树（二叉树与多棵树之间的关系）</p><ul><li><p>将个棵树分别转换成二叉树</p></li><li><p>将每棵树的根结点用线相连</p></li><li><p>以第一课树根结点为二叉树的根，再以根结点为轴心，顺时针旋转，构成二叉树型结构</p><p><strong>森林变二叉树：树变二叉根相连</strong></p></li></ul></li><li><p>二叉树转换成森林</p><ul><li><p>抹线：将二叉树中根结点与其右孩子连线，及沿有分支搜索到的所有右孩子之间的连线抹掉，使之变成孤立的二叉树</p></li><li><p>还原：将孤立的二叉树还原成树</p><p><strong>二叉树变森林：去掉全部右孩线，孤立二叉再还原</strong></p></li></ul></li></ul><h4 id="5-6-4-树和森林的遍历">5.6.4 树和森林的遍历</h4><h5 id="5-6-4-1树的遍历">5.6.4.1树的遍历</h5><ul><li>先根遍历：若树不为空，则先访问根结点，然后一次先根遍历各棵子树。</li><li>后根遍历：若树不为空，则先依次后根遍历各棵子树，然后访问根结点。</li><li>层次遍历：若树不为空，则自上而下自左至右访问树种每个结点。</li></ul><h5 id="5-6-4-2-森林的遍历">5.6.4.2 森林的遍历</h5><ul><li><p>将森林看作由三部分构成：</p><ol><li>森林中第一棵树的根结点；</li><li>森林中第一棵树的子树森林；</li><li>森林中其他树构成的森林。</li></ol></li><li><p>先序遍历：若森林不为空，则依次从左至右对森林中的每一棵树进行先根遍历</p></li><li><p>中序遍历：若森林不为空，则</p><ol><li><p>中序遍历森林中第一棵树的子树森林；</p></li><li><p>访问森林中第一棵树的根结点；</p></li><li><p>中序遍历森林中（除第一棵树之外）其余树构成的森林。</p><p>即：依次从左至右对森林中每一个棵树进行后根遍历</p></li></ol></li></ul><h3 id="5-7哈夫曼树的基本概念">5.7哈夫曼树的基本概念</h3><h4 id="5-7-1-哈夫曼树的基本概念">5.7.1 哈夫曼树的基本概念</h4><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011154849.png" alt=""></p><p><em><strong>效率最高的判别树，就是哈夫曼树（也称最优二叉树）</strong></em></p><ul><li><p><strong>路径</strong>：从树中一个结点到另一个结点之间的<strong>分支</strong>构成这两个结点间的路径</p></li><li><p><strong>结点的路径长度</strong>：两结点间路径上的<strong>分支数</strong>。</p></li><li><p><strong>树的路径长度</strong>：从<strong>树根</strong>到没一个结点的<strong>路径长度之和</strong>。<strong>记作：TL</strong></p><ul><li>结点数目相同的二叉树中，完全二叉树是路径长度最短的二叉树，但路径长度最短的不一定是完全二叉树</li></ul></li><li><p><strong>权</strong>(weight)：将树中结点赋给一个有着某种含义的数值，则这个数值称为该<strong>结点的权。</strong></p></li><li><p><strong>结点的带权路径长度</strong>：从<strong>根</strong>结点到该结点之间的<strong>路径长度</strong>与该结点的<strong>权</strong>的<strong>乘积</strong>。</p></li><li><p><strong>树的带权路径长度</strong>：树中所有<strong>叶子</strong>结点的<strong>带权路径长度之和</strong>。记作：$\large WPL=\sum\limits_{k=1}^nw_kl_K$(weighted path length)</p></li><li><p>哈夫曼树：<strong>最优树</strong>/<strong>最优二叉树</strong>（<strong>带权路径长度最短的树</strong>）</p><ul><li>因为构造这种树的算法是由哈夫曼教授于<strong>1952</strong>年提出的，所以被称为<strong>哈夫曼树</strong>，相应的算法称为<strong>哈夫曼算法</strong></li><li>满二叉树不一定是哈夫曼树</li><li>哈夫曼树中全越大的叶子离根越近</li><li>具有相同带权结点的哈夫曼树不唯一</li></ul></li><li><p>贪心算法：构造哈夫曼树时首先选择权值小的叶子结点</p></li></ul><h4 id="5-7-2-哈夫曼树的构造算法">5.7.2 哈夫曼树的构造算法</h4><ul><li><p>构造过程</p><ol><li><p>根据给定的n个权值{$w_1.w_2.w_3,……,w_n$}，构造n棵只有根结点的二叉树，这n棵二叉树构成一个森林F。</p></li><li><p>在森林 F 中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树，且置新的二叉树的根结点的权值为其左 、右子树上根结点的权值之和</p></li><li><p>在森林F中删除这两棵树，同时将新得到的二叉树加入森林中。</p></li><li><p>重复2和3的步骤，直到森林中只有一棵树为止，这棵树即为哈夫曼树。</p></li></ol></li><li><p>口诀</p><ol><li>构造森林全是根</li><li>选用两小造新树</li><li>删除两小添新人</li><li>重复二三剩单根</li></ol><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011201244.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011201620.png" alt=""></p></li><li><p>总结</p><ol><li><p>在哈夫曼树算法中，初始时有n棵二叉树，要经过n-1次合并最终形成哈夫曼树</p></li><li><p>经过n_1次合并产生n-1个新结点，且这n-1个新结点都是具有两个孩子的分支节点</p><p>可见：哈夫曼树中共有n+n-1=2n-1个结点，且其所有的分支结点的度均不为1.</p></li></ol></li><li><p>哈夫曼树构造算法的实现</p></li><li><p>采用顺序存储结构——一维结构数组 HuffmanTree H;</p></li><li><p>结点类型定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> weight;</span><br><span class="line">    <span class="type">int</span> parent,lch,rch;</span><br><span class="line">&#125;HTNode,*HuffmanTree;</span><br></pre></td></tr></table></figure><table><thead><tr><th>哈夫曼树中结点下标i</th><th>weight</th><th>parent</th><th>lch</th><th>rch</th></tr></thead><tbody><tr><td>1</td><td></td><td></td><td></td><td></td></tr><tr><td>2</td><td></td><td></td><td></td><td></td></tr><tr><td>3</td><td></td><td></td><td></td><td></td></tr><tr><td>4</td><td></td><td></td><td></td><td></td></tr><tr><td>……</td><td></td><td></td><td></td><td></td></tr><tr><td>2n_1</td><td></td><td></td><td></td><td></td></tr></tbody></table></li><li><p>构造哈夫曼树</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreateHuffmanTree</span><span class="params">(HuffmanTree &amp;HT,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(n&lt;=l) <span class="keyword">return</span>;</span><br><span class="line">m=<span class="number">2</span>*n-l;<span class="comment">//数组一共2n-1个元素</span></span><br><span class="line">HT=new HTNode[m+l);<span class="comment">//0号单元未用，HT[m]表示根结点</span></span><br><span class="line">    <span class="keyword">for</span>(i=l;i&lt;=m;++i)&#123;<span class="comment">//将2n-1个元素的lch/rch/parent置为0</span></span><br><span class="line">        HT[i].parent=O;HT[i].lchild=O;HT[i].rchild=O;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(i=l;i&lt;=n;++i&#125;<span class="built_in">cin</span>&gt;&gt;HT[i] .weight;<span class="comment">//输入前n个元素weight的值，初始化结束</span></span><br><span class="line">    <span class="keyword">for</span> (i=n+l;i&lt;=m;++i)</span><br><span class="line">        Select (HT,i<span class="number">-1</span>,sl,s2);</span><br><span class="line">        HT[sl] .parent=i;HT[s2] .parent=i;</span><br><span class="line">        HT[i] .lchild=sl;HT [i]. rchild=s2;</span><br><span class="line">        HT[i] .weight=HT[sl] .weight+HT[s2] .weight;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>例题：</p><p>巳知 w = (5,29,7,8,14,23,3,11), 利用算法 5.10 试构造一棵哈夫曼树， 计算树的带权路径长度， 并给出其构造过程中存储结构HT的初始状态和终结状态。</p><table><thead><tr><th>哈夫曼树中结点下标i</th><th>weight</th><th>parent</th><th>lch</th><th>rch</th></tr></thead><tbody><tr><td>1</td><td><strong>5</strong></td><td>9</td><td>0</td><td>0</td></tr><tr><td>2</td><td><strong>29</strong></td><td>14</td><td>0</td><td>0</td></tr><tr><td>3</td><td><strong>7</strong></td><td>10</td><td>0</td><td>0</td></tr><tr><td>4</td><td><strong>8</strong></td><td>10</td><td>0</td><td>0</td></tr><tr><td>5</td><td><strong>14</strong></td><td>12</td><td>0</td><td>0</td></tr><tr><td>6</td><td><strong>23</strong></td><td>13</td><td>0</td><td>0</td></tr><tr><td>7</td><td><strong>3</strong></td><td>9</td><td>0</td><td>0</td></tr><tr><td>8</td><td><strong>11</strong></td><td>11</td><td>0</td><td>0</td></tr><tr><td>9</td><td><strong>8</strong></td><td>11</td><td>1</td><td>7</td></tr><tr><td>10</td><td><strong>15</strong></td><td>12</td><td>3</td><td>4</td></tr><tr><td>11</td><td><strong>19</strong></td><td>13</td><td>8</td><td>9</td></tr><tr><td>12</td><td><strong>29</strong></td><td>14</td><td>5</td><td>10</td></tr><tr><td>13</td><td><strong>42</strong></td><td>15</td><td>6</td><td>11</td></tr><tr><td>14</td><td><strong>58</strong></td><td>15</td><td>2</td><td>12</td></tr><tr><td>15</td><td><strong>100</strong></td><td>0</td><td>13</td><td>14</td></tr></tbody></table></li></ul><h4 id="5-7-3-哈夫曼编码">5.7.3 哈夫曼编码</h4><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreatHuffmanCode</span><span class="params">(HuffmanTree HT,HuffmanCode &amp;HC,<span class="type">int</span> n)</span>&#123;<span class="comment">//从叶子到根逆向求每个字符的哈夫曼编码，存储在编码表HC中</span></span><br><span class="line">    HC=nuw <span class="type">char</span>*[n+<span class="number">1</span>];<span class="comment">//分配n个字符编码的头指针矢量</span></span><br><span class="line">    cd=new <span class="type">char</span>[n];<span class="comment">//分配临时存放编码的动态数组空间</span></span><br><span class="line">    cd[n<span class="number">-1</span>]=<span class="string">&#x27;\0&#x27;</span>;<span class="comment">//编码结束符</span></span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;=n;++i)&#123;<span class="comment">//逐个字符求哈夫曼编码</span></span><br><span class="line">        start=n<span class="number">-1</span>;c=i;f=HT[i].parent;</span><br><span class="line">        <span class="keyword">while</span>(f!=<span class="number">0</span>)&#123;<span class="comment">//从叶子结点开始向上回溯，直到根节点</span></span><br><span class="line">            --start;<span class="comment">//回溯一次start向前指一个位置</span></span><br><span class="line">            <span class="keyword">if</span></span><br><span class="line">                (HT[f].lchild==c)cd[start]=<span class="string">&#x27;0&#x27;</span>;<span class="comment">//结点c是f的左孩子，则生成代码0</span></span><br><span class="line">            <span class="keyword">else</span> cd[start]=<span class="string">&#x27;1&#x27;</span>;<span class="comment">//结点c是f的右孩子，则生成代码1</span></span><br><span class="line">                c=f;f=HT[f].parent;<span class="comment">//继续向上回溯</span></span><br><span class="line">        &#125;<span class="comment">//求出第i个字符的编码</span></span><br><span class="line">        HC[i]=new <span class="type">char</span>[n-start];<span class="comment">//为第i个字符串编码分配空间</span></span><br><span class="line">        <span class="built_in">strcpy</span>(HC[i],&amp;cd[start]);<span class="comment">//将求得的编码从临时空间cd复制到HC的当前行中</span></span><br><span class="line">    &#125;</span><br><span class="line">   delete cd;<span class="comment">//释放临时空间</span></span><br><span class="line">&#125;<span class="comment">//creatHuffmanCode</span></span><br></pre></td></tr></table></figure><ol><li>编码：<ol><li>输入各字符及其权值</li><li>构造哈夫曼树——HT[i]  n+n-1个</li><li>进行哈夫曼编码——HC[i]</li><li>查HC[i]，得到各字符的哈夫曼编码</li></ol></li><li>解码：<ol><li>构造哈夫曼树</li><li>依次读入二进制码</li><li>读入0，则走向左孩子，读入1，则走向右孩子</li><li>一旦到达某叶子时，即可译出字符</li><li>然后再从根出发继续译码，直到结束</li></ol></li></ol><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012173517.png" alt=""></p>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第五章--王卓老师课堂三周笔记的总结</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第五章(下)</title>
    <link href="https://www.fomal.cc/posts/15c51329.html"/>
    <id>https://www.fomal.cc/posts/15c51329.html</id>
    <published>2023-10-11T13:31:15.000Z</published>
    <updated>2023-10-12T12:40:13.986Z</updated>
    
    <content type="html"><![CDATA[<h3 id="5-7哈夫曼树的基本概念">5.7哈夫曼树的基本概念</h3><h4 id="5-7-1-哈夫曼树的基本概念">5.7.1 哈夫曼树的基本概念</h4><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011154849.png" alt=""></p><p><em><strong>效率最高的判别树，就是哈夫曼树（也称最优二叉树）</strong></em></p><ul><li><p><strong>路径</strong>：从树中一个结点到另一个结点之间的<strong>分支</strong>构成这两个结点间的路径</p></li><li><p><strong>结点的路径长度</strong>：两结点间路径上的<strong>分支数</strong>。</p></li><li><p><strong>树的路径长度</strong>：从<strong>树根</strong>到没一个结点的<strong>路径长度之和</strong>。<strong>记作：TL</strong></p><ul><li>结点数目相同的二叉树中，完全二叉树是路径长度最短的二叉树，但路径长度最短的不一定是完全二叉树</li></ul></li><li><p><strong>权</strong>(weight)：将树中结点赋给一个有着某种含义的数值，则这个数值称为该<strong>结点的权。</strong></p></li><li><p><strong>结点的带权路径长度</strong>：从<strong>根</strong>结点到该结点之间的<strong>路径长度</strong>与该结点的<strong>权</strong>的<strong>乘积</strong>。</p></li><li><p><strong>树的带权路径长度</strong>：树中所有<strong>叶子</strong>结点的<strong>带权路径长度之和</strong>。记作：$\large WPL=\sum\limits_{k=1}^nw_kl_K$(weighted path length)</p></li><li><p>哈夫曼树：<strong>最优树</strong>/<strong>最优二叉树</strong>（<strong>带权路径长度最短的树</strong>）</p><ul><li>因为构造这种树的算法是由哈夫曼教授于<strong>1952</strong>年提出的，所以被称为<strong>哈夫曼树</strong>，相应的算法称为<strong>哈夫曼算法</strong></li><li>满二叉树不一定是哈夫曼树</li><li>哈夫曼树中全越大的叶子离根越近</li><li>具有相同带权结点的哈夫曼树不唯一</li></ul></li><li><p>贪心算法：构造哈夫曼树时首先选择权值小的叶子结点</p></li></ul><h4 id="5-7-2-哈夫曼树的构造算法">5.7.2 哈夫曼树的构造算法</h4><ul><li><p>构造过程</p><ol><li><p>根据给定的n个权值{$w_1.w_2.w_3,……,w_n$}，构造n棵只有根结点的二叉树，这n棵二叉树构成一个森林F。</p></li><li><p>在森林 F 中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树，且置新的二叉树的根结点的权值为其左 、右子树上根结点的权值之和</p></li><li><p>在森林F中删除这两棵树，同时将新得到的二叉树加入森林中。</p></li><li><p>重复2和3的步骤，直到森林中只有一棵树为止，这棵树即为哈夫曼树。</p></li></ol></li><li><p>口诀</p><ol><li>构造森林全是根</li><li>选用两小造新树</li><li>删除两小添新人</li><li>重复二三剩单根</li></ol><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011201244.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011201620.png" alt=""></p></li><li><p>总结</p><ol><li><p>在哈夫曼树算法中，初始时有n棵二叉树，要经过n-1次合并最终形成哈夫曼树</p></li><li><p>经过n_1次合并产生n-1个新结点，且这n-1个新结点都是具有两个孩子的分支节点</p><p>可见：哈夫曼树中共有n+n-1=2n-1个结点，且其所有的分支结点的度均不为1.</p></li></ol></li><li><p>哈夫曼树构造算法的实现</p></li><li><p>采用顺序存储结构——一维结构数组 HuffmanTree H;</p></li><li><p>结点类型定义</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> weight;</span><br><span class="line">    <span class="type">int</span> parent,lch,rch;</span><br><span class="line">&#125;HTNode,*HuffmanTree;</span><br></pre></td></tr></table></figure><table><thead><tr><th>哈夫曼树中结点下标i</th><th>weight</th><th>parent</th><th>lch</th><th>rch</th></tr></thead><tbody><tr><td>1</td><td></td><td></td><td></td><td></td></tr><tr><td>2</td><td></td><td></td><td></td><td></td></tr><tr><td>3</td><td></td><td></td><td></td><td></td></tr><tr><td>4</td><td></td><td></td><td></td><td></td></tr><tr><td>……</td><td></td><td></td><td></td><td></td></tr><tr><td>2n_1</td><td></td><td></td><td></td><td></td></tr></tbody></table></li><li><p>构造哈夫曼树</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreateHuffmanTree</span><span class="params">(HuffmanTree &amp;HT,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(n&lt;=l) <span class="keyword">return</span>;</span><br><span class="line">m=<span class="number">2</span>*n-l;<span class="comment">//数组一共2n-1个元素</span></span><br><span class="line">HT=new HTNode[m+l);<span class="comment">//0号单元未用，HT[m]表示根结点</span></span><br><span class="line">    <span class="keyword">for</span>(i=l;i&lt;=m;++i)&#123;<span class="comment">//将2n-1个元素的lch/rch/parent置为0</span></span><br><span class="line">        HT[i].parent=O;HT[i].lchild=O;HT[i].rchild=O;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(i=l;i&lt;=n;++i&#125;<span class="built_in">cin</span>&gt;&gt;HT[i] .weight;<span class="comment">//输入前n个元素weight的值，初始化结束</span></span><br><span class="line">    <span class="keyword">for</span> (i=n+l;i&lt;=m;++i)</span><br><span class="line">        Select (HT,i<span class="number">-1</span>,sl,s2);</span><br><span class="line">        HT[sl] .parent=i;HT[s2] .parent=i;</span><br><span class="line">        HT[i] .lchild=sl;HT [i]. rchild=s2;</span><br><span class="line">        HT[i] .weight=HT[sl] .weight+HT[s2] .weight;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>例题：</p><p>巳知 w = (5,29,7,8,14,23,3,11), 利用算法 5.10 试构造一棵哈夫曼树， 计算树的带权路径长度， 并给出其构造过程中存储结构HT的初始状态和终结状态。</p><table><thead><tr><th>哈夫曼树中结点下标i</th><th>weight</th><th>parent</th><th>lch</th><th>rch</th></tr></thead><tbody><tr><td>1</td><td><strong>5</strong></td><td>9</td><td>0</td><td>0</td></tr><tr><td>2</td><td><strong>29</strong></td><td>14</td><td>0</td><td>0</td></tr><tr><td>3</td><td><strong>7</strong></td><td>10</td><td>0</td><td>0</td></tr><tr><td>4</td><td><strong>8</strong></td><td>10</td><td>0</td><td>0</td></tr><tr><td>5</td><td><strong>14</strong></td><td>12</td><td>0</td><td>0</td></tr><tr><td>6</td><td><strong>23</strong></td><td>13</td><td>0</td><td>0</td></tr><tr><td>7</td><td><strong>3</strong></td><td>9</td><td>0</td><td>0</td></tr><tr><td>8</td><td><strong>11</strong></td><td>11</td><td>0</td><td>0</td></tr><tr><td>9</td><td><strong>8</strong></td><td>11</td><td>1</td><td>7</td></tr><tr><td>10</td><td><strong>15</strong></td><td>12</td><td>3</td><td>4</td></tr><tr><td>11</td><td><strong>19</strong></td><td>13</td><td>8</td><td>9</td></tr><tr><td>12</td><td><strong>29</strong></td><td>14</td><td>5</td><td>10</td></tr><tr><td>13</td><td><strong>42</strong></td><td>15</td><td>6</td><td>11</td></tr><tr><td>14</td><td><strong>58</strong></td><td>15</td><td>2</td><td>12</td></tr><tr><td>15</td><td><strong>100</strong></td><td>0</td><td>13</td><td>14</td></tr></tbody></table></li></ul><h4 id="5-7-3-哈夫曼编码">5.7.3 哈夫曼编码</h4><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreatHuffmanCode</span><span class="params">(HuffmanTree HT,HuffmanCode &amp;HC,<span class="type">int</span> n)</span>&#123;<span class="comment">//从叶子到根逆向求每个字符的哈夫曼编码，存储在编码表HC中</span></span><br><span class="line">    HC=nuw <span class="type">char</span>*[n+<span class="number">1</span>];<span class="comment">//分配n个字符编码的头指针矢量</span></span><br><span class="line">    cd=new <span class="type">char</span>[n];<span class="comment">//分配临时存放编码的动态数组空间</span></span><br><span class="line">    cd[n<span class="number">-1</span>]=<span class="string">&#x27;\0&#x27;</span>;<span class="comment">//编码结束符</span></span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;=n;++i)&#123;<span class="comment">//逐个字符求哈夫曼编码</span></span><br><span class="line">        start=n<span class="number">-1</span>;c=i;f=HT[i].parent;</span><br><span class="line">        <span class="keyword">while</span>(f!=<span class="number">0</span>)&#123;<span class="comment">//从叶子结点开始向上回溯，直到根节点</span></span><br><span class="line">            --start;<span class="comment">//回溯一次start向前指一个位置</span></span><br><span class="line">            <span class="keyword">if</span></span><br><span class="line">                (HT[f].lchild==c)cd[start]=<span class="string">&#x27;0&#x27;</span>;<span class="comment">//结点c是f的左孩子，则生成代码0</span></span><br><span class="line">            <span class="keyword">else</span> cd[start]=<span class="string">&#x27;1&#x27;</span>;<span class="comment">//结点c是f的右孩子，则生成代码1</span></span><br><span class="line">                c=f;f=HT[f].parent;<span class="comment">//继续向上回溯</span></span><br><span class="line">        &#125;<span class="comment">//求出第i个字符的编码</span></span><br><span class="line">        HC[i]=new <span class="type">char</span>[n-start];<span class="comment">//为第i个字符串编码分配空间</span></span><br><span class="line">        <span class="built_in">strcpy</span>(HC[i],&amp;cd[start]);<span class="comment">//将求得的编码从临时空间cd复制到HC的当前行中</span></span><br><span class="line">    &#125;</span><br><span class="line">   delete cd;<span class="comment">//释放临时空间</span></span><br><span class="line">&#125;<span class="comment">//creatHuffmanCode</span></span><br></pre></td></tr></table></figure><ol><li>编码：<ol><li>输入各字符及其权值</li><li>构造哈夫曼树——HT[i]  n+n-1个</li><li>进行哈夫曼编码——HC[i]</li><li>查HC[i]，得到各字符的哈夫曼编码</li></ol></li><li>解码：<ol><li>构造哈夫曼树</li><li>依次读入二进制码</li><li>读入0，则走向左孩子，读入1，则走向右孩子</li><li>一旦到达某叶子时，即可译出字符</li><li>然后再从根出发继续译码，直到结束</li></ol></li></ol><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012173517.png" alt=""></p><h2 id="6-图">6 图</h2><p><em><strong>图是一种比线性表和树更为复杂的数据结构。</strong></em></p><h3 id="6-1-图的定义和基本术语">6.1 图的定义和基本术语</h3><h4 id="6-1-1-图的定义">6.1.1 图的定义</h4><ul><li><p>图：G=（V.E）</p><ul><li>V：顶点（数据元素）的<strong>有穷非空</strong>集合；</li><li>E：边的<strong>有穷</strong>集合</li></ul></li><li><p>无向图：每条边都是无方向的</p></li><li><p>有向图：每条边都是有方向的</p></li><li><p>完全图：任意两个点都有一条边相连</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012174252.png" alt=""></p></li><li><p>稀疏图：有很少边或弧的图（$e&lt;nlog_2n$）</p></li><li><p>稠密图：有较多边或弧的图</p></li></ul><h4 id="6-1-2-图的基本术语">6.1.2 图的基本术语</h4><ul><li><p>网，边/弧带权的图</p></li><li><p>邻接：有边/弧相连的两个顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）,则称为$v_i和v_j$互为<strong>邻接点</strong></li><li>存在&lt;$v_i,v_j$&gt;，则称$v_i$邻接到$v_j$,$v_j$邻接于$v_i$,</li></ul></li><li><p>关联（依附）：边/弧与顶点之间的关系。</p><ul><li>存在（$v_i,v_j$）/&lt;$v_i,v_j$&gt;，则称该边/弧关联于$v_i$和$v_j$</li></ul></li><li><p>顶点的度：与该顶点相关联的边的数目，记为TD（v）</p><ul><li><p>在<strong>有向图</strong>中，顶点的度等于该顶点的<strong>入度</strong>和<strong>出度</strong>之和。</p></li><li><p>顶点<strong>v的入度</strong>是以v为终点的有向边的条数，记作ID（v）</p></li><li><p>顶点<strong>v的出度</strong>是以v为始点的有向边的条数，记作OD（v）</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012175741.png" alt=""></p></li></ul></li><li><p>路径：连续的边构成的顶点序列</p></li><li><p>路径长度：路径上边或弧的数目/权值之和</p></li><li><p>回路（环）：第一个顶点和最后一个顶点相同的路径</p></li><li><p>简单路径：除路径起点和终点可以相同外，其余顶点均不相同的路径</p></li><li><p>简单回路（环）：除路径起点和终点相同外，其余顶点均不相同的路径</p></li><li><p>连通图（强连通图）：在无（有）向图G=(V,{E})中，若对任意两个顶点v、u都存在从v到u的路径，则称G是<strong>连通图</strong>（强连通图）</p></li><li><p>权与网：</p><ul><li>图中边或弧所具有的相关数称为<strong>权</strong>，表明从一个顶点到另一个顶点的距离或耗费。</li><li>带权的图称为<strong>网</strong></li></ul></li><li><p>子图：设有两个图$G=（V，{E}）、G_1=(V_1,{E_1})$,若$V_1属于V，E_1属于E$则称$G_1是G$的子图</p></li><li><p>连通分量（强连通分量）</p><ul><li><p>无向图G的<strong>极大联通子图</strong>称为G的<strong>连通分量</strong></p><ul><li>极大连通子图意思是：该子图是G联通子图，将G的任何不在该子图汇总的顶点加入，子图不再连通</li></ul></li><li><p>有向图G的<strong>极大连通子图</strong>称为G的<strong>强连通分量</strong></p><ul><li>极大强连通子图意思是: 该子图是G的强连通子图，将D的任何不在该子图中的顶点加入，子图不再是强连通的。</li></ul></li><li><p>极小联通子图：该子图是G的连通子图，在该子图中删除任何一条边，联通子图不再连通</p></li><li><p>生成树:包含无向图G所有顶点的极小连通子图</p></li><li><p>生成森林:对非连通图，由各个连通分量的生成树的集合</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231012201656.png" alt=""></p></li></ul></li></ul><h3 id="6-2-案例引入">6.2 案例引入</h3><h4 id="6-2-1-六度空间理论">6.2.1 六度空间理论</h4><p><strong>理论又称作六度分隔论 (Six Degrees of Separation)。六度空间理论是20世纪60年代由美国的心理学家米格兰姆(Stanley Milgram)提出的，理论指出：“<em>你和任何一个陌生人之间所间隔的人不会超过六个</em>”</strong></p><h4 id="6-3-图的类型定义">6.3 图的类型定义</h4><p>图的抽象数据类型定义如下：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">ADT Graph&#123;</span><br><span class="line">    数据对象V：具有相同特性的数据元素的集合，称为顶点集。</span><br><span class="line">    数据关系R：R&#123;VR&#125;</span><br><span class="line">    VR=&#123;&lt;V,W&gt;|&lt;V,W&gt;|V,W属于V^p(v,w)&#125;， &lt;v, w&gt;表示从v到w的弧，P (v, w)定义了弧&lt;v, w&gt;的信息</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>基本操作：</p><ul><li><p>CreateGraph{&amp;G,V,VR}</p><p>初始条件：V是图的顶点集，VR是图中弧的集合。</p><p>操作结果：按V和VR的定义<strong>构造图G</strong>。</p></li><li><p>DFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行<strong>深度优先遍历</strong>，在遍历过程中对每个顶点访问一次。</p></li><li><p>BFSTraverse(G)</p><p>初始条件：图G存在。</p><p>操作结果：对图进行广度优先遍历，在遍历过程中对每个顶点访问一次.</p></li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第五章下篇--王卓老师第九周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>文言文鉴赏——水调歌头</title>
    <link href="https://www.fomal.cc/posts/6de92aaf.html"/>
    <id>https://www.fomal.cc/posts/6de92aaf.html</id>
    <published>2023-10-11T07:26:04.000Z</published>
    <updated>2023-10-11T07:36:27.052Z</updated>
    
    <content type="html"><![CDATA[<div class='poem'><div class='poem-title'>水调歌头</div><div class='poem-author'>苏轼</div><p>丙辰中秋，欢饮达旦，大醉，作此篇，兼怀子由。<br>明月几时有？把酒问青天。<br>不知天上宫阙，今夕是何年？<br>我欲乘风归去，又恐琼楼玉宇，高处不胜寒。<br>起舞弄清影，何似在人间？</p><p>转朱阁，低绮户，照无眠。<br>不应有恨，何事长向别时圆？<br>人有悲欢离合，月有阴晴圆缺，此事古难全。<br>但愿人长久，千里共婵娟。</p></div>]]></content>
    
    
    <summary type="html">东坡之《水调歌头》，则伫兴之作，格高千古，不能以常调论也。—— 王国维《人间词话》</summary>
    
    
    
    <category term="鉴赏" scheme="https://www.fomal.cc/categories/%E9%89%B4%E8%B5%8F/"/>
    
    
    <category term="文言文鉴赏" scheme="https://www.fomal.cc/tags/%E6%96%87%E8%A8%80%E6%96%87%E9%89%B4%E8%B5%8F/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第五章(中)</title>
    <link href="https://www.fomal.cc/posts/d61b900d.html"/>
    <id>https://www.fomal.cc/posts/d61b900d.html</id>
    <published>2023-10-10T15:23:53.000Z</published>
    <updated>2023-10-12T12:40:21.254Z</updated>
    
    <content type="html"><![CDATA[<ul><li><p>二叉树先序遍历算法</p> <figure class="highlight c"><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">Status PreOrderTiraverse（BiTree T）&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>)<span class="keyword">return</span> OK;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        visit(T);<span class="comment">//访问根结点</span></span><br><span class="line">        PreOrderTiraverse(T-&gt;lchild);<span class="comment">//递归遍历左子树，递归调用</span></span><br><span class="line">        PreOrderTiraverse(T-&gt;rchild);<span class="comment">//递归遍历右子树</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010124050.png" alt=""></p><p><strong>其中涉及到了递归调用的逐层返回</strong></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010143819.png" alt=""></p></li></ul></li></ul><p>如果去掉输出语句，从递归的角度看，三种三发事完全相同的，只是访问的十几不同</p><p>时间复杂度是3n，其中3是常数可以去掉，所以O(n)</p><h5 id="5-5-1-1-遍历二叉树的非递归算法">5.5.1.1 遍历二叉树的非递归算法</h5><ul><li><p>二叉树<strong>中序遍历的非递归算法的关键</strong>：在中序遍历过某结点的整个左子树后，如何找到该结点的<strong>根</strong>以及<strong>右子树</strong></p></li><li><p>基本思想</p><ol><li><p>建立一个<strong>栈</strong></p></li><li><p>根结点<strong>进栈</strong>，遍历<strong>左子树</strong></p></li><li><p>根结点<strong>出栈</strong>，输出根结点，遍历<strong>右子树</strong></p></li><li><pre><code class="language-c">Status InOrderTraverse(BiTree T)&#123;    BiTree p;InitStack(S);P=T;    while(p||!StackEmpty(S))&#123;//StackEmpty（S），S空返回true,非空返回false        if(p)&#123;Push (S,p); p=p-&gt;lchild;&#125;//push（S，p）是把p值入栈S        else &#123;Pop(S,q); printf(&quot;%c&quot;, q-&gt;data);//pop（S，q）是出栈值给q             p=q-&gt;rchild;&#125;    &#125;//while&#125;<figure class="highlight clean"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">     ***走到D时，左右子树都为空返回时，q会指向A应为栈内一开始就存放了A，然后再访问A的右子树***</span><br><span class="line"></span><br><span class="line">##### <span class="number">5.5</span><span class="number">.1</span><span class="number">.2</span> 二叉树的层次遍历</span><br><span class="line"></span><br><span class="line">- 队列类型定义</span><br><span class="line"></span><br><span class="line">  ```c</span><br><span class="line">  typedef struct&#123;</span><br><span class="line">      BTNode data[MaxSize];</span><br><span class="line">      int front,rear;</span><br><span class="line">  &#125;SqQueue;</span><br></pre></td></tr></table></figure></code></pre></li></ol><p>二叉树层次遍历算法：</p><figure class="highlight c"><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="type">void</span> <span class="title function_">LevelOrder</span><span class="params">(BTNode*b)</span>&#123;</span><br><span class="line">    BTNode *p;   SqQueue *qu;</span><br><span class="line">    InitQueue(qu);<span class="comment">//初始化队列</span></span><br><span class="line">    enQueue(qu, b);<span class="comment">//根结点指针进入队列</span></span><br><span class="line">    <span class="keyword">while</span>(!QueueEmpty（qu)）&#123;</span><br><span class="line">        deQueue(qu,p);<span class="comment">//出队结点p</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%c&quot;</span>,p-&gt;data);<span class="comment">//访问结点p</span></span><br><span class="line">        <span class="keyword">if</span>(p-&gt;lchild!=<span class="literal">NULL</span>)enQueue(qu,p-&gt;lchild);<span class="comment">//有左孩子时将其进队</span></span><br><span class="line">        <span class="keyword">if</span>(p-&gt;rchild!=<span class="literal">NULL</span>)enQueue(qu,p-&gt;rchild);<span class="comment">//有右孩子时将其进队</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-5-1-3-遍历算法的应用——二叉树的建立">5.5.1.3 遍历算法的应用——二叉树的建立</h5><ul><li>算法描述：</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Statys <span class="title function_">CreatBiTree</span><span class="params">(BiTree &amp;T)</span>&#123;</span><br><span class="line">    <span class="built_in">scanf</span>(&amp;ch);</span><br><span class="line">    <span class="keyword">if</span>(ch==<span class="string">&quot;#&quot;</span>)T=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(!(BiTNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(BiTNode)))<span class="comment">//分配一块儿结点空间</span></span><br><span class="line">            <span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">        T-&gt;data=ch;<span class="comment">//生成根结点</span></span><br><span class="line">        CreateBiTree(T-&gt;lchild);</span><br><span class="line">        CreateBiTree(T-&gt;rchild);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-4-复制二叉树">5.5.1.4 复制二叉树</h5><figure class="highlight c"><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="type">int</span> <span class="title function_">Copy</span><span class="params">(BiTree T,BiTree &amp;NewT)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>)&#123;</span><br><span class="line">        NewT==<span class="literal">NULL</span>;<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span>&#123;</span><br><span class="line">        NewT=new BiTNode;</span><br><span class="line">        NewT-&gt;data=T-&gt;data;</span><br><span class="line">        copy(T-&gt;lchild, NewT-&gt;lchild);</span><br><span class="line">        copy(T-&gt;rchild, NewT-&gt;rchild);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-5-计算二叉树的深度">5.5.1.5 计算二叉树的深度</h5><ul><li>如果是空树，则深度为0</li></ul><p>​否则，递归计算左子树深度记为m，右子树深度为n，二叉树深度则为n和m的较大者加1</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">Depth</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T==<span class="literal">NULL</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        m=Depth &#123;T-&gt;lchild); </span><br><span class="line">n=Depth &#123;T-&gt;rchild);</span><br><span class="line"><span class="keyword">if</span>&#123;m&gt;n) <span class="keyword">return</span>&#123;m+l);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">return</span>(n+l);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-6-计算二叉树的结点总数">5.5.1.6 计算二叉树的结点总数</h5><ul><li>如果是空树，则结点个数为0</li></ul><p>​否则，结点个数为左子树的结点个数+右子树的结点个数再加1</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">NodeCount</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (T==<span class="literal">NULL</span>) </span><br><span class="line">        <span class="keyword">return</span> O;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> NodeCount (T-&gt;lchild) +Node Count (T-&gt;rchild) + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-5-1-7-计算二叉树叶子结点数">5.5.1.7 计算二叉树叶子结点数</h5><figure class="highlight c"><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="type">int</span> <span class="title function_">leafcount</span><span class="params">(BiTree T)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(T=<span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>:</span><br><span class="line">    <span class="keyword">if</span>(T-&gt;lchild==<span class="literal">NULL</span>&amp;&amp;T-&gt;rchild==<span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> LeafCount(T-&gt;lchild)+LeafCount(T-&gt;rchild;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5-5-2-线索二叉树">5.5.2 线索二叉树</h4><ul><li><p>利用二叉链表中的空指针域：</p><p>如果某个节点的左孩子为空，则将空的<strong>左孩子</strong>指针域改为<strong>指向其前驱</strong>；如果<strong>右孩子</strong>为空，则其指针域改为<strong>指向其后继</strong>————将<strong>改变指向的指针</strong>称为**“线索”<strong>，加上了线索的二叉树称为</strong>线索二叉树**</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010195613.png" alt=""></p></li><li><p>为了区分是指向孩子的指针还是指向前驱或者后继的指针，对二叉链表中每个结点增设两个标志域ltag和rtag，这样结点的结构就为【lchild | ltag | data | rtag | rchild 】并约定：</p><ul><li><p>ltag=0/rtag=0  指针指向孩子</p></li><li><p>ltag=1/rtag=1  指针指向前驱/后继</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231010201924.png" alt=" "></p></li></ul></li></ul><h3 id="5-6-树和森林">5.6 树和森林</h3><p><em><strong>本节将讨论树的表示及其遍历操作，并建立森林与二叉树的对应关系。</strong></em></p><h4 id="5-6-1-树的存储结构">5.6.1 树的存储结构</h4><h5 id="5-6-6-1-双亲表示法">5,6,6,1 双亲表示法</h5><p>实现：定义结构数组存放树的结点，每个结点含两个域；</p><ul><li><p>数据域：存放结点本身信息</p></li><li><p>双亲域：指示本结点的双亲结点在数组中的位置<br><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011113024.png" alt=""></p></li><li><p>C语言的类型描述：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">PTNode</span>&#123;</span></span><br><span class="line">    TElemType data;</span><br><span class="line">    <span class="type">int</span> parent;</span><br><span class="line">&#125;PTNode;</span><br></pre></td></tr></table></figure></li><li><p>树结构：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAX_TREE_SIZE 100</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    PTNode nodes[MAX_TREE_SIZE];</span><br><span class="line">    <span class="type">int</span> r,n;<span class="comment">//根结点的位置和结点个数</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-6-1-2-孩子链表">5.6.1.2 孩子链表</h5><ul><li>把每个结点的孩子结点排列起来。看成一个线性表，用单链表存储</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011114137.png" alt=""></p><ul><li><p>C语言描述：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">CTNode</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> child;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">CTNode</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;*Childptr;</span><br></pre></td></tr></table></figure></li><li><p>树结构</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    CTBox nodes[MAX_TREE_SIZE];</span><br><span class="line">    <span class="type">int</span> n,r;</span><br><span class="line">&#125;CTree;</span><br></pre></td></tr></table></figure></li></ul><h5 id="5-6-1-3-孩子兄弟表示法">5.6.1.3 孩子兄弟表示法</h5><ul><li><p>C语言描述</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">CSNode</span>&#123;</span></span><br><span class="line">    ElemType data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">CSNode</span> *<span class="title">firstchild</span>,*<span class="title">nextsibling</span>;</span></span><br><span class="line">&#125;CSNode,*CSTree;</span><br></pre></td></tr></table></figure><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231011115045.png" alt=""></p></li></ul><h4 id="5-6-2-树与二叉树的转换">5.6.2 树与二叉树的转换</h4><ul><li><p>将树转换为二叉树进行处理，利用二叉树的算法来实现对树的操作。</p></li><li><p>由于树和二叉树都可以用二叉链表做存储结构，则以二叉链表做媒介可以导出树与二叉树之间的一个对应关系。</p></li><li><p>给定一棵树可以找到唯一的一颗二叉树与之对应</p><ul><li><p>加线：在兄弟之间加一条线，</p></li><li><p>抹线：对每个结点，除了其左孩子外，去除其余孩子之间的关系</p></li><li><p>旋转：以树根结点为轴心，将整树顺时针旋转45°</p><p><strong>树变二叉树：兄弟相连留长子。<em>其中根结点的油茶树一定为空</em></strong></p></li></ul></li><li><p>反之可以把二叉树转换成树</p><ul><li><p>加线：若p结点是双亲节点的左孩子，则将p的右孩子，右孩子的右孩子……沿分支找到所有右孩子，都与p的双亲用线连起来</p></li><li><p>抹线：抹掉原二叉树中双亲与右孩子之间的连线</p></li><li><p>调整：将结点按层次排列，形成树结构。</p><p><strong>二叉树变树：左孩右右连双亲，去掉原来右孩线</strong></p></li></ul></li></ul><h4 id="5-6-3-森林与二叉树的转换">5.6.3 森林与二叉树的转换</h4><ul><li><p>森林转换成二叉树（二叉树与多棵树之间的关系）</p><ul><li><p>将个棵树分别转换成二叉树</p></li><li><p>将每棵树的根结点用线相连</p></li><li><p>以第一课树根结点为二叉树的根，再以根结点为轴心，顺时针旋转，构成二叉树型结构</p><p><strong>森林变二叉树：树变二叉根相连</strong></p></li></ul></li><li><p>二叉树转换成森林</p><ul><li><p>抹线：将二叉树中根结点与其右孩子连线，及沿有分支搜索到的所有右孩子之间的连线抹掉，使之变成孤立的二叉树</p></li><li><p>还原：将孤立的二叉树还原成树</p><p><strong>二叉树变森林：去掉全部右孩线，孤立二叉再还原</strong></p></li></ul></li></ul><h4 id="5-6-4-树和森林的遍历">5.6.4 树和森林的遍历</h4><h5 id="5-6-4-1树的遍历">5.6.4.1树的遍历</h5><ul><li>先根遍历：若树不为空，则先访问根结点，然后一次先根遍历各棵子树。</li><li>后根遍历：若树不为空，则先依次后根遍历各棵子树，然后访问根结点。</li><li>层次遍历：若树不为空，则自上而下自左至右访问树种每个结点。</li></ul><h5 id="5-6-4-2-森林的遍历">5.6.4.2 森林的遍历</h5><ul><li><p>将森林看作由三部分构成：</p><ol><li>森林中第一棵树的根结点；</li><li>森林中第一棵树的子树森林；</li><li>森林中其他树构成的森林。</li></ol></li><li><p>先序遍历：若森林不为空，则依次从左至右对森林中的每一棵树进行先根遍历</p></li><li><p>中序遍历：若森林不为空，则</p><ol><li><p>中序遍历森林中第一棵树的子树森林；</p></li><li><p>访问森林中第一棵树的根结点；</p></li><li><p>中序遍历森林中（除第一棵树之外）其余树构成的森林。</p><p>即：依次从左至右对森林中每一个棵树进行后根遍历</p></li></ol></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第五章中篇--王卓老师第八周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第五章(上)</title>
    <link href="https://www.fomal.cc/posts/cde2268.html"/>
    <id>https://www.fomal.cc/posts/cde2268.html</id>
    <published>2023-10-08T13:40:33.000Z</published>
    <updated>2023-10-12T12:40:15.832Z</updated>
    
    <content type="html"><![CDATA[<h2 id="5-树和二叉树">5 树和二叉树</h2><p>​ <em>树形结构与线性结构的不同就是，<strong>线性结构</strong>的前驱和后继是<strong>一对一</strong>的，树状结构属于<strong>非线性结构</strong>，有多个后继。前驱与后继是<strong>一对n</strong>的</em></p><h3 id="5-1-树和二叉树的定义">5.1 树和二叉树的定义</h3><h4 id="5-1-1-树的定义">5.1.1 树的定义</h4><ul><li>树是个n个结点的<strong>有限集</strong>：<ul><li>若n=0，称为<strong>空树</strong>；</li><li>若n&gt;0，则他满足如下两个条件：<ol><li>有且仅有一个特定的称为<strong>根</strong>的结点</li><li>其余结点可分为吗（m&gt;=0）个互不相交的有限集T1,T2,T3。。</li></ol></li><li><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008135419.png" alt=""></li></ul></li></ul><h4 id="5-1-2-树的基本术语">5.1.2 树的基本术语</h4><ul><li><strong>根结点</strong>：非空树中无前驱结点的结点。</li><li>结点的<strong>度</strong>：结点拥有的子树数。</li><li>树的度：树内各结点的度的最大值。</li><li>结点的子树的根称为该结点的<strong>孩子</strong>，该结点称为该孩子的<strong>双亲</strong>，还有结点的<strong>祖先</strong>，结点的<strong>子孙</strong>就不展开描述了。</li><li>有序树：树中结点的各子树从左至右有次序（最左边的为第一个孩子）。</li><li>无序树：树中结点的各子树无次序。</li><li>森林：是m棵互不相交的树的集合<ul><li>把根结点删除树就变成了森林</li><li>一棵树可以看成是一个特殊的森林，<em><strong>树一定是森林，森林不一定是树</strong></em></li><li>给森林中各子树加上一个双亲节点，森林就变成了树</li></ul></li></ul><h4 id="5-1-3-二叉树的定义">5.1.3 二叉树的定义</h4><p>二叉树的结构最简单，规律性最强，可以证明所有树都能转化为<strong>唯一对应的二叉树</strong>，不失一般性。</p><ul><li><p>二叉树：是n个结点的有限集，他或者是空集或者是由<strong>一个根结点</strong>及两棵<strong>互不相交</strong>的分别称作这个根的<strong>左子树</strong>和<strong>右子树</strong>的二叉树组成</p><ul><li><p>特点：每个节点最多两个孩子</p></li><li><p>子树有左右之分(即使只有一棵子树也进行区分)，<strong>次序不能颠倒</strong></p></li><li><p>二叉树可以是空集合，根可以有空的左子树或空的右子树。</p></li><li><p>具有三个结点的树可能有几种形态？</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008151232.png" alt=""></p></li></ul></li></ul><h3 id="5-2-案例引入">5.2 案例引入</h3><h4 id="5-2-1-数据压缩问题">5.2.1 数据压缩问题</h4><p>将数据文件转换成由0、1组成的二进制串，称之为编码</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008152034.png" alt=""></p><p><em>具体的方法到哈夫曼树和哈弗曼编码那里学习，这里暂且按下不表</em></p><h4 id="5-2-2-利用二叉树求解表达式的值">5.2.2 利用二叉树求解表达式的值</h4><ul><li>以二叉树表示表达式的递归定义如下：<ol><li>若表达式为数或简单变量，则相应二叉树中仅有一个根结点，其数据域存放该表达式信息；</li><li>若表达式为 “ 第一操作数 运算符 第二操作数” 的形式， 则相应的二叉树中以<strong>左子树表示第一操作数</strong>，<strong>右子树表示第二操作数</strong>，根结点存放<strong>运算符</strong>，其中错作数本身又为表达式。</li></ol></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008152737.png" alt=""></p><p>具体实现，我们会在[5.8 案例分析与实现](####5.8 案例分析与实现)进行讲解</p><h3 id="5-3-树和二叉树的抽象数据类型定义">5.3 树和二叉树的抽象数据类型定义</h3><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008155701.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231008155916.png" alt=""></p><h3 id="5-4-二叉树的性质和存储结构">5.4 二叉树的性质和存储结构</h3><ol><li><p>在二叉树的第i层上之多有$\large 2^{i-1}$个结点</p></li><li><p>深度为k的二叉树<strong>至多</strong>有$\large 2^k-1$个结点</p></li><li><p>对任何一棵二叉树T，如果其叶子树为$\large n_0$,度为2的结点数为$\large n_2$，则$\large n_0=n_2+1$</p><ul><li>总边数$\large B=n-1   =   B=n_2<em>2+n_1</em>1$</li><li>总结点数为$\large n=n_2<em>2+n_1</em>1+1$  又   $n=n_2+n_1+n_0$</li><li>推导出$\large n_0=n_2+1$</li></ul></li><li><p>具有n个结点的完全二叉树的深度为[log_2n]（向下取整）+1</p><p>性质4表明了完全二叉树<strong>结点数n</strong>与完全二叉树<strong>深度k</strong>之间的关系</p></li><li><p>如果对一棵有 n个结点的完全二叉树，其结点按层序编号（从第 1 层到第[log2n]+ 1 层， 每层从左到右）， 则对任一结点(n=&gt;i&gt;=1), 有如果i=1，无双亲，如果i&gt;1.则其双亲是结点[$\large i/2$]。</p><ol><li>性质5表明了完全二叉树中<strong>双亲结点</strong>编号与<strong>孩子结点</strong>编号之间的关系</li></ol></li></ol><ul><li>两种特殊形式的二叉树<ul><li>满二叉树：一颗深度为k且有$\large 2^k-1$个结点的二叉树称为<strong>满二叉树</strong></li><li>特点：<ul><li>每一层上的结点数</li><li>编号从上到下，从左到右</li></ul></li><li>完全二叉树：深度为K的， 有n个结点的二叉树， 当且仅当其每一个结点都与深度为K的<strong>满二叉树中编号</strong>从1至n的结点<strong>一一对应</strong>时， 称之为<strong>完全二叉树</strong>。<ul><li>在满二叉树中，从<strong>最后一个结点开始</strong>，<strong>连续</strong>去掉<strong>任意</strong>个结点，都是一棵完全二叉树</li><li>叶子只能分布在最大的两层上</li><li>对任意一结点，如果其右子树的最大层次为i，则其左子树的最大层次必为i或i-1</li></ul></li></ul></li></ul><h3 id="5-5-遍历二叉树和线索二叉树">5.5 遍历二叉树和线索二叉树</h3><h5 id="5-5-1-遍历二叉树">5.5.1 遍历二叉树</h5><ul><li><p>遍历的定义——顺着某一条搜索路径巡访二叉树中的结点，使得每个结点均被访问一次，而且仅被访问一次</p><ul><li>“访问”可以看做对结点作各种处理</li></ul></li><li><p>遍历的目的——得到树中所有结点的一个线性排列</p></li><li><p>遍历的用途——它是树结构插入、删除、修改、查找和排序运算的前提，是二叉树一切运算的基础和核心</p></li><li><p>遍历的算法</p><ul><li><p>DLR——先（根）序遍历</p></li><li><p>LDR——中（根）序遍历 【从最左边 开始左根右】，可以吧空序都先画出来，然后再开始遍历</p></li><li><p>LRD——后（根）序遍历</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231009184307.png" alt=""></p></li><li><p>例题——已知中序序列和后续序列求二叉树</p></li><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231009185844.png" alt=""></p></li><li><p><em><strong>前后确定根，中序辨左右</strong></em>      重点</p></li></ul></li></ul>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第五章上篇--王卓老师第七周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>文言文鉴赏——采桑子·恨君不似江楼月</title>
    <link href="https://www.fomal.cc/posts/b4844049.html"/>
    <id>https://www.fomal.cc/posts/b4844049.html</id>
    <published>2023-10-08T10:34:37.000Z</published>
    <updated>2023-10-11T07:36:31.394Z</updated>
    
    <content type="html"><![CDATA[<div class='poem'><div class='poem-title'>采桑子·恨君不似江楼月</div><div class='poem-author'>吕本中</div><p>恨君不似江楼月，<br>南北东西，南北东西，<br>只有相随无别离。<br>恨君却似江楼月，<br>暂满还亏，暂满还亏，<br>待得团圆是几时？</p></div>]]></content>
    
    
    <summary type="html">曾季狸《艇斋诗话》：本中长短句，浑然天成，不减唐、《花间》之作。</summary>
    
    
    
    <category term="鉴赏" scheme="https://www.fomal.cc/categories/%E9%89%B4%E8%B5%8F/"/>
    
    
    <category term="文言文鉴赏" scheme="https://www.fomal.cc/tags/%E6%96%87%E8%A8%80%E6%96%87%E9%89%B4%E8%B5%8F/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第四章</title>
    <link href="https://www.fomal.cc/posts/69ea4c49.html"/>
    <id>https://www.fomal.cc/posts/69ea4c49.html</id>
    <published>2023-10-06T13:40:33.000Z</published>
    <updated>2023-10-12T12:40:38.649Z</updated>
    
    <content type="html"><![CDATA[<h2 id="4-串、数组和广义表">4 串、数组和广义表</h2><h3 id="4-1-串的定义——几个术语">4.1 串的定义——几个术语</h3><ul><li>串(String)——由零个或多个任意<strong>字符组成的有限序列</strong></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005141054.png" alt=""></p><ul><li><p>子串：一个串中任意个连续字符组成的子序列（含空串）称为该串的子串</p><ul><li>真子串，<strong>不包含自身</strong>的所有子串</li></ul></li><li><p>字符位置：字符在序列中的序号为该字符在串中的位置</p></li><li><p>子串位置：子串第一个字符在主串中的位置</p></li><li><p>空格串：由一个或多个空格组成的串，与空串不同</p><ul><li>计算他们的长度时，要包括空格</li></ul></li><li><p>串相等：当且仅当两个串的长度相等并且<strong>各个对应位置上的字符都相同</strong>时，两个串才是<strong>相等</strong>的。</p></li></ul><h3 id="4-2-案例引入">4.2 案例引入</h3><p>​“<em>串的应用非常广泛，计算机上的非数值处理的对象大部分是字符串数据，例如：文字编辑、符号处理、各种信息处理系统等等。</em>”</p><h4 id="4-2-1-病毒感染检测">4.2.1 病毒感染检测</h4><p>研究者将人的DNA和病毒DNA均表示成由一些字母组成的字符串序列 （字符串的匹配）</p><h3 id="4-3-串的类型定义、存储结构及运算">4.3 串的类型定义、存储结构及运算</h3><h4 id="4-3-1-串的抽象类型定义">4.3.1 串的抽象类型定义</h4><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005154515.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005154617.png" alt=""></p><h4 id="4-3-2-串的存储结构">4.3.2 串的存储结构</h4><h5 id="4-3-2-1-串的顺序存储结构">4.3.2.1 串的顺序存储结构</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAXLEN 255</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">char</span> ch[MAXLEN+<span class="number">1</span>];</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SString;</span><br></pre></td></tr></table></figure><p><em>顺序存储结构用的更多一些</em></p><h5 id="4-3-2-2-串的链式存储结构">4.3.2.2 串的链式存储结构</h5><p>如果是普通的链式存储的话，虽然方便操作，但是存储密度较低，所以在这里，我们将多个字符存放在一个结点中，以克服其缺点。</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005155456.png" alt=""></p><ul><li><strong>我们称之为——块链结构</strong></li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> CHUNKSIZE BO <span class="comment">//可由用户定义的块大小</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Chunk</span>&#123;</span></span><br><span class="line">    <span class="type">char</span> ch [CHUNKSIZE];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Chunk</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;)Chunk;</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span> </span><br><span class="line">Chunk *head,*tail; <span class="comment">//串的头指针和尾指针</span></span><br><span class="line"><span class="type">int</span> length; <span class="comment">//串的当前长度</span></span><br><span class="line">) LString;<span class="comment">//字符串的块链结构</span></span><br></pre></td></tr></table></figure><h4 id="4-3-3-串的模式匹配算法">4.3.3 串的模式匹配算法</h4><ul><li>算法目的：<ul><li>确定主串中所含子串（模式串）第一次出现的位置（定位）</li></ul></li><li>算法应用：<ul><li>搜索引擎、拼写检查、语言翻译、数据压缩</li></ul></li><li>算法种类：<ul><li>BF算法（暴力破解，朴素的、穷举的）</li><li>KMP算法（速度快）</li></ul></li></ul><h5 id="4-3-3-1-BF算法">4.3.3.1 BF算法</h5><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005173505.png" alt=""></p><ul><li><strong>匹配失败：</strong><ul><li><strong>$\large i=(i-j+1)+1=i-j+2$</strong>(<em><u><strong>i和j走的路程是一样的，i-j表示退回原位，而模式串是从下标1开始计算的，则要+1，这个时候才是真正退回了原位，再+1，就是原位的下一位了</strong></u></em>)</li><li><strong>$\large j=i$</strong></li></ul></li><li><strong>匹配成功：</strong><ul><li><strong>$\large i=7$</strong></li><li><strong>$\large j=5$</strong></li><li><strong>返回$\large i-t.length=3$</strong></li></ul></li><li>index(S,P,pos)<ul><li>将主串的第pos个字符和模式串的第一个字符比较。</li><li>若相等，继续这个比较后续字符</li><li>若不等，就从主串的下一字符起，重新逐个比较</li><li>直到发现一个连续子串序列与模式串相等，返回值为S中与T匹配的子序列第一个字符的序号即匹配成功</li><li>否则匹配失败，返回0</li></ul></li></ul><figure class="highlight c"><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="type">int</span> <span class="title function_">Index_BF</span><span class="params">(SString S,SString T)</span>&#123;</span><br><span class="line">    <span class="type">int</span> i=<span class="number">1</span>,j=<span class="number">1</span>:</span><br><span class="line">    <span class="keyword">while</span>(i&lt;=S.length &amp;&amp; j&lt;=T.length)&#123;</span><br><span class="line">        <span class="keyword">if</span>(s.ch[i]==t.ch[j])&#123;++i.++j:&#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;i=i-j+<span class="number">2</span>;j=<span class="number">1</span>;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (j&gt;=T.length)<span class="keyword">return</span> i-T.length<span class="comment">//返回匹配的第一个字符的下标</span></span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><h6 id="4-3-3-1-1-BF算法的时间复杂度">4.3.3.1.1 BF算法的时间复杂度</h6><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005180719.png" alt=""></p><h5 id="4-3-3-2-KMP算法">4.3.3.2 KMP算法</h5><p>​这种改进算法是由 <strong>K</strong>nuth 、 <strong>M</strong>orris 和 <strong>P</strong>ratt 同时设计实现的， 因此简称 <strong>KMP</strong> 算法。</p><ul><li><p>利用已经<strong>部分匹配</strong>的结果而加快模式串的滑动速度</p></li><li><p>且主串S的指针<strong>i不必回溯</strong>！可提速到<strong>O(N+M</strong>)！</p><p>为此，定义next[j]函数，表明当模式中第j个字符与主串中相应字符“失配”时，在模式中需重新和主串中该字符进行比较的字符的位置</p></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005191941.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005192856.png" alt=""></p><p><em><strong>第四个这里是1因为比较的时候不包括末尾元素但包括首元素</strong></em></p><p>$\large k-1=最大公共前后缀的长度$</p><figure class="highlight c"><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="type">int</span> <span class="title function_">Index_KMP</span><span class="params">(SString S,SString T,<span class="type">int</span> pos)</span>&#123;</span><br><span class="line">    i=pos;j=l;</span><br><span class="line">    <span class="keyword">while</span> (i&lt;S.length &amp;&amp; j&lt;T.length) &#123;</span><br><span class="line">        <span class="keyword">if</span> </span><br><span class="line">            &lt;j==o||s.ch[i]==T.ch[j])&#123; ++i;++j;&#125;<span class="comment">//继续比较后继字符</span></span><br><span class="line">        <span class="keyword">else</span> </span><br><span class="line">            j=next[i]; <span class="comment">//i不变，j后退</span></span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">if</span> </span><br><span class="line">        (j&gt;T.length)<span class="keyword">return</span> i-T.length;<span class="comment">//匹配成功</span></span><br><span class="line"><span class="keyword">else</span> </span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>; <span class="comment">//匹配失败</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>根据next值求nextval值的方法</strong></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231005202447.png" alt=""></p><h3 id="4-4-数组">4.4 数组</h3><p><em><strong>栈和队列是操作受限的线性表。串是内容受限的线性表。数组和广义表是对线性表的推广。</strong></em></p><h4 id="4-4-1-数组的类型定义">4.4.1 数组的类型定义</h4><ul><li><p>数组：按一定格式排列起来，具有<strong>相同类型</strong>的数据元素的集合</p></li><li><p>一维数组：若线性表中的数据元素为非结构的简单元素，一维数组</p><ul><li><p>一维数组的逻辑结构：线性结构。定长的线性表。</p></li><li><p>声明格式： 数据类型 变量名称[长度]</p><ul><li>例：int num[5]={0,1,2,3,4};</li></ul></li></ul></li><li><p>二维数组：若一维数组中的数据元素又是一维数组结构，则称为二维数组。</p><ul><li><p>二维数组的逻辑结构：</p><ul><li>非线性结构：每一个数据元素即在一个行表中，又在一个行列中</li><li>线性结构：该线性表的每个数据元素也是一个定长的线性表</li></ul></li><li><p>声明格式：数据类型 变量名称[行数][列数];</p><ul><li>例：int num[5] [8]</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006102457.png" alt=""></p></li></ul></li><li><p>n维数组：若n-1维数组中的元素又是一个一维数组结构，则称作n维数组。</p></li><li><p><em><strong>结论：线性表结构是数组结构的一个特例，二数组结构又是线性表结构的拓展</strong></em></p></li><li><p>数组特点：<strong>结构固定</strong>——定义后，维数和维界不再改变。</p></li><li><p>数组基本操作：除了结构的<strong>初始化</strong>和<strong>销毁</strong>之外，只有<strong>取元素</strong>和<strong>修改元素值</strong>的操作。</p></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006105402.png" alt=""></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006110049.png" alt=""></p><h4 id="4-4-2-数组的顺序存储">4.4.2 数组的顺序存储</h4><ul><li>二维数组可有两种存储方式<ul><li>以行序为主序</li><li>以列序为主序</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006154729.png" alt=""></p><h4 id="4-4-3-特殊矩阵的压缩存储">4.4.3 特殊矩阵的压缩存储</h4><p>矩阵：一个由M*N个元素排成的m行n列的表。</p><p>$\large \begin{pmatrix}a_{11}&amp;a_{12}&amp;a_{13}&amp;……&amp;a_{1n}\a_{21}&amp;a_{22}&amp;a_{23}&amp;……&amp;a_{2n}\……&amp;……&amp;……&amp;……&amp;……&amp;\a_{m1}&amp;a_{m2}&amp;a_{m3}&amp;……&amp;a_{mn}\end{pmatrix}$</p><p>矩阵的常规存储：</p><ol><li>将矩阵描述为一个二维数组。</li></ol><p>矩阵的常规存储的特点：</p><ol><li><p>可以对其元素进行随机存取 ；</p></li><li><p>矩阵运算非常简单；存储密度为1</p></li></ol><p>不适宜常规存储的矩阵：</p><ol><li>值相同的元素很多且呈某种规律分布；</li><li>零元素多</li></ol><p><strong>矩阵的压缩存储:</strong></p><ol><li><p>为多个相同的非零元素只分配一个存储空间；</p></li><li><p>对零元素不分配空间</p></li><li><p>一些特殊的矩阵可以压缩，如：<strong>对称矩阵，对角矩阵，三角矩阵，稀疏矩阵</strong>(矩阵中非零元素的个数较少，不到百分之五)等。</p><ol><li><p>对称矩阵：在N*N的矩阵中满足**$\large a_{ij}=a_{ji}$<strong>,存储方法，<strong>只存储上（或下）三角的数据元素</strong>，共占用</strong>N(N+1)/2**个元素空间</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006174010.png" alt=""></p><p>下标k的求法为$(i-1)+(j-1)$,其中$a_{n-1}$前面行前面有$n-1$个数求和之后得到$\dfrac {n(n-1)}{2}$，列前面没有</p></li><li><p>三角矩阵：对角线一下或以上的数据元素全为常数C</p><ol><li><p>存储方法：重复元素c共享一个元素存储空间，共占用n（n+1)/2+1个元素空间：sa[1…n（n+1)/2+1]</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006174742.png" alt=""></p></li></ol></li><li><p>对角矩阵（带状矩阵）</p><ol><li>特点：在n*n的方阵中，所有的<strong>非零元素</strong>都集中在以<strong>主对角线</strong>为中心的带状区域中，区域外的值全为零 ，则称为对角矩阵，常见的有三对角矩阵，五对角矩阵，起对角矩阵。（几条对角线有数值就是几对角矩阵）</li><li>存储方法：以对角线的顺序存储</li></ol></li><li><p>稀疏矩阵</p><ol><li>特点：设在m*n的矩阵中又t个非零元素，且t所占总体百分比小于五时称为稀疏矩阵</li><li>压缩存储原则：存各非零元素的值，行列位置和矩阵的行列数<ol><li>例： $\large （i,j,a_{ij}）$</li><li>三元组顺序表又称有序的双下标<ol><li>优点：便于进行依行顺序处理的矩阵运算。</li><li>缺点：不能随机存取，若按行号存取需要从头开始进行查找</li></ol></li></ol></li></ol></li></ol></li></ol><h3 id="4-5-广义表">4.5 广义表</h3><ul><li>广义表：又称列表Lists是n&gt;=0个元素$a_0,a_1,……，a_{n-1}$的有限序列，其中没一个$a_i$或者是<strong>原子</strong>，或者是一个<strong>广义表</strong></li><li>广义表通常记作：LS=$a_0,a_1,……，a_{n}$<ul><li>其中LS为表名，n为表长，$a_i$为表的元素</li><li>通常用<strong>大写字母</strong>表示<strong>广义表</strong>，<strong>小写字母</strong>表示<strong>原子</strong></li><li><strong>表头</strong>：若LS非空，则其<strong>第一个</strong>元素$a_1$就是表头<ul><li>记作 $head（LS）=a_1$</li></ul></li><li><strong>表尾</strong>，除了表头的<strong>其他元素</strong>组成的表<ul><li>记作$tail（LS）=（a_2,……，a_{n}）$</li></ul></li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006212717.png" alt=""></p><ol><li><p>广义表中的数据元素有相对次序；一个直接前驱和一个直接后继</p></li><li><p>广义表的<strong>长度</strong>定义为<strong>最外层</strong>所包含元素的个数</p></li><li><p>广义表的<strong>深度</strong>定义为该广义表<strong>展开后所含括号的重数</strong></p></li><li><p>广义表可以为其他广义表共享</p></li><li><p>广义表可以是一个递归的表</p></li><li><p>广义表是<strong>多层次</strong>结构，广义表的元素可以是单元素，也饿可以是子表，而子表的元素还可以是子表类似<strong>二叉树</strong>。</p></li></ol><p><em><strong>广义表可以看做线性表的推广，线性表是广义表的特例</strong></em></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231006213403.png" alt=""></p><p><em>函数本身带括号，所以最后的c应该外面加一个括号</em></p>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第四章--王卓老师第六周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第三章</title>
    <link href="https://www.fomal.cc/posts/dec7552c.html"/>
    <id>https://www.fomal.cc/posts/dec7552c.html</id>
    <published>2023-10-04T13:46:00.000Z</published>
    <updated>2023-10-12T12:40:39.221Z</updated>
    
    <content type="html"><![CDATA[<h2 id="3-栈和队列">3 栈和队列</h2><p>​<strong>栈和队列是两种重要的线性结构。从数据结构角度看，栈和队列也是线性表，其特殊性在千栈和队列的基本操作是线性表操作的子集，它们是操作受限的线性表， 因此，可称为限定性的数据结构。但从数据类型角度看，它们是和线性表不相同的两类重要的抽象数据类型。</strong></p><h3 id="3-1-栈和队列的定义和特点">3.1 栈和队列的定义和特点</h3><ul><li><p><strong>栈和队列</strong>是两种常用的、重要的数据结构</p></li><li><p>栈和队列是<strong>限定插入和删除</strong>只能在表的<strong>端点</strong>进行的<strong>线性表</strong></p><ul><li>线性表可以删除任意位置</li><li><strong>栈</strong>只能在<strong>表尾</strong>插入或删除（<em>类似弹夹，后面对应名词压入弹出都能十分契合</em>）</li><li><strong>队列</strong>只能在<strong>表尾插入</strong>或<strong>表头删除</strong>（<em>类似于排队，队头先走，人来了在队尾</em>）<ul><li>特性：<strong>栈——后进先出</strong></li></ul></li></ul></li><li><p>由于栈的操作具有后进先出的固有特性，使得栈成为程序设计中的有用工具</p><ul><li><p><strong>数制转换</strong><strong>表达式求值</strong><strong>括号匹配检验</strong><strong>八皇后问题</strong></p><p><strong>行编辑程序</strong><strong>函数调用</strong><strong>迷宫求解</strong><strong>递归调用的实现</strong></p></li></ul></li><li><p>由于队列具有先进先出的特性，使得队列可以解决类似排位问题的有用工具</p><ul><li><p><strong>脱机打印输出</strong>：按申请的先后顺序依次输出</p><p><strong>多用户系统中</strong>，多个用户排成队，分时地循环使用CPU和主存，按用户的优先级排成多个队，每个优先级一个队列</p><p><strong>实时控制系统中</strong>，信号按接收的先后顺序依次处理</p><p><strong>网络电文传输</strong>，按到达的时间先后顺序依次进行</p></li></ul></li></ul><h4 id="3-1-1-栈的定义和特点">3.1.1 栈的定义和特点</h4><ul><li>栈的定义：是一种特殊的线性表，是限定仅在一端（通常是表尾）进行插入和删除操作的线性表<ul><li>又称为<strong>后进先出的线性表</strong>，简称LIFO结构（Last in First Out）</li><li><strong>表尾</strong>称为<strong>栈顶</strong>，<strong>表头</strong>称为<strong>栈底</strong>，插入 元素到栈顶的操作，称为入栈（压入、进栈、压栈），从栈顶删除最后一个元素的操作，称为出栈（弹出、弹栈）</li></ul></li><li>栈的逻辑结构：<strong>与线性表相同</strong>，仍为一对一关系。</li><li>栈的存储结构：用<strong>顺序栈或链栈存储</strong>均可，但以顺序栈更常见</li><li>栈的运算规则：只能在栈顶运算，且访问结点时依照<strong>后进先出</strong>（LIFO）的原则</li><li>栈的实现方式：关键是<strong>编写入栈和出栈函数</strong>，具体实现依顺序栈或链栈的不同而不同</li></ul><h4 id="3-1-2-队列的定义和特点">3.1.2 队列的定义和特点</h4><ul><li><p>队列的定义：是一种先进先出的线性表，简称FIFO结构</p><ul><li>只能插入到队尾，从队头删除</li></ul></li><li><p>队列的逻辑结构：<strong>同线性表</strong>，仍为一对一关系。</p></li><li><p>队列的存储结构：<strong>顺序队或链队</strong>，以循环顺序队列更常见</p></li><li><p>队列的运算规则：只能在队首和队尾运算，且访问结点时依照<strong>先进先出</strong>（FIFO）的原则</p></li><li><p>队列的实现方式：关键是掌握<strong>入队和出队</strong>操作，具体实现依顺序队或链队的不同而不同</p></li></ul><h3 id="3-2-案例引入">3.2 案例引入</h3><p><strong>案例3.1：进制转换</strong></p><ul><li><p>十进制整数N向其他进制数d（二、八、十六）的转换是计算机实现计算的基本问题。</p><p><strong>转换法则：除以d倒取余</strong></p><p>该转换法则对应于一个简单算法原理：</p><p>​*<em>n=(n div d)<em>d+n mod d</em></em></p><p>其中：div为整除运算，mod为求余运算</p></li><li><p>例：十进制把159转换成八进制</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230916182719.png" alt=""></p><p><em><strong>运用到栈的后进先出的特性</strong></em></p></li></ul><h4 id="3-2-1：括号匹配的检验">3.2.1：括号匹配的检验</h4><ul><li><p>假设表达式中允许包含两种括号：圆括号和方括号</p></li><li><p>检验（（）】）是否匹配</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230916183231.png" alt=""></p><p>后进的括号与前面括号进行匹配，如果为<strong>相同括号的两边</strong>，则栈顶的左括号弹出，也符合<em><strong>后进先出的特性</strong></em></p><ul><li>若遇到一下集中情况之一，说明括号不匹配<ol><li>遇到某一个右括号时，栈已空，说明到目前为止，<strong>右括号多于左括号；</strong></li><li>当栈中弹出的左括号与当前检验的右括号类型不同，则说明出现了<strong>括号交叉</strong>情况；</li><li>表达式输入完毕后，但栈中还有没匹配的左括号，说明<strong>左括号多于右括号</strong>。</li></ol></li></ul></li></ul><h4 id="3-3-2：表达式求值">3.3.2：表达式求值</h4><ul><li><p>表达式求值是程序设计语言编译中一个最基本的问题，他的实现也需要运用栈</p></li><li><p>这里介绍的算法是由运算符优先级确定运算顺序的对表达式求值算法</p><p>——<strong>算符优先算法</strong></p><ul><li><p>表达式的组成</p><ul><li><strong>操作数</strong>：常数、变量。</li><li><strong>运算符</strong>：算术运算符、关系运算符和逻辑运算符。</li><li><strong>界限符</strong>：左右括弧和表达式结束符。</li></ul></li><li><p>任何一个算术表达式都由<strong>操作数、算术运算符和界限符</strong>组层。<strong>后两者</strong>统称为<strong>算符</strong></p><ul><li><p>例如：#3*（7-2）#<br>为了实现表达式求值。需要设置两个栈</p><ul><li>一个是算符栈OPTR，用于寄存运算符</li><li>另一个称为操作数栈OPND，用于寄存运算数和运算结果</li></ul><p>求值的处理过程是自左至右扫描表达式的每一个字符</p><ul><li>当扫描到的是运算数，则将其压入栈OPND</li><li>当扫描到的是运算符时<ul><li>若这个运算符比OPTR栈顶运算符的优先级高，则入栈OPTR，继续向后处理</li><li>若这个运算符比OPTR栈顶运算符优先级低，则从OPND栈中弹出两个运算数，从栈OPTR中弹出栈顶运算符进行运算，并将运算结果压入栈OPND。</li><li>继续处理当前字符，直到遇到结束符为止。</li></ul></li></ul></li></ul></li></ul></li></ul><h4 id="3-2-3：舞伴问题">3.2.3：舞伴问题</h4><ul><li>舞会上男女各排一队，舞会开始从队头各出一人配成舞伴，如果两队初始人数不同，则较长那一队未配对者等待下一轮舞曲。</li><li>该问题具有典型的<strong>先进先出</strong>特性，可以用<strong>队列</strong>作为是算法的数据结构<ul><li>首先构造两个队列</li><li>依次将队头元素出队配成舞伴</li><li>某队为空，则另外一队等待者为下一舞曲第一个可获得舞伴的人。</li></ul></li></ul><h3 id="3-3-栈的表示和操作的实现">3.3 栈的表示和操作的实现</h3><h4 id="3-3-1-栈的抽象数据类型和类型的定义">3.3.1 栈的抽象数据类型和类型的定义</h4><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230916195554.png" alt=""></p><p><strong>基本操作：</strong></p><p>​InitStack(&amp;S)</p><p>​<strong>操作结果：构造一个空栈s。</strong></p><p>​DestroyStack(&amp;S)</p><p>​<strong>初始条件：栈s巳存在。</strong></p><p>​<strong>操作结果：栈S被销毁。</strong></p><p>​ClearStack(&amp;S)</p><p>​<strong>初始条件：栈S已存在。</strong></p><p>​<strong>操作结果：将S清为空栈。</strong></p><p>​StackEmpty(S)</p><p>​<strong>初始条件：栈S巳存在。</strong></p><p>​<strong>操作结果：若栈</strong> s <strong>为空栈， 则返回</strong> true, <strong>否则返回</strong> false**。**</p><p>​StackLength (S)</p><p>​<strong>初始条件：栈S已存在。</strong></p><p>​<strong>操作结果：返回s的元素个数， 即栈的长度。</strong></p><p>​GetTop(S)</p><p>​<strong>初始条件：栈S已存在且非空。</strong></p><p>​<strong>操作结果：返回s的栈顶元素， 不修改栈顶指针。</strong></p><p>​Push(&amp;S,e)</p><p>​<strong>初始条件：栈S已存在。</strong></p><p>​<strong>操作结果：插入元素e为新的栈顶元素。</strong></p><p>​Pop(&amp;S,&amp;e)</p><p>​<strong>初始条件：栈s已存在且非空。</strong></p><p>​<strong>操作结果：删除S的栈顶元素，并用e返回其值。</strong></p><p>​StackTraverse(S)</p><p>​<strong>初始条件：栈S已存在且非空。</strong></p><p>​<strong>操作结果：从栈底到栈顶依次对S的每个数据元素进行访问。</strong></p><h4 id="3-3-2-顺序栈的表示和实现">3.3.2 <strong>顺序栈的表示和实现</strong></h4><ul><li>有与栈本身就是线性表，于是栈也有顺序存储和链式存储两种实现方式。<ul><li>栈的顺序存储——顺序栈</li><li>栈的链式存储——链栈</li></ul></li></ul><p>​存储方式：同一般线性表的顺序存储结构完全相同，利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。</p><p>​设<strong>top</strong>指针，指栈顶元素在顺序栈中的位置</p><p>​另设<strong>base</strong>指针，指示栈底元素在顺序栈中的位置</p><ul><li><p><em>但是为了方便操作，通常<strong>top</strong>指示真正的栈顶<strong>元素之上的下标地址</strong></em></p><p><em>另外用<strong>stacksize</strong>表示栈可使用的最大容量</em></p><p><strong>空栈</strong>：base==top是栈空的标志</p><p><strong>栈满</strong>：top-base==stacksize</p><p>​栈满时的处理方法：</p><ol><li><strong>报错</strong>，返回操作系统。</li><li><strong>分配更大的空间</strong></li></ol></li><li><p>使用数组作为顺序栈存储方式的特点：</p><ul><li>简单、方便、但容易产生溢出<ul><li><strong>上溢</strong>：栈已满，又要压入元素</li><li><strong>下溢</strong>：栈已空，还要弹出元素</li></ul></li><li>一般<strong>上溢是错误</strong>，<strong>下溢是</strong>一种<strong>结束条件</strong>，即问题处理已结束</li></ul></li><li><p>顺序栈的表示</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXSIZE 100</span></span><br><span class="line">typeldef <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    SElemType *base;</span><br><span class="line">    SElemType *top;</span><br><span class="line">    <span class="type">int</span> stacksize;</span><br><span class="line">&#125;SqStack;</span><br></pre></td></tr></table></figure></li></ul><h5 id="3-3-2-1-顺序栈的初始化"><strong>3.3.2.1 顺序栈的初始化</strong></h5><figure class="highlight c"><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">Status <span class="title function_">lnitStack</span><span class="params">(SqStack &amp;S)</span>&#123;<span class="comment">//构造空栈</span></span><br><span class="line">S.base=new SElemType[MAXSIZE]; </span><br><span class="line">    <span class="comment">//或S.base=(SElemType*)malloc(MAXSIZE*sizeof（SElemType));</span></span><br><span class="line"><span class="keyword">if</span> (!S.base)<span class="built_in">exit</span> (OVERFLOW);<span class="comment">//存储分配失败</span></span><br><span class="line">S.top=S.base;<span class="comment">//栈顶指针等于栈底指针</span></span><br><span class="line">S.stacksize=MAXSIZE;</span><br><span class="line">    <span class="keyword">return</span> OK；</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：顺序栈判断栈是否为空</strong></p><figure class="highlight c"><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">Status StackEmpty（SqStack S)&#123;</span><br><span class="line">    <span class="comment">//若栈为空，返回TRUE否则返回FALSE</span></span><br><span class="line">    <span class="keyword">if</span>(S.top==S.base)</span><br><span class="line">        <span class="keyword">return</span> TRUE;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> FALSE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：求顺序栈长度</strong></p><figure class="highlight c"><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="type">int</span> <span class="title function_">StackLength</span><span class="params">(SqStack S)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> S.top-S.base;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：清空顺序栈</strong></p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">ClearStack</span><span class="params">(SqStack S)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (S.base)S.top=S.base;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>算法补充：销毁顺序栈</strong></p><figure class="highlight c"><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">Status <span class="title function_">DestroyStack</span><span class="params">(SqStack &amp;S)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(S.base)&#123;</span><br><span class="line">        delete S.base;</span><br><span class="line">        S.stacksize=<span class="number">0</span>:</span><br><span class="line">        S.base=S.top=<span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-2-2-顺序栈的入栈"><strong>3.3.2.2 顺序栈的入栈</strong></h5><figure class="highlight c"><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">Status <span class="title function_">Push</span><span class="params">(SqStack &amp;S.SElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>（S.top-S.base==S.stacksize)</span><br><span class="line">        <span class="keyword">return</span> ERROR;</span><br><span class="line">    *S.top++=e:</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-2-3-顺序栈的出栈"><strong>3.3.2.3 顺序栈的出栈</strong></h5><figure class="highlight c"><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">Status <span class="title function_">Pop</span><span class="params">(SqStack &amp;S,SElemType &amp;e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(S.top==S.base)</span><br><span class="line">        <span class="keyword">return</span> ERROR;</span><br><span class="line">    e=*--S.top;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-3-3-链栈的表示和实现">3.3.3 链栈的表示和实现</h4><ul><li>链栈是<strong>运算受限</strong>的单链表，只能在<strong>链表头部</strong>进行操作</li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">StackNode</span>&#123;</span></span><br><span class="line">    SElemType data；</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">StackNode</span> *<span class="title">next</span>；  </span></span><br><span class="line"><span class="class">&#125;<span class="title">StackNode</span>,*<span class="title">LinkStack</span>;</span></span><br><span class="line">LinkStack S;</span><br></pre></td></tr></table></figure><ol><li>链表头结点就是栈顶</li><li>不需要头结点</li><li>基本不存在栈满的情况</li><li>空栈相当于头指针指向空</li><li>插入和删除仅在栈顶处执行</li></ol><h5 id="3-3-3-1-链栈的初始化">3.3.3.1 链栈的初始化</h5><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> lnit <span class="title function_">Stack</span><span class="params">(LinkStack &amp;S)</span>&#123;</span><br><span class="line">    S=NuLL;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法补充：判断链栈是否为空</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">StackEmptyl</span><span class="params">(LinkStack S)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(S==<span class="literal">NULL</span>)<span class="keyword">return</span> TRUE;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">return</span> FALSE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-3-3-2-链栈的入栈">3.3.3.2 链栈的入栈</h5><figure class="highlight c"><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">Status <span class="title function_">Push</span><span class="params">(LinkStack &amp;S,SElemType e)</span>&#123;</span><br><span class="line">    p=new StackNode;<span class="comment">//生成新结点p</span></span><br><span class="line">    p-&gt;data=e;<span class="comment">//将新结点数据域置为e</span></span><br><span class="line">    p-&gt;nextg=S;<span class="comment">//将新结点插入栈顶</span></span><br><span class="line">    S=p;<span class="comment">//修改栈顶指针</span></span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>相当于头插法</em></p><h5 id="3-3-3-3-链栈的出栈">3.3.3.3 链栈的出栈</h5><figure class="highlight c"><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">Status Pop（LinkStack &amp;S,SElemType &amp;e)&#123;</span><br><span class="line">    <span class="keyword">if</span>(S==<span class="literal">NULL</span>)<span class="keyword">return</span> ERROR;</span><br><span class="line">    e=S-&gt;data;</span><br><span class="line">    p=S;</span><br><span class="line">    S=S-&gt;next;</span><br><span class="line">    delete p;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h6 id="3-3-3-4-取栈顶元素">3.3.3.4 取栈顶元素</h6><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">SElemType <span class="title function_">GetTop</span><span class="params">(LinkStack S)</span>&#123;</span><br><span class="line"><span class="keyword">if</span>(S!=<span class="literal">NULL</span>)</span><br><span class="line">    <span class="keyword">return</span> S-&gt;data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4栈与递归">3.4栈与递归</h3><ul><li><p>递归的定义</p><ul><li><p>若一个对象<strong>部分地包含它自己</strong>，或用他<strong>自己给自己定义</strong>，则称这个对象是递归的</p></li><li><p>若一个过程<strong>直接地或间接地调用自己</strong>，则称这个过程是递归的过程。</p><ul><li><p>例如：递归求n的阶乘</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="title function_">Fact</span><span class="params">(<span class="type">long</span> n)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(n==<span class="number">0</span>)<span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> n*Fact(n<span class="number">-1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li><li><p>以下三种情况尝尝用到递归方法</p><ul><li>递归定义的数学函数<ul><li>阶乘函数</li><li>二阶fibonaci数列</li></ul></li><li>具有递归特性的数据结构<ul><li>二叉树</li><li>广义表</li></ul></li><li>可递归求解的问题<ul><li>迷宫问题</li><li>hanoi塔问题</li></ul></li></ul></li><li><p>递归问题——用分治法求解</p><ul><li>分治法：对于一个较为复杂的问题，能够分解成几个相对简单的且解法相同或类似的子问题来求解</li><li>必备的三个条件<ol><li>能够将一个问题转变成一个新的问题，而新的问题与原问题解法相同或类同，不同的仅是处理的对象，且这些处理对象是变化有规律的</li><li>可以通过上述转化而使问题简化</li><li>必须有一个明确的递归出口或递归的边 界</li></ol></li></ul></li></ul></li></ul><p>分治法求解递归问题算法的一般形式：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231003194556.png" alt=""></p><ul><li><p>函数调用过程</p><p>调用前，系统完成：</p><ol><li>将<strong>实参，返回地址</strong>等传递给被调用函数</li><li>为被调用函数的<strong>局部变量</strong>分配存储区</li><li>将控制转移到被调用函数的<strong>入口</strong></li></ol><p>调用后，系统完成：</p><ol><li>保存被调用函数的计算<strong>结果</strong></li><li>释放被调用函数的<strong>数据区</strong></li><li>依照被调用函数保存的<strong>返回地址</strong>将控制转移到调用函数</li></ol></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231003195721.png" alt=""></p><ul><li><p>递归的优缺点</p><ul><li><p>优点：结构清晰，程序易读</p></li><li><p>缺点：每次调用都要生成工作记录，保存状态信息，入栈；返回时要出栈，恢复状态信息。时间开销大</p><p>递归-&gt;非递归</p><ol><li>尾递归、单项递归-&gt;循环结构</li><li>自用栈模拟系统的运行时栈</li></ol></li></ul></li></ul><h3 id="3-5-队列的表示和操作的实现">3.5 队列的表示和操作的实现</h3><h4 id="3-5-1-队列的抽象数据类型定义">3.5.1 队列的抽象数据类型定义</h4><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20231004200831.png" alt=""></p><h4 id="3-5-2-队列的顺序表示和实现">3.5.2 队列的顺序表示和实现</h4><ul><li><p>队列的物理存储可以用顺序存储结构，也可以用链式存储结构。相应的队列的存储方式也分为两种，即<strong>顺序队列</strong>和<strong>链式队列</strong></p></li><li><p><strong>队列的顺序表示</strong>——用一维数组base[MAXQSIZE]</p></li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXQSIZE 100<span class="comment">//最大队列长度</span></span></span><br><span class="line">Typedef <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    QElemType *base;<span class="comment">//初始化的动态分配存储空间</span></span><br><span class="line">    <span class="type">int</span> front;<span class="comment">//头指针</span></span><br><span class="line">    <span class="type">int</span> rear;<span class="comment">//尾指针</span></span><br><span class="line">&#125;SqQueue;</span><br></pre></td></tr></table></figure><ul><li><p>若front=0且rear=MAXQSIZE时，再入队——真溢出</p></li><li><p>若front不为0，rear=MAXQSIZE时，再入队——假溢出，此时队列中还有空间可以存放</p><ul><li><p>解决假溢出可以吧队的空间想象成一个循环的表</p><ul><li><p>引入循环队列</p><ol><li><p>实现方法：利用%运算</p></li><li><p>插入元素：Q.base[Q.rear]=x;</p><p>​                   Q.rear=（Q.rear+1)%MAXQSIZE;</p></li><li><p>删除元素：x=Q.base[s.front]</p><p>​                   Q.front=(Q.front+1)%MAXQSIZE</p></li><li><p>循环队列:循环使用为队列分配的存储空间。</p></li></ol></li></ul></li></ul></li><li><p>因为队空队满都是：front==rear</p><ul><li><p>所以我们常常另设一个标志来区别队空队满、另设一个变量，记录元素个数或者少用一个元素空间。</p></li><li><p>队满时——少用一个元素空间</p><ul><li>队空：front==rear</li><li>队满：（rear+1)%MAXQSIZE</li></ul></li></ul></li></ul><p>3.5.2.1 队列的初始化</p><figure class="highlight c"><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">Status <span class="title function_">InitQueue</span><span class="params">(SqQueue &amp;Q)</span>&#123;</span><br><span class="line">    Q.base=(QElemType*)</span><br><span class="line">    <span class="built_in">malloc</span>(MAXQSIZE*<span class="keyword">sizeof</span>（QElemType)）；</span><br><span class="line">    <span class="keyword">if</span>（!Q.base)<span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">    Q.fornt=Q.rear=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.2.2 求队列的长度</p><figure class="highlight c"><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="type">int</span> <span class="title function_">QueueLength</span><span class="params">(SqQueue Q)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span>(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.2.3 循环队列入队</p><figure class="highlight c"><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">Status <span class="title function_">EnQueue</span><span class="params">(SqQueue &amp;Q，QElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>((Q.rear+<span class="number">1</span>)%MAXQSIZE==Q.front) <span class="keyword">return</span> ERROR;</span><br><span class="line">    Q.base[Q.rear]=e;</span><br><span class="line">    Q.rear=(Q.rear+<span class="number">1</span>)%MAXQSIZE;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.2.4 循环队列出队</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">DeQueue</span><span class="params">(SqQueue &amp;Q,QElemType &amp;e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.rear==Q.front) <span class="keyword">return</span> ERROR;</span><br><span class="line">    e=Q.base[Q.front];</span><br><span class="line">    Q.front=(Q.front+<span class="number">1</span>)%MAXQSIZE;</span><br><span class="line">    <span class="keyword">return</span> OK；</span><br></pre></td></tr></table></figure><p>3.5.2.5 取队头元素</p><figure class="highlight c"><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">Status <span class="title function_">GetHead</span><span class="params">(SqQueue Q)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.front!==Q.rear);</span><br><span class="line">    <span class="keyword">return</span> Q.base[Q.front];<span class="comment">//返回队头指针元素的值，队头指针不变</span></span><br></pre></td></tr></table></figure><p>3.5.3 链队——队列的链式表示和实现</p><p>若用户无法估计所用队列长度，则宜采用链队列</p><ul><li>链队的类型定义</li></ul><figure class="highlight c"><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"><span class="meta">#<span class="keyword">define</span> MAXQSIZE 100 </span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Qnode</span>&#123;</span></span><br><span class="line">    QElemType data;</span><br><span class="line">    stuct Qnode *next;</span><br><span class="line">&#125;QNode,*QueuePtr<span class="comment">//ptr是pointer的缩写</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    QueuePtr front;</span><br><span class="line">    QueuePtr rear;</span><br><span class="line">&#125;LinkQueue;</span><br></pre></td></tr></table></figure><p>3.5.3.1 链队的初始化</p><figure class="highlight c"><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">Status <span class="title function_">InitQueue</span> <span class="params">(LinkQueue &amp;Q)</span>&#123;</span><br><span class="line">    Q.front=Q.rear=new QNode;</span><br><span class="line">    <span class="keyword">if</span>(!Q.front)<span class="built_in">exit</span>(OVERFLOW);</span><br><span class="line">    Q.front-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.2 链队列的销毁</p><figure class="highlight c"><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">Status <span class="title function_">DestroyQueue</span><span class="params">(LinkQueue &amp;Q)</span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(Q.front)&#123;</span><br><span class="line">        p=Q.front-&gt;next;</span><br><span class="line">        ferr(Q.front);</span><br><span class="line">        Q.front=p;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.3 链队列的入队</p><figure class="highlight c"><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">Status <span class="title function_">EnQueue</span> <span class="params">(LinkQueue &amp;Q, QElemType e)</span>&#123;</span><br><span class="line">    p=new QNode;</span><br><span class="line">    p-&gt;data=e;</span><br><span class="line">    p-&gt;next=<span class="literal">NULL</span>; Q. rear-&gt;next=p;</span><br><span class="line">    Q.rear=p;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.4 链队列的出队</p><figure class="highlight c"><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">Status <span class="title function_">DeQueue</span><span class="params">(LinkQueue &amp;Q,QElemType &amp;e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.front==Q.rear) <span class="keyword">return</span> ERROR;</span><br><span class="line">    p=Q.front-&gt;next;</span><br><span class="line">    e=p-&gt;data;</span><br><span class="line">    Q.front-&gt;next=p-&gt;next;</span><br><span class="line">    <span class="keyword">if</span>(Q.rear==p) Q.rear=Q.front;</span><br><span class="line">    delete p;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3.5.3.5 链队列取队头</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">SElemType GetHead&#123;LinkQueue Q)&#123;</span><br><span class="line">    <span class="keyword">if</span>(Q.front!=Q.rear)</span><br><span class="line">        <span class="keyword">return</span> Q.front-&gt;next-&gt;data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第三章--王卓老师第五周课程笔记</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>数据结构第二章(汇总)</title>
    <link href="https://www.fomal.cc/posts/b7d0efe8.html"/>
    <id>https://www.fomal.cc/posts/b7d0efe8.html</id>
    <published>2023-09-13T11:51:00.000Z</published>
    <updated>2023-09-14T11:54:04.152Z</updated>
    
    <content type="html"><![CDATA[<h2 id="2线性表">2线性表</h2><p><em>“<strong>以下四章为线性结构相关的知识，同时本章是整个课程的重点与核心内容，也是其他后续章节的重要基础。</strong>”</em></p><h3 id="2-1-线性表的定义和特点">2.1 线性表的定义和特点</h3><p><strong>线性表</strong>是具有相同特性的数据元素的一个<strong>有限序列</strong><br>$\large (a1,a2,a3,……a_i,a_{i+1},……,a_n)$    <strong><s>也就是说数组就是线性表咯</s></strong></p><p>其中数据元素的个数n定义为表的长度。</p><ul><li><p>当n=0时称为空表。</p></li><li><p>将非空的线性表记作(a1,a2……，an)</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%7B6DD26F23-D900-46e8-8D0A-6566181F7242%7D.png" alt=""></p></li></ul><p>可以看出线性表的逻辑特征是：</p><ol><li>有且仅有一个开始结点$a1$，他没有直接前驱，而仅有一个直接后继$a2$。</li><li>有且仅有一个终端结点$a_n$，他没有直接前驱，而仅有一个直接前驱$a{n-1}$。</li><li>其余内部结点都有且仅有一个直接前驱和一个直接后继。</li></ol><h3 id="2-2-案例引入">2.2 案例引入</h3><h4 id="2-2-1-一元多项式的运算">2.2.1  <strong>一元多项式的运算</strong></h4><p>$\large P_n(x)=p_0+p_1x+p_2x^{2}+……+p_nx^n$       $\large Q_n(x)=q_0+q_1x+q_2x^{2}+……+q_nx^n$</p><ul><li>其中$\large p_x和\large q_x$是系数<ul><li>那么两个多项式相加的结果$R_n(x)=P_n(x)+Q_m(x)$可用线性表R表示：<ul><li>$\large R=(p_0+q_0,p1+q1,p2+q2+……+p_n+q_n)$</li></ul></li></ul></li></ul><h4 id="2-2-2-稀疏多项式的运算">2.2.2 <strong>稀疏多项式的运算</strong></h4><p>$\large S(x)=1+3x^{10000}+2x^{20000}$</p><ul><li><strong>仅记录</strong>系数<strong>不为零</strong>的系数即可，这样可以大大节省空间。</li></ul><p>​线性表A：(7,0)、(3,1)、(9,8)、(5,7)</p><p>​线性表B：(8,1)、(22,7)、(-9,8)</p><ul><li>创建一个<strong>新数组C</strong></li><li>分别从头比较A、B的每一项     PS：<em><strong>就是相当于走一遍一元多项式运算的步骤</strong></em><ul><li>指数相同：对应系数相加，若其和不为零，则在C中增加一个新项</li><li>指数不相同：则将指数较小的项复制到C中</li><li>一个线性表添加完后，将另一个线性表剩余项依次复制到C中即可</li></ul></li></ul><table><thead><tr><th style="text-align:center">0</th><th style="text-align:center">1</th><th style="text-align:center">7</th><th style="text-align:center">8</th><th style="text-align:center">7</th></tr></thead><tbody><tr><td style="text-align:center">7</td><td style="text-align:center">11</td><td style="text-align:center">22</td><td style="text-align:center">0</td><td style="text-align:center">5</td></tr></tbody></table><ul><li>顺序结构存在的问题<ol><li>存储空间分配不灵活</li><li>运算的空间复杂度高</li></ol></li><li>由此引出了<strong>链式存储结构</strong></li><li><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230807005946.png" alt=""><ul><li>不需要额外的操作空间</li></ul></li></ul><h4 id="2-2-3-图书信息管理系统">2.2.3 图书信息管理系统</h4><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%7B1126D267-16B6-4517-B623-6DF9F0740407%7D.png" alt=""></p><ul><li>将图书表抽象为线性表</li><li>表中每本图书抽象线性表中数据元素</li><li>具体实现，学完这一章就会了，暂时按下不表</li></ul><p>总结：</p><ol><li><em>线性表中数据元素的类型可以为<strong>简单类型</strong>，也可以为<strong>复杂类型</strong>。</em></li><li><em>许多实际应用问题所涉及的<strong>基本操作有很大相似性</strong>，不应为每个具体应用单独编写一个程序。</em></li><li><em>从具体应用中抽象出共性的逻辑结构和基本操作（抽象数据类型），然后实现其<strong>存储结构和基本操作</strong>。</em></li></ol><h3 id="2-3-线性表的类型定义">2.3 线性表的类型定义</h3><ul><li>抽象数据类型线性表定义如下：</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%7B883A9202-FB59-4492-8B15-C34074448926%7D.png" alt=""></p><p><em><strong>PS: List是线性表的英文名</strong></em></p><ul><li>基本操作：<ul><li>InitList(&amp;L) (Initialization List)<br>操作结果：构造一个空的线性表L。</li><li>DestroyList(&amp;L)<br>初始条件:线性表L已存在。<br>操作结果：将L重置为空表。</li><li>ListEmpty(L)<br>初始条件：线性表L已存在。<br>操作结果：若L为空表， 则返回true, 否则返回false。</li><li>ListLength(L)<br>初始条件：线性表L已存在。<br>操作结果：返回L中数据元素个数。</li><li>GetElem(L,i,&amp;e)<br>初始条件：线性表L巳存在，且1:,s;i:os;ListLength(L)。<br>操作结果：用e返回L中第1个数据元素的值。</li><li>LocateElem(L,e)<br>初始条件：线性表L已存在<br>操作结果：返回L中第1个 值与e相同的元素在 L中的位置 。若这样的数据元素不存在 ， 则返回值为0。</li><li>PriorElem(r,cur_e,&amp;pre_e)<br>初始条件：线性表L已存在。<br>操作结果：若cur_e是L的数据元素，且不是第一个，则用pre_e返回其前驱，否则操作失败，pre_e无定义。</li><li>NextElem(L,cur_e,&amp;next_e)<br>初始条件：线性表L已存在。<br>操作结果：若cur_e是L的数据元素，且不是最后一个，则用next_e返回其后继，否则操作失败，next_e无定义。</li><li>Listinsert(&amp;L,i,e)<br>初始条件：线性表L已存在，且1:,s;i:os;ListLength (L) +l。<br>操作结果：在 L中第1个位置之前插入新的数据元素 e, L的长度加1。</li><li>ListDelete(&amp;L,i)<br>初始条件：线性表L已存在且非空 ，且l:os;i:os;ListLength(L)。<br>操作结果：删除L的第1个数据元素，L的长度减1。</li><li>TraverseList(L)<br>初始条件：线性表L已存在<br>操作结果：对线性表L进行遍历，在遍历过程中对 L的每个结点访问一次。</li></ul></li></ul><h3 id="2-4-线性表的顺序表示和实现">2.4 线性表的顺序表示和实现</h3><h4 id="2-4-1-线性表的顺序存储表示">2.4.1 线性表的顺序存储表示</h4><ul><li>线性表的顺序表示又称为<strong>顺序存储结构或顺序映像</strong><ul><li><strong>顺序存储定义</strong>：把<strong>逻辑上相邻</strong>的数据元素存储在<strong>物理上相邻</strong>的存储单元中的存储结构</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230808182158.png" alt=""></p><p>线性表的第一个数据元素$a_1$的存储位置，称作线性表的<strong>起始位置或基地址</strong></p><ul><li><p><strong>顺序表的特点</strong>：<em>以物理位置相邻表示逻辑关系，任一元素均可随机存取</em>。（优点）</p><ul><li>PS：必须是<strong>占用一片连续的存储空间</strong>，中间不存在空的存储单元</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230808223209.png" alt=""></p></li><li><p><strong>线性表类型定义</strong>的模版：</p></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> LIST_INIT_SIZE 100<span class="comment">//线性表存储空间的初始分配量</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType elem[LIST_INIT_SIZE];<span class="comment">//ElemType改成需要的元素类型，把他当做未知数x,根据题目需求而改变</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//当前长度</span></span><br><span class="line">&#125;SqList;</span><br></pre></td></tr></table></figure><ul><li>例一：多项式的顺序存储结构类型定义</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%7B94BBDEEB-EF8F-47a5-8044-49117A0E257F%7D.png" alt=""></p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAXSIZE 1000<span class="comment">//多项式可能达到的最大长度</span></span></span><br><span class="line"> <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//多项式非零项的定义</span></span><br><span class="line">     <span class="type">float</span> p;<span class="comment">//系数</span></span><br><span class="line">     <span class="type">int</span> e;<span class="comment">//指数</span></span><br><span class="line"> &#125;Ploynomial;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    polynomial *elem;<span class="comment">//存储空间的基地址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//多项式中当前项的个数</span></span><br><span class="line">&#125;SqList;<span class="comment">//多项式的顺序存储结构类型为SqList</span></span><br></pre></td></tr></table></figure><ul><li>例二：图书表的顺序存储结构类型定义</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%7B1126D267-16B6-4517-B623-6DF9F0740407%7D.png" alt=""></p><figure class="highlight c"><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="meta">#<span class="keyword">define</span> MAXSIZE 10000</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span><span class="comment">//图书信息定义</span></span><br><span class="line">    <span class="type">char</span> no [<span class="number">20</span>];<span class="comment">//图书的ISBN</span></span><br><span class="line">    <span class="type">char</span> name[<span class="number">50</span>];</span><br><span class="line">    <span class="type">float</span> price;</span><br><span class="line">&#125;Book;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    Book *elem;<span class="comment">//存储空间的基地址</span></span><br><span class="line">    <span class="type">int</span> length;<span class="comment">//图书表中当前图书个数</span></span><br><span class="line">&#125;SqList;<span class="comment">//图书表的顺序存储结构类型为SqList</span></span><br></pre></td></tr></table></figure><ul><li><p>补充：数组定义</p><ul><li><p>数组静态分配</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType data[MaxSize];</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;<span class="comment">//顺序表类型</span></span><br></pre></td></tr></table></figure></li><li><p>数组动态分配</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType *data;</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;<span class="comment">//顺序表类型</span></span><br></pre></td></tr></table></figure></li><li><p>C语言的内存动态分配</p><p>SqList L；</p><p>L.data=(<u>ElemType*</u><a href="%E8%BD%AC%E5%8C%96%E4%B8%BA%E8%AF%A5%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%8C%87%E9%92%88">^f</a>)<strong>malloc</strong>(<strong>sizeof</strong>(ElemType)*MaxSize);</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230808233553.png" alt=""></p><p>需要加载头文件：&lt;stdlid.h&gt;</p></li></ul></li><li><p>传地址方式——指针变量做参数(<strong>c++</strong>)</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">swap</span><span class="params">(<span class="type">float</span>*m,<span class="type">float</span>*n)</span>&#123;</span><br><span class="line">    <span class="type">float</span> t:</span><br><span class="line">    t=*m;</span><br><span class="line">    *m=*n;</span><br><span class="line">    *n=t;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">float</span> a,b,*p1,*p2;</span><br><span class="line">    <span class="built_in">cin</span>&gt;&gt;a&gt;&gt;b;<span class="comment">//cin&gt;&gt;输入的意思</span></span><br><span class="line">    p1=&amp;a; p2=&amp;b;</span><br><span class="line">    swap(p1,p2);</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;a&lt;&lt;<span class="built_in">endl</span>&lt;&lt;b&lt;&lt;<span class="built_in">endl</span>;<span class="comment">//endl换行的意思</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>传地址方式——数组名作参数</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">sub</span><span class="params">(<span class="type">char</span> b[])</span>&#123;</span><br><span class="line">    b[]=<span class="string">&quot;world&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span> <span class="params">(<span class="type">void</span>)</span>&#123;</span><br><span class="line">    <span class="type">char</span> a[<span class="number">10</span>]=<span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    sub(a):</span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;a&lt;&lt;<span class="built_in">endl</span>;<span class="comment">//结果为world</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>传地址方式——引用类型作参数</p><figure class="highlight c"><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"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;iostream.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> i=<span class="number">5</span>;</span><br><span class="line">    <span class="type">int</span> &amp;j=i;<span class="comment">//i是本名；j是小名</span></span><br><span class="line">    i=<span class="number">7</span>;<span class="comment">//i值改变j值也会跟着改变</span></span><br><span class="line">    <span class="built_in">cout</span>&lt;&lt;<span class="string">&quot;i=&quot;</span>&lt;&lt;i&lt;&lt;<span class="string">&quot;j=&quot;</span>&lt;&lt;j;<span class="comment">//输出i=7 j=7</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>形参变化实参也发生变化</li><li>占有同一片区域</li></ul></li><li><p>顺序表示意图</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAXSIZE 100</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    ElemType *elem;</span><br><span class="line">    <span class="type">int</span> length;</span><br><span class="line">&#125;SqList;<span class="comment">//定义顺序表类型</span></span><br></pre></td></tr></table></figure><ul><li>$\large SqList$   $\large L$；  //定义变量L，L是SqList这种类型的，L是个顺序表</li></ul></li></ul><h4 id="2-4-2-顺序表的基本操作的实现">2.4.2 顺序表的基本操作的实现</h4><ol><li><p>初始化线性表L</p><p><strong>算法2.1  顺序表的初始化</strong></p><ol><li>elem指向这段空间的基地址</li><li>将表的当前长度设为0</li></ol><figure class="highlight c"><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">Status <span class="title function_">lnitLIS_Sq</span><span class="params">(SqList &amp;L)</span>&#123;<span class="comment">//构造一个空的顺序表</span></span><br><span class="line">L.elem=new ElemType[MAXSIZE];<span class="comment">//为顺序表分配空间</span></span><br><span class="line"><span class="keyword">if</span>(!L.elem)<span class="built_in">exit</span>(OVERFLOW);<span class="comment">//存储分配失败</span></span><br><span class="line">L.length=<span class="number">0</span>;<span class="comment">//空表长度为0</span></span><br><span class="line">retunrn OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>销毁线性表L</p><figure class="highlight c"><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="type">void</span> <span class="title function_">DestroyList</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(L.elem)delete L.elem:<span class="comment">//释放存储空间</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>清空线性表L</p><figure class="highlight c"><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="type">void</span> <span class="title function_">ClearList</span><span class="params">(SqList &amp;L)</span>&#123;</span><br><span class="line">    L.length=<span class="number">0</span>;<span class="comment">//将线性表长度置为0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>求线性表L的长度</p><figure class="highlight c"><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="type">int</span> <span class="title function_">GetLength</span><span class="params">(SqList L)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span>(L.length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>判断线性表L是否为空</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">IsEmpty</span><span class="params">(SqList L)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(L.length==<span class="number">0</span>)<span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>取值</p><p><strong>算法2.2  顺序表的取值</strong></p><ol><li>根据指定的位置序号$\large i$，获取顺序表中第$\large i$个数据元素的值。</li><li>若是$\large i$​值合理，则将将第 $\large i $个数据元素 $\large L.elem[i-1]$赋给参数$\large e$, 通过$\large e$返回第 1 个数据元素的传值。</li></ol><figure class="highlight c++"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">GetElem</span><span class="params">(SqList L,<span class="type">int</span> i,ElemType &amp;e)</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(i&lt;<span class="number">1</span>||i&gt;L.length)<span class="keyword">return</span> ERROR: <span class="comment">//i是序号，所以不能小于1</span></span><br><span class="line">    e=L.elem[i<span class="number">-1</span>];</span><br><span class="line">    <span class="keyword">return</span> OK</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>查找</p><p><strong>算法2.3 顺序表的查找</strong></p><ol><li>从第一个元素开始，往后找，查找成功返回该元素的序号i+1</li><li>若找到最后还是没找到，则查找失败，返回0</li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">LocateELem</span><span class="params">(SqList L,ElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;L.length;i++)</span><br><span class="line">        jif(L.elem[i]==e)<span class="keyword">return</span> i+<span class="number">1</span>;  <span class="comment">//查找成功返回i+1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>； <span class="comment">//查找失败返回0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>平均查找长度ASL</strong> ：<em>在查找时，为确定元素在顺序表中的位置， 需和给定值进行比较的数据元素个数的期望值称为查找算法在查找成功时的平均查找长度</em></p><p><strong>$\large ASL=\sum\limits_{i=1}^nP_iC_i$</strong>          $P_i$是第i个记录被查找的概率          $C_i$是第i个记录需要比较的次数</p></li></ol><p>​$\large ASL=p_1+2p_2+3p_3+np_n=\dfrac{1}{n}(1+2+3+……+n)$         每一个记录被查找的概率都相等是$\large p_i=\dfrac{1}{n}$</p><p>​$ASL=\dfrac{1}{n}\dfrac{n(n+1)}{2}=\dfrac{n+1}{2}$</p><p>​<em>tips：这里和C语言的查找有着异曲同工之妙，还是要打好C语言的基础学这个就会轻松很多了</em></p><ol start="8"><li><p>插入</p><p><strong>算法2.4 顺序表的插入</strong></p><ol><li>看插入位置是否合法（1&lt;=i&lt;=n+1）,不合法则返回error</li><li>看顺序表存储空间是否已满，已满返回error</li><li>将第n干活到第i个位置元素一次后移一个位置</li><li>将e放入第i个位置</li><li>表长+1</li></ol><figure class="highlight c"><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">status <span class="title function_">ListInsert</span><span class="params">(SqList &amp;L,<span class="type">int</span> i,ElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(i&lt;=<span class="number">1</span>||i&gt;L.length+<span class="number">1</span>)<span class="keyword">return</span> ERROR;</span><br><span class="line">    <span class="keyword">if</span>(L.length==MAXSIZE)<span class="keyword">return</span> ERROR;</span><br><span class="line">    <span class="keyword">for</span>(j=L.length<span class="number">-1</span>;j&gt;=i<span class="number">-1</span>;j--)</span><br><span class="line">        L,elem[j+<span class="number">1</span>]=L.elem[j];<span class="comment">//元素后移</span></span><br><span class="line">    L.elem[i<span class="number">-1</span>]=e;<span class="comment">//赋值</span></span><br><span class="line">    L.length++;<span class="comment">//表长加一</span></span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>顺序表插入算法分析</strong>：$\large E_{ins}=\dfrac{1}{n+1}\sum\limits_{i=1}^{n+1}(n-i+1)=\dfrac{n}{2}$</p><p>i是第几个位置，x是移动次数，第1个移动n次，第2个移动n-1次最后一个移动0次，可发现$\large i+x=n+1$</p></li></ol><p>​所以顺序表插入算法的平均复杂度为$\large O(n)$    (前面最高次项的系数二分一是常数，删除了)</p><ol start="9"><li><p>删除</p><p><strong>算法2.5 顺序表的删除</strong></p><ol><li>判断位置i是否合法</li><li>将位置i后的元素一次前移</li><li>表长-1，删除成功返回OK、</li></ol><figure class="highlight c"><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">status <span class="title function_">ListInsert</span><span class="params">(SqList &amp;L,<span class="type">int</span> i,ElemType e)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(i&lt;=<span class="number">1</span>||i&gt;L.length+<span class="number">1</span>)<span class="keyword">return</span> ERROR;</span><br><span class="line">    <span class="keyword">for</span>(j=i.j&lt;length<span class="number">-1</span>;j++</span><br><span class="line">         L,elem[j<span class="number">-1</span>]=L.elem[j];<span class="comment">//元素前移</span></span><br><span class="line">        L，length--;</span><br><span class="line">        ruturn OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>顺序表删除算法分析</strong>：$\large E_{del}=\dfrac{1}{n}\sum\limits_{i=1}^{n}(n-i)=\dfrac{n-1}{2}$</p></li></ol><p>​i是第一个移动n-1次，i是2移动n-2次,i是第n个移动0次，可发现$\large x=n-i$</p><h3 id="2-5-线性表的链式表示和实现">2.5 线性表的链式表示和实现</h3><p>​<em>通过上一小节的学习，我们不难发现顺序表的优点是<strong>任一元素均可随机存取</strong>。但他的缺点也很明显，<strong>在进行插入和删除操作时，需移动大量元素，存储空间不灵活</strong></em></p><p>​<em>所有这些问题，都可以通过线性表的另一种表示方法——链式存储结构来解决。</em></p><ul><li><em><strong>再开始本章的学习之前，我们先了解一下什么是链表：</strong></em><ul><li>用一组<strong>物理位置任意</strong>的存储单元来存放线性表的数据元素。</li><li>这组存储单元既可以是连续的也可以是<strong>不连续的</strong>，甚至是<strong>零散分布</strong>在存储中任意位置上的。</li><li>链表中的<strong>元素</strong>的<strong>逻辑次序和物理次序</strong>不一定相同</li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%7B21BEC0FB-3FDB-42ea-941E-EF5F32182E39%7D.png" alt=""></p><p>其中姓名列为<strong>数据域</strong>，数据域是用来存储元素数值数据</p><p>后面的四位编码为<strong>指针域</strong>，用来存储直接后继结点的存储位置</p><p>我们可以通过<strong>头指针来命名</strong>单链表</p><ul><li>与链式存储相关的术语<ol><li>结点：数据元素的存储映像。有数据域和指针域两部分组成</li><li>链表：n个结点由指针链组成一个链表。它是线性表的链式存储映像，称为线性表的链式存储结构。</li></ol></li><li>单链表，双链表，循环链表<ul><li>节点只有一个指针域的链表称为单链表或线性链表</li><li>结点有两个指针域的链表，称为双链表</li><li>首位相连的链表称为循环链表</li></ul></li><li>头指针，头结点，首元结点（头指针&gt;头结点&gt;首元结点）<ul><li>头指针：指向链表中第一个结点的指针</li><li>首元结点：存储第一个元素的结点</li><li>头结点：首元结点前附设的一个结点<ul><li>带头结点</li><li>不带头结点</li></ul></li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230830212633.png" alt=""></p><ul><li><p>空表如何表示？</p><p>无头结点时，头指针为空</p><p>有头结点时，头结点指针域为空</p></li><li><p>多设置一个头结点有什么好处？</p><ul><li><p><strong>便于首元结点的处理</strong></p><p>​        结点是由结构体构造的，没有头结点的话，第一个数据元素的地址储存在头指针里，就没有储存在结构体里，操作上就和其他数据元素不同，而有了头结点，在链表里第一个位置的操作和其他位置一致，无须进行特殊处理</p></li><li><p><strong>便于空表和非空表的统一处理</strong></p><p>​        结点是由结构体构造的，没有头结点的话，第一个数据元素的地址储存在头指针里，就没有储存在结构体里，操作上就和其他数据元素不同</p></li><li><p>头结点的数据域内装什么</p><p>可以空着也可以存放线性表长度等附加信息，但该结点<strong>不能计入链表长度值</strong></p></li></ul><p>顺序表是随机存取法：找到要取的元素直接找他的位置就可以了</p><p>链表是顺序存取：只能从头指针进入链表，然后顺序扫描其余结点。</p></li></ul><h4 id="2-5-1-单链表的定义和表示">2.5.1 单链表的定义和表示</h4><ul><li><p>单链表的存储结构为： 数据域|指针域     =》   【data|next】</p></li><li><p>其中data什么类型取决于<strong>数据元素</strong>什么类型，如果处理的是学生表这种，比较复杂的数据类型，则通常定义为ElemType类型</p></li><li><p>next的类型取决于他<strong>存放的地址的数据元素是什么类型</strong>  比如存放的是int a=5,那么就是int *P</p></li></ul><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span>&#123;</span><span class="comment">//声明结点的类型和指向结点的指针类型</span></span><br><span class="line">    ElemType data;<span class="comment">//结点的数据域</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span> *<span class="title">nmet</span>;</span><span class="comment">//结点的指针域</span></span><br><span class="line">&#125;Lnode,*LinkList<span class="comment">//LinkList为指向结构体Lnode的指针类型</span></span><br></pre></td></tr></table></figure><p>定义链表L：LinkList L；</p><p>定义结点指针P：LNode *P；</p><ul><li>例如：有一个存储学生学号、姓名、成绩的单链表结点类型定义如下：</li></ul><figure class="highlight c"><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"><span class="keyword">typedef</span> Struct student&#123;</span><br><span class="line">    <span class="type">char</span> num[<span class="number">8</span>];</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>];</span><br><span class="line">    <span class="type">int</span> score;<span class="comment">//以上三个数据域</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">student</span> *<span class="title">next</span>;</span><span class="comment">//指针域</span></span><br><span class="line">&#125;Lnode,*LinkList;</span><br></pre></td></tr></table></figure><p>​这样写不常用，不方便不统一，通常用一下格式</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> Struct&#123;</span><br><span class="line">    <span class="type">char</span> num[<span class="number">8</span>];</span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>];</span><br><span class="line">    <span class="type">int</span> score;</span><br><span class="line">&#125;ElemType;<span class="comment">//将存储的多个数据项定义为一个结构类型</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span>&#123;</span></span><br><span class="line">    ElemType data;<span class="comment">//用这个结构类型定义data</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">Lnode</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;Lnode,*LinkList</span><br></pre></td></tr></table></figure><h4 id="2-5-2-单链表基本操作和实现-（重点）">2.5.2 单链表基本操作和实现    （重点）</h4><h5 id="2-5-2-1-初始化">2.5.2.1 初始化</h5><p><strong>算法2.6 单链表的初始化</strong></p><ol><li><strong>生成新结点作为头结点，用头指针L 指向头结点。</strong></li><li><strong>头结点的指针域置空。</strong>     =》  【  |^】</li></ol><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">lnitList_L</span><span class="params">(LinkList &amp;L)</span>&#123;</span><br><span class="line">    L=new LNode；   <span class="comment">//或L=(LinkList) malloc (sizeof (LNode))</span></span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>补充单链表的几个常用简单算法：</p><ol><li><p><strong>判断链表是否为空</strong></p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">ListEmpty</span><span class="params">(LinkList L)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(L-&gt;next)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span></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></li><li><p><strong>单链表的销毁</strong></p><ul><li><p>从头指针开始，依次释放所有结点</p><figure class="highlight c"><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">Status <span class="title function_">DestroyList_L</span><span class="params">(LinkList &amp;L)</span>&#123;<span class="comment">//销毁单链表P</span></span><br><span class="line">    Lnode *p;<span class="comment">//或LinkLIst p</span></span><br><span class="line">    <span class="keyword">while</span>(L)&#123;<span class="comment">//L指向空停止循环</span></span><br><span class="line">        p=L;<span class="comment">//把L的地址给p</span></span><br><span class="line">        L=L-&gt;next;<span class="comment">//L指向下一个地址</span></span><br><span class="line">        delete p;<span class="comment">//删除p所指的结点</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>清空链表</strong></p><ul><li><p>依次释放所有结点，并将头结点的指针域设置为空</p><figure class="highlight c"><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">Status <span class="title function_">clearList_L</span><span class="params">(LinkList &amp;L)</span>&#123;<span class="comment">//将L重置为空表</span></span><br><span class="line">    Lnode *p,*q;</span><br><span class="line">    p=L-&gt;next;</span><br><span class="line">    <span class="keyword">while</span>(p)&#123;<span class="comment">//没到表尾</span></span><br><span class="line">        g=p-&gt;next;</span><br><span class="line">        delete p;</span><br><span class="line">        p=q;</span><br><span class="line">    &#125;</span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>; <span class="comment">//头结点指针域为空</span></span><br><span class="line">    <span class="keyword">return</span> OK:</span><br><span class="line">     </span><br><span class="line">&#125;</span><br><span class="line">     </span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>求链表表长</strong></p><ul><li><p>从首元结点开始，依次计数所有结点</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">ListLength_L</span><span class="params">(LinkList L)</span>&#123;<span class="comment">//LinkLIst定义的变量就是指针型的</span></span><br><span class="line">    LinkList p:</span><br><span class="line">    P=L-&gt;next;</span><br><span class="line">    i=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(p)&#123;</span><br><span class="line">        i++;</span><br><span class="line">        p=p-&gt;next</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ol><p>重要知识点重温：</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230831181457.png" alt=""></p><h5 id="2-5-2-2-取值">2.5.2.2 取值</h5><p><strong>算法2.7 取值</strong> ——取链表中第i个元素的内容</p><ol><li><p>从头开始，顺着链域往下搜索，指针后移且计数器x++直到x=i.</p><figure class="highlight c"><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">Status <span class="title function_">GetElem_L</span><span class="params">(LinkList L,<span class="type">int</span> i, ElemType &amp;e)</span>&#123;</span><br><span class="line">    p=L-&gt;next;i=<span class="number">1</span>;<span class="comment">//初始化</span></span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;j&lt;i)&#123;<span class="comment">//向后扫，知道p指向第i个元素或p为空</span></span><br><span class="line">        p=p-&gt;next;++j;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(!p||j&gt;i)<span class="keyword">return</span> ERROR;<span class="comment">//第i个元素不存在</span></span><br><span class="line">    e=p-&gt;data;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;<span class="comment">//GetElem_L</span></span><br></pre></td></tr></table></figure></li></ol><h5 id="2-5-2-3-查找">2.5.2.3 查找</h5><p><strong>算法2.8 按值查找</strong>——根据指定数据获取改数据所在位置</p><ol><li><p>从第一个结点开，依次和e比较</p></li><li><p>如果找到了与e值相等的数据元素，则返回其地址</p></li><li><p>没找到则返回0或NULL</p><figure class="highlight c"><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">Lnode *<span class="title function_">LocateELem_L</span> <span class="params">(LinkList L, Elemtype e)</span> &#123;</span><br><span class="line">    p=L-&gt;next;</span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;p-&gt;data!=e)</span><br><span class="line">        p=p-&gt;next;</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight c"><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"><span class="type">int</span> <span class="title function_">LocateELem_L</span><span class="params">(LinkList L, Elemtype e)</span>&#123;</span><br><span class="line">    p=L-&gt;next;i=<span class="number">1</span>:</span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;p-&gt;data!=e)</span><br><span class="line">        &#123;p=p-&gt;next;j++;&#125;</span><br><span class="line">    <span class="keyword">if</span>(p)<span class="keyword">return</span> j;<span class="comment">//没找到的话p指向空</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h5 id="2-5-2-4-插入">2.5.2.4 插入</h5><p><strong>算法2.9 插入——在第i个结点前插入值为e的新结点</strong></p><ol><li><p>找到结点i-1</p></li><li><p>建构新结点s</p></li><li><p>s-&gt;next=p-&gt;next；p-&gt;next=s；</p><figure class="highlight c"><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">Status <span class="title function_">Listlnsert_L</span><span class="params">(lLinkList &amp;L,<span class="type">int</span> i,ElemType e)</span>&#123;</span><br><span class="line">    p=L;j=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>(p&amp;&amp;j&lt;i<span class="number">-1</span>)&#123;p=p-&gt;next;++j;&#125;<span class="comment">//找节点</span></span><br><span class="line">    <span class="keyword">if</span>(!p||j&gt;i<span class="number">-1</span>)<span class="keyword">return</span> ERROR;<span class="comment">//判断位置是否合法（是否小于一或大于表长加一）</span></span><br><span class="line">    s=new LNode; s-&gt;data=e;<span class="comment">//生成新结点s,数据域为e</span></span><br><span class="line">    s-&gt;next=p-&gt;next;<span class="comment">//插入新结点</span></span><br><span class="line">    p-&gt;next=s;</span><br><span class="line">    <span class="keyword">return</span> OK;</span><br><span class="line">&#125;<span class="comment">//Listlnsert_L</span></span><br></pre></td></tr></table></figure></li></ol><h5 id="2-5-2-5-删除">2.5.2.5 删除</h5><p><strong>算法2.10 删除——删除第i个结点</strong></p><ol><li><p>找到结点i-1</p></li><li><p>p-&gt;next=p-&gt;next-&gt;next</p></li><li><p>释放结点i的空间</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line">Status <span class="title function_">ListDelet_L</span><span class="params">(LinkList &amp;L,<span class="type">int</span> i,ElemType &amp;e)</span>&#123;</span><br><span class="line">    p=L;j=<span class="number">0</span>;q;i;</span><br><span class="line">    <span class="keyword">while</span>(p-&gt;next&amp;&amp;j&lt;i<span class="number">-1</span>)&#123;p=p-next;++j;&#125;   <span class="comment">//找到i-1</span></span><br><span class="line">    <span class="keyword">if</span>(!(p-&gt;next)||j&gt;i<span class="number">-1</span>)<span class="keyword">return</span> ERROR;<span class="comment">//删除位置不合理</span></span><br><span class="line">    q=p-&gt;next;<span class="comment">//q指向要删除的结点</span></span><br><span class="line">    p-&gt;next=q-&gt;next;<span class="comment">//是p指针next域指向删除域的下一个结点</span></span><br><span class="line">    e=q-&gt;data;<span class="comment">//保存删除结点的数据域</span></span><br><span class="line">    delete q;<span class="comment">//释放空间</span></span><br><span class="line">    retun OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p><em><strong>分析单链表查找，插入，删除算法的时间效率分析：</strong></em></p><ol><li>查找：应为只能顺序存取，即要从头指针开始找起，查找时间复杂度为$\large O(n)$</li><li>插入和删除：因为线性链表不需要移动元素，只用修改至臻，一般情况下的时间复杂度为$\large O(1)$</li></ol><h5 id="2-5-2-6-创建单链表">2.5.2.6 创建单链表</h5><h6 id="2-5-2-6-1-头插法——插到链表头部">2.5.2.6.1 头插法——插到链表头部</h6><ol><li><p>L=new LNode;</p><p>L=（LinkList）malloc(sizeof(LNode));//C语言//生成一个结点</p><p>p-&gt;data=$a_n$//数据域赋值</p></li><li><p>p-&gt;next=L-&gt;next;   L-&gt;next=p;                        //L指针域的空赋给p的指针域,L指针域指向p</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">CreateList_H</span><span class="params">(LinkList &amp;L,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    L=new LNode;</span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>;<span class="comment">//建立带头结点的单链表</span></span><br><span class="line">    <span class="keyword">for</span>(i=n;i&gt;<span class="number">0</span>;--i)&#123;</span><br><span class="line">        p=（LNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>（LNode));<span class="comment">//生成新结点</span></span><br><span class="line">        <span class="built_in">scanf</span>(&amp;p-&gt;data);</span><br><span class="line">        p-&gt;next=L-&gt;next;</span><br><span class="line">        L-&gt;next=P:</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h6 id="2-5-2-6-2-尾插法——插到链表尾部">2.5.2.6.2 尾插法——插到链表尾部</h6><ol><li><p>从空表L开始，将新结点逐个插入到链表尾部，尾指针r指向链表的尾节点</p></li><li><p>初始时，r同L均指向头结点。每读入一个数据元素则申请一个新结点，将新结点插入到尾结点后，r指向新结点。</p><figure class="highlight c"><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="type">void</span> <span class="title function_">CreatList_R</span><span class="params">(LinkList &amp;L,<span class="type">int</span> n)</span>&#123;</span><br><span class="line">    L=new LNode;</span><br><span class="line">    L-&gt;next=<span class="literal">NULL</span>;</span><br><span class="line">    r=L;</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">0</span>;i&lt;n;++i)&#123;</span><br><span class="line">         p=（LNode*)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>（LNode));<span class="comment">//生成新结点</span></span><br><span class="line">         <span class="built_in">scanf</span>(&amp;p-&gt;data);</span><br><span class="line">        r-&gt;next=p;</span><br><span class="line">        r=p;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h4 id="2-5-3-循环链表">2.5.3 循环链表</h4><ul><li><p>循环链表：头尾相接的链表</p><ul><li>优点：从表中任意结点出发均可找到表中其他结点</li><li>注意：终止条件为判断他们是否等于头指针</li><li>如果是<strong>头指针表示</strong>的单循环链表<ul><li>找$a_1$的时间复杂度：O(1)</li><li>找$a_n$的时间复杂度：O(n)</li></ul></li><li>若是<strong>尾指针表示</strong>的单循环链表<ul><li>找$a_1$的存储是：R-&gt;next-&gt;next    时间复杂度：O(1)</li><li>找$a_n$的存储是：R                           时间复杂度：O(1)</li></ul></li></ul></li><li><p>带尾指针循环链表的合并</p><ul><li><p>p存表头结点  将Tb表头连接到Ta表尾    释放Tb表头结点    修改指针</p></li><li><p>p=Ta-&gt;next ; Ta-&gt;next=Tb-&gt;next-&gt;next ; delete Tb-&gt;next ; Tb-&gt;next=p;</p></li></ul><figure class="highlight c"><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">LinkList <span class="title function_">Connect</span><span class="params">(LinkList Ta,LinkList Tb)</span>&#123;</span><br><span class="line">    p=Ta-&gt;next;<span class="comment">//  an指向b1,没毛病，但是bn指向a头结点p就找不到了因为第一步你覆盖了an，所以an指向b1前，要利用an找到a头结点，bn指向bn的next</span></span><br><span class="line">    Ta-&gt;next=Tb-&gt;next-&gt;next:</span><br><span class="line">    delete Tb-&gt;next:</span><br><span class="line">    Tb-&gt;next=p;</span><br><span class="line">    <span class="keyword">return</span> Tb;</span><br><span class="line">&#125;<span class="comment">//时间复杂度O(1)</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="2-5-4-双向链表">2.5.4 双向链表</h4><ul><li><p>双向链表的结构可以定义如下：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">DuLNode</span>&#123;</span><span class="comment">//前面的Du是double双向的意思</span></span><br><span class="line">    Elemtype         data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">DuLNode</span>   *<span class="title">prior</span>,*<span class="title">next</span>;</span>          <span class="comment">//一个指向前驱，一个指向后驱</span></span><br><span class="line">&#125;DuLNode,*DuLinkList;</span><br></pre></td></tr></table></figure><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230913190111.png" alt=""></p></li><li><p>双向循环列表</p><ul><li>让头结点的前驱指针指向链表的最后一个结点</li><li>让最后一个结点的后继指针指向头结点</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230913190710.png" alt=""></p></li><li><p>双向链表结构的对称性：</p><ul><li>$\large p-&gt;prior-&gt;next=p=p-&gt;next-&gt;prior$</li></ul></li></ul><p><strong>算法2.13 双向链表的插入</strong></p><ol><li><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230913191707.png" alt=""></p></li><li><pre><code class="language-c">void Listlnsert_DuL(DuLinkList &amp;L, lnt i, ElemType e)&#123;    //双向链表头结点指针L，第i个位置，值为e的元素    if(!(p=GetElemP_DuL(L,i)))return ERROR;//确定第i个元素的位置指针P    S=new DuLNode;s-&gt;date=e;    s-&gt;prior=p-&gt;prior;P-&gt;prior-&gt;next=s;    s-&gt;next=p;p-&gt;prior=s;    return OK;&#125;//Listlnsert_DuL<figure class="highlight xl"><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><br><span class="line">**算法<span class="number">2.14</span> 双向链表的删除**</span><br><span class="line"></span><br><span class="line">![](E:\备考文件\数据结构\笔记图片\QQ截图<span class="number">20230913192652</span>.png)</span><br><span class="line"></span><br><span class="line">```c</span><br><span class="line">void ListDelete_DuL(DuLinkList &amp;L, lnt i, ElemType &amp;e)&#123;</span><br><span class="line">    <span class="comment">//用e返回</span></span><br><span class="line">    <span class="keyword">if</span>(!(p=GetElemP_DuL(L,i)))return ERROR;<span class="comment">//确定第i个元素的位置指针P，顺着链表查找时间复杂度为O(n)</span></span><br><span class="line">    <span class="function"><span class="title">e</span>=p-&gt;</span><span class="keyword">data</span>;</span><br><span class="line">    <span class="function"><span class="title">p</span>-&gt;</span><span class="function"><span class="title">prior</span>-&gt;</span><span class="function"><span class="title">next</span>=p-&gt;</span>next;</span><br><span class="line">    <span class="function"><span class="title">p</span>-&gt;</span><span class="function"><span class="title">next</span>-&gt;</span><span class="function"><span class="title">prior</span>=p-&gt;</span>prior;</span><br><span class="line">    free(p);</span><br><span class="line">    return OK;</span><br><span class="line">&#125;<span class="comment">//ListDelete_DuL</span></span><br></pre></td></tr></table></figure></code></pre></li></ol><ul><li><p>单链表、循环链表和双向链表的时间效率比较</p><table><thead><tr><th style="text-align:left"></th><th style="text-align:left">查找表头结点（首元结点）</th><th>查找表尾结点</th><th>查找结点*p的前驱结点</th></tr></thead><tbody><tr><td style="text-align:left">带头结点的单链表L</td><td style="text-align:left">L-&gt;next<br />时间复杂度O(1)</td><td>L-&gt;next依次向后遍历<br />时间复杂度O(n)</td><td>通过p-&gt;next无法找到其前驱</td></tr><tr><td style="text-align:left">带头结点仅设头指针L的循环单链表</td><td style="text-align:left">L-&gt;next<br />时间复杂度O(1)</td><td>L-&gt;next依次向后遍历<br />时间复杂度O(n)</td><td>通过p-&gt;next可以找到其前驱<br />时间复杂度O(n)</td></tr><tr><td style="text-align:left">带头结点仅设尾指针R的循环单链表</td><td style="text-align:left">R-&gt;next<br />时间复杂度O(1)</td><td>R<br />时间复杂度O(1)</td><td>通过p-&gt;next可以找到其前驱<br />时间复杂度O(n)</td></tr><tr><td style="text-align:left">带头结点的双向循环链表L</td><td style="text-align:left">L-&gt;next<br />时间复杂度O(1)</td><td>L-》prior<br />时间复杂度O(1)</td><td>p-&gt;prior<br />时间复杂度O(1)</td></tr></tbody></table></li></ul><h3 id="2-6顺序表和链表的比较">2.6顺序表和链表的比较</h3><ul><li><p>链式存储结构的优点：</p><ul><li><p><strong>结点空间</strong>可以<strong>动态申请和释放</strong></p></li><li><p>数据元素的逻辑次序靠结点的指针来指示，插入和删除时不需要移动数据元素</p></li></ul></li><li><p>链式存储结构的缺点：</p><ul><li><p><strong><u>存储密度小</u></strong>，每个结点的<strong>指针域需要额外占用存储空间</strong>。当每个结点的数据域所占字节不多时，指针域所占存储空间的比重显得很大。</p><ul><li><p>$\large 存储密度=\dfrac {结点数据本身占用的空间}{结点占用的空间总量}$</p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230914151606.png" alt=""></p></li><li><p>链式存储结构是<strong>非随机存储</strong>结构。对任一结点的操作都要从头遍历，这增加了算法的复杂度</p></li></ul></li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230914152539.png" alt=""></p></li></ul><h3 id="2-7-线性表的应用">2.7 线性表的应用</h3><h4 id="2-7-1-线性表的合并">2.7.1 线性表的合并</h4><ul><li><p>线性表的合并</p><ul><li><p>问题描述：</p><p>假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=AUB</p><p>La=(7,5,3, 11) Lb=(2, 6, 3)     ====》  La=(7, 5, 3, 11, 2, 6)</p></li><li><p>算法步骤：</p><p>从Lb中的每个元素,执行以下操作：</p><ol><li>在La中查找该元素</li><li>如果找不到，则将其插入到La的最后</li></ol></li><li><p>代码实现:</p><figure class="highlight c"><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="type">void</span> <span class="title function_">union</span><span class="params">(List &amp;La, List Lb)</span>&#123;</span><br><span class="line">    La_len=ListLength(La);</span><br><span class="line">    Lb_len=ListLength(Lb);</span><br><span class="line">    <span class="keyword">for</span>(i=<span class="number">1</span>;i&lt;=Lb_len;i++)&#123;</span><br><span class="line">        GetElem(Lb,i,e);</span><br><span class="line">        <span class="keyword">if</span>(!LocateElem(La,e))  Listlnsert(&amp;La,++La_len,e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度为La的长度乘以Lb的长度</p></li></ul></li></ul><h4 id="2-7-2-有序表的合并">2.7.2 有序表的合并</h4><ul><li><p>有序表的合并</p><ul><li><p>问题描述：</p><p>已知线性表La 和Lb中的数据元素按值非递减有序排列，现要求将La和Lb归并为一个新的线性表Lc，且Lc中的数据元素仍按值非递减有序排列。</p></li><li><p>算法步骤：</p><ol><li>创建一个空表Lc</li><li>依次从 La 或 Lb 中“摘取”元素值较小的结点插入到 Lc 表的最后，直至其中一个表变空为止</li><li>继续将La或Lb其中一个表的剩余结点插入在Lc表的最后</li></ol></li><li><p>用顺序表实现合并的代码实现：</p><figure class="highlight c"><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="type">void</span> <span class="title function_">MergeList_Sq</span><span class="params">(SqList LA,SqList LB,SqList &amp;LC)</span>&#123;</span><br><span class="line">    pa=LA.elem;</span><br><span class="line">    pb=LB.elem;<span class="comment">//指针pa和pb的初值分别指向两个表的第一个元素      </span></span><br><span class="line">    LC.length=LA.length+LB.length;<span class="comment">//新表的长度等于两表长之和</span></span><br><span class="line">    LC.elem=new ElemType[LC.length];<span class="comment">//为合并后的新表分配一个数组空间</span></span><br><span class="line">    pc=LC.elem;<span class="comment">//指针pc指向新表第一个元素</span></span><br><span class="line">    pa_last=LA.elem+LA.length<span class="number">-1</span>;<span class="comment">//指针pa_last指向LA表的最后一个元素（基地址加上长度减一）</span></span><br><span class="line">    pa_last=LB.elem+LB.length<span class="number">-1</span>;<span class="comment">//指针pa_last指向LB表的最后一个元素（基地址加上长度减一）</span></span><br><span class="line"><span class="keyword">while</span>(pa&lt;pa_last&amp;&amp;pb&lt;=pb_last)&#123;<span class="comment">//两个表都非空</span></span><br><span class="line">    <span class="keyword">if</span>(*pa&lt;=*pb)*pc++=*pa++;<span class="comment">//依次“摘取”两表中值较小的结点</span></span><br><span class="line">    <span class="keyword">else</span> *pc++=*pb++;</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">while</span>(pa&lt;=pa_last) *pc++=*pa++;<span class="comment">//LB表已经到达表位，将LA中剩余元素加入LC</span></span><br><span class="line">    <span class="keyword">while</span>(pb&lt;=pb_last) *pc++=*pb++;<span class="comment">//LA表已经到达表位，将LB中剩余元素加入LC</span></span><br><span class="line">&#125;<span class="comment">//MergeList_Sq</span></span><br></pre></td></tr></table></figure></li><li><p>用链表实现合并的代码实现：</p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">MergeList_L</span><span class="params">(LinkList &amp;La,LinkList &amp;Lb,LinkList &amp;Lc)</span>&#123;</span><br><span class="line">    pa=La-&gt;next; pb=Lb-&gt;next; </span><br><span class="line">    pc=Lc=La;<span class="comment">//用La的头结点作为Lc的头结点</span></span><br><span class="line">    <span class="keyword">while</span>( pa &amp;&amp; pb)&#123;</span><br><span class="line">        <span class="keyword">if</span>(pa-&gt;data&lt;=pb-&gt;data) &#123;pc-&gt;next=pa; pc=pa; pa=pa-&gt;next;&#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;pc-&gt;next=pb; pc=pb; pb=pb-&gt;next;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    pc-&gt;next=pa?pa:pb; <span class="comment">//插入剩余段delete Lb;</span></span><br><span class="line">    delete LB;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>算法的时间复杂度是：O(ListLength(La)+ListLength(Lb)）</p></li></ul></li></ul><h3 id="2-8案例分析与实现">2.8案例分析与实现</h3><p><strong>案例2.1 一元多项式的运算：实现两个多项式加、减、乘、除运算</strong>   <em>线性表</em></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230914190355.png" alt=""></p><ul><li>实现两个多项式相加运算</li></ul><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230914190624.png" alt=""></p><p><strong>案例2.2：稀疏多项式的运算</strong>   <em>链表</em></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230914190843.png" alt=""></p><ul><li>创建一个新数组C</li><li>分别从头遍历比较A和B的每一项<ul><li><strong>指数相同</strong>，系数相加，和若不为零，则在C中新加一项</li><li><strong>指数不同</strong>，则将指数较小的项复制到C中</li></ul></li><li>一个多项式已遍历完毕时，将另一个剩余项依次复制到C中即可</li></ul><p>用顺序存储结构的话，存储空间分配不灵活，运算的空间复杂度高，所以我们常用链式存储结构</p><ol><li>创建一个只有头结点的空链表。</li><li>根据多项式的项的个数n，循环n次执行以下操作:</li><li>生成一个新结点*s;</li><li>输入多项式当前项的系数和指数赋给新结点*s的数据域：</li><li>设置一前驱指针pre，用于指向待找到的第一个大于输入项指数的结点的前驱pre初值指向头结点;</li><li>指针q初始化，指向首元结点;</li><li>循链向下逐个比较链表中当前结点与输入项指数，找到第一个大于输入项指数的结点*q;</li><li>将输入项结点<em>s插入到结点</em>q之前。</li></ol><ul><li>算法步骤：<ol><li>指针p1和p2初始化，分别指向Pa和Pb的首元结点</li><li>p3指向和多项式的当前结点，初值为Pa的头结点</li><li>当指针p1和p2均未到达相应表尾时，则循环比较p1和p2所指结点对应的指数值(p1-&gt;expn与p2-&gt;expn) 有下列3种情况:<ol><li>当p1-&gt;expn==p2-&gt;expn时，则将两个结点中的系数相加<ol><li>若和不为零，则修改p1所指结点的系数值，同时删除p2所指结点</li><li>若和为零，则删除p1和p2所指结点;</li></ol></li><li>当p1-&gt;expn<p2->expn时，则应摘取p1所指结点插入到“和多项式”链表中去</li><li>当p1-&gt;expn&gt;p2-&gt;expn时，则应摘取p2所指结点插入到“和多项式”链表中去</li></ol></li><li>将非空多项式的剩余段插入到p3所指结点之后</li><li>释放Pb的头结点</li></ol></li></ul><p><strong>案例2.3 图书信息管理系统</strong>      <em>线性表或链表</em></p><p><img src="E:%5C%E5%A4%87%E8%80%83%E6%96%87%E4%BB%B6%5C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5CQQ%E6%88%AA%E5%9B%BE20230914192307.png" alt=""></p>]]></content>
    
    
    <summary type="html">本文是数据结构与算法基础第二章--王卓老师课堂三周笔记的总结</summary>
    
    
    
    <category term="笔记" scheme="https://www.fomal.cc/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="C语言" scheme="https://www.fomal.cc/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="数据结构与算法基础" scheme="https://www.fomal.cc/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
</feed>
