1.冒泡排序
冒泡算法算是排序里面比较基础的一种算法。
它的实现原理为:从第一个元素开始,将它与下一个索引元素比较。如果当前元素的值比下一个元素的值要大,则两者交换位置,否则位置不变。接下来,索引往后移一位,继续比较接下来两个数的值。一直重复上面的操作,直到比较到最后一个元素,因为大的元素位置会交换位置向后移,所以,最后一个元素已经是最大的。那么下次在比较的时候,不需要比较最后一个元素,只需要比较到length-2的位置
分步解释一下过程:
- 最开始要从位置为0开始比较到整个length。那我们设置 i = length - 1;如果这次排序流程走完,那么最后数组最后一位已经是最大值,接下来只需要比较到length-2。重复以上操作。
- 那么,i 限定了每次比较的位置。接下来我们来设置另外一个变量 j,来控制每次相邻两个元素是否交换。代码如下:
//首先我们先声明两个通用函数,在下面的排序算法中都会用到。
function checkArray(array){
if (Object.prototype.toString.call(array) !== "[object Array]") return;
}
function swap(array,left,right){
let rightValue = array[right];
let array[right] = array[left];
let array[left] = rightValue;
}
//冒泡排序
function bubble(array){
checkArray(array);
for (let i=array.length-1;i>0;i--){
for (let j=0;j<i;j++){
if (array[j]>array[j+1]){
swap(array,j,j+1)
}
}
}
return array
}
复制代码
排序过程如图所示:
2.插入排序
插入排序原理: 从整个待排序列中选出一个元素插入到已经有序的子序列中去,得到一个有序的、元素加一的子序列,直到整个序列的待插入元素为0,则整个序列全部有序。
分步来说:
- 我们首先默认第一个元素是已经有序的(因为只有一个元素)。
- 我们将后面的未排序的元素逐渐插入到有序的序列中。即第二个和第一个有序的比较,如果比第一个小,则插入到第一个有序元素的前面。我们用i来控制每次待比较的元素。用j来控制每个元素大小的对比。
- 接下来到第三个元素,和前面两个有序的元素比较,将第三个插入到前面两个有序的序列中,使得前三个序列有序。
- 重复3操作,直到整个序列有序。
function insertion(array){
checkArray(array)
for (let i=1;i<array.length;i++){
for (let j=i-1;j>=0 && array[j]>array[j+1];j--){
swap(array,j,j+1);
}
}
return array
}复制代码
插入排序过程如图所示:
3.选择排序
选择排序原理:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放待排序序列的起始位置(或末尾位置),直到全部待排序的数据元素排完。
分步说明:
1.找出带排序元素中的最小值。如何找?我们可以先假设最小值的索引为0,如果取出的值要比最小索引上面的值小,则将最小值与索引为0的值交换。这样索引为0的值就是最小值。
2.将索引置为1,然后向后找出,比索引1上的值小的,然后与索引1上的值交换。
3.重复步骤2,直到整个序列有序。
function selector(array){
checkArray(array)
for (let i=0;i<array.length-1;i++){
let minIndex = i
for(let j=i+1;j<array.length;j++){
minIndex = array[j] > array[minIndex]?minIndex:j;
}
swap(array,i,minIndex)
}
return array
}复制代码
选择排序过程如图:
4.归并排序
归并原理:归并排序的原理是分而治之。先将数组两两分开直到最多包含两个元素,然后进行排序。之后在进行合并,将两两合并的数列进行比较然后排序。直到排序完成。
分步如下:
1.假如我们有这样一组序列 [1,2,6,3,8,4,7,5]
2.先分解数列成左右两部分[1,2,6,3]和[8,4,7,5]
3.继续分解左边的数组为[1,2]和[6,3]
4.排序上面的数组为[1,2]和[3,6].
5.在排序数组[1,2,3,6]
6.左边排序完成,然后进行右边数组的排序,过程不变.
代码如下:
function mergeSortArray(array){
checkArray(array)
mergeSort(array,0,array.length-1)
return array
}
function mergeSort(array,left,right){
if (left === right) return;
let mid = parseInt(left + (right-left)/2) ;
mergeSort(array,left,mid);
mergeSort(array,mid+1,right)
let tmp = [];
let p1 = left;
let p2 = mid + 1;
let i = 0;
while(p1 <= mid && p2 <= right){
tmp[i++] = array[p1] < array[p2] ? array[p1++]:array[p2++]
}
while (p1 <= mid){
tmp[i++] = array[p1++]
}
while (p2 <= right){
tmp[i++] = array[p2++]
}
for (let i=0;i<tmp.length;i++){
array[left + i] = tmp[i]
}
return array
}复制代码
排序算法如图:
5.快速排序
快速排序原理:快速排序算法应该算是排序算法里比较复杂的一种算法了
快速排序算法的原理简单来说,就是我们找出一个基准值(以第一个数为基准值):使得基准值的左边的数都比基准值小,右边的数都比基准值大。接下来对基准值左边的序列,以第一个数为基准值,使得基准值左边的数比基准值小,后面的数比基准值大。
所以问题就来到了,如何使得基准值的左边比基准值小,右边比基准值大呢?
一般方法有两钟,一种是挖坑法,一种是指针交换法。我们来介绍一下指针交换法如何实现。
1.如果有这样一个数组[4,2,5,3,6,1]
我们首先选第一个数6为基准数。
2.首先我们规定两个指针分别指向数组的左边和右边。简称哨兵左和哨兵右。
3.我们首先(重要)从哨兵右开始,从右往左移动,直到找到第一个比基准数小的数,然后停止,很幸运,第一次就找到了,为1。
4.接下来我们哨兵左开始行动,从左往右找,直到找到一个比基准数大的数,然后停止,此时为5.
5.接下来,交换哨兵所在位置的元素 即数组变成 4,2,1,3,6,5
6.接下来继续,先移动哨兵左,(在元素为5的位置),继续找比基准元素小的,此时找到了 3,然后停下。
7.哨兵右开始从左往右找,发现,当移动到3的时候,哨兵左和哨兵右相遇了。此时将基准值与哨兵位置上的值做交换。得到了3,2,1,4,6,5
这样使得基准值左边的值都比基准值小,右边的值都比基准值大。接下来对3,2,1进行同样的操作。直到长度为1的时候返回。
所以代码如下:
function quickSort(array){
checkArray(array);
if (arr.length<2) return;
let left = 0;
let right = arr.length-1;
while (left<right){
while(array[right] > array[0] && left < right ){
right = right -1;
}
while(array[left] < array[0] && left < right) {
left = left + 1;
}
// 两个指针相遇了,就将基准值与哨兵位置上的值交换
if (left === right) {
swap(array,0,left)
}
//找到了,左边指针小于右边指针,就交换上面的值
swap(array,left,right)
}
// 递归实现,获取哨兵左边的值,哨兵位置上的值,哨兵右边的值
return quickSort(arr.slice(0,left)).concat(arr.slice(left,right+1)).concat(quickSort(arr.slice(right+1)));
}复制代码
排序过程如图:
6.堆排序
首先我们要理解什么是完全二叉树?
- 从作为第一层的根开始,除了最后一层之外,第N层的元素个数都必须是2的N次方;第一层2个元素,第二层4个,第三层8个,以此类推。
- 而最后一行的元素,都要紧贴在左边,换句话说,每一行的元素都从最左边开始安放,两个元素之间不能有空闲,具备了这两个特点的树,就是一棵完全二叉树。
那么完全二叉树和堆又有什么关系呢?
我们假设有一棵完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值;这样层层递推,就是根节点的值最小,这样的树,称为小根堆。
左边为大根堆,右边则为小根堆。
所谓的堆排序,就是我们要将给我们的数组模拟成堆的数据结构,然后将最大的根节点,与最后一个节点交换。交换过后,继续维护堆结构,使得根节点上的数又是最大数,然后继续交换,直到排序完成。
1.假如给定无序序列:节点的左边子节点索引是 i * 2 + 1
,右边是 i * 2 + 2
,父节点是 (i - 1) /2
我们分步来看,首先构造一个大根堆:
2.由于叶子节点没有元素去移动位置,所以我们要从最后一个非叶子节点来开始调整。即我们从6开始,比较其子元素和父元素的大小,如果子元素大于父元素,则两者交换位置。交换位置后,继续比较,保证父元素比子元素大就行。
3.继续找非叶节点,9,4,8中9最大,所以和根元素交换位置
4.交换完成后,要继续看是不是每个非叶子节点都满足最大堆的要求,如果不满足,就继续交换位置,直到满足最大堆的要求。
5.第一次构建最大堆完成,接下来就是交换的过程。
6.将最大堆的根元素,与末尾的值交换。
7.然后使最大堆的size(5-1)减1.
8.继续调整结构,使其满足最大堆的定义
9.将最大堆的根元素8与末尾元素5交换。
10.将最大堆的size减1(4-1)。然后继续上面的过程,直到size=0停止。
以上就是堆排序的流程,接下来,是代码时间:
function heap(array){
checkArray(array);
//第一次将无序数组排列成大根堆
for (let i=0;i<array.length;i++){
heapInsert(array,i)
}
let size = array,length
swap(array,0,--size)
while(size>0){
heapify(array,0,size);
swap(array,0,--size);
}
return array
}
function heapInsert(array,index){
while(array[index] > array[parseInt((index-1)/2)]){
swap(array,index,parseInt((index-1)/2));
//将索引变成父节点
index = parseInt((index-1)/2)
}
}
function heapify(array,index,size){
let left = 2*index + 1
while (left > size){
//先判断左右子节点的大小
let largest = left + 1>size && array[left] > array[left+1]?left:left+1
//再判断子节点和父节点的大小
largest = array[index] < array[largest]?largest:index;
if (largest === index) break;
swap(array,index,largest);
index = largest;
left = 2*index + 1
}
}复制代码
排序如图所示: