参考博客:
1、各种排序算法总结(全面)
2、[算法总结] 十大排序算法
(假设以下算法的排序方向均是:从小到大。数组元素的个数为:n)
一、冒泡排序
1、原理:
——待排元素个数为n
(1)先把n个中,最大的元素排到最后,待排元素个数为n-1。【一个元素的位置是确定的】
(2)在把n-1个中,第二大的元素排到待排元素的最后,待排元素个数为n-2。【确定了两个元素的位置】
……
(n-1)重复以上步骤n-1趟,直到待排元素的个数为1。【确定了n-1个元素的位置,剩下那个元素就不用确定了吧?省了一趟循环】
举例:arr=[2,6,4,10,3]
第(1)趟:2比6小,不交换;6比4大,交换;6比10小,不交换;10比3大,交换
此时数组变成:[2,4,6,3,10],10的位置是确定的了!
第(2)趟:2比4小,不交换;4比6小,不交换;6比3大,交换;
此时数组变成:[2,4,3,6,10],6的位置是确定的了!
第(3)趟:2比4小,不交换;4比3大,交换;
此时数组变成:[2,3,4,6,10],4的位置是确定的了!
第(4)趟:2比3小,不交换
此时数组变成:[2,3,4,6,10],3的位置是确定的了!
2的位置自然也确定了!
2、代码:
将待排元素中最大值放到最后,其实就是相邻元素两两进行比较,如果arr[j]>arr[j+1],交换它们的位置。直到j+1==arr.length-1,一趟循环结束
<script>
let arr = [2,6,4,10,3];
console.log(bubbleSort(arr));
/**
* @param {array} arr
*/
function bubbleSort(arr){
// 要确定原数组中,哪一个元素的位置
for(let i=arr.length-1; i>0; i--){
// 待排元素的范围:为什么 j 要小于i,为了保证 j+1 不越界
for(let j=0; j<i; j++){
if(arr[j]>arr[j+1]) swap(arr, j, j+1);
}
}
return arr;
}
/**
* @param {array} arr
* @param {int} i
* @param {int} j
*/
function swap(arr, i, j){
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
</script>
元素之间位置交换,在排序算法中,是很常用的。所以,这里把它单独写成一个swap函数,增加代码的复用性。
3、优化
如果把数组换成一个相对有序的数组,比如:[2,10,3,4,5,6],是不是还要遍历这么多趟?
很显然,这个数组只需要一趟遍历,就已经是有序的了。
【思考】:可不可以增加一个标志位change,当数组已经有序时,结束循环。
<script>
function bubbleSort(arr){
for(let i=arr.length-1; i>0; i--){
let change = false; // 标志位
for(let j=0; j<i; j++){
if(arr[j]>arr[j+1]){
swap(arr, j, j+1);
change = true; // 修改标志位
}
}
if(change == false) break; // 标志位。结束循环
}
return arr;
}
</script>
[2,10,3,4,5,6]
第一趟循环结束后,数组已经有序;
第二趟循环结束后,并不会修改标志位;所以会结束循环,
不会执行第三趟。
二、简单选择排序
1、原理
跟冒泡排序其实很相似,也是两两之间比较,但是此时并不会交换,而是记录下来;
当遍历完整个未排序序列后,找到未排序序列中的最值才进行交换。
【可以认为选择排序是冒泡排序的一种改进】
假设:数组的前边是已排序序列,后边是未排序序列。
因为是从小到大排序,所以我们从后边的序列中,选出min值,排到已排序序列的末尾(即:跟已排序序列的后一位元素进行交换)
比如:arr=[4,3,2,5,6]
第(1)趟:已排序序列为空,[],未排序序列为:[4,3,2,5,6],选出min值:2,跟已排序序列的后一位元素,即,跟4交换。[2,3,4,5,6]
第(2)趟:已排序序列为空,[2],未排序序列为:[3,4,5,6],选出min值:3,跟已排序序列的后一位元素,即,跟3交换。[2,3,4,5,6]
第(3)趟:已排序序列为空,[2,3],未排序序列为:[4,5,6],选出min值:4,跟已排序序列的后一位元素,即,跟4交换。[2,3,4,5,6]
第(4)趟:已排序序列为空,[2,3,4],未排序序列为:[5,6],选出min值:5,跟已排序序列的后一位元素,即,跟5交换。[2,3,4,5,6]
第(5)趟:已排序序列为空,[2,3,4,5],未排序序列为:[6],选出min值:6,跟已排序序列的后一位元素,即,跟6交换。[2,3,4,5,6]
即使整个数组已经有序,但是只要未排序序列不为空,就仍然会寻找min值。
如果说比冒泡排序改进了什么的话,应该是减少交换次数?
2、代码
function selectSort(arr){
// 已排序序列的右区间+1。
// i是从0开始,已排序右区间最开始应该理解成-1?
for(let i in arr){
// 假设未排序序列的第一个元素为min
let min_index = i;
// 未排序序列的左区间
for(let j=i; j<arr.length; j++){
min_index = arr[j] < arr[min_index] ? j : min_index;
}
swap(arr, i, min_index);
}
return arr;
}
三、插入排序
1、原理
跟选择排序也很相似,将数组分为已排序序列、未排序序列两部分。不过,这里默认第一个元素是已排序,而前面的选择排序是默认已排序序列为空(因为在选择排序中,已排序序列中的位置,一旦确定就不改变)
1、取出未排序序列的第一个元素key,在已排序序列中,从后往前扫描,一旦遇到比key大的元素arr[j],就往后挪一位(因为key比arr[j]小,肯定在前面,要给key腾出一个位置)。
2、直到找到一个元素**arr[j]**比key小,那key就放在arr[j]的后面。arr[j+1]=key
3、如果遍历完已排序序列都没有找到一个比key小的值,说明key要放在最前面
2、代码
function insertSort(arr){
// 未排序序列
for(let i=1; i<arr.length; i++){
// 记录下要排序的元素
let key = arr[i];
// 已排序序列
for(var j=i-1; j>=0 && key < arr[i]; j--){
arr[j+1] = arr[j];
}
// 跳出循环的条件:j<0 || 遇到比key小的值
arr[j+1] = key;
}