-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
157 lines (75 loc) · 154 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>数据结构(二)树</title>
<link href="/posts/95423b6d.html"/>
<url>/posts/95423b6d.html</url>
<content type="html"><![CDATA[<h1 id="知识体系结构"><a href="#知识体系结构" class="headerlink" title="知识体系结构"></a>知识体系结构</h1><p><img src="https://www.pdai.tech/_images/alg/alg-tree-0.png" alt="img"></p><h1 id="树"><a href="#树" class="headerlink" title="树"></a>树</h1><p>树是一种数据结构,它是n(n≥0)个结点的有限集合。n=0时被称为空树。n>0时,具备以下性质:</p><ul><li>有一个特殊结点被称为<code>根结点</code></li><li>其余结点可以分为m(m > 0)个互不相交的有限集T<sub>1</sub>,T<sub>2</sub>,T<sub>3</sub>,…,T<sub>m</sub>,其中每个集合本身又是一棵树,称为原来树的<code>子树</code>。</li></ul><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916102852891.png" alt="图1"></p><p>上面这棵树中,<code>1</code>结点被称作根结点,这棵树的子树包括:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916103045051.png" alt="图2"></p><p>如图3,它不是树,因为其中<code>2</code>和<code>7</code>结点相交了。</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916103311387.png" alt="图3"></p><p>所以这里引申出来树的一些其他性质:</p><ul><li>子树是<code>不相交</code>的</li><li>除根结点外,<code>每个结点有且仅有一个父节点</code></li><li>一颗有N个节点的树有<code>N-1</code>条边</li></ul><h2 id="树的相关概念"><a href="#树的相关概念" class="headerlink" title="树的相关概念"></a>树的相关概念</h2><ol><li><code>结点的度(Degree)</code>:结点的子树个数</li><li><code>树的度</code>:树的所有结点中最大的度数 </li><li><code>叶结点(Leaf)</code>:度为0的结点 </li><li><code>父结点(Parent)</code>:有子树的结点是其子树的根结点的父结点 </li><li><code>子结点(Child)</code>:若A结点是B结点的父结 点,则称B结点是A结点的子结点;子结点也 称<code>孩子结点</code>。</li><li><code>兄弟结点(Sibling)</code>:具有同一父结点的各 结点彼此是兄弟结点</li><li> <code>路径和路径长度</code>:路径即从一个节点出发到另一个节点经过的节点,这些节点共同组成一条路径,路径长度是路径中间所有边的总和</li><li><code>祖先结点(Ancestor)</code>:沿树根到某一结点路 径上的所有结点都是这个结点的祖先结点</li><li><code>子孙结点(Descendant)</code>:某一结点的子树 中的所有结点是这个结点的子孙</li><li><code>结点的层次(Level)</code>:规定根结点在1层, 其它任一结点的层数是其父结点的层数加1</li><li><code>树的深度(Depth)</code>:树中所有结点中的最 大层次是这棵树的深度。</li></ol><h2 id="二叉树"><a href="#二叉树" class="headerlink" title="二叉树"></a>二叉树</h2><p>二叉树满足<code>树</code>的性质,并且由<code>根结点</code>、<code>左子树</code>和<code>右子树</code>构成。也就是说每个结点最多有两个子树。使用上面<code>树的度</code>概念来描述,二叉树就是一颗最大度为<code>2</code>的树。</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916113609145.png" alt="图4"></p><h3 id="特殊二叉树"><a href="#特殊二叉树" class="headerlink" title="特殊二叉树"></a>特殊二叉树</h3><ul><li><p>斜二叉树</p><p> <img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916114142331.png" alt="图5"></p></li><li><p>完美二叉树(满二叉树)</p><p> 即二叉树中除<code>叶子结点</code>外,其他结点的度都为2,且叶子结点都在同一层次上。</p><p> <img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916114924783.png" alt="图6"></p></li><li><p>完全二叉树</p><p> 假如将上面的完美二叉树进行编号,编号规则为从上往下、从左往右,得到下面的结果</p><p> <img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916135431874.png" alt="图7"></p><p> 编号为i(1≤i≤n)的结点与完美二叉树中编号为i的结点在二叉树中的位置相同(这个例子里n为15),那么称这棵树为<code>完全二叉树</code>。也就是说,完全二叉树相对于完美二叉树来说,叶结点可以从右至左依次减少,当叶结点减少至0时,则变成<code>完美二叉树</code>。即完美二叉树是完全二叉树的特殊形式。</p></li></ul><h3 id="重要性质"><a href="#重要性质" class="headerlink" title="重要性质"></a>重要性质</h3><ul><li><p>一个二叉树第i层的最大结点数为:2<sup>i-1</sup>,i≥1</p></li><li><p>深度为K的二叉树(也就是有K层)有最大结点总数为:2<sup>k</sup>-1,k≥1</p><p>比如我们假设有m层,则每一层的最大结点数之和则为:1+ 2<sup>1</sup>+ 2<sup>2</sup>+ … + 2 <sup>m-1</sup> = 2<sup>m</sup> - 1</p><p><strong>式1:</strong>s=2^0+2^1+……2^(n-1)<br><strong>式2:</strong>2s= 2^1+……2……(n-1)+2^n</p><p>将式2减去式1,得到结果为:2^n-2^0 = 2<sup>n</sup> - 1</p></li><li><p>将二叉树结点分为三种,分别是<code>叶结点</code>(没有儿子)、度为1的结点和度为2的结点。若n<sub>0</sub>表示叶结点的个数,n<sub>1</sub>表示度为1的结点个数,n<sub>2</sub>表示度为2的非叶结点个数,那么两者满足关系为n<sub>0</sub>= n<sub>2</sub> + 1</p><p>观察二叉树可以知道,每一个结点对应都有一条向上的边(根结点除外),则一个二叉树的边总数应该为:n<sub>0</sub> + n<sub>1</sub> + n<sub>2</sub> -1 = 0 x n<sub>0</sub> + 1 x n<sub>1</sub> + 2 x n<sub>2</sub>,化简后可得:上面列出来的关系</p></li></ul><h3 id="二叉树的遍历"><a href="#二叉树的遍历" class="headerlink" title="二叉树的遍历"></a>二叉树的遍历</h3><p>首先,在讨论遍历的方式之前,我们先使用如下来描述一个结点:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TNode</span></span>{</span><br><span class="line"> Object data;</span><br><span class="line"> TNode left;</span><br><span class="line"> TNode right;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="先序遍历"><a href="#先序遍历" class="headerlink" title="先序遍历"></a>先序遍历</h4><p>遍历过程为:</p><ol><li>访问根结点</li><li>先序遍历左子树</li><li>先序遍历右子树</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">preOrder</span><span class="params">(TNode node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span>(node != <span class="keyword">null</span>){</span><br><span class="line"> <span class="comment">//先访问根结点,使用打印模拟</span></span><br><span class="line"> System.out.println(node.data);</span><br><span class="line"> preOrder(node.left);<span class="comment">//左子树</span></span><br><span class="line"> preOrder(node.right);<span class="comment">//右子树</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="中序遍历"><a href="#中序遍历" class="headerlink" title="中序遍历"></a>中序遍历</h4><p>遍历过程为:</p><ol><li>先序遍历左子树</li><li>访问根结点</li><li>先序遍历右子树</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">preOrder</span><span class="params">(TNode node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span>(node != <span class="keyword">null</span>){</span><br><span class="line"> preOrder(node.left);<span class="comment">//左子树</span></span><br><span class="line"> <span class="comment">//访问根结点,使用打印模拟</span></span><br><span class="line"> System.out.println(node.data);</span><br><span class="line"> preOrder(node.right);<span class="comment">//右子树</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="后序遍历"><a href="#后序遍历" class="headerlink" title="后序遍历"></a>后序遍历</h4><p>遍历过程为:</p><ol><li>先序遍历左子树</li><li>先序遍历右子树</li><li>访问根结点</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">preOrder</span><span class="params">(TNode node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span>(node != <span class="keyword">null</span>){</span><br><span class="line"> preOrder(node.left);<span class="comment">//左子树</span></span><br><span class="line"> preOrder(node.right);<span class="comment">//右子树</span></span><br><span class="line"> <span class="comment">//访问根结点,使用打印模拟</span></span><br><span class="line"> System.out.println(node.data);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>看看下面这颗树的前、中、后序的遍历结果:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210916231115436.png" alt="图8"></p><p>前序:A B D E H C F I G</p><p>中序:D B H E A F I C G</p><p>后序:D H E B I F G C A</p><h4 id="层次遍历"><a href="#层次遍历" class="headerlink" title="层次遍历"></a>层次遍历</h4><h3 id="存储结构"><a href="#存储结构" class="headerlink" title="存储结构"></a>存储结构</h3><p>二叉树可以使用数组或者链表的方式来存储。</p><p>但是一般使用链表的形式来存储二叉树,上面介绍的<code>完全二叉树</code>可以使用数组的方式来存储数据</p><p>比如有如下的二叉树,图9:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210923224359876.png" alt="图9"></p><p>从上到下、从左到右存储9个结点,对应数组存储方式为:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210923224611283.png" alt="图 10"></p><p>这样做有几个好处</p><ul><li>可以很快求出一个结点的父结点,比如某个结点的下标为i,则父结点的下标为|i / 2|</li><li>可以很快求出一个结点的左孩子和右孩子,比如某个结点的下标为i,左孩子结点的下标为2i,右孩子结点的下标为2i + 1</li></ul><blockquote><p><code>完全二叉树</code>可以这样存储,没有什么问题,但是如果是一般的二叉树呢?显然我们如果按照这种“从上到下、从左至右”的方式将二叉树存储在数组中时,就会有空间浪费。</p></blockquote><h4 id="链表存储二叉树"><a href="#链表存储二叉树" class="headerlink" title="链表存储二叉树"></a>链表存储二叉树</h4><p>所以我们通常使用链表来存储二叉树。</p><p>定义这样一个结构,它可以保存当前结点的值,且可以访问左孩子和右孩子。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TreeNode</span></span>{</span><br><span class="line"> Object data;</span><br><span class="line"> TreeNode left;</span><br><span class="line"> TreeNode right;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面这种结构就可以表示二叉树中任意一个结点。</p><h2 id="二叉查找树-BST"><a href="#二叉查找树-BST" class="headerlink" title="二叉查找树(BST)"></a>二叉查找树(BST)</h2><blockquote><p>二叉查找树又称为二叉搜索树。二叉树除了满足树的性质外,当不为一棵空树的时候,对于任意一个节点它的左子树上所有节点均小于根节点的值,它的右子树上所有节点均大于根节点的值。其左右子树也是一颗二叉查找树。</p></blockquote><p>如图11,左边的是一颗二叉查找树,而右边不是。</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210925183520140.png" alt="图 11"></p><hr><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * BST实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BSTree</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">Comparable</span><<span class="title">T</span>>> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 根节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> BSTNode<T> root;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BSTree</span><span class="params">(T value)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.root = <span class="keyword">new</span> BSTNode<>(value,<span class="keyword">null</span>,<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 节点定义</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">BSTNode</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">Comparable</span><<span class="title">T</span>>></span>{</span><br><span class="line"> T value;</span><br><span class="line"> BSTNode<T> left;</span><br><span class="line"> BSTNode<T> right;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">BSTNode</span><span class="params">(T value, BSTNode<T> left, BSTNode<T> right)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> <span class="keyword">this</span>.left = left;</span><br><span class="line"> <span class="keyword">this</span>.right = right;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的实现中,使用<code>root</code>来保存根节点的值,然后使用左右节点分别表示左右子树。但是有个问题,如果现在让我们通过某个节点获取它对应的父节点或者兄弟节点,显然是比较费劲的,这个时候可以在<code>BSTNode</code>类中增加一个类型为<code>BSTNode<T></code>的属性名为<code>parent</code>用来表示父亲节点,可以很方便的访问到兄弟节点。</p><hr><h4 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h4><ul><li><p>递归</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="function"><span class="keyword">public</span> BSTNode<T> <span class="title">find</span><span class="params">(T key)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.find(root,key);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> BSTNode<T> <span class="title">find</span><span class="params">(BSTNode<T> node,T key)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (node == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> compare = key.compareTo(node.value);</span><br><span class="line"> <span class="keyword">if</span> (compare < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> find(node.left,key);</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (compare > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> find(node.right,key);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>递归的程序非常好理解,但是递归有个坏处,就是程序会不停的入栈,入栈就会有栈帧的创建,而<code>JVM</code>对一个线程而言,它能容忍的栈深度和大小是有限的,所以有没有可能去掉这个递归呢?上面这种在方法<code>return</code>时才递归调用的递归方式称为<code>尾递归</code>,一般来说,<code>尾递归</code>都可以使用循环替代。</p></li><li><p>非递归</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="function"><span class="keyword">public</span> BSTNode<T> <span class="title">find</span><span class="params">(T key)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.find(root,key);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> BSTNode<T> <span class="title">find</span><span class="params">(BSTNode<T> node, T key)</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (node != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">int</span> compare = key.compareTo(node.value);</span><br><span class="line"> <span class="keyword">if</span> (compare < <span class="number">0</span>) {</span><br><span class="line"> node = node.left;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (compare > <span class="number">0</span>) {</span><br><span class="line"> node = node.right;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><hr><h4 id="最大和最小元素"><a href="#最大和最小元素" class="headerlink" title="最大和最小元素"></a>最大和最小元素</h4><ul><li><p>最大元素</p><p>最大元素一定在树的最右分支的叶节点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="function"><span class="keyword">public</span> BSTNode<T> <span class="title">max</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> max(root);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> BSTNode<T> <span class="title">max</span><span class="params">(BSTNode<T> node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (node == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (node.right == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> max(node);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到本质也是一种尾递归,所以接下来查找最小元素采用<code>while</code>实现。</p></li><li><p>最小元素</p><p>最小元素一定在树的最左分支的叶节点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> BSTNode<T> <span class="title">min</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> min(root);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> BSTNode<T> <span class="title">min</span><span class="params">(BSTNode<T> node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (node != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">while</span> (node.left != <span class="keyword">null</span>){</span><br><span class="line"> node = node.left;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h4 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h4><p>插入的逻辑和查找相似,不同之处时插入时需要将插入的值放在某个节点的左节点或者右节点。</p><p>同样,插入也可以分为递归实现和非递归实现</p><ul><li><p>递归</p><p>我们在递归时需要做的事情是:首先判断当前节点是否为null,如果为null则证明当前节点是不存在的,这时候直接new一个节点并返回就行了,请注意,这里返回指的是返回到递归程序的上一层。</p><p>比如有如下的树:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210925203141251.png" alt="图 12"></p><p>插入的逻辑为:</p><blockquote><ol><li>和5开始比较,比5小,所以递归去比较2,并且将返回值赋值给5的左孩子。</li><li>和2开始比较,比2大,所以递归去比较3,并且将返回值赋值给2的右孩子。</li><li>和3开始比较,比3大,所以递归去比较3的右孩子,并且将返回值赋值给3的右孩子。</li><li>3的右孩子为null,则直接新建一个节点并返回,返回后将返回到上一层,即将返回值赋值给3的右孩子。</li></ol></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 插入</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insert</span><span class="params">(T value)</span></span>{</span><br><span class="line"> insert(root,value);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> BSTNode<T> <span class="title">insert</span><span class="params">(BSTNode<T> root, T value)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (root == <span class="keyword">null</span>) {</span><br><span class="line"> root = <span class="keyword">new</span> BSTNode<>(value, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">int</span> cmp = value.compareTo(root.value);</span><br><span class="line"> <span class="comment">//说明插入值比当前节点值小</span></span><br><span class="line"> <span class="keyword">if</span> (cmp < <span class="number">0</span>) {</span><br><span class="line"> root.left = insert(root.left,value);</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (cmp > <span class="number">0</span>) {</span><br><span class="line"> root.right = insert(root.right,value);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> root;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最终效果:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210925210109643.png" alt="图 13"></p></li><li><p>非递归</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 插入</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insert</span><span class="params">(T value)</span></span>{</span><br><span class="line"> BSTNode<T> newNode = <span class="keyword">new</span> BSTNode<>(value, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> BSTNode<T> x = <span class="keyword">this</span>.root;</span><br><span class="line"> BSTNode<T> p = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(x != <span class="keyword">null</span>){</span><br><span class="line"> <span class="comment">//记录当前节点值,下面做比较</span></span><br><span class="line"> p = x;</span><br><span class="line"> <span class="keyword">int</span> cmp = value.compareTo(x.value);</span><br><span class="line"> <span class="keyword">if</span> (cmp < <span class="number">0</span>) {</span><br><span class="line"> x = x.left;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(cmp > <span class="number">0</span>){</span><br><span class="line"> x = x.right;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//说明root节点就是空的</span></span><br><span class="line"> <span class="keyword">if</span> (p == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">this</span>.root = newNode;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//比较当前p节点和要插入节点的大小</span></span><br><span class="line"> <span class="keyword">int</span> cmp = value.compareTo(p.value);</span><br><span class="line"> <span class="keyword">if</span> (cmp < <span class="number">0</span>) {</span><br><span class="line"> p.left = newNode;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(cmp > <span class="number">0</span>){</span><br><span class="line"> p.right = newNode;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><p>考虑三种情况</p><ul><li><p>叶节点删除:直接删除,并且修改父节点的指针域指向null</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210927223655257.png" alt="图 14"></p></li><li><p>删除只有一个孩子的节点:也就是只有左儿子或者右儿子,这个时候我们将父节点的指针指向被删除节点的孩子节点就可以了</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210927224112186.png" alt="图 15"></p></li><li><p>删除有两个孩子的节点:用“另一个节点”来代替被删除的节点,即被删除节点右子树的最小元素或左子树的最大元素</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210927230308341.png" alt="图 15"></p><p>如上图所示,我们可以将右子树的最小元素50这个值拷贝到原来的41位置,然后将值为50的节点删除;也可以将左子树最大元素35这个值拷贝到原来的位置,然后将值为35的节点删除。</p><blockquote><p>这里可以很清楚地看到,左子树最大值和右子树最小值一定不是有两个儿子的节点。</p></blockquote><p>递归实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 删除指定值为value的节点</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">delete</span><span class="params">(T value)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.delete(value,<span class="keyword">this</span>.root);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> BSTNode<T> <span class="title">delete</span><span class="params">(T value,BSTNode<T> node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (node == <span class="keyword">null</span> || value == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> cmp = value.compareTo(node.value);</span><br><span class="line"> <span class="comment">//左子树找</span></span><br><span class="line"> <span class="keyword">if</span> (cmp < <span class="number">0</span>) {</span><br><span class="line"> node.left = delete(value,node.left);</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (cmp > <span class="number">0</span>) {<span class="comment">//右子树找</span></span><br><span class="line"> node.right = delete(value,node.right);</span><br><span class="line"> }<span class="keyword">else</span>{<span class="comment">//找到了</span></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 分两种情况</span></span><br><span class="line"><span class="comment"> * 1、有左右两个孩子</span></span><br><span class="line"><span class="comment"> * 2、只有一个孩子或者没有</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> BSTNode<T> temp;</span><br><span class="line"> <span class="keyword">if</span> (node.left != <span class="keyword">null</span> && node.right != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//找左子树最大值或者右子树最小值,这里以右子树为例</span></span><br><span class="line"> temp = <span class="keyword">this</span>.max(node.right);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//将找到的节点的值拷贝至被删除节点</span></span><br><span class="line"> node.value = temp.value;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//去右子树中删除找到的那个最小值,即单个孩子或者无孩子的节点</span></span><br><span class="line"> node.right = delete(value, node.right);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span> (node.left == <span class="keyword">null</span>) {</span><br><span class="line"> node = node.right;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (node.right == <span class="keyword">null</span>) {</span><br><span class="line"> node = node.left;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>非递归实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">delete</span><span class="params">(T value)</span></span>{</span><br><span class="line"> BSTNode<T> dn = <span class="keyword">this</span>.find(<span class="keyword">this</span>.root, value);</span><br><span class="line"> <span class="keyword">if</span> (dn != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (dn.left != <span class="keyword">null</span> && dn.right != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//通过查找右子树的最小节点</span></span><br><span class="line"> BSTNode<T> min = <span class="keyword">this</span>.min(dn);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//拷贝值</span></span><br><span class="line"> dn.value = min.value;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//在右子树中删除min这个节点</span></span><br><span class="line"> <span class="comment">//和上面递归一样就不写了</span></span><br><span class="line"></span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span> (dn.left == <span class="keyword">null</span>) {</span><br><span class="line"> dn = dn.right;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (dn.right == <span class="keyword">null</span>) {</span><br><span class="line"> dn = dn.left;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="平衡二叉树"><a href="#平衡二叉树" class="headerlink" title="平衡二叉树"></a>平衡二叉树</h2><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>在<code>二叉查找树</code>中同样一堆元素,如果是按照不同规律进行存放,树的高度是不同的。而在搜索树中我们知道高度决定了这棵树的查询效率(或者说平均查找长度ASL)。</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210930113902230.png" alt="图16"></p><p>对于上面的树而言,查找5时,因为高度为0,只需要查找一次;对于节点2和9在查询时都要查询两次;以此类推,当前这棵树的ASL为:1+2x2+3x4=17/7 ≈ 2.4。</p><p>但是当我们将树重新定义一下:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-2021093011471101911.png" alt="图 17"></p><p>这也是一颗搜索树,同样我们算一下最终的ASL为:1+ 2x2 + 3x2 + 4 x2 = 19 / 7 = 2.7。</p><p>说明在保证搜索树的前提下,我们需要一种策略,能使查询效率最高。</p><h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><p>平衡二叉树是一种特殊的二叉搜索树,具备以下性质:</p><blockquote><p>如果不为空树,则它的左右子树的高度差的绝对值不超过1,并且左右子树都是一颗平衡二叉树。即<code>任何节点的两个子树的高度差绝对值不能超过1</code>。</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210930145228291.png" alt="图 18"></p><h3 id="实现-1"><a href="#实现-1" class="headerlink" title="实现"></a>实现</h3><p>平衡二叉树的动画效果参考<a href="https://www.cs.usfca.edu/~galles/visualization/AVLtree.html">AVL Tree</a>。</p><p>实现平衡二叉树时基本结构和搜索树是相同的,不同的地方在于平衡二叉树的节点多了一个值为<code>高度</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="comment">/**</span></span><br><span class="line"><span class="comment"> * 平衡二叉树</span></span><br><span class="line"><span class="comment"> * 为了提升二叉搜索树的查找效率,因为树相对比较平衡,高度较低,搜索效率也就高</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <T></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AVLTree</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">Comparable</span><<span class="title">T</span>>></span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 根节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> AVLTreeNode<T> root;</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">AVLTreeNode</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">Comparable</span><<span class="title">T</span>>></span>{</span><br><span class="line"> <span class="comment">//节点值</span></span><br><span class="line"> <span class="keyword">private</span> T value;</span><br><span class="line"> <span class="comment">//左儿子</span></span><br><span class="line"> <span class="keyword">private</span> AVLTreeNode<T> left;</span><br><span class="line"> <span class="comment">//右儿子</span></span><br><span class="line"> <span class="keyword">private</span> AVLTreeNode<T> right;</span><br><span class="line"> <span class="comment">//所在高度</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> height;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AVLTreeNode</span><span class="params">(T value, AVLTreeNode<T> left, AVLTreeNode<T> right)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.value = value;</span><br><span class="line"> <span class="keyword">this</span>.left = left;</span><br><span class="line"> <span class="keyword">this</span>.right = right;</span><br><span class="line"> <span class="keyword">this</span>.height = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="树的高度"><a href="#树的高度" class="headerlink" title="树的高度"></a>树的高度</h3><p>在这里,树的高度被定义为最大<code>层次</code>。即空树的高度为0,非空树的高度等于它的最大层次。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取当前树的高度</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">height</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">return</span> height(<span class="keyword">this</span>.root);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">height</span><span class="params">(AVLTreeNode<T> tree)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (tree != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> tree.height;</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">}</span><br></pre></td></tr></table></figure><h3 id="旋转"><a href="#旋转" class="headerlink" title="旋转"></a>旋转</h3><p>当我们进行插入或者删除某个节点时,二叉树就有可能失去平衡,为了保证平衡的特性,我们需要对树进行调整,这种调整就叫做平衡二叉树的<code>旋转</code>。</p><p>旋转共有四种形态,分别是<code>LL旋转</code>、<code>RR旋转</code>、<code>LR旋转</code>和<code>RL旋转</code>。</p><h4 id="LL旋转"><a href="#LL旋转" class="headerlink" title="LL旋转"></a>LL旋转</h4><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20211008105650255.png" alt="图 19"></p><p>在上图的左边,新插入了一个节点0,导致节点5的平衡性被破坏,这个时候需要使用一种方法调整至平衡。</p><p><strong>节点5是被破坏者,破坏者是0,将这种破坏者在被破坏者左子树的左边的这种情况称作<code>LL旋转</code>。</strong></p><blockquote><p>旋转的主要策略:将被破坏者的左儿子节点调整为父亲节点,被破坏者作为父亲节点的右儿子。如果被破坏者的左儿子节点的右儿子不为空,则将被破坏者的左儿子节点的右儿子调整为被破坏者的左儿子节点。</p></blockquote><p>比如上图,左边插入0,被破坏者为节点5,被破坏者的左儿子为节点2,将节点2调整为父亲节点,将被破坏者节点5调整为父亲节点的右儿子。接着将节点2的右儿子节点3调整为被破坏者节点5的左儿子。</p><h4 id="RR旋转"><a href="#RR旋转" class="headerlink" title="RR旋转"></a>RR旋转</h4><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20211008110319357.png" alt="图 20"></p><p><strong>节点5是被破坏者,破坏者是11,将这种破坏者在被破坏者右子树的右边的这种情况称作<code>RR旋转</code>。</strong></p><blockquote><p>旋转的主要策略:将被破坏者的右儿子节点调整为父亲节点,被破坏者作为父亲节点的左儿子。如果被破坏者的右儿子节点的左儿子不为空,则将被破坏者的右儿子节点的左儿子调整为被破坏者的右儿子节点。</p></blockquote><p>比如上图,左边插入11,被破坏者为节点5,被破坏者的右儿子为节点9,将节点9调整为父亲节点,将被破坏者节点5调整为父亲节点的左儿子。接着将节点9的左儿子节点7调整为被破坏者节点5的右儿子。</p><h4 id="LR旋转"><a href="#LR旋转" class="headerlink" title="LR旋转"></a>LR旋转</h4><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20211008135652452.png" alt="图 21"></p><p><strong>节点5是被破坏者,破坏者是4,将这种破坏者在被破坏者左子树的右边的这种情况称作<code>LR旋转</code>。</strong></p><blockquote><p>旋转的主要策略:将被破坏者的左儿子节点的右儿子调整为父亲节点,然后将被破坏者调整为父亲节点的右儿子。当破坏者属于左节点时,调整至父亲节点的左儿子节点的右儿子;当破坏者属于右节点时,调整至父亲节点的右儿子节点的左儿子。</p></blockquote><p>比如上图中,如果插入的节点不是4,而是2.5时,则会发生如下的<code>LR旋转</code></p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20211008135708859.png" alt="图 22"></p><h4 id="RL旋转"><a href="#RL旋转" class="headerlink" title="RL旋转"></a>RL旋转</h4><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20211008135857297.png" alt="图 23"></p><p><strong>节点5是被破坏者,破坏者是6,将这种破坏者在被破坏者右子树的左子树的这种情况称作<code>RL旋转</code>。</strong></p><blockquote><p>旋转的主要策略:将被破坏者的右儿子节点的左儿子调整为父亲节点,然后将被破坏者调整为父亲节点的左儿子。当破坏者属于左节点时,调整至父亲节点的左儿子节点的右儿子;当破坏者属于右节点时,调整至父亲节点的右儿子节点的左儿子。</p></blockquote><p>比如上图中,如果插入的节点不是6,而是8时,则会发生如下的<code>RL旋转</code></p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20211008140217714.png" alt="图 24"></p><h4 id="实现-2"><a href="#实现-2" class="headerlink" title="实现"></a>实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">max</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> a> b ? a : b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * LL旋转</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> lostBalanceNode 失去平衡的节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> AVLTreeNode<T> <span class="title">llRotation</span><span class="params">(AVLTreeNode<T> lostBalanceNode)</span></span>{</span><br><span class="line"> <span class="comment">//被破坏者左儿子</span></span><br><span class="line"> AVLTreeNode<T> left = lostBalanceNode.left;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//被破坏者左儿子的右儿子</span></span><br><span class="line"> AVLTreeNode<T> right = left.right;</span><br><span class="line"></span><br><span class="line"> left.right = lostBalanceNode;</span><br><span class="line"> lostBalanceNode.left = right;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//重新计算高度</span></span><br><span class="line"> <span class="comment">//被破坏者的高度为其左子树高度和右子树高度最大的那一个</span></span><br><span class="line"> lostBalanceNode.height = max(height(lostBalanceNode.right),height(lostBalanceNode.left)) + <span class="number">1</span>;</span><br><span class="line"> left.height = max(lostBalanceNode.height,height(left.left)) + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> left;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * RR旋转</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> lostBalanceNode 失去平衡的节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> AVLTreeNode<T> <span class="title">rrRotation</span><span class="params">(AVLTreeNode<T> lostBalanceNode)</span></span>{</span><br><span class="line"> <span class="comment">//被破坏者右儿子</span></span><br><span class="line"> AVLTreeNode<T> right = lostBalanceNode.right;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//被破坏者右儿子的左儿子</span></span><br><span class="line"> AVLTreeNode<T> left = right.left;</span><br><span class="line"></span><br><span class="line"> right.left = lostBalanceNode;</span><br><span class="line"> lostBalanceNode.right = left;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//重新计算高度</span></span><br><span class="line"> <span class="comment">//被破坏者的高度为其左子树高度和右子树高度最大的那一个</span></span><br><span class="line"> lostBalanceNode.height = max(height(lostBalanceNode.right),height(lostBalanceNode.left)) + <span class="number">1</span>;</span><br><span class="line"> right.height = max(lostBalanceNode.height,height(right.right)) + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> right;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * LR旋转</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> lostBalanceNode 失去平衡的节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> AVLTreeNode<T> <span class="title">lrRotation</span><span class="params">(AVLTreeNode<T> lostBalanceNode)</span></span>{</span><br><span class="line"> <span class="comment">//被破坏者左儿子</span></span><br><span class="line"> AVLTreeNode<T> left = lostBalanceNode.left;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//被破坏者左儿子的右儿子</span></span><br><span class="line"> AVLTreeNode<T> right = lostBalanceNode.left.right;</span><br><span class="line"></span><br><span class="line"> right.left = left;</span><br><span class="line"> right.right = lostBalanceNode;</span><br><span class="line"> lostBalanceNode.left = right.right;</span><br><span class="line"> left.right = right.left;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> right;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//如果上面的太复杂,则可以进行两次旋转即可,即先RR失去平衡节点的左子树后,LL失去平衡节点</span></span><br><span class="line"> <span class="comment">//lostBalanceNode.left = rrRotation(lostBalanceNode.left);</span></span><br><span class="line"> <span class="comment">//return llRotation(lostBalanceNode);</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * RR旋转</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> lostBalanceNode 失去平衡的节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> AVLTreeNode<T> <span class="title">rlRotation</span><span class="params">(AVLTreeNode<T> lostBalanceNode)</span></span>{</span><br><span class="line"> lostBalanceNode.right = llRotation(lostBalanceNode.right);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> rrRotation(lostBalanceNode);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="插入-1"><a href="#插入-1" class="headerlink" title="插入"></a>插入</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 插入方法要注意的事情</span></span><br><span class="line"><span class="comment"> * 1、保证平衡不被打破</span></span><br><span class="line"><span class="comment"> * 2、保证当前树还是一颗搜索树</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insert</span><span class="params">(T value)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.root = insert(value,<span class="keyword">this</span>.root);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> AVLTreeNode<T> <span class="title">insert</span><span class="params">(T value,AVLTreeNode<T> tree)</span></span>{</span><br><span class="line"> <span class="comment">//空树则直接新建一个节点返回</span></span><br><span class="line"> <span class="keyword">if</span> (tree == <span class="keyword">null</span>) {</span><br><span class="line"> tree = <span class="keyword">new</span> AVLTreeNode<>(value,<span class="keyword">null</span>,<span class="keyword">null</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">int</span> cmp = value.compareTo(tree.value);</span><br><span class="line"> <span class="keyword">if</span> (cmp < <span class="number">0</span>) {<span class="comment">//应该插入到tree树的左子树</span></span><br><span class="line"> tree.left = insert(value,tree.left);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//判断插入后是否失去平衡</span></span><br><span class="line"> <span class="keyword">if</span> (height(tree.left) - height(tree.right) == <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">if</span> (value.compareTo(tree.left.value) < <span class="number">0</span>)</span><br><span class="line"> tree = llRotation(tree);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tree = lrRotation(tree);</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> (cmp > <span class="number">0</span>){<span class="comment">//应该插入到tree树的右子树</span></span><br><span class="line"> tree.right = insert(value,tree.right);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//判断插入后是否失去平衡</span></span><br><span class="line"> <span class="keyword">if</span> (height(tree.right) - height(tree.left) == <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">if</span> (value.compareTo(tree.right.value) > <span class="number">0</span>)</span><br><span class="line"> tree = rrRotation(tree);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tree = rlRotation(tree);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> System.out.println(<span class="string">"已有相同节点!!"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//计算tree树的高度</span></span><br><span class="line"> tree.height = max( height(tree.left), height(tree.right)) + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> tree;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>插入操作是一个递归操作。</p></blockquote><h3 id="删除-1"><a href="#删除-1" class="headerlink" title="删除"></a>删除</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">delete</span><span class="params">(T value)</span></span>{</span><br><span class="line"> AVLTreeNode<T> node;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ((node = <span class="keyword">this</span>.find(value)) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">this</span>.root = <span class="keyword">this</span>.delete(<span class="keyword">this</span>.root,node);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 删除元素</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> tree 当前高度的树根节点</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> node 待删除节点</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 新的根节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> AVLTreeNode<T> <span class="title">delete</span><span class="params">(AVLTreeNode<T> tree, AVLTreeNode<T> node)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (tree == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> cmp = node.value.compareTo(tree.value);</span><br><span class="line"> <span class="keyword">if</span> (cmp < <span class="number">0</span>) {<span class="comment">//待删除节点在“tree”的左子树中</span></span><br><span class="line"> tree.left = delete(tree.left,node);</span><br><span class="line"> <span class="comment">//判断删除后是否失去平衡</span></span><br><span class="line"> <span class="keyword">if</span> (height(tree.right) - height(tree.left) == <span class="number">2</span>) {</span><br><span class="line"> AVLTreeNode<T> r = tree.right;</span><br><span class="line"> <span class="keyword">if</span> (height(r.left) > height(r.right))</span><br><span class="line"> tree = rlRotation(tree);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tree = rrRotation(tree);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (cmp > <span class="number">0</span>) {<span class="comment">//待删除节点在“tree”的右子树中</span></span><br><span class="line"> tree.right = delete(tree.right,node);</span><br><span class="line"> <span class="comment">//判断删除后是否失去平衡</span></span><br><span class="line"> <span class="keyword">if</span> (height(tree.left) - height(tree.right) == <span class="number">2</span>) {</span><br><span class="line"> AVLTreeNode<T> l = tree.left;</span><br><span class="line"> <span class="keyword">if</span> (height(l.right) > height(l.left))</span><br><span class="line"> tree = lrRotation(tree);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tree = llRotation(tree);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }<span class="keyword">else</span>{<span class="comment">//当前“tree”节点即需要删除的节点</span></span><br><span class="line"> <span class="comment">//左右儿子都不为空</span></span><br><span class="line"> <span class="keyword">if</span> (tree.left != <span class="keyword">null</span> && tree.right != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (height(tree.left) > height(tree.right)) {</span><br><span class="line"> <span class="comment">//寻找左子树中最大结点</span></span><br><span class="line"> AVLTreeNode<T> max = maximum(tree.left);</span><br><span class="line"> tree.value = max.value;</span><br><span class="line"> tree.left = <span class="keyword">this</span>.delete(tree.left,max);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//寻找右子树中最小结点</span></span><br><span class="line"> AVLTreeNode<T> min = minimum(tree.right);</span><br><span class="line"> tree.value = min.value;</span><br><span class="line"> tree.right = <span class="keyword">this</span>.delete(tree.right,min);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{<span class="comment">//只有左儿子或者只有右儿子或者都没有</span></span><br><span class="line"> tree = (tree.left!=<span class="keyword">null</span>) ? tree.left : tree.right;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> tree;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 数据结构 </category>
</categories>
<tags>
<tag> 基础知识 </tag>
<tag> 数据结构 </tag>
<tag> 树 </tag>
</tags>
</entry>
<entry>
<title></title>
<link href="/posts/0.html"/>
<url>/posts/0.html</url>
<content type="html"><![CDATA[<p>数据结构(二)—线性结构</p><p>本篇文章主要涉及到<code>线性表</code>、<code>队列</code>以及<code>堆栈</code>。</p><h1 id="线性表"><a href="#线性表" class="headerlink" title="线性表"></a>线性表</h1><p><code>线性表</code>顾名思义是一种线性的逻辑结构,指<code>数据元素</code>在逻辑结构上存在线性关系。</p><blockquote><p>线性关系:指的是数据一个挨着一个,总体呈线性分布。换句话说,线性表中的数据在逻辑结构上互相挨着,中间是没有空隙。每个数据元素,在它的前面可以找到唯一一个数据和它挨着,后面也只有一个数据和它挨着。这样,数据在逻辑结构呈线性分布,称这样的存储结构为线性表。</p></blockquote><p>:star:但请注意,线性表只是描述了数据之间的逻辑关系,具体到计算机存储中可以是顺序存储或随机存储。</p><blockquote><p>存取结构:分为随机存取和顺序存取</p><ul><li>随机存取表示在利用程序存取数据时和数据存储在计算机中的位置无关</li><li>顺序存取表示在利用程序存取数据时,只能按照存储的顺序进行存取,比如需要获取第N个数据,则必须先访问前N-1个数据</li></ul><p>存储结构:分为顺序存储和随机存储</p><ul><li>顺序存储即在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构</li><li>随机存储即内存空间可以是连续的也可以不是连续的</li></ul></blockquote><h2 id="顺序存储实现"><a href="#顺序存储实现" class="headerlink" title="顺序存储实现"></a>顺序存储实现</h2><p>利用<code>数组</code>的连续存储空间来存放线性表的元素。</p><h2 id="随机存储实现"><a href="#随机存储实现" class="headerlink" title="随机存储实现"></a>随机存储实现</h2>]]></content>
</entry>
<entry>
<title>ReactNative修改项目名称</title>
<link href="/posts/9449c48d.html"/>
<url>/posts/9449c48d.html</url>
<content type="html"><![CDATA[<h2 id="修改项目名称"><a href="#修改项目名称" class="headerlink" title="修改项目名称"></a>修改项目名称</h2><blockquote><p>只针对安卓</p></blockquote><p>作为演示,将名为<code>DvaReactNativeDemo</code>的项目修改为<code>PDAAndroidControl</code>。</p><ol><li><p>修改文件名称为想要的项目名称</p></li><li><p>修改<code>app.json</code>文件,将<code>name</code>修改为项目名称</p> <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"PDAAndroidControl"</span>,</span><br><span class="line"> <span class="attr">"displayName"</span>: <span class="string">"PDA"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>修改<code>android\settings.gradle</code>文件中的<code>rootProject.name</code>属性,修改为项目名称</p> <figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">rootProject.name</span> = <span class="string">'PDAAndroidControl'</span></span><br></pre></td></tr></table></figure></li><li><p>修改<code>android\app\src\main\AndroidManifest.xml</code>文件,将其中的<code>package</code>修改为和项目名称相同的小写。</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><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="tag"><<span class="name">manifest</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">package</span>=<span class="string">"com.pdaandroidcontrol"</span>></span></span><br><span class="line"> ...</span><br><span class="line"><span class="tag"></<span class="name">manifest</span>></span></span><br></pre></td></tr></table></figure></li><li><p>修改包名,将包名修改为<code>com.pdaandroidcontrol</code></p></li><li><p>修改文件中引用的包名,以下作为示例,其他文件一样的操作。</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.pdaandroidcontrol;</span><br></pre></td></tr></table></figure></li><li><p>修改<code>MainActivity.java</code>文件中的<code>getMainComponentName</code>方法返回值,返回值和项目名称一样。</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> String <span class="title">getMainComponentName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"PDAAndroidControl"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>修改<code>android\app\BUCK</code>文件,找到<code>android_build_config</code>和<code>android_resource</code>,修改和包名一致,如下:</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><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">android_build_config(</span><br><span class="line"> name = <span class="string">"build_config"</span>,</span><br><span class="line"> package = <span class="string">"com.pdaandroidcontrol"</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">android_resource(</span><br><span class="line"> name = <span class="string">"res"</span>,</span><br><span class="line"> package = <span class="string">"com.pdaandroidcontrol"</span>,</span><br><span class="line"> res = <span class="string">"src/main/res"</span>,</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li><li><p>修改<code>android\app\build.gradle</code>文件,找到<code>defaultConfig</code>,修改<code>applicationId</code>和包名一致:</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><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">defaultConfig {</span><br><span class="line"> applicationId <span class="string">"com.pdaandroidcontrol"</span></span><br><span class="line"> minSdkVersion rootProject.ext.minSdkVersion</span><br><span class="line"> targetSdkVersion rootProject.ext.targetSdkVersion</span><br><span class="line"> versionCode 1</span><br><span class="line"> versionName <span class="string">"1.0"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><p>至此,修改完毕,重新<code>npm install</code>,然后<code>yarn run android</code>就能启动项目了。</p><h2 id="可能的问题"><a href="#可能的问题" class="headerlink" title="可能的问题"></a>可能的问题</h2><p>Q:Invariant Violation:Application 项目名 has not been registered</p><p>A:上述步骤项目名称没改全。</p><p>Q:Metro (the local dev server) is run from the wrong folder</p><p>A:核对你的<code>Metro</code>(启动器)是否在当前目录启动。</p><p>Q:A module failed to load due to an error and <code>AppRegistry.registerComponent</code> wasn’t called</p><p>A:核对你的<code>app.json</code>是否修改正确。</p>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> RN </tag>
<tag> android </tag>
</tags>
</entry>
<entry>
<title>数据结构(一)基本概念</title>
<link href="/posts/bb4fe337.html"/>
<url>/posts/bb4fe337.html</url>
<content type="html"><![CDATA[<h1 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h1><p>什么是<code>数据结构</code>?</p><blockquote><p>数据结构是<code>数据对象</code>的集合,即可以体现数据对象之间的关系,以及可以对数据进行引用的函数和操作。</p></blockquote><p>:star:数据结构 = 数据对象 + 对象之间的关系</p><ul><li><p><code>数据结构</code>是<code>数据对象</code>在计算机中的组织方式</p></li><li><p><code>数据对象</code>必定与一系列加在其上的<code>操作</code>相关联 </p></li><li><p>完成这些<code>操作</code>所用的方法就是<code>算法</code></p></li></ul><h2 id="逻辑结构"><a href="#逻辑结构" class="headerlink" title="逻辑结构"></a>逻辑结构</h2><p><code>逻辑结构</code>是对<code>数据对象</code>之间关系的一种描述,是一种抽象的概念。</p><table><thead><tr><th>名称</th><th>说明</th></tr></thead><tbody><tr><td>集合</td><td>数据结构中的元素之间除了“同属一个集合” 的相互关系外,别无其他关系</td></tr><tr><td>线性结构(线性表)</td><td>数据结构中的元素存在一对一的相互关系</td></tr><tr><td>树</td><td>数据结构中的元素存在一对多的相互关系</td></tr><tr><td>图</td><td>数据结构中的元素存在多对多的相互关系</td></tr></tbody></table><p>:tipping_hand_man:特别注意,<code>逻辑结构</code>只是数据对象之间抽象关系的描述,是特定数据对象下本身携带的一种关系,这和下面要说的<code>存储结构</code>不太一样。</p><h2 id="存储结构"><a href="#存储结构" class="headerlink" title="存储结构"></a>存储结构</h2><p><code>存储结构</code>是指<code>数据对象</code>在计算机里面的物理存储结构。什么意思呢,也就是说上面提到的各种<code>逻辑结构</code>在机器上应该一个怎么放法。比如上面提到的线性结构,我们可以使用数组、链表等等来实现。</p><hr><p>存储结构分为以下几类:</p><ul><li><p><strong>顺序存储</strong></p><p>所有数据元素在存储器中占有一整块存储空间,<strong>两个逻辑上相邻的元素在存储器中的位置同样相邻</strong>。通俗来说,就是每个逻辑序号对应一个数据元素,通过次序号可直接找到对应元素的存储地址,进而获得元素值。顺序存储结构通常借助程序设计语言中的数组来加以实现。</p><p>优点:节省存储空间,随机访问快。</p><p>缺点:不便于修改。</p></li><li><p><strong>链式存储</strong></p><p>不要求逻辑上相邻的节点在物理位置上也相邻,节点间的逻辑关系是由附加的<strong>指针</strong>字段表示的</p><p>优点:便于修改。</p><p>缺点:随机访问慢,空间利用率低(因为要额外保存节点间的指针关系)。</p></li><li><p><strong>索引存储</strong></p><p>在存储数据元素的同时还增加了一个索引表。索引表中的每一项包括关键字和地址,关键字是能够唯一标示一个数据元素的数据项,地址是指示数据元素的存储地址或者存储区域的首地址的。</p><p>优点:大大提高了数据查找的速度。</p><p>缺点:增加了索引表、因而降低了存储空间的利用率。</p></li><li><p><strong>散列(或哈希)存储</strong></p><p>散列存储也称为哈希存储,这种存储方式将数据元素存储在一个连续的区域,每一个数据元素的具体存储位置是根据该数据的关键字值,通过散列(哈希)函数直接计算出来的。</p></li></ul><h2 id="结构图"><a href="#结构图" class="headerlink" title="结构图"></a>结构图</h2><p>下图展示了<code>数据结构</code>的分类图。</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210901231908549.png" alt="image-20210901231908549"></p><h2 id="抽象数据模型"><a href="#抽象数据模型" class="headerlink" title="抽象数据模型"></a>抽象数据模型</h2><h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><p>通常,我们需要描述数据结构,描述数据结构的一个重要的方法就是<code>抽象数据类型</code>。这里面有两个关键词:抽象、数据类型。</p><ul><li><p>数据类型</p><ul><li>数据对象集(描述的是什么东西)</li><li>数据集合相关联的操作集(如何操作数据集合)</li></ul><p>在C语言中这两个是分开处理的,即数据对象集的定义和操作是分别在不同的地方,而在面向对象编程中,往往是一个类,封装了<code>数据集</code>和<code>操作集</code>,比如<code>ArrayList</code>。</p></li><li><p>抽象</p><p>描述数据类型的方法不依赖具体的实现,即和机器无关、和数据存储结构无关、和实现操作的算法无关、和编程语言无关。</p></li></ul><h3 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h3><p>:qatar: 给出“矩阵”的抽象数据定义。</p><ol><li><p>给出类型的名称:矩阵</p></li><li><p>描述数据对象集:一个MxN的矩阵A<sub>MxN</sub>=(a<sub>ij</sub>)(i=1,…,M;j=1,…N)由MxN个三元组<a,i,j>,其中a是矩阵元素的值,i代表矩阵元素所在的行号,j是元素所在的列号。</p></li><li><p>描述操作集:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Matrix <span class="title">create</span><span class="params">(<span class="keyword">int</span> M,<span class="keyword">int</span> N)</span>:返回一个MxN的矩阵</span></span><br><span class="line"><span class="function">ElementType <span class="title">getEntry</span><span class="params">(Matrix a,<span class="keyword">int</span> i, <span class="keyword">int</span> j)</span>:给定一个矩阵a,返回矩阵a中第i行、第j列的矩阵元素</span></span><br></pre></td></tr></table></figure></li></ol><blockquote><p>1、从上面可以看到,在进行抽象数据类型定义时,描述数据对象集仅仅规定了这个数据集应该是符合什么条件,但并没有说明该数据集应该使用什么<code>存储结构</code>,也没有说明矩阵元素值具体应该是什么,比如float、double等。</p><p>2、定义操作仅仅说明操作想要的效果,并没有说明操作具体的实现逻辑,以及操作所使用的语言和平台。</p></blockquote><h1 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h1><h2 id="定义-1"><a href="#定义-1" class="headerlink" title="定义"></a>定义</h2><p><code>算法</code>是一个有限的指令集,接受一些输入(特殊情况不需要输入,比如生成随机值),并且会产生输出(没有输出就没有意义了),一定在有限步骤之后终止。</p><p>:star:描述算法应该不依赖任何一种计算机语言以及具体的实现,这个很好理解,算法就是对现实处理某件事情的方法的抽象,抽象则不局限于某种特定的环境下。然后实现这个方法可能会有成千上万中实现。</p><h2 id="什么是好的算法"><a href="#什么是好的算法" class="headerlink" title="什么是好的算法"></a>什么是好的算法</h2><p>通常来说,一个问题会有多种不同的解决办法,就像是去姥姥家,有三种交通一样。如果你想省钱,那就去坐绿皮火车,如果你想省时间,那你就去坐飞机,如果你想省点时间同时也省点钱,那就去坐动车或者高铁。</p><p>算法也是一样,解决一个问题的<code>算法</code>可能会有很多种,那么怎么衡量一个算法的好坏呢?往往要根据<code>空间复杂度</code>和<code>时间复杂度</code>共同决定。</p><h3 id="空间复杂度"><a href="#空间复杂度" class="headerlink" title="空间复杂度"></a>空间复杂度</h3><p>空间复杂度<code>S(n)</code>:根据算法写成的程序在执行时占用存储单元的长度(即占用内存的大小)。通常情况下,这个长度和输入数据的规模相关。空间复杂度太大容易内存溢出。</p><h3 id="时间复杂度"><a href="#时间复杂度" class="headerlink" title="时间复杂度"></a>时间复杂度</h3><p>时间复杂度<code>T(n)</code>:根据算法写成的程序在执行时耗费时间的长度(即用了多久)。通常情况下,这个长度和输入数据的规模相关。时间复杂度太大容易导致程序死等或超出预期等待时间。</p><p>比如有以下函数,这是一个求多项式在给定点x处的值。</p><p>多项式:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210902223219877.png" alt="image-20210902223219877"></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><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="comment">//n为阶数,a[]为每一项的系数数组,x为给定点</span></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">f</span><span class="params">( <span class="keyword">int</span> n, <span class="keyword">double</span> a[], <span class="keyword">double</span> x )</span></span></span><br><span class="line"><span class="function"></span>{ </span><br><span class="line"> <span class="keyword">int</span> i;</span><br><span class="line"> <span class="keyword">double</span> p = a[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">for</span> ( i=<span class="number">1</span>; i<=n; i++ )</span><br><span class="line"> p += (a[i] * <span class="built_in">pow</span>(x, i)); <span class="comment">//pow表示</span></span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面是最常见的一种实现方式,即从1到n,逐个计算结果并相加,那么此时的时间复杂度需要怎么计算呢?</p><p>一般在程序中,加减法是很快的,可以忽略不计,我们只需要计算一个程序中出现了多少此乘除法即可。</p><p>上面的代码中共出现了<code>1+2+3+...+n</code>次乘法,即**(n<sup>2</sup> + n) /2<strong>次。所以时间复杂度为</strong>T(n) = C<sub>1</sub>n<sup>2</sup> + C<sub>2</sub> n**</p><p>再看看另一种实现方式,我们可以将上面的多项式抽取为:</p><p><img src="https://cdn.jsdelivr.net/gh/BoyZhouY/myimg@main/image-20210902224902717.png" alt="image-20210902224902717"></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><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="function"><span class="keyword">double</span> <span class="title">f</span><span class="params">( <span class="keyword">int</span> n, <span class="keyword">double</span> a[], <span class="keyword">double</span> x )</span></span></span><br><span class="line"><span class="function"></span>{ </span><br><span class="line"> <span class="keyword">int</span> i;</span><br><span class="line"><span class="keyword">double</span> p = a[n];</span><br><span class="line"><span class="keyword">for</span> ( i=n; i><span class="number">0</span>; i-- )</span><br><span class="line"> p = a[i<span class="number">-1</span>] + x*p;</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个方法同样实现了一开始的要求,不过这个算法中总共出现了n次乘法。所以时间复杂度为<strong>T(n) = C · n</strong>。</p><p>请注意,上面两个时间复杂度表示法中都有常数<code>C</code>,举例说明:</p><blockquote><p>(1) 题目给你<code>n</code>个整数,让你求和。这道题很简单,你边读入边累加,求完了。时间复杂度是<code> O(n)</code>。</p><p>(2) 题目给你 <code>n</code> 个整数,不仅让你求和,还要你求出最大值。还是很简单,你边读入边累加,同时比较当前数字与已经出现的最大值哪个大。时间复杂度还是<code> O(n)</code>。</p><p>上述两个例子中,时间复杂度都记作 <code>O(n)</code>,但需要的运行时间显然不同。如果忽略掉读入数据的过程,你可以粗略认为第二个算法运行时间是第一个的二倍。也就是说,这两个算法的时间消耗差了一个常数倍——2。</p></blockquote><p>当输入规模<code>n</code>很大时,n<sup>2</sup>和n差别是特别大的,也就是说上面实现方式二的效率要高。</p><h3 id="回到正题"><a href="#回到正题" class="headerlink" title="回到正题"></a>回到正题</h3><p>那么究竟什么是好的算法呢?在分析一般算法的效率时,我们经常关注下面 两种复杂度:</p><ul><li>最坏情况复杂度 T<sub>worst</sub>(n)</li><li>平均复杂度 T<sub>avg</sub>(n)</li></ul><p>$$<br>Tavg(<br>n )<br>≤<br>Tworst(<br>n<br>)<br>$$</p><p>显然,平均复杂度比最坏情况复杂度要小,但一般分析时分析<code>最坏复杂度</code>。</p><h2 id="复杂度渐进表示"><a href="#复杂度渐进表示" class="headerlink" title="复杂度渐进表示"></a>复杂度渐进表示</h2><p>当说一个算法到底执行了多少次时,其实不需要明确知道多少次的。我们关心的是随着数据规模的增大,复杂度增长的性质,比如上面提到的实现方式一和二,第一种方式复杂度增长的性质主要是n<sup>2</sup>起主要作用,第二种方式主要是n起主要作用,那么久能判断出来哪种算法的效率比较好了。</p><h3 id="大O复杂度表示法"><a href="#大O复杂度表示法" class="headerlink" title="大O复杂度表示法"></a>大O复杂度表示法</h3><p> 公式:<br>$$<br>T(n) = O(f(n))<br>$$</p><p>T(n)表示代码执行的时间; n表示数据规模的大小; f(n) 表示每行代码执行的次数总和。因为这是一个公式, 所以用f(n)来表示。公式中的O,表示代码的执行时间T(n)与f(n)表达式成正比。</p><p> 大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度。</p><h3 id="复杂度分析法则"><a href="#复杂度分析法则" class="headerlink" title="复杂度分析法则"></a>复杂度分析法则</h3><p>1)单段代码看高频:比如循环。<br>2)多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。<br>3)嵌套代码求乘积:比如递归、多重循环等<br>4)多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。</p><h3 id="时间复杂度分析"><a href="#时间复杂度分析" class="headerlink" title="时间复杂度分析"></a>时间复杂度分析</h3><ul><li>只关注循环执行次数最多的一段代码</li><li>加法法则:总复杂度等于量级最大的那段代码的复杂度</li><li>乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积</li></ul>]]></content>
<categories>
<category> 数据结构 </category>
</categories>
<tags>
<tag> 基础知识 </tag>
<tag> 数据结构 </tag>
</tags>
</entry>
<entry>
<title>LinkedList源码分析</title>
<link href="/posts/475fb381.html"/>
<url>/posts/475fb381.html</url>
<content type="html"><![CDATA[<p>本文基于<code>JDK1.8</code>。<br>看完本篇文章你将学习到:</p><ul><li><code>LinkedList</code>可以插入<code>null</code>值的原因</li><li><code>LinkedList</code>可以允许重复的原因</li><li> <code>LinkedList</code>插入快,查询慢的原因</li></ul><p>上一篇说了<code>ArrayList</code>,这篇文章主要谈谈<code>LinkedList</code>的实现。这两个集合类在我们学习或工作过程中是很常用的,某度上面随便一搜,出来的就是它们两个之间的异同点。我们知道<code>ArrayList</code>是基于动态数组的,而<code>LinkedList</code>是基于<strong>链表</strong>的。往下我会逐层剖析<code>LinkedList</code>为什么<strong>插入快,查询慢</strong>的问题。但在这之前,让我们先看看<strong>链表</strong>是什么。</p><h3 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h3><p>先看一下<code>链表</code>的定义:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。<br>上面描述的可能比较复杂,我们画个图理解一下:<br><img src="https://img-blog.csdnimg.cn/20210304202120762.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5OTM0MjM2MTc5,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>在上图中,每个节点都有两个部分,即第一部分是用来保存自身的数据的,第二部分则是保存了指向下一个节点的指针。</p><h3 id="双向链表"><a href="#双向链表" class="headerlink" title="双向链表"></a>双向链表</h3><p>双向链表和单向链表最大的不同,是每个节点即维护了下一个节点的指针,也维护了上一个节点的指针。<br><img src="https://img-blog.csdnimg.cn/20210304212051619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5OTM0MjM2MTc5,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><blockquote><p><code>单向链表</code>只有后一节点指针,在节点删除,移动的时候,需要暂存前一节点,删除的时候将前一节点和后一节点连接,因为比双向链表少维护一个前节点,只在删除的时候暂存,所以比单向链表节省资源,但是增加了操作的复杂性。<br><code>双向链表</code>有前后两个节点指针,可以回溯指针,方便节点删除,移动,在做删除操作时只需要将索引节点前后两个节点连接即可,但是相比单向链表会耗费额外资源。</p></blockquote><p>总结起来一句话:双向链表就是以空间换时间。我们接下来要分析的<code>LinkedList</code>就是基于此。</p><h3 id="Node类"><a href="#Node类" class="headerlink" title="Node类"></a>Node类</h3><p>这里的<code>Node</code>类是<code>LinkedList</code>的一个静态内部类,也是我们上述所说双向链表中的<code>节点</code>的具体实现。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span><<span class="title">E</span>> </span>{</span><br><span class="line"> E item;<span class="comment">//当前节点保存的数据</span></span><br><span class="line"> Node<E> next;<span class="comment">//指向下一个节点的引用</span></span><br><span class="line"> Node<E> prev;<span class="comment">//指向上一个节点的引用</span></span><br><span class="line"></span><br><span class="line"> Node(Node<E> prev, E element, Node<E> next) {</span><br><span class="line"> <span class="keyword">this</span>.item = element;</span><br><span class="line"> <span class="keyword">this</span>.next = next;</span><br><span class="line"> <span class="keyword">this</span>.prev = prev;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结构非常简单,也恰恰验证了上文所说,每个节点除了维护自身的数据外,还分别维护了前一节点和后一节点的引用,用图表示就是:<br><img src="https://img-blog.csdnimg.cn/20210304213541619.png" alt="在这里插入图片描述"></p><h3 id="成员变量"><a href="#成员变量" class="headerlink" title="成员变量"></a>成员变量</h3><p><code>LinkedList</code>的成员变量很简单,主要有以下几个:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//记录链表中节点的个数</span></span><br><span class="line"><span class="keyword">transient</span> <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line"><span class="comment">//记录链表中第一个节点</span></span><br><span class="line"><span class="keyword">transient</span> Node<E> first;</span><br><span class="line"><span class="comment">//记录链表中最后一个节点</span></span><br><span class="line"><span class="keyword">transient</span> Node<E> last;</span><br></pre></td></tr></table></figure><p>由于<code>LinkedList</code>继承自<code>Deque</code>,所以需要支持<code>removeFirst</code>、<code>removeLast</code>等一系列操作,所以需要记录链表中的首尾节点。</p><h3 id="添加元素"><a href="#添加元素" class="headerlink" title="添加元素"></a>添加元素</h3><p>假设有如下代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span></span>{</span><br><span class="line"> List<String> list = <span class="keyword">new</span> LinkedList<String>();<span class="comment">//1</span></span><br><span class="line"> list.add(<span class="string">"hello"</span>);<span class="comment">//2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们逐步分析以上两处代码,首先是第1处,看一下源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">LinkedList</span><span class="params">()</span> </span>{ }</span><br></pre></td></tr></table></figure><p>可以看到初始化时并没有什么特殊的操作,接着是第2处,看一下源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>{</span><br><span class="line"><span class="comment">//调用</span></span><br><span class="line"> linkLast(e);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">linkLast</span><span class="params">(E e)</span> </span>{</span><br><span class="line"><span class="comment">//首先获取最后一个节点</span></span><br><span class="line"> <span class="keyword">final</span> Node<E> l = last;</span><br><span class="line"> <span class="comment">//这里创建出一个新的节点,它的前一个节点指向l,要保存的数据即传入进来的数据,后一个节点为null</span></span><br><span class="line"> <span class="keyword">final</span> Node<E> newNode = <span class="keyword">new</span> Node<>(l, e, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">//接着将这个新节点当做链表的最后一个节点保存起来</span></span><br><span class="line"> last = newNode;</span><br><span class="line"> <span class="comment">//如果刚开始最后一个节点为null,说明是第一次添加</span></span><br><span class="line"> <span class="keyword">if</span> (l == <span class="keyword">null</span>)</span><br><span class="line"> first = newNode;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">//否则将刚开始最后一个节点的下一个节点的引用指向新创建的节点</span></span><br><span class="line"> l.next = newNode;</span><br><span class="line"> size++;</span><br><span class="line"> modCount++;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其实上面注释已经写得很明白了,这里总结一下:</p><ol><li>保存当前<code>LinkedList</code>的第最后一个节点</li><li>创建出一个新的节点,并将要添加的数据赋值给新节点的<code>e</code>属性,那么这个时候新节点的上一个节点就应该是一开始保存的最后一个节点,即<code>l</code></li><li>接着将<code>LinkedList</code>的<code>last</code>引用指向新创建的节点,<code>last</code>保存的是链表的最后一个节点,因为这个时候新的节点成为了最后一个节点,所以需要重新指向。</li><li>我们在第一次添加时,<code>l</code>肯定是<code>null</code>,这个时候链表中只有一个节点,那么就是新创建出来的节点,所以同时将<code>first</code>引用指向新节点(如果不是第一次添加,那么则<code>l</code>是不为空的,则需要将<code>l</code>的下一个节点的引用指向新节点)。</li></ol><p>第一次添加后,链表结构:<br><img src="https://img-blog.csdnimg.cn/20210304220449988.png#pic_center" alt="在这里插入图片描述"><br>第二次添加后,链表结构:</p><p><img src="https://img-blog.csdnimg.cn/20210304220643915.png#pic_center" alt="在这里插入图片描述"></p><h3 id="查看元素"><a href="#查看元素" class="headerlink" title="查看元素"></a>查看元素</h3><p>这里以最简单的<code>get(int index)</code>方法为例:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"><span class="comment">//参数合法检验</span></span><br><span class="line">checkElementIndex(index);</span><br><span class="line"><span class="comment">//node方法返回节点,获取该节点保存的值并返回</span></span><br><span class="line"> <span class="keyword">return</span> node(index).item;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">Node<E> <span class="title">node</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"><span class="comment">//size >> 1 相当于size / 2 位运算比较高效,不需要转换为10进制计算</span></span><br><span class="line"><span class="comment">//index如果小于链表大小的一半,则从头遍历</span></span><br><span class="line"> <span class="keyword">if</span> (index < (size >> <span class="number">1</span>)) {</span><br><span class="line"> Node<E> x = first;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < index; i++)</span><br><span class="line"> x = x.next;</span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//index如果大于等于链表大小的一半,则从尾部遍历</span></span><br><span class="line"> Node<E> x = last;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = size - <span class="number">1</span>; i > index; i--)</span><br><span class="line"> x = x.prev;</span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码就体现出了双向链表的好处了。双向链表增加了一点点的空间消耗(每个<code>Node</code>里面还要维护它的前置<code>Node</code>的引用,相对于单链表来说空间消耗增加),同时也增加了一定的编程复杂度,却大大提升了效率。<br>举例:假设<code>LinkedList</code>中有10000个元素,如果我要找到第10000的元素,则直接从尾部开始遍历,只需要一次就能找到想要的元素。但最坏情况下如果查询第5000个元素,那么效率大打折扣。</p><h3 id="删除元素"><a href="#删除元素" class="headerlink" title="删除元素"></a>删除元素</h3><p>看完查看元素后,我们看一下如何删除一个元素,这里以按下标删除举个例子好了,下面先用图示解释一下如何删除元素。<br>假设现在链表中存在三个节点:<br><img src="https://img-blog.csdnimg.cn/20210304223216306.png#pic_center" alt="在这里插入图片描述"><br>现在需要删除中间的节点,即将第一个节点的<code>next</code>引用指向第三个节点,再将最后一个节点的<code>pre</code>引用指向第一个节点:<img src="https://img-blog.csdnimg.cn/2021030422334365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5OTM0MjM2MTc5,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>最终结果:<br><img src="https://img-blog.csdnimg.cn/20210304223359587.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5OTM0MjM2MTc5,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>那么接下来看看应用到<code>LinkedList</code>具体是怎么实现的:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">remove</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> checkElementIndex(index);</span><br><span class="line"> <span class="comment">//node方法和查看元素时相同</span></span><br><span class="line"> <span class="keyword">return</span> unlink(node(index));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">E <span class="title">unlink</span><span class="params">(Node<E> x)</span> </span>{</span><br><span class="line"><span class="comment">//分别获取当前要删除节点的值、前置节点、后置节点</span></span><br><span class="line"><span class="keyword">final</span> E element = x.item;</span><br><span class="line"><span class="keyword">final</span> Node<E> next = x.next;</span><br><span class="line"><span class="keyword">final</span> Node<E> prev = x.prev;</span><br><span class="line"></span><br><span class="line"><span class="comment">//前置节点为null,说明当前节点为首节点</span></span><br><span class="line"><span class="keyword">if</span> (prev == <span class="keyword">null</span>) {</span><br><span class="line"> first = next;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"><span class="comment">//前置节点不为null,将前置节点的next指向后置节点</span></span><br><span class="line"> prev.next = next;</span><br><span class="line"> <span class="comment">//1</span></span><br><span class="line"> x.prev = <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//后置节点为null,说明当前节点为尾节点</span></span><br><span class="line"><span class="keyword">if</span> (next == <span class="keyword">null</span>) {</span><br><span class="line"> last = prev;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"><span class="comment">//后置节点不为null,将后置节点的prev指向前置节点</span></span><br><span class="line"> next.prev = prev;</span><br><span class="line"> <span class="comment">//2</span></span><br><span class="line"> x.next = <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//3</span></span><br><span class="line">x.item = <span class="keyword">null</span>;</span><br><span class="line">size--;</span><br><span class="line">modCount++;</span><br><span class="line"><span class="keyword">return</span> element;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里注意一点:上面源码中的1、2、3步骤都设置为了<code>null</code>,目的是为了<code>GC</code>。</p><h3 id="插入元素"><a href="#插入元素" class="headerlink" title="插入元素"></a>插入元素</h3><p>插入元素其实和上面讲的几种是一个道理,如果读者理解了上面的逻辑,插入元素也就能想通怎么回事了。</p><h3 id="LinkedList和ArrayList的区别"><a href="#LinkedList和ArrayList的区别" class="headerlink" title="LinkedList和ArrayList的区别"></a>LinkedList和ArrayList的区别</h3><p>这个问题不管是在平时面向搜索编程还是在基础面试过程中都算是老生常谈了,在这里我们逐个分析一下这两个的优缺点:</p><ul><li>插入速度比较。网上大部分说<code>LinkedList</code>插入比<code>ArrayLst</code>快。这种说法是不准确的。<code>LinkedList</code>做插入、删除的时候,慢在寻址,快在只需要改变前后<code>Node</code>的引用地址,而<code>ArrayList</code>做插入、删除的时候,慢在数组元素的批量<code>copy</code>,快在寻址。<br>所以如果待插入的元素位置在数据结构的前半段尤其是非常靠前时,<code>ArrayList</code>需要拷贝大量的元素,势必<code>LinkedList</code>会更快;如果带插入元素位置在数据结构后半段尤其是非常靠后,<code>ArrayList</code>需要拷贝的元素个数会越来越少,所以速度也会提升,甚至超过<code>LinkedList</code>。</li><li><code>ArrayLst</code>基于动态数组,所以内存上是连续的,而<code>LinkedList</code>基于链表,内存上不需要保持连续。</li><li>一般遍历<code>LinkedList</code>时最好不要使用普通for循环,而是使用迭代器代替。</li></ul><blockquote><p>我们在实际工作中,还是要根据实际情况来确定选用哪种数据结构存储数据,最好是根据需求,经过理论支撑和实际测试,最终选择合适的数据结构。</p></blockquote><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>本篇文章介绍了<code>LinkedList</code>增加元素、查看元素、移除元素、插入元素等,以图示和源码结合的方式掌握了<code>LinkedList</code>实现原理,其内部实现就是一个双向链表,通过以空间换时间的方式提高查询的效率。</p>]]></content>
<categories>
<category> 集合 </category>
</categories>
<tags>
<tag> LinkedList </tag>
<tag> 源码分析 </tag>
</tags>
</entry>
<entry>
<title>ArrayList源码分析</title>
<link href="/posts/c352a0f3.html"/>
<url>/posts/c352a0f3.html</url>
<content type="html"><![CDATA[<p>提起ArrayList,相信很多小伙伴都用过,而且还不少用。但在几年之前,我在一场面试中,面试官要求说出ArrayList的扩容机制。很显然,那个时候的我并没有关注这些,从而错过了一次机会。不过好在我还算比较喜欢搞事情的,所以今天这篇文章也算是填坑吧。<br>看完这边文章你将了解到:</p><ul><li><code>ArrayList</code>底层实现</li><li><code>ArrayList</code>为什么允许null值</li><li><code>ArrayList</code>为什么可重复</li><li><code>ArrayList</code>查询效率和插入效率对比</li></ul><h3 id="类图"><a href="#类图" class="headerlink" title="类图"></a>类图</h3><p>下图是<code>ArrayList</code>的类图结构<br><img src="https://img-blog.csdnimg.cn/20210303214625117.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5OTM0MjM2MTc5,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><code>ArrayList</code>继承于 <code>AbstractList </code>,实现了 <code>List</code>, <code>RandomAccess</code>, <code>Cloneable</code>, <code>java.io.Serializable</code> 这些接口。<br>这里逐个分析一下这里接口的意义:</p><ul><li><code>RandomAccess </code>是一个标志接口,表明实现这个这个接口的 <code>List </code>集合是支持快速随机访问的。有兴趣可以看看<code>Collections</code>类中哪个方法用到了这个标志性接口。</li><li>实现 <code>Cloneable </code>接口并覆盖了方法<code>clone()</code>,能被克隆。</li><li>实现了java.io.Serializable 接口,这意味着<code>ArrayList</code>支持序列化,能通过序列化去传输(请注意,<code>ArrayList</code>的序列化是有点小特殊的,后面会讲解)。</li></ul><h3 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h3><h3 id="成员变量"><a href="#成员变量" class="headerlink" title="成员变量"></a>成员变量</h3><p>在正式进入源码分析之前,我们有必要先看看它的成员变量都有哪些,这里列举比较重要的成员变量:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> size; <span class="comment">// 实际元素个数</span></span><br><span class="line"><span class="keyword">transient</span> Object[] elementData; <span class="comment">//真正保存元素的数组</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_CAPACITY = <span class="number">10</span>;<span class="comment">//默认的初始容量大小</span></span><br></pre></td></tr></table></figure><h3 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h3><p>我们有三种初始化办法:无参数直接初始化、指定大小初始化、指定初始数据初始化,源码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="comment">//1、无参数直接初始化,数组大小为空</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ArrayList</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//2、指定初始数据初始化</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ArrayList</span><span class="params">(Collection<? extends E> c)</span></span>{</span><br><span class="line"> <span class="comment">//elementData是保存数组的容器,默认为null</span></span><br><span class="line"> elementData=c.toArray();</span><br><span class="line"> <span class="comment">//如果给定的集合(c)数据有值</span></span><br><span class="line"> <span class="keyword">if</span>((size=elementData.length)!=<span class="number">0</span>){</span><br><span class="line"> <span class="comment">//c.toArray might(incorrectly)not return Object[](see 6260652)</span></span><br><span class="line"> <span class="comment">//如果集合元素类型不是Object类型,我们会转成Object</span></span><br><span class="line"> <span class="keyword">if</span>(elementData.getClass()!=Object[].class){</span><br><span class="line"> elementData=Arrays.copyOf(elementData,size,Object].class);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//给定集合(c)无值,则默认空数组</span></span><br><span class="line"> <span class="keyword">this</span>.elementData=EMPTY_ELEMENTDATA</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">//3、指定初始容量</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ArrayList</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>{</span><br><span class="line"><span class="comment">//指定的初始容量大于0,将elementData初始化为指定大小的数组</span></span><br><span class="line"> <span class="keyword">if</span> (initialCapacity > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">this</span>.elementData = <span class="keyword">new</span> Object[initialCapacity];</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (initialCapacity == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">//否则初始化成一个空数组</span></span><br><span class="line"> <span class="keyword">this</span>.elementData = EMPTY_ELEMENTDATA;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>除过源码中注释外,补充几点:</p><ol><li><code>ArrayList</code>无参构造器初始化时,默认大小是空数组,并不是大家常说的10,10是在第一次<code>add</code>的时候扩容的数组值。</li><li>使用方式二进行创建对象时,如果入参容器保存的对象不是<code>Object</code>,则转换为<code>Object</code>。<br><code>DEFAULTCAPACITY_EMPTY_ELEMENTDATA</code>和<code>EMPTY_ELEMENTDATA</code>又是什么鬼?它其实是定义在成员变量的两个空数组,<figure class="highlight java"><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">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object[] EMPTY_ELEMENTDATA = {};</span><br></pre></td></tr></table></figure>很明显问题来了,既然都是空数组,为什么要声明两个?一个不行吗?读者请先思考一下,带着疑问往下看。<h3 id="新增和扩容实现"><a href="#新增和扩容实现" class="headerlink" title="新增和扩容实现"></a>新增和扩容实现</h3>通过构造方法可以很清楚的看到,<code>ArrayList</code>的确是基于数组的,但<code>动态</code>又从何说起?<br>新增时就是给数组中添加元素,主要分为两步走:</li><li>判断是否需要扩容,如果需要扩容执行扩容操作;</li><li>直接赋值。<br>对应源码如下:<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>{</span><br><span class="line"><span class="comment">//确保数组大小是否足够,不够执行扩容,size为当前数组元素个数,判断size+1是因为后面还要size++</span></span><br><span class="line"> ensureCapacityInternal(size + <span class="number">1</span>); <span class="comment">//1</span></span><br><span class="line"> elementData[size++] = e;<span class="comment">//2</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>我们先来看一下扩容部分的源码:<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">ensureCapacityInternal</span><span class="params">(<span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"><span class="comment">//先调用calculateCapacity计算容量</span></span><br><span class="line"> ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">calculateCapacity</span><span class="params">(Object[] elementData, <span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"> <span class="comment">//如果当前数组还是个空数组,也就是他用过无参构造去初始化的</span></span><br><span class="line"> <span class="comment">//那么直接返回DEFAULT_CAPACITY,即10</span></span><br><span class="line"> <span class="keyword">if</span> (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {</span><br><span class="line"> <span class="keyword">return</span> Math.max(DEFAULT_CAPACITY, minCapacity);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> minCapacity;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">ensureExplicitCapacity</span><span class="params">(<span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"> modCount++;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果当前容量已经大于当前数组的长度了,说明需要去扩容了</span></span><br><span class="line"> <span class="keyword">if</span> (minCapacity - elementData.length > <span class="number">0</span>)</span><br><span class="line"> <span class="comment">//扩容</span></span><br><span class="line"> grow(minCapacity);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">grow</span><span class="params">(<span class="keyword">int</span> minCapacity)</span></span>{</span><br><span class="line"> <span class="keyword">int</span> oldCapacity = elementData.length;</span><br><span class="line"> <span class="comment">//oldCapacity>>1是把oldCapacity除以2的意思</span></span><br><span class="line"> <span class="keyword">int</span> newCapacity=oldCapacity+(oldCapacity>><span class="number">1</span>);</span><br><span class="line"> <span class="comment">//如果扩容后的值<我们的期望值,扩容后的值就等于我们的期望值</span></span><br><span class="line"> <span class="keyword">if</span>(newCapacity-minCapacity<<span class="number">0</span>)</span><br><span class="line"> newCapacity = minCapacity;</span><br><span class="line"> <span class="comment">//如果扩容后的值>jvm所能分配的数组的最大值,那么就用Integer的最大值</span></span><br><span class="line"> <span class="keyword">if</span>(newCapacity-MAX_ARRAY_SIZE><span class="number">0</span>)</span><br><span class="line"> elementData=Arrays.copyOf(elementData,newCapacity);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>注释相对来说已经比较详细了,这里需要注意以下几点:</li><li>上面有个问题是为什么需要声明两个空数组。我们在看到上面源码的时候有一个方法为<code>calculateCapacity</code>,这个方法内部逻辑只有在通过无参构造初始化<code>ArrayList</code>的时候才会改变将要返回的<code>minCapacity</code>。而返回的这个值将会决定下面的数组是否需要扩容。如果我们通过指定大小的方式初始化<code>ArrayList</code>并指定大小为0,这说明我们需要的就是一个空的<code>ArrayList</code>,不需要去扩容,你细品;</li><li>新增时,没有对值进行校验,所以新增值可以为<code>null</code>,且没有做重复值判断,所以元素可以<code>重复</code>;</li><li>ArrayList中的数组的最大值是<code>Integer.MAX_VALUE</code>,超过这个值,<code>JVM</code>就不会给数组分配<br>内存空间了;</li><li>扩容是原来容量大小+容量大小的一半,简单说就是扩容后的大小是原来容量的1.5倍。</li></ol><p>扩容完成之后,就是简单的赋值了,赋值时并没有加锁,所以是线程<code>不安全</code>的。</p><h3 id="扩容的本质"><a href="#扩容的本质" class="headerlink" title="扩容的本质"></a>扩容的本质</h3><p>在<code>grow</code>方法的最后,扩容是通过<code>Arrays.copyOf(elementData,newCapacity);</code>这行代码实现的。这个方法实际上调用的方法是我们经常使用的<code>System.arraycopy</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">*<span class="doctag">@param</span> src 被拷贝的数组</span></span><br><span class="line"><span class="comment">*<span class="doctag">@param</span> srcPos 从数组那里开始</span></span><br><span class="line"><span class="comment">*<span class="doctag">@param</span> dest 目标数组</span></span><br><span class="line"><span class="comment">*<span class="doctag">@param</span> destPos从目标数组那个索引位置开始拷贝</span></span><br><span class="line"><span class="comment">*<span class="doctag">@param</span> length 拷贝的长度</span></span><br><span class="line"><span class="comment">*此方法是没有返回值的,通过dest的引用进行传值</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">arraycopy</span><span class="params">(Object src, <span class="keyword">int</span> srcPos,Object dest, <span class="keyword">int</span> destPos,<span class="keyword">int</span> length)</span></span>;</span><br></pre></td></tr></table></figure><p>这个方法是一个<code>native</code>方法,虽然不能看到方法内部的具体实现,但通过参数也可以管中窥豹。这个方法会移动元素。所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,时间复杂度 O(N);而且你如果想在数组中间进行插入和删除,每次必须搬移后面的所有数据以保持连续,时间复杂度 O(N)。由于数组又是一块连续的内存空间,能够根据索引快速访问元素。<br>上面也就解释了一开始那个问题:<code>ArrayList</code>为什么插入慢,查询快。</p><h3 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h3><p><code>ArrayList</code>有多种删除方法,这里以根据值删除的方式进行说明(其他原理类似):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(Object o)</span> </span>{</span><br><span class="line"> <span class="comment">//如果要删除的值是null,删除第一个是null的值</span></span><br><span class="line"> <span class="keyword">if</span>(o==<span class="keyword">null</span>){</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> index=<span class="number">0</span>;index<size;index++)</span><br><span class="line"> <span class="keyword">if</span>(elementData[index]==<span class="keyword">null</span>){</span><br><span class="line"> fastRemove(index)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//如果要删除的值不为null,找到第一个和要删除的值相等的删除</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> index=<span class="number">0</span>;index<size;index++)</span><br><span class="line"> <span class="comment">//这里是根据 equals来判断值相等的,相等后再根据索引位置进行删除</span></span><br><span class="line"> <span class="comment">//所以根据对象删除时,一般来说,如果你确定要删除的是某一类的业务对象,则需要重写equals</span></span><br><span class="line"> <span class="keyword">if</span>(o.equals(elementData[index]){</span><br><span class="line"> fastRemove(index)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>核心其实是<code>fastRemove</code>方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">fastRemove</span><span class="params">(<span class="keyword">int</span> index)</span></span>{</span><br><span class="line"> <span class="comment">//记录数组的结构要发生变动了</span></span><br><span class="line"> nodCount++;</span><br><span class="line"> <span class="comment">//numMoved表示删除index位置的元素后,需要从index后移动多少个元素到前面去</span></span><br><span class="line"> <span class="comment">//减1的原因,是因为size从1开始算起,index从0开始算起</span></span><br><span class="line"> <span class="keyword">int</span> numMoved=size-index-<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span>(numMoved><span class="number">0</span>)</span><br><span class="line"> <span class="comment">//从index+1位置开始被拷贝,拷贝的起始位置是index,长度是numMoved</span></span><br><span class="line"> System.arraycopy(elementData, index+<span class="number">1</span>, elementData, index, numMoved);</span><br><span class="line"> <span class="comment">//数组最后一个位置赋值null,帮助GC(没有引用则自动回收了)</span></span><br><span class="line"> elementData[--size] = <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从源码中,我们可以看出,某一个元素被删除后,为了维护数组结构,我们都会把数组后面的元素往前移动,同时释放最后一个引用,便于回收。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>本文主要从ArrayList的源码入手,分别从初始化、新增、扩容、删除四个方面展开学习。我们发现ArrayList内部其实就是围绕了一个数组,在数组容量不足时将数组扩容至更大,所以也就自然被称作基于<code>动态数组</code>。</p>]]></content>
<categories>
<category> 集合 </category>
</categories>
<tags>
<tag> 源码分析 </tag>
<tag> ArrayList </tag>
</tags>
</entry>
</search>