JavaScript实现排序算法
<h2 id="javascript实现排序算法">JavaScript实现排序算法</h2><h3 id="一大o表示法">一、大O表示法</h3>
<p><strong>大O表示法:</strong></p>
<ul>
<li>在计算机中采用<strong>粗略的度量</strong>来描述计算机算法的<strong>效率</strong>,这种方法被称为<strong>“大O”表示法</strong></li>
<li>在<strong>数据项个数</strong>发生改变时,<strong>算法的效率</strong>也会跟着改变。所以说算法A比算法B快两倍,这样的比较是<strong>没有意义</strong>的。</li>
<li>因此我们通常使用<strong>算法的速度</strong>随着<strong>数据量的变化</strong>会如何变化的方式来表示算法的效率,大O表示法就是方式之一。</li>
</ul>
<p><strong>常见的大O表示形式</strong></p>
<table>
<thead>
<tr>
<th>符号</th>
<th>名称</th>
</tr>
</thead>
<tbody>
<tr>
<td>O(1)</td>
<td>常数</td>
</tr>
<tr>
<td>O(log(n))</td>
<td>对数</td>
</tr>
<tr>
<td>O(n)</td>
<td>线性</td>
</tr>
<tr>
<td>O(nlog(n))</td>
<td>线性和对数乘积</td>
</tr>
<tr>
<td>O(n²)</td>
<td>平方</td>
</tr>
<tr>
<td>O(2<sup>n</sup>)</td>
<td>指数</td>
</tr>
</tbody>
</table>
<p><strong>不同大O形式的时间复杂度:</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/1.png" alt="image-20200304164951223" loading="lazy"></p>
<p>可以看到效率从大到小分别是:O(1)> O(logn)> O(n)> O(nlog(n))> O(n²)> O(2<sup>n</sup>)</p>
<p><strong>推导大O表示法的三条规则:</strong></p>
<ul>
<li><strong>规则一</strong>:用常量1取代运行时间中所有的加法常量。如7 + 8 = 15,用1表示运算结果15,大O表示法表示为O(1);</li>
<li><strong>规则二</strong>:运算中只保留最高阶项。如N^3 + 3n +1,大O表示法表示为:O(N<sup>3</sup>);</li>
<li><strong>规则三</strong>:若最高阶项的常数不为1,可将其省略。如4N<sup>2</sup>,大O表示法表示为:O(N<sup>2</sup>);</li>
</ul>
<h3 id="二排序算法">二、排序算法</h3>
<p>这里主要介绍几种简单排序和高级排序:</p>
<ul>
<li><strong>简单排序:</strong>冒泡排序、选择排序、插入排序;</li>
<li><strong>高级排序:</strong>希尔排序、快速排序;</li>
</ul>
<p>此处创建一个列表类ArrayList并添加一些属性和方法,用于存放这些排序方法:</p>
<pre><code> //创建列表类
function ArrayList() {
//属性
this.array = []
//方法
//封装将数据插入到数组中方法
ArrayList.prototype.insert = function(item){
this.array.push(item)
}
//toString方法
ArrayList.prototype.toString = function(){
return this.array.join('-')
}
//交换两个位置的数据
ArrayList.prototype.swap = function(m, n){
let temp= this.array
this.array = this.array
this.array = temp
}
</code></pre>
<h4 id="1冒泡排序">1.冒泡排序</h4>
<p><strong>冒泡排序的思路:</strong></p>
<ul>
<li>对未排序的各元素<strong>从头到尾</strong>依次比较<strong>相邻的两个元素</strong>大小关系;</li>
<li>如果<strong>左边的人员高</strong>,则将两人<strong>交换位置</strong>。比如1比2矮,不交换位置;</li>
<li>向<strong>右移动一位</strong>,继续比较2和3,最后比较 length - 1 和 length - 2这两个数据;</li>
<li>当到达<strong>最右端</strong>时,<strong>最高的人</strong>一定被放在了<strong>最右边</strong>;</li>
<li>按照这个思路,从最左端重新开始时,只需要走到<strong>倒数第二个位置</strong>即可;</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/2.png" alt="image-20200304191223265" loading="lazy"></p>
<p><strong>实现思路:</strong></p>
<p>两层循环:</p>
<ul>
<li>
<p>外层循环控制冒泡趟数:</p>
<ul>
<li>第一次:j = length - 1,比较到倒数第一个位置 ;</li>
<li>第二次:j = length - 2,比较到倒数第二个位置 ;</li>
</ul>
</li>
<li>
<p>内层循环控制每趟比较的次数:</p>
<ul>
<li>第一次比较: i = 0,比较 0 和 1 位置的两个数据;</li>
<li>最后一次比较:i = length - 2,比较length - 2和 length - 1两个数据;</li>
</ul>
</li>
</ul>
<p>详细过程如下图所示:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/3.png" alt="image-20200304210611689" loading="lazy"></p>
<p>动态过程:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/4.gif" alt="" loading="lazy"></p>
<p><strong>代码实现:</strong></p>
<pre><code class="language-//冒泡排序"> //冒泡排序
ArrayList.prototype.bubblesor = function(){
//1.获取数组的长度
let length = this.array.length
//外层循环控制冒泡趟数
for(let j = length - 1; j >= 0; j--){
//内层循环控制每趟比较的次数
for(let i = 0; i < j; i++){
if (this.array > this.array) {
//交换两个数据
let temp= this.array
this.array = this.array
this.array = temp
}
}
}
}
</code></pre>
<p><strong>测试代码:</strong></p>
<pre><code> //测试类
let list = new ArrayList()
//插入元素
list.insert(66)
list.insert(88)
list.insert(12)
list.insert(87)
list.insert(100)
list.insert(5)
list.insert(566)
list.insert(23)
//验证冒泡排序
list.bubblesor()
console.log(list);
</code></pre>
<p><strong>测试结果:</strong><br>
<img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/5.png" alt="image-20200304210433388" loading="lazy"></p>
<p><strong>冒泡排序的效率:</strong></p>
<ul>
<li>上面所讲的对于7个数据项,比较次数为:6 + 5 + 4 + 3 + 2 + 1;</li>
<li>对于N个数据项,<strong>比较次数</strong>为:(N - 1) + (N - 2) + (N - 3) + ... + 1 = N * (N - 1) / 2;如果两次比较交换一次,那么<strong>交换次数</strong>为:N * (N - 1) / 4;</li>
<li>使用大O表示法表示比较次数和交换次数分别为:O( N * (N - 1) / 2)和O( N * (N - 1) / 4),根据大O表示法的三条规则都化简为:<strong>O(N^2)</strong>;</li>
</ul>
<h4 id="2选择排序">2.选择排序</h4>
<p><strong>选择排序改进了冒泡排序:</strong></p>
<ul>
<li>将<strong>交换次数</strong>由<strong>O(N^2)</strong>减小到<strong>O(N)</strong>;</li>
<li>但是<strong>比较次数</strong>依然是<strong>O(N^2)</strong>;</li>
</ul>
<p><strong>选择排序的思路:</strong></p>
<ul>
<li>选定<strong>第一个索引的位置</strong>比如1,然后依次和后面的元素<strong>依次进行比较</strong>;</li>
<li>如果后面的元素,<strong>小于</strong>索引1位置的元素,则<strong>交换位置</strong>到索引1处;</li>
<li>经过一轮的比较之后,可以确定一开始指定的索引1位置的元素是<strong>最小的</strong>;</li>
<li>随后使用同样的方法除索引1意外<strong>逐个比较剩下的元素</strong>即可;</li>
<li>可以看出选择排序,<strong>第一轮</strong>会选出<strong>最小值</strong>,<strong>第二轮</strong>会选出<strong>第二小的值</strong>,直到完成排序。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/6.png" alt="image-20200304213253241" loading="lazy"></p>
<p><strong>实现思路:</strong></p>
<p>两层循环:</p>
<ul>
<li>
<p>外层循环控制指定的索引:</p>
<ul>
<li>第一次:j = 0,指定第一个元素 ;</li>
<li>最后一次:j = length - 1,指定最后一个元素 ;</li>
</ul>
</li>
<li>
<p>内层循环负责将指定索引(i)的元素与剩下(i - 1)的元素进行比较;</p>
</li>
</ul>
<p>动态过程:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/7.gif" alt="" loading="lazy"></p>
<p><strong>代码实现:</strong></p>
<pre><code> //选择排序
ArrayList.prototype.selectionSort = function(){
//1.获取数组的长度
let length = this.array.length
//2.外层循环:从0开始获取元素
for(let j = 0; j < length - 1; j++){
let min = j
//内层循环:从i+1位置开始,和后面的元素进行比较
for(let i = min + 1; i < length; i++){
if (this.array > this.array) {
min = i
}
}
this.swap(min, j)
}
}
</code></pre>
<p><strong>测试代码:</strong></p>
<pre><code> //测试类
let list = new ArrayList()
//插入元素
list.insert(66)
list.insert(88)
list.insert(12)
list.insert(87)
list.insert(100)
list.insert(5)
list.insert(566)
list.insert(23)
//验证选择排序
list.selectionSort()
console.log(list);
</code></pre>
<p><strong>测试结果:</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/8.png" alt="image-20200304222224801" loading="lazy"></p>
<p><strong>选择排序的效率:</strong></p>
<ul>
<li>选择排序的<strong>比较次数</strong>为:N * (N - 1) / 2,用大O表示法表示为:<strong>O(N^2)</strong>;</li>
<li>选择排序的<strong>交换次数</strong>为:(N - 1) / 2,用大O表示法表示为:<strong>O(N)</strong>;</li>
<li>所以选择排序的效率高于冒泡排序;</li>
</ul>
<h4 id="3插入排序">3.插入排序</h4>
<p>插入排序是简单排序中效率<strong>最高</strong>的一种排序。</p>
<p><strong>插入排序的思路:</strong></p>
<ul>
<li>插入排序思想的核心是<strong>局部有序</strong>。如图所示,X左边的人称为<strong>局部有序</strong>;</li>
<li>首先指定一数据X(从第一个数据开始),并将数据X的左边变成局部有序状态;</li>
<li>随后将X右移一位,再次达到局部有序之后,继续右移一位,重复前面的操作直至X移至最后一个元素。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/9.png" alt="image-20200304231400959" loading="lazy"></p>
<p>插入排序的详细过程:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/10.png" alt="image-20200304231643777" loading="lazy"></p>
<p>动态过程:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/11.gif" alt="" loading="lazy"></p>
<p><strong>代码实现:</strong></p>
<pre><code> //插入排序
ArrayList.prototype.insertionSort = function(){
//1.获取数组的长度
let length = this.array.length
//2.外层循环:从第二个数据开始,向左边的已经局部有序数据进行插入
for(let i = 1; i < length; i++){
//3.内层循环:获取i位置的元素,使用while循环(重点)与左边的局部有序数据依次进行比较
let temp = this.array
let j = i
while(this.array > temp && j > 0){
this.array = this.array//大的数据右移
j--
}
//4.while循环结束后,index = j左边的数据变为局部有序且array最大。此时将array重置为排序前的数据array,方便下一次for循环
this.array = temp
}
}
</code></pre>
<p><strong>测试代码:</strong></p>
<pre><code> //测试类
let list = new ArrayList()
//插入元素
list.insert(66)
list.insert(88)
list.insert(12)
list.insert(87)
list.insert(100)
list.insert(5)
list.insert(566)
list.insert(23)
// console.log(list);
//验证插入排序
list.insertionSort()
console.log(list);
</code></pre>
<p><strong>测试结果:</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/12.png" alt="image-20200304235529516" loading="lazy"></p>
<p><strong>插入排序的效率:</strong></p>
<ul>
<li>
<p><strong>比较次数:</strong>第一趟时,需要的最大次数为1;第二次最大为2;以此类推,最后一趟最大为N-1;所以,插入排序的总比较次数为N * (N - 1) / 2;但是,实际上每趟发现插入点之前,平均只有全体数据项的一半需要进行比较,所以比较次数为:<strong>N * (N - 1) / 4</strong>;</p>
</li>
<li>
<p><strong>交换次数:</strong>指定第一个数据为X时交换0次,指定第二个数据为X最多需要交换1次,以此类推,指定第N个数据为X时最多需要交换N - 1次,所以一共需要交换N * (N - 1) / 2次,平局次数为<strong>N * (N - 1) / 2</strong>;</p>
</li>
<li>
<p>虽然用大O表示法表示插入排序的效率也是<strong>O(N^2)</strong>,但是插入排序整体操作次数更少,因此,在简单排序中,插入排序<strong>效率最高</strong>;</p>
</li>
</ul>
<h4 id="4希尔排序">4.希尔排序</h4>
<p><strong>希尔排序</strong>是<strong>插入排序</strong>的一种高效的<strong>改进版</strong>,效率比插入排序要<strong>高</strong>。</p>
<p><strong>希尔排序的历史背景:</strong></p>
<ul>
<li>希尔排序按其设计者希尔(Donald Shell)的名字命名,该算法由<strong>1959年公布</strong>;</li>
<li>希尔算法首次突破了计算机界一直认为的<strong>算法的时间复杂度都是O(N^2)</strong>的大关,为了纪念该算法里程碑式</li>
</ul>
<p>的意义,用<strong>Shell</strong>来命名该算法;</p>
<p><strong>插入排序的问题:</strong></p>
<ul>
<li>假设一个<strong>很小的数据项</strong>在<strong>很靠近右端的位置</strong>上,这里本应该是<strong>较大的数据项的位置</strong>;</li>
<li>将这个<strong>小数据项移动到左边</strong>的正确位置,所有的<strong>中间数据项都必须向右移动一位</strong>,这样效率非常低;</li>
<li>如果通过<strong>某种方式</strong>,不需要<strong>一个个移动所有中间的数据项</strong>,就能把较小的数据项移到左边,那么这个算法的执行速度就会有很大的改进。</li>
</ul>
<p><strong>希尔排序的实现思路:</strong></p>
<ul>
<li>希尔排序主要通过对数据进行<strong>分组</strong>实现快速排序;</li>
<li>根据设定的增量(gap)将数据分为gap个组(<strong>组数等于gap</strong>),再在每个分组中进行局部排序;</li>
</ul>
<blockquote>
<p>假如有数组有10个数据,第1个数据为黑色,增量为5。那么第二个为黑色的数据index=5,第3个数据为黑色的数据index = 10(不存在)。所以黑色的数据每组只有2个,10 / 2 = 5一共可分5组,即<strong>组数等于增量gap</strong>。</p>
</blockquote>
<ul>
<li>排序之后,减小增量,继续分组,再次进行局部排序,直到增量gap=1为止。随后只需进行微调就可完成数组的排序;</li>
</ul>
<p>具体过程如下:</p>
<ul>
<li>排序之前的,储存10个数据的原始数组为:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/13.png" alt="image-20200305102330304" loading="lazy"></p>
<ul>
<li>设初始增量gap = length / 2 = 5,即数组被分为了5组,如图所示分别为:、、、、:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/14.png" alt="image-20200305104914438" loading="lazy"></p>
<ul>
<li>随后分别在每组中对数据进行局部排序,5组的顺序如图所示,变为:、、、、:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/15.png" alt="image-20200305103136251" loading="lazy"></p>
<ul>
<li>然后缩小增量gap = 5 / 2 = 2,即数组被分为了2组,如图所示分别为:、:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/16.png" alt="image-20200305104933858" loading="lazy"></p>
<ul>
<li>随后分别在每组中对数据进行局部排序,两组的顺序如图所示,变为:、:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/17.png" alt="image-20200305103815262" loading="lazy"></p>
<ul>
<li>然后然后缩小增量gap = 2 / 1 = 1,即数组被分为了1组,如图所示为::</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/18.png" alt="image-20200305104847458" loading="lazy"></p>
<ul>
<li>最后只需要对该组数据进行插入排序即可完成整个数组的排序:</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/19.png" alt="image-20200305104707789" loading="lazy"></p>
<p>动态过程:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/20.gif" alt="" loading="lazy"></p>
<p>图中d表示增量gap。</p>
<p><strong>增量的选择:</strong></p>
<ul>
<li><strong>原稿</strong>中希尔建议的初始间距为<strong>N / 2</strong>,比如对于N = 100的数组,增量序列为:50,25,12,6,3,1,可以发现不能整除时向下取整。</li>
<li><strong>Hibbard增量序列:</strong>增量序列算法为:2^k - 1,即1,3,5,7... ...等;这种情况的最坏复杂度为<strong>O(N<sup>3/2)**,平均复杂度为**O(N</sup>5/4)</strong>但未被证明;</li>
<li><strong>Sedgewcik增量序列:</strong></li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/21.png" alt="image-20200305110724309" loading="lazy"></p>
<p>以下代码实现中采用希尔排序原稿中建议的增量即<strong>N / 2</strong> 。</p>
<p><strong>代码实现:</strong></p>
<pre><code> //希尔排序
ArrayList.prototype.shellSort = function(){
//1.获取数组的长度
let length = this.array.length
//2.初始化增量
let gap = Math.floor(length / 2)
//3.第一层循环:while循环(使gap不断减小)
while(gap >= 1 ){
//4.第二层循环:以gap为增量,进行分组,对分组进行插入排序
//重点为:将index = gap作为选中的第一个数据
for(let i = gap; i < length; i++){
let temp = this.array
let j = i
//5.第三层循环:寻找正确的插入位置
while(this.array > temp && j > gap - 1){
this.array = this.array
j -= gap
}
//6.将j位置的元素设置为temp
this.array = temp
}
gap = Math.floor(gap / 2)
}
}
</code></pre>
<p>这里解释一下上述代码中的三层循环:</p>
<ul>
<li><strong>第一层循环:</strong>while循环,控制gap递减到1;</li>
<li><strong>第二层循环:</strong>分别取出根据g增量gap分成的gap组数据:将index = gap的数据作为选中的第一个数据,如下图所示,gap=5,则index = gap的数据为3,index = gap - 1的数据为8,两个数据为一组。随后gap不断加1右移,直到gap < length,此时实现了将数组分为5组。</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/21.5.png" alt="image-20200305104914438" loading="lazy"></p>
<ul>
<li><strong>第三层循环:</strong>对每一组数据进行插入排序;</li>
</ul>
<p><strong>测试代码:</strong></p>
<pre><code> //测试类
let list = new ArrayList()
//插入元素
list.insert(66)
list.insert(88)
list.insert(12)
list.insert(87)
list.insert(100)
list.insert(5)
list.insert(566)
list.insert(23)
// console.log(list);
//验证希尔排序
list.shellSort()
console.log(list);
</code></pre>
<p><strong>测试结果:</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/22.png" alt="image-20200305114934209" loading="lazy"></p>
<p><strong>希尔排序的效率:</strong></p>
<ul>
<li>希尔排序的效率和增量有直接关系,即使使用原稿中的增量效率都高于简单排序。</li>
</ul>
<h4 id="5快速排序">5.快速排序</h4>
<p>快速排序的介绍:</p>
<ul>
<li>
<p><strong>快速排序</strong>可以说是<strong>目前所有排序算法</strong>中,<strong>最快</strong>的一种排序算法。当然,没有任何一种算法是在任意情况下都是最优的。但是,大多数情况下快速排序是比较好的选择。</p>
</li>
<li>
<p><strong>快速排序</strong>其实是<strong>冒泡排序</strong>的升级版;</p>
</li>
</ul>
<p>快速排序的核心思想是<strong>分而治之</strong>,先选出一个数据(比如65),将比其小的数据都放在它的左边,将比它大的数据都放在它的右边。这个数据称为<strong>枢纽</strong></p>
<p>和冒泡排序的不同:</p>
<ul>
<li>我们选择的65可以一次性将它放在最正确的位置,之后就不需要做任何移动;</li>
<li>而冒泡排序即使已经找到最大值,也需要继续移动最大值,直到将它移动到最右边;</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/23.png" alt="image-20200305154504624" loading="lazy"></p>
<p><strong>快速排序的枢纽:</strong></p>
<ul>
<li><strong>第一种方案:</strong>直接选择第一个元素作为枢纽。但是,当第一个元素就是最小值的情况下,效率不高;</li>
<li><strong>第二种方案:</strong>使用随机数。随机数本身十分消耗性能,不推荐;</li>
<li><strong>优秀的解决方法:</strong>取index为头、中、位的三个数据排序后的中位数;如下图所示,按下标值取出的三个数据为:92,31,0,经排序后变为:0,31,92,取其中的中位数31作为<strong>枢纽</strong>(当(length-1)/2不整除时可向下或向上取整):</li>
</ul>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/24.png" alt="image-20200305182934710" loading="lazy"></p>
<p><strong>实现枢纽选择:</strong></p>
<pre><code>//交换两个位置的数据
let swap = function(arr, m, n){
let temp= arr
arr = arr
arr = temp
}
//快速排序
//1.选择枢纽
let median = function(arr){
//1.取出中间的位置
let center = Math.floor(arr.length / 2)
let right = arr.length - 1
let left = 0
//2.判断大小并进行交换
if (arr > arr) {
swap(arr, left, center)
}
if (arr > arr){
swap(arr, center, right)
}
if (arr > arr) {
swap(arr, left, right)
}
//3.返回枢纽
return center
}
</code></pre>
<p>数组经过获取枢纽函数操作之后,选出的3个下标值对应的数据位置变为:</p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/25.png" alt="image-20200320091750654" loading="lazy"></p>
<p><strong>动态过程:</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/26.gif" alt="" loading="lazy"></p>
<p><strong>快速排序代码实现:</strong></p>
<pre><code>//2.快速排序
let QuickSort = function(arr){
if (arr.length == 0) {
return []
}
let center = median(arr)
let c = arr.splice(center, 1)
let l = []
let r = []
for (let i = 0; i < arr.length; i++) {
if (arr < c) {
l.push(arr)
}else{
r.push(arr)
}
}
return QuickSort(l).concat(c, QuickSort(r))
}
</code></pre>
<p>算法的巧妙之处在于通过:</p>
<pre><code>QuickSort(l).concat(c, QuickSort(r))
</code></pre>
<p>递归调用<code>QuickSort</code>函数实现了枢纽<code>Center</code>左边数据<code>l</code>和右边数据<code>r</code>的排序;</p>
<p><strong>测试代码:</strong></p>
<pre><code>let arr =
console.log(QuickSort(arr));
</code></pre>
<p><strong>测试结果</strong></p>
<p><img src="https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/28.png" alt="image-20200320092254048" loading="lazy"></p>
<p><strong>快速排序的效率:</strong></p>
<ul>
<li>快速排序最坏情况下的效率:每次选择的枢纽都是最左边或最右边的数据,此时效率等同于冒泡排序,时间复杂度为<strong>O(n<sup>2</sup>)</strong>。可根据不同的枢纽选择避免这一情况;</li>
<li>快速排序的平均效率:为<strong>O(N*logN)</strong>,虽然其他算法效率也可达到O(N*logN),但是其中快速排序是<strong>最好的</strong>。</li>
</ul>
<blockquote>
<p>参考资料:JavaScript数据结构与算法</p>
</blockquote>
</div>
<div id="MySignature" role="contentinfo">
多抽出1分钟来学习,让你的生命更加精彩!<br><br>
来源:https://www.cnblogs.com/AhuntSun-blog/p/12529656.html
頁:
[1]