排序
常见的排序算法
将常见的几种排序手写了一边,一些心得和理解写在了代码的注释中,其中桶排序、计数排序、堆排序未写代码,写了一些自己的理解。
本文参考:
1、排序图解:js排序算法实现
2、五分钟学会一个高难度算法:希尔排序
3、排序算法(九):桶排序
4、堆排序及其分析
let unSortArr = [17, 25, 3, 14, 20, 9]; let unSortArr2 = [1, 2, 3, 4, 5, 6]; //冒泡 function Bubble(preArr) { let temp, arr = preArr.slice(0); for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length - i; j++) { if (arr[j] > arr[j + 1]) { temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } } return arr; } //冒泡改进,对于上次没有交换任何元素时(说明已经有序),可不进行接下来的遍历,减少运行次数 function Bubble2(preArr) { let temp, arr = preArr.slice(0), exchangeFlag = true; for (let i = 0; i < arr.length; i++) { exchangeFlag = false; for (let j = 0; j < arr.length - i; j++) { if (arr[j] > arr[j + 1]) { temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; exchangeFlag = true; } } if (!exchangeFlag) break; } return arr; } // 插入排序 从下标1开始每增1项排序一次,越往后遍历次数越多 function insert(arr) { let res = arr.slice(0), j, temp; for (let i = 1; i < res.length; i++) { j = i - 1; //此处的j+1不应该写为i,因为每次的对比是相邻的两个对比,是变动的,纵观这几种排序,外部循环的值一般只参与控制和判断,不会深入到内部的循环中 while (j >= 0 && res[j + 1] < res[j]) { temp = res[j]; res[j] = res[j + 1]; res[j + 1] = temp; j--; } } return res; } //改进插入排序,每次的交换稍显多余,可先用前一个数值覆盖,最后将需要插入的值赋到合适的地方 function insert2(array) { var len = array.length, i, j, tmp, result; result = array.slice(0); for (i = 1; i < len; i++) { tmp = result[i]; j = i - 1; while (j >= 0 && tmp < result[j]) { result[j + 1] = result[j]; j--; } result[j + 1] = tmp; } return result; } //改进插入排序,可优化的点在于可利用二分法查找应该插入的位置,减少遍历的时间 function insert3(array) { var len = array.length, i, j, tmp, low, high, mid, result; // 赋予数组副本 result = array.slice(0); for (i = 1; i < len; i++) { tmp = result[i]; low = 0; high = i - 1; while (low <= high) { mid = parseInt((low + high) / 2, 10); if (tmp < result[mid]) high = mid - 1; else low = mid + 1; } for (j = i - 1; j >= high + 1; j--) { result[j + 1] = result[j]; } result[j + 1] = tmp; } return result; } //选择排序,其实在写插入排序改进版的时候就有这样的念头,这种交换效率的问题,因为冒泡的交换所持有的待比较的数并不确定 //不能像改进的插入排序那样,最后再选择插入位置。选择排序的核心有点像冒泡,一轮下来,将最大或者最小的放置在边缘 //(遍历时,保存当时的最大或者最小的序号)遍历结束后再进行交换 function selection(array) { let k, temp, res = array.slice(0); for (let i = 0; i < res.length; i++) { k = i; for (let j = i + 1; j < res.length; j++) { if (res[j] < res[k]) k = j; //保存当前最大的序号 } if (k !== i) { //序号不一致则交换 temp = res[i]; res[i] = res[k]; res[k] = temp; } } return res; } //快排:选取合适的中间数,比其小的放一侧,大的放另一侧,再迭代 function quick(arr) { if (arr.length <= 1) return arr; let mid, left = [], right = []; mid = arr.splice(Math.floor(arr.length / 2), 1); for (let i = 0; i < arr.length; i++) { if (arr[i] < mid) left.push(arr[i]); else right.push(arr[i]); } return quick(left).concat(mid, quick(right)); } //希尔排序:选取不同的增量进行多次插入排序 //对于增量的选取,只要最后增量为1即可,回想希尔排序的实质,就是利用插入排序对有序序列效率高(运行次数小)这一点 //进行改进,让其在增量为1(插入排序增量一直为1)时,尽可能的有序,来减少遍历判断移动的次数 function shell(arr) { let i, j, temp, len = arr.length, gap, res = arr.slice(0); gap = parseInt(len / 2); while (gap > 0) { for (i = gap; i < len; i++) { temp = res[i]; j = i - gap; while (j >= 0 && temp < res[j]) { res[j + gap] = res[j]; j = j - gap; } res[j + gap] = temp; } gap = parseInt(gap / 2); } return res; } //归并排序,对无序数组拆分成若干个小数组(最小数组长度为1)进行排序,再把一个一个小数组合并,合并的时候根据大小顺序合并 function merge(arr) { function Sort(_arr) { if (_arr.length == 1) return _arr; let len = _arr.length, mid = parseInt(len / 2), left = _arr.slice(0, mid), right = _arr.slice(mid, len); return mergeArr(Sort(left), Sort(right)); } function mergeArr(left, right) { let res = []; while (left.length || right.length) { //left和right肯定是已经排好序的,且left和right的长度最多相差1(sort函数中切分数组取的是中间mid),故分下三种情况 if (left.length && right.length) { if (left[0] < right[0]) { res.push(left.shift()); } else { res.push(right.shift()); } } else if (left.length) { res.push(left.shift()); } else { res.push(right.shift()); } } return res; } return Sort(arr); } console.log(merge(unSortArr));
- 对于堆排序,主要是利用大根堆这个数据结构(最大元素在堆顶),主要步骤为:
1、调整二叉树,形成大根堆(一种完全二叉树)
2、交换堆顶元素跟最后元素位置,最后元素弹出堆。然后继续回到1,调整堆,
3、重复1、2步骤,直至所有元素出堆,得到的就是有序的序列。 - 计数排序:根据取值的值域开辟连续的空间,遍历无需序列,依次累加相应的值次数,最后按值域大小依次输出,典型例子:统计分数(开辟分数值域数组,记录每个分数的学生个数,再按分数的高低输出)
- 对于桶排序,其实是一种计数排序的优化,弱化了计数排序中空间的浪费,通俗些说,增大了桶,减少了桶的数量,即最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况,之后每个桶再进行排序,排序的算法可以是之前的任何一种,桶排序算法的复杂度和稳定性,都根据选择的排序算法不同而不同。
不同算法的时间空间复杂度分析
参考链接八大排序算法时间和空间复杂度