学过的东西总是容易忘记阿,想要再重头建起来也不是那么容易的一件事。温故而知新,嘿嘿!
今天就把插入排序算法理清。
顾名思义,插入,那首先得要有一个能够插的对象——这就是已经排好了序的一个序列。生活中这种事很常见,比如斗地主,如果是一张牌一张牌地拿在手上的话,普通人的大小王永远是放在两端(这取决于每个人的排序习惯)。
如上图所示,这时候你拿起了一张7,左手上的牌是排好序的,现在你就要帮这个7选择一个合适的位置。这就是插入排序的思想。
再举一个例子,体育课第一节课一般都是要先从高到矮先站位。然后来了一个迟到的学生,这时候就要帮这个学生寻找一个合适的位置,让他也能参与到课堂上来。总不能就一直让他在一边干瞪着吧。哈哈!生活中这些例子都应用到了插入排序算法。
插入排序比较厉害,他的子孙后代在自己的基础上发展出了更多性能改进的算法,比如折半插入排序(二分插入排序)这个“儿子”。他想到“爸爸”这么一个个的对比,真是迟钝又浪费时间,所以他跳跃着对比。省去了很多时间。
折半插入排序JS代码
另外一个“儿子”——希尔排序。这个智商爆表的二儿子,让人很难以理解他是怎么想出来这么处理问题。大家就看图体会一下吧。排序过程图如下:
希尔排序JS代码:
说了三个排序算法,但是不知道什么情况应该使用它们,也就只是纸上谈兵。大家会觉得程哥没诚意,现在就给大家分享一下。
性能问题: 如果目标是把n个元素的序列升序排列,那么采用插入排序存在 最好情况 和 最坏情况 。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如, 量级小于千 ,那么插入排序还是一个不错的选择。
稳定性: 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。
今天就把插入排序算法理清。
顾名思义,插入,那首先得要有一个能够插的对象——这就是已经排好了序的一个序列。生活中这种事很常见,比如斗地主,如果是一张牌一张牌地拿在手上的话,普通人的大小王永远是放在两端(这取决于每个人的排序习惯)。
如上图所示,这时候你拿起了一张7,左手上的牌是排好序的,现在你就要帮这个7选择一个合适的位置。这就是插入排序的思想。
再举一个例子,体育课第一节课一般都是要先从高到矮先站位。然后来了一个迟到的学生,这时候就要帮这个学生寻找一个合适的位置,让他也能参与到课堂上来。总不能就一直让他在一边干瞪着吧。哈哈!生活中这些例子都应用到了插入排序算法。
不讲那么多废话,直接上插入排序JS代码
function insert_sort(arr){
var len = arr.length;
for (var i = 1; i < len; i++){
if (arr[i] < arr[i - 1]){ //发生逆序,往前插入
var temp = arr[i];
var j;
for (j = i - 1;j>=0 && arr[j]>temp; j--){
arr[j+1] = arr[j];
}
arr[j+1] = temp;
}
}
return arr;
}
var startTime = new Date();
var result = insert_sort([2,4,1,3,8,7,4,0,6,10]);
console.log(result);
var endTime = new Date();
console.log(endTime-startTime);
插入排序比较厉害,他的子孙后代在自己的基础上发展出了更多性能改进的算法,比如折半插入排序(二分插入排序)这个“儿子”。他想到“爸爸”这么一个个的对比,真是迟钝又浪费时间,所以他跳跃着对比。省去了很多时间。
折半插入排序JS代码
function insert_binary_sort(arr) { // 插入排序
var temArr = []; // 临时数组,存储已排序项
function getSortTmpIndex(subArr, num) {
var len = subArr.length;
if(0 == len) return 0; // 当数组为空时,返回最开始位置
var mid = Math.ceil(len / 2); // 计算中间元素所在位置
if(mid > len - 1) {
mid = len - 1;
}
if(num == subArr[mid]) { // 相等时直接返回
return mid;
}
if(num > subArr[mid]) { // 向后折半查找
mid++;
return mid + getSortTmpIndex(subArr.slice(mid), num);
}
if(num < subArr[mid]) { // 向前折半查找
return getSortTmpIndex(subArr.slice(0, mid), num);
}
}
for (var i in arr) {
var index = getSortTmpIndex(temArr, arr[i]); // 查找arr[i]在temArr中的位置
console.log('index:', index, ' num:', arr[i], ' arr:', temArr);
temArr.splice(index, 0 , arr[i]); // 将元素插入到查找位置
}
return temArr;
}
var startTime = new Date();
var result = insert_binary_sort([2,4,1,3,8,7,4,0,6,10]);
console.log(result);
var endTime = new Date();
console.log(endTime-startTime);
另外一个“儿子”——希尔排序。这个智商爆表的二儿子,让人很难以理解他是怎么想出来这么处理问题。大家就看图体会一下吧。排序过程图如下:
希尔排序JS代码:
function shell_sort(arr){
len=arr.length;
for(var fraction = Math.floor(len/2) ; fraction>0 ; fraction = Math.floor(fraction/2)){
for(var i = fraction ; i < len ; i++){
for(var j = i - fraction ; j >= 0 && arr[j] > arr[ fraction + j ] ; j -= fraction){
var temp = arr[j];
arr[j] = arr[fraction+j];
arr[fraction+j] = temp;
}
}
}
return arr;
}
var startTime = new Date();
var result = shell_sort([2,4,1,3,8,7,4,0,6,10]);
console.log(result);
var endTime = new Date();
console.log(endTime-startTime);
说了三个排序算法,但是不知道什么情况应该使用它们,也就只是纸上谈兵。大家会觉得程哥没诚意,现在就给大家分享一下。
性能问题: 如果目标是把n个元素的序列升序排列,那么采用插入排序存在 最好情况 和 最坏情况 。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如, 量级小于千 ,那么插入排序还是一个不错的选择。
稳定性: 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。