1 冒泡
冒泡排序的过程,就是从第一个元素开始,重复比较相邻的两个项,若第一项比第二项更大,则交换两者的位置;反之不动。
每一轮操作,都会将这一轮中最大的元素放置到数组的末尾。假如数组的长度是 n,那么当我们重复完 n 轮的时候,整个数组就有序了。
冒泡排序的时间复杂度
我们分最好、最坏和平均来看:
- 最好时间复杂度:它对应的是数组本身有序这种情况。在这种情况下,我们只需要作比较(n-1 次),而不需要做交换。时间复杂度为 O(n)
- 最坏时间复杂度: 它对应的是数组完全逆序这种情况。在这种情况下,每一轮内层循环都要执行,重复的总次数是 n(n-1)/2 次,因此时间复杂度是 O(n^2)
- 平均时间复杂度:这个东西比较难搞,它涉及到一些概率论的知识。实际面试的时候也不会有面试官摁着你让你算这个,这里记住平均时间复杂度是 O(n^2) 即可。
function bubbleSort(arr) {const len = arr.length;for (let i = 0; i < len; i++) {for (let j = 0; j < len - 1; j++) {if (arr[j] > arr[j + 1]) {[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]}}}return arr}//改进 数组的后n个元素 已经有序了function bubbleSort2(arr) {const len = arr.length;for (i = 0; i < len; i++) {for (let j = 0; j < len - 1 - i; j++) {if (arr[j] > arr[j + 1]) {[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]}}}return arr}//最好情况function bubbleSort3(arr){const len = arr.length;for(let i=0;i<len;i++){let flag = false;for(let j=0;j<len-1-i;j++){if(arr[j]>arr[j+1]){[arr[j],arr[j+1]]=[arr[j+1],arr[j]]flag =true}}if(flag == false) return arr;}return arr}
2 选择排序
选出最小值 和该序列的第一个交换位置
选择排序的时间复杂度
在时间复杂度这方面,选择排序没有那么多弯弯绕绕:最好情况也好,最坏情况也罢,两者之间的区别仅仅在于元素交换的次数不同,但都是要走内层循环作比较的。因此选择排序的三个时间复杂度都对应两层循环消耗的时间量级: O(n^2)。
function selectSort(arr) {// 缓存数组长度const len = arr.length// 定义 minIndex,缓存当前区间最小值的索引,注意是索引let minIndex// i 是当前排序区间的起点for(let i = 0; i < len - 1; i++) {// 初始化 minIndex 为当前区间第一个元素minIndex = i// i、j分别定义当前区间的上下界,i是左边界,j是右边界for(let j = i; j < len; j++) {// 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 jif(arr[j] < arr[minIndex]) {minIndex = j}}// 如果 minIndex 对应元素不是目前的头部元素,则交换两者if(minIndex !== i) {[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]}}return arr}
3 插入排序
前面的都是有序的 拿到一个数后 开始往前比较 如果遇到比它 大的则将大的移到它的位置 然后前面的位置空出来 在继续往前比较 知道找到正确的 位置
function inserSort(arr) {const len=arr.length;let tempfor (let i = 1; i < len; i++) {temp= array[i];let j =i;while(j>0&&temp<arr[j-1]){arr[j] = arr[j-1];j--;}arr[j]=temp;}return arr}
插入排序的时间复杂度
- 最好时间复杂度:它对应的数组本身就有序这种情况。此时内层循环只走一次,整体复杂度取决于外层循环,时间复杂度就是一层循环对应的 O(n)。
- 最坏时间复杂度:它对应的是数组完全逆序这种情况。此时内层循环每次都要移动有序序列里的所有元素,因此时间复杂度对应的就是两层循环的 O(n^2)
- 平均时间复杂度:O(n^2)
归并排序
从中间分割 在分割 分割到最小单元在有序合并
log(n) 轮对应 log(n) 次合并操作,因此归并排序的时间复杂度就是 O(nlog(n))
function mergeSort(arr) {const len = arr.length// 处理边界情况if(len <= 1) {return arr}// 计算分割点const mid = Math.floor(len / 2)// 递归分割左子数组,然后合并为有序数组const leftArr = mergeSort(arr.slice(0, mid))// 递归分割右子数组,然后合并为有序数组const rightArr = mergeSort(arr.slice(mid,len))// 合并左右两个有序数组arr = mergeArr(leftArr, rightArr)// 返回合并后的结果return arr}function mergeArr(arr1, arr2) {// 初始化两个指针,分别指向 arr1 和 arr2let i = 0, j = 0// 初始化结果数组const res = []// 缓存arr1的长度const len1 = arr1.length// 缓存arr2的长度const len2 = arr2.length// 合并两个子数组while(i < len1 && j < len2) {if(arr1[i] < arr2[j]) {res.push(arr1[i])i++} else {res.push(arr2[j])j++}}// 若其中一个子数组首先被合并完全,则直接拼接另一个子数组的剩余部分if(i<len1) {return res.concat(arr1.slice(i))} else {return res.concat(arr2.slice(j))}}
快速排序
选基线条件 大于 小于分组 递归
平均O(nlogn) 最糟:O(n2)
function quickSort(array) {if (array.length <= 1) {return array;}const pivotIndex = Math.floor(array.length / 2)console.log(pivotIndex);const pivot = array.splice(pivotIndex,1)[0]// 0 数组扁平化const left = [], right = [];array.forEach(element => {if (element < pivot) {left.push(element);} else {right.push(element);}});return quickSort(left).concat([pivot], quickSort(right));}
