排序:冒泡排序、快速排序、插入排序...
搜索:二分搜索、顺序搜索...
排序
冒泡排序--交换
冒泡排序是一种简单的排序算法,它重复地遍历要排序的列表,比较每对相邻的项,然后交换它们的顺序(如果需要)。遍历列表的工作是重复地进行直到没有更多需要交换的元素,也就是说列表已经排序完成了。
function bubbleSort(arr) {
let len = arr.length;
for (let 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]
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
选择排序--取最小/最大值
选择排序是一种简单的排序算法,它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
function selectionSort(arr) {
let len = arr.length;
for (let i = 0; i < len; i++) {
let minIndex = i;
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex !== i) {
// 交换 arr[i] 和 arr[minIndex]
let temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
return arr;
}
插入排序--打扑克
对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
function insertionSort(arr) {
let len = arr.length;
for (let i = 1; i < len; i++) {
let key = arr[i];
let j = i - 1;//默认已排序的元素
//在已经排序好的队列进行从后往前的扫描
while (j >= 0 && arr[j] > key) {
//已排序的元素大于新元素,将该元素移动到下一个位置
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
return arr;
}
归并排序--二路归并 2分组
其主要思想是采用分治法,将已有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
function mergeSort(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);
return merge(mergeSort(left), mergeSort(right));
}
// 合并两个已经排序的数组
function merge(left, right) {
let result = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
快速排序--基准元素
其基本思想是选择一个基准元素,将列表划分为两个子列表,一个包含比基准元素小的元素,另一个包含比基准元素大的元素。然后对这两个子列表递归地应用快速排序算法。
function quickSort(arr, low = 0, high = arr.length - 1) {
if (low < high) {
let pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
return arr;
}
function partition(arr, low, high) {
let pivot = arr[high];
let i = (low - 1);
for (let j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return (i + 1);
}
排序的时间复杂度
排序算法的时间复杂度会根据不同的情况和算法类型有所不同。以下是一些常见的排序算法及其不同情况下的时间复杂度:
-
快速排序(Quick Sort):
- 最好情况:O(n log n)
- 平均情况:O(n log n)
- 最坏情况:O(n^2)
-
归并排序(Merge Sort):
- 最好情况:O(n log n)
- 平均情况:O(n log n)
- 最坏情况:O(n log n)
-
堆排序(Heap Sort):
- 最好情况:O(n log n)
- 平均情况:O(n log n)
- 最坏情况:O(n log n)
-
冒泡排序(Bubble Sort):
- 最好情况:O(n)(当输入已经排序)
- 平均情况:O(n^2)
- 最坏情况:O(n^2)
-
插入排序(Insertion Sort):
- 最好情况:O(n)(当输入已经排序)
- 平均情况:O(n^2)
- 最坏情况:O(n^2)
-
选择排序(Selection Sort):
- 最好情况:O(n^2)
- 平均情况:O(n^2)
- 最坏情况:O(n^2)
以上时间复杂度都是在考虑元素比较次数的前提下得出的。在实际应用中,还需要考虑其他因素,如数据的初始顺序、算法的空间复杂度、元素移动次数等,来选择最适合具体情况的排序算法。
搜索
二分搜索-折半搜索
二分搜索(也称为二分查找或折半查找)是一种在有序数组中查找特定元素的搜索算法。它的工作原理是不断将数组分成两半并查找目标元素,直到找到目标元素或搜索范围为空为止。
function binarySearch(arr, target) {
let low = 0;
let high = arr.length - 1;
while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (arr[mid] === target) {
return mid; // 返回目标元素的索引
} else if (arr[mid] < target) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1; // 如果没有找到目标元素,返回-1
}
分治
一个大问题,分成多个小问题,递归解决小问题,将结果合并从而来解决原来的问题
猜数字大小
假设你需要猜测一个在1到n之间的整数(包含1和n),这个数字是预先确定的,我们可以使用二分查找策略(一种分治法的应用)来最小化猜测次数。
以下是使用分治法(二分查找策略)解决猜数字大小问题的基本步骤:
-
首先,猜测中间的数字mid = (low + high) / 2,其中low是可能的最小值,high是可能的最大值。
-
如果你猜测的数字mid等于目标数字,那么恭喜你,你已经找到了正确的答案,游戏结束。
-
如果你猜测的数字mid大于目标数字,那么目标数字一定在low和mid-1之间。因此,你可以将high设为mid - 1,然后回到步骤1。
-
如果你猜测的数字mid小于目标数字,那么目标数字一定在mid+1和high之间。因此,你可以将low设为mid + 1,然后回到步骤1。
重复以上步骤,直到找到目标数字为止。
以下是使用JavaScript实现这个策略的代码:
function guessNumber(n, target) {
let low = 1;
let high = n;
while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (mid === target) {
return mid; // 找到目标数字,返回结果
} else if (mid > target) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1; // 如果没有找到目标数字,返回-1
}
多数元素
多数元素是指在一个数组中出现次数大于总数一半的元素。我们可以使用分治法来寻找多数元素,这种方法的基本思想是将原问题分解成若干个规模更小的子问题,然后递归地解决这些子问题,最后合并子问题的解以得到原问题的解。
以下是使用分治法寻找多数元素的基本步骤:
-
将数组分为两半。
-
对于每一半,找到多数元素。这可以通过递归地调用相同的函数来完成。
-
现在,我们有两个元素,每个元素都是其各自一半的多数元素。其中一个元素可能是整个数组的多数元素,也可能没有多数元素。为了找出哪一个是多数元素,我们需要在整个数组中计算这两个元素的出现次数。
var majorityElement=function(nums){ const countInRang=(start,end,num)=>{ let count = 0 for(let i = start;i<=end;i++){ if(nums[i]=num){ count++; v } } return count; } const majority=(start,end)=>{ if(start == end) return nums[start]; let mid=start+Math.floor((end-start/2 )); //左侧众数 const l=majority(start,mid); //右侧众数 const r=majority(start+1,mid); if(l==r) return l; } return majority(0,nums.length-1) }
动态规划
动态规划(Dynamic Programming,简称DP)是一种用于求解多阶段决策问题的优化算法。它的主要思想是将大问题分解为小问题进行解决,然后从小问题的解推导出大问题的解。
动态规划的关键是解决子问题并将结果存储在一个表中,以便在解决后续问题时可以直接使用,而不需要重复计算。这种方法称为"记忆化"。
动态规划算法通常用于求解具有某种最优性质的问题。例如,最短路径问题、最大子序列和问题、背包问题等等。
动态规划的步骤通常包括:
定义子问题:将原问题分解成一系列子问题。
实现要反复执行来解决子问题的部分。
识别并求解出边界条件。
组合子问题的解以得到原问题的解。
动态规划并不是可以用于解决所有问题的万能工具,它只适合于具有"重叠子问题"和"最优子结构"特性的问题。其中,"重叠子问题"是指在递归过程中反复出现的子问题,而"最优子结构"是指一个问题的最优解包含其子问题的最优解。
贪心算法
贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。
贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。这种问题最适合使用贪心算法,反之,贪心算法并不是在所有问题都能得到最优解。
一个例子是找零问题:要找给顾客45美分的硬币,我们会从最大的硬币开始,先找给他两个25美分的,再找给他一个10美分的,最后找给他一个5美分的。这样就得到了最少的硬币数量。
贪心算法可以解决一些最优化问题,如:求图中的最小生成树、求哈夫曼编码等。然而对于工程和生活中的问题,贪心算法一般不能得到我们所要求的答案,因为贪心算法在每一步的选择上都是看眼前,不从整体最优上加以考虑,所以一般用来解决一些最优化问题,如最小生成树、单源最短路径、任务调度问题等。