算法(一)搜索排序
1. 定义
排序:把某个乱序的数组编程升序或者降序的数组
搜索:找出数组中某个元素的下标
JS中的排序:数组的sort方法
JS中的搜索:数组的indexOf方法
排序算法:
- 冒泡排序
- 选择排序
- 插入排序
- 归并排序
- 快速排序
- …
搜索算法:
2. 排序算法
2-1 冒泡排序 bubbleSort
最简单的一种排序算法,但性能不太好
- 比较所有相邻元素,如果第一个比第二个大,则交换他们
一轮下来,可以保证最后一个数是最大的
执行n-1论,就可以完成排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Array.prototype.bubbleSort = function() { for (let i =0; i < this.length - 1; i++) { for (let j = 0; j < this.length - 1; j++) { if (this[j] > this[j + 1]) { const temp = this[j]; this[j] = this[j+1]; this[j+1] = temp; } } } } const arr = [5,4,3,2,1]; arr.bubbleSort()
|
两个嵌套循环
时间复杂度:O(n^2)
时间复杂度:
- 最好情况:O(n)
- 平均情况:O(n^2)
- 最坏情况:O(n^2)
稳定性:稳定
额外的空间:O(1)
原地排序,不需要额外空间
2-2 选择排序 selectionSort
选择排序 vs 冒泡排序:
- 选择排序是记录这一轮遍历数组里的最小/最大值,遍历完成后将这个最值放在起始位置,只需要一次交换
- 冒泡排序是两两之间进行交换,需要交换很多次
- 找到数组中得知最小值,选中它并将其放置在第一位
- 接着找到第二小的值,选中它并将其放置在第二位
- 以此类推,执行n-1轮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Array.prototype.selectionSort = function() { for (let i = 0; i < this.length - 1; i++) { let indexMin = i; for (let j = i; j < this.length; j++) { if(this[j] < this[indexMin]) { indexMin = j; } } if (indexMin !== i) { const temp = this[i]; this[i] = this[indexMin]; this[indexMin] = temp; } } } const arr = [5,4,3,2,1]; arr.selectionSort()
|
两个嵌套循环
时间复杂度:O(n^2)
时间复杂度:
- 最好情况:O(n^2)
- 平均情况:O(n^2)
- 最坏情况:O(n^2)
稳定性:不稳定
额外的空间:O(1)
2-3 插入排序 insertionSort
- 从第二数开始往前比
- 比它大就往后排
- 依次类推进行到最后一个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Array.prototype.insertionSort = function() { for (let i = 1; i < this.length; i++) { const temp = this[i]; let j = i; while(j) { if(this[j-1] > temp) { this[j] = this[j-1]; } else { break; } j--; } this[j] = temp; } } const arr = [5,4,3,2,1]; arr.insertionSort()
|
两个嵌套循环
时间复杂度:O(n^2)
2-4 归并排序 mergeSort
比前面几个排序性能都要好,其中火狐浏览器的sort就是使用的归并排序
- 分:把数组劈成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数
- 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子树组合并为一个完整数组
- 新建一个空数组res,用于存放最终排序后的数组
- 比较两个有序数组的头部,较小者出队并推入res中
- 如果两个数组还有值,就重复第二步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| Array.prototype.mergeSort = function() { const rec = (arr) => { if (arr.length === 1) return arr; const mid = Math.floor(ar.length / 2); const left = arr.slice(0, mid); const right = arr.slice(mid, arr.length); const orderLeft = rec(left); const orderRight = rec(right); const res = []; while (orderLeft.length || orderRight.length) { if (orderLeft.length && orderRight.length) { res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift()) } else if (orderLeft.length) { res.push(orderLeft.shift()) } else if (orderRight.length) { res.push(orderRight.shift()) } } return res; } const res = rec(this); res.forEach(n, i) => { this[i] = res; } } const arr = [5,4,3,2,1]; arr.mergeSort()
|
分的时间复杂度是O(logN)
合的时间复杂度是O(n)
∴ 时间复杂度为 O(n * logN)
- 时间复杂度:
- 最好情况:O(nlogn)
- 平均情况:O(nlogn)
- 最坏情况:O(nlogn)
- 稳定性:稳定
- 额外的空间:O(n)
2-5 快速排序 quickSort
比前三种排序性能都要好
- 分区:从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在基准的后面
- 递归:递归地对基准前后的子数组进行分区
第一轮之后的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Array.prototype.quickSort = function() { const rec = () => { if (arr.length === 1) return arr; const left = []; const right = []; const mid = arr[0]; for (let i = 1; i < arr.length; i++) { if (arr[i] < mid) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...rec[left], mid, ...rec(right)]; } const res = rec(this); res.forEach(n, i) => { this[i] = res; } } const arr = [5,4,3,2,1]; arr.quickSort()
|
递归的时间复杂度是 O(logN)
分区操作的时间复杂度是 O(n)
∴ 时间复杂度为 O(n*logN)
3. 搜索算法
3-1 顺序算法 sequentialSearch
性能很差
- 遍历数组
- 找到跟目标值相等的元素,就返回其下标
- 遍历结束后,如果没有搜索到目标值,就返回-1
1 2 3 4 5 6 7 8 9
| Arrary.prototype.sequentialSearch = function(item) { for (let i = 0; i < this.length; i++) { if (this[i] === item) { return i; } } return -1; } const res = [1, 2, 3, 4, 5].sequentialSearch(3)
|
遍历数组是一个循环操作
时间复杂度:O(n)
3-2 二分搜索 binarySearch
在==有序==排序的数组中搜索
- 从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束
- 如果目标值大于或小于中间元素,则在大于或小于中间元素的那一般数组中搜索
- 否则返回-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Arrary.prototype.binarySearch = function(item) { let low = 0; let hight = this.length - 1; while (low <= hight) { const mid = Math.floor((low + hight) / 2); const element = this[mid]; if (element < item) { low = mid + 1; } else if (element > item) { hight = mid - 1; } else { return mid; } } return -1; } const res = [1, 2, 3, 4, 5].binarySearch(3)
|
每一次比较都使搜索范围缩小一般
时间复杂度:O(logN)
4. LeetCode题
21. 合并两个有序链表
解题思路:
- 与归并排序中的合并有序数组很相似
- 将数组替换成链表
解题步骤:
- 新建一个新链表,作为返回结果
- 用指针遍历两个有序链表,并比较两个链表的当前节点,较小者先接入新链表,并将指针后移一步
- 链表遍历结束,返回新链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var mergeTwoLists = function(list1, list2) { const res = new ListNode(); let p = res; let p1 = list1; let p2 = list2; while (p1 && p2) { if (p1.val < p2.val) { p.next = p1; p1 = p1.next; } else { p.next = p2; p2 = p2.next; } p = p.next; } if (p1) { p.next = p1; } if (p2) { p.next = p2; } return res.next; };
|
374. 猜数字大小
解题思路:
- 二分搜索
- 调用guess函数,来判断中间元素是否是目标值
解题步骤:
- 从数组的中间元素开始,如果中间元素正好是目标值,则搜索过程结束
- 如果目标值大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var guessNumber = function (n) { let low = 1; let high = n; while (low <= high) { const mid = Math.floor((low + high) / 2); const res = guess(mid); if (res === 0) { return mid; } else if (res === 1) { low = mid + 1 } else { high = mid - 1 } } };
|