【原标题:排序算法归并排序及其优化 数据结构和算法排序算法】排序介绍:
一旦我们将数据放置在某个数据结构(比如数组)中存储起来后,就可以根据需求对数据进行不同方式的排序:
比如对姓名按字母排序
对商品按照价格排序
etc.
排序算法又分为简单排序和高级排序。其中简单排序包括冒泡排序、选择排序和插入排序。高级排序包括希尔排序、归并排序和快速排序。【⚠️这里仅介绍了六种排序算法】
下面我们逐个排序算法来讲解下:
冒泡排序之所以叫冒泡排序,是因为使用这种排序算法时,数据值就会像气泡那样从数组的一端漂浮到另一端。假设正在将一组数字按照升序排列,较大的值会浮动在数组的右侧,而较小的值则会浮动到数组的左侧。产生这种冒泡的现象是因为算法会多次在数组中移动过,比较相邻的数据,当左侧值大于右侧值的时候将它们互换。
⚠️ 后面讲到的排序算法如无说明,则默认为升序
比如下面的简单列表的例子。
E A D B H
经过第一次的排序后,列表会变成:
A E D B H
前面两个元素进行了交互。接下来再次排序:
A D E B H
第二个元素和第三个元素进行了交互。继续进行排序:
A D B E H
第三个元素和第四个元素进行了交换。这一轮最后进行排序:
A D B E H
因为第四个元素比最后一个元素小,所以比较后保持所在位置。反复对第一个元素执行上面的操作(已经固定的值不参与排序,如第一轮固定的H不参与第二轮的比较了),得到如下的最终结果:
A B D E H
相关的动效图如下:
bubble-sort-gif
关键代码如下:
bubbleSort(){ let numElements = this.arr.length; for(let outer = numElements-1; outer >= 2; --outer){ for(let inner = 0; inner <=> this.arr[inner+1]){ this.swap(inner, inner+1); // 交换数组两个元素 } } }}
选择排序选择排序从数组的开头开始,将第一个元素和其它元素进行比较。检查完所有的元素之后,最小的元素会被放在数组的第一个位置,然后算法会从第二个位置继续。这个过程进行到数组的倒数第二个位置时,所有的数据便完成了排序。
原理:
选择排序用到双层嵌套循环。外循环从数组的第一个元素移动到倒数第二个元素;内循环从当前外循环所指元素的第二个元素开始移动到最后一个元素,查找比当前外循环所指元素小的元素。每次内循环迭代后,数组中最小的值都会被赋值到合适的位置。
下面是对五个元素的列表进行选择排序的简单例子。初始列表为:
E A D H B
第一次排序会找到最小值,并将它和列表的第一个元素进行交换:
A E D H B
接下查找第一个元素后面的最小值(第一个元素此时已经就位),并对它们进行交换:
A B D H E
D已经就位,因此下一步会对E H进行互换,列表已按顺序排列好如下:
A B D E H
通过gif图可能容易理解:
selection-sort-gif
关键代码如下:
selectionSort(){ let min, numElements = this.arr.length; for(let outer = 0; outer <= numElements-2; outer++){ min = outer; for(let inner = outer+1; inner <= numElements-1; inner++){ if(this.arr[inner] < this.arr[min]){ min = inner; } } this.swap(outer, min); }}
插入排序插入排序类似我们按照数字或字母的顺序对数据进行降序或升序排序整理~
原理:
插入排序也用了双层的嵌套循环。外循环将数组挨个移动,而内循环则对外循环中选中的元素以及内循环数组后面的那个元素进行比较。如果外循环中选中的元素比内循环中选中的元素要小,那么内循环的数组元素会向右移动,腾出一个位置给外循环选定的元素。
上面表达的晦涩难懂。简单来说,插入排序就是未排序的元素对已经排序好的序列数据进行合适位置的插入。如果还是不懂,结合下面的排序示例来理解下:
下面对五个元素进行插入排序。初始列表如下:
E B A H D
第一次插入排序,第二个元素挪动到第一位:
B E A H D
第二次插入排序是对A进行操作:
B A E H DA B E H D
第三次是对H进行操作,因为它比之前的元素都大,所以保持位置。最后一次是对D元素进行插入排序了,过程和最后结果如下:
A B E D HA B D E H
相关的gif图了解一下:
gif
相关代码如下:
insertionSort(){ let temp, inner, numElements = this.arr.length; for(let outer = 1; outer <= temp="this.arr[outer];" inner=""> 0 && this.arr[inner-1] >= temp){ this.arr[inner] = this.arr[inner-1]; inner--; } this.arr[inner] = temp; }}
希尔排序希尔排序是插入排序的优化版,但是,其核心理念与插入排序不同,希尔排序会首先比较距离较远的元素,而非相邻的元素。
原理:
希尔排序通过定义一个间隔序列来表示数据在排序过程中进行比较的元素之间有多远的间隔。我们可以动态定义间隔序列,不过对于大部分的实际应用场景,算法用到的间隔序列可以提前定义好。
如下演示希尔排序中,间隔序列是如何运行的:
how-hash-sort-run
通过下面的gif图你也许会更好理解:
hash-sort-gif
实现的代码:
shellSort(){ let temp, j, numElements = this.arr.length; for(let g = 0; g < this.gaps.length; ++g){ for(let i = this.gaps[g]; i < numElements; ++i){ temp = this.arr[i]; for(j = i; j >= this.gaps[g] && this.arr[j - this.gaps[g]] > temp; j -= this.gaps[g]){ // 之前的已经拍好序的了 this.arr[j] = this.arr[j - this.gaps[g]]; } this.arr[j] = temp; // 这里和上面的for循环是互换两个数据位置 } }}