-
排序:把某个乱序的数组变成升序或者降序的数组,JS 中的排序:数组的 sort 方法
-
搜索:找出数组中某个元素的下标,JS 中的搜索:数组的 indexOf 方法。
排序
冒泡排序
- 比较所有相邻元素,如果第一个比第二个大,则交换它们
- 一轮下来,可以保证最后一个数是最大的
- 执行 n-1 轮,就可以完成排序
// 时间复杂度:O(n ^ 2) 两个嵌套循环
Array.prototype.bubbleSort = function() {
for(let i = 0; i < this.length - 1; i++) {
for(let j = 0; j < this.length - 1 - i; j++) {
if(this[j] > this[j + 1]) {
// 前一个大于后一个就交换值
[this[j], this[j + 1]] = [this[j + 1], this[j]]
// 等价于
// const temp = this[j];
// this[j] = this[j + 1];
// this[j + 1] = temp;
}
}
}
}
const arr = [3, 2, 1, 5, 4];
arr.bubbleSort();
console.log("arr", arr); // [1, 2, 3, 4, 5]
选择排序
- 找到数组中的最小值,选中它并将其放置在第一位
- 接着找到第二小的值,选中它并将其放置在第二位
- 以此类推,执行 n-1 轮
// 时间复杂度:O(n ^ 2) 两个嵌套循环
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(i !== indexMin) {
[this[i], this[indexMin]] = [this[indexMin], this[i]];
}
}
}
}
插入排序
- 从第二个数开始往前比
- 比它大就往后排
- 此此类推进行到最后一个数
// 时间复杂度:O(n ^ 2) 两个嵌套循环
Array.prototype.insertionSort = function() {
// 从第二个元素开始遍历
for(let i = 1; i < this.length; i++) {
const temp = this[i];
let j = i;
while(j > 0) {
// 如果前面的元素大于后面的,往后移动
if(this[j - 1] > temp) {
this[j] = this[j - 1];
}else {
break;
}
j--;
}
this[j] = temp;
}
}
归并排序
思路:
- 分:把数组劈成两半,再递归地对子数组进行“分〞操作,直到分成一个个单独的数
- 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组
合并两个有序的数组
- 新建一个空数组 res,用于存放最终排序后的数组
- 比较两个有序数组的头部,,较小者出队并推入 res 中
- 如果两个数组还有值,就重复第二步
// 时间复杂度:O(n * logn) 分需要劈开数组,所以是logn, 合则是n
Array.prototype.mergeSort = function() {
const rec = (arr) => {
// 递归终点
if(arr.length === 1) return arr;
// 获取中间位置索引
const mid = Math.floor(arr.length / 2);
// 获取分割后的左右数组
const left = arr.slice(0, mid);
const right = arr.slice(mid);
// 递归分割
const leftOrderArr = rec(left);
const rightOrderArr = rec(right);
const res = [];
while(leftOrderArr.length || rightOrderArr.length) {
// 如左边和右边数组都有值
if(leftOrderArr.length && rightOrderArr.length) {
res.push(leftOrderArr[0] < rightOrderArr[0] ? leftOrderArr.shift() : rightOrderArr.shift());
}else if(leftOrderArr.length) {
// 把左边的队头放入数组
res.push(leftOrderArr.shift());
}else if(rightOrderArr.length) {
// 把右边的队头放入数组
res.push(rightOrderArr.shift());
}
}
return res;
}
const resultArr = rec(this);
// 把结果赋值原数组
resultArr.forEach((item, index) => {this[index] = item});
}
快速排序
- 分区:从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在基准的后面
- 递归:递归地对基准前后的子数组进行分区
// 时间复杂度:O(n * logN) 递归的时间复杂度是 O(logN),分区操作的时间复杂度是 O(n)
Array.prototype.quickSort = function() {
const rec = (arr) => {
// 如果数组长度小于等于 1 就不用排序了
if(arr.length <= 1) return arr;
// 存放基准前后的数组
const left = [];
const right = [];
// 取基准
const middle = arr[0];
for(let i = 1; i < arr.length; i++) {
// 如果当前值小于基准就放到基准前数组里面
if(arr[i] < middle) {
left.push(arr[i]);
}else {
// 否则就放到基准后数组里面
right.push(arr[i]);
}
}
// 递归调用两边的子数组
return [...rec(left), middle, ...rec(right)];
}
const res = rec(this);
res.forEach((item, index) => { this[index] = item })
}
搜索
顺序搜索
- 遍历数组
- 找到跟目标值相等的元素,就返回它的下标
Array.prototype.sequentialSearch = function(item) {
for(let i = 0; i < this.length; i++) {
if(this[i] === item) {
return i;
}
}
return -1;
};
console.log(["a", "b", "c"].sequentialSearch("b")); // 1
console.log(["a", "b", "c"].sequentialSearch("d")); // -1
二分搜索
- 从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束
- 如果目标值大于或者小于中间元素,则在大于或小于中间元素的那一半数组中搜索
- 需要保证数组是有序的
// 时间复杂度:O(logN) 每一次比较都使搜索范围缩小一半
// 空间复杂度:O(1)
Array.prototype.binarySearch = function(item) {
let low = 0;
let high = this.length - 1;
while(low <= high) {
const mid = Math.floor((low + high) / 2);
const midElement = this[mid];
// 如果中间元素小于目标值,代表查找值在较大的一半数组
if(midElement < item) {
low = mid + 1;
// 如果中间元素大于目标,代表查找值在较小的一半数组
}else if(midElement > item) {
high = mid - 1;
}else {
return mid;
}
}
return -1;
};
console.log([1, 2, 3, 4, 5].binarySearch(3)); // 2
LeetCode: 21.合并两个有序链表
- 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
解题思路:
- 与归并排序中的合并两个有序数组很相似
- 将数组替换成链表就能解此题
解题步骤:
- 新建一个新链表,作为返回结果
- 用指针遍历两个有序链表,并比较两个链表的当前节点,较小者先接入新链表,并将指针后移一步
// 时间复杂度:O(m + n) m + n是两个链表长度之和
// 空间复杂度:O(1) 只有指针变量
const mergeTwoLists = function (list1, list2) {
// 新建链表和三个指针
const res = new ListNode(0);
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;
}
LeetCode: 374. 猜数字大小
解题思路:
- 典型的二分搜索
- 调用 guess 函数,来判断中间元素是否是目标值
解题步骤:
- 从数组的中间元素开始,如果中间元素正好是目标值,则搜索过程结束
- 如果目标值大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找
// 时间复杂度:O(logN) 分割两半一般都是logN
// 空间复杂度:O(1)
const 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;
}
}
};