前端算法基础
前端常见的数据结构
- 数组(Array)
- 特点:有序的元素集合,元素可以是任意类型,通过索引访问。
- 用途:存储列表数据、遍历操作。
- 对象(Object)
- 特点:键值对集合,键通常是字符串(或
Symbol),值可以是任意类型。 - 用途:存储结构化数据,通过键快速访问值。
- 特点:键值对集合,键通常是字符串(或
- 集合(Set)
- 特点:无须且唯一的元素集合(自动去重),支持添加、删除、判断元素是否存在等操作。
- 用途:数组去重、检查元素存在性、存储不重复的ID列表等。
- 映射(Map)
- 特点:键值对集合,键可以是任意属性,且键唯一,迭代顺序为插入顺序。
- 用途:需要非字符串作为键的场景(如用DOM元素作为键存储数据)、频繁增删键值对的场景。
- 栈(Stack)
- 特点:后进先出(
LIFO),仅允许在顶部添加(push)和删除(pop)元素。 - 用途:函数调用栈(
JS引擎内部机制)、历史记录回退、括号匹配校验。
- 特点:后进先出(
- 队列(Queue)
- 特点:先进先出(
FIFO),允许在尾部添加(enqueue)和头部删除(dequeue)元素。 - 用途:任务调度(异步任务队列)、消息队列、广度优先搜索(
BFS)。
- 特点:先进先出(
- 链表(Linked List)
- 特点:由节点组成,每个节点包含数据和指向下一节点的指针,内存不连续。
- 用途:频繁插入/删除元素的场景(如动态列表更新),前端使用较少,一般用于底层实现。
- 树(Tree)
- 层级结构,根节点下有子节点,子节点又有自己的子节点(如DOM树)。
- 常见类型:二叉树、红黑树、DOM树
- 用途:DOM操作(遍历DOM树)、状态管理(如
Vue的虚拟DOM树)、路由配置(嵌套路由)。
基础算法
数组的基础算法
遍历数组
功能:访问数组中的每一个元素,执行自定义逻辑。
实现方式:for循环、for…of、forEach、map等。
应用:数据汇总、格式转换。
常见查找算法
-
线性查找
功能:逐个遍历数组,寻找目标元素的索引。
时间复杂度:
O(n)(适合小规模数组)。应用:查找元素是否存在、获取元素位置索引。
-
二分查找(折半查找)
前提:数组已排序
功能:通过不断的将数组分成两半,缩小查找范围。
时间复杂度:
O(log n)(适合大规模有序数组)。
常见排序算法
-
冒泡排序
思路:重复遍历数组,比较相邻元素,根据大小情况调整顺序。
时间复杂度:
O(n²)(简单但效率低,适合小规模数组)。// 冒泡排序 升序 const bubbleSort = arr => { const len = arr.length let hasSwapped = false // 用于判断是否提前完成排序的标识 // 处理数组项小于2的情况 if (len < 2) return arr for (let i = 0; i < len - 1; i++) { // 每次冒泡循环开始前,将hasSwapped设为false hasSwapped = false for (let j = 0; j < len - i -1; j++) { if (arr[j] > arr[j + 1]) { // 前面的元素比后面的元素大,交换两项 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]] // 只要有一项交换证明排序未提前完成 hasSwapped = true } } // 若提前完成排序直接终止 if (!hasSwapped) return arr } return arr } -
选择排序
思路:选择排序提高了冒泡排序的性能,它每遍历一次列表只交换一次数据,即进行一次遍历时找到最小(或最大)的项,完成遍历后,再把它换到正确的位置。
时间复杂度:
O(n²)(简单但效率低,适合小规模数组)。// 选择排序 升序 const selectionSort = arr => { const len = arr.length // 处理数组项小于2的情况 if (len < 2) return arr for (let i = 0; i < len - 1; i++) { let miniIndex = i for (let j = i + 1; j < len; j++) { if (arr[j] < arr[miniIndex]) { miniIndex = j } } [arr[i], arr[miniIndex]] = [arr[miniIndex], arr[i]] } return arr } -
插入排序
思路:将数组分为 “已排序” 和 “未排序” 两部分,每次从后者取一个元素插入前者的正确位置。
时间复杂度:
O(n²)(适合近乎有序的数组,实际性能优于冒泡)。// 选择排序 升序 const insertionSort = arr => { const len = arr.length; if (len < 2) return arr; const sortedArr = [arr[0]]; for (let i = 1; i < len; i++) { const current = arr[i]; let j = sortedArr.length - 1; // 从尾部开始比较 // 找到第一个小于等于 current 的位置 while (j >= 0 && sortedArr[j] > current) { j--; } // 插入到 j+1 的位置(若 j=-1,说明 current 是最小值,插入到头部) sortedArr.splice(j + 1, 0, current); } return sortedArr; }; -
快速排序
思路:选择 “基准值”,将数组分为 “小于基准” 和 “大于基准” 两部分,递归排序子数组。
时间复杂度:平均
O(n log n),最坏O(n²)(但实际应用中最常用,效率高)。// 快速排序 升序 const quickSort = arr => { if (arr.length <= 1) return arr; const base = arr[Math.floor(Math.random() * arr.length)]; // 随机基准值 const left = [] const right = [] const equal = [] for (const item of arr) { if (item > base) { right.push(item) } else if (item < base) { left.push(item) } else { equal.push(item) } } return [...quickSort(left), ...equal, ...quickSort(right)] }
栈的基础算法
算法:入栈、出栈、查找栈顶。
class Stack {
constructor() {
this.#items = []
}
// 入栈:添加元素到栈顶
push(element) {
this.#items.push(element);
}
// 出栈:移除并返回栈顶元素(若栈空返回null)
pop() {
if (this.isEmpty()) return null;
return this.#items.pop();
}
// 查看栈顶元素(不删除)
peek() {
if (this.isEmpty()) return null;
return this.#items[this.#items.length - 1];
}
// 判断栈是否为空
isEmpty() {
return this.#items.length === 0;
}
// 获取栈的大小
size() {
return this.#items.length;
}
// 清空栈
clear() {
this.#items = [];
}
}
队列的基础算法
算法:入队、出队、查看队头。
class Queue {
constructor() {
this.#items = []; // 用数组存储队列元素
}
// 入队:添加元素到队尾
enqueue(element) {
this.#items.push(element);
}
// 出队:移除并返回队头元素(若队空返回null)
dequeue() {
if (this.isEmpty()) return null;
return this.#items.shift(); // 数组shift()效率低(O(n)),仅作基础演示
}
// 查看队头元素(不删除)
front() {
if (this.isEmpty()) return null;
return this.#items[0];
}
// 判断队列是否为空
isEmpty() {
return this.#items.length === 0;
}
// 获取队列大小
size() {
return this.#items.length;
}
// 清空队列
clear() {
this.#items = [];
}
}
链表的基础算法
遍历链表、查找节点
遍历是链表操作的基础,需通过指针逐个访问节点,直到遇到 null(链表尾部)。根据值或索引查找节点,需从头遍历并比对。
用途:打印所有节点、查找元素、计算长度等。
// 打印链表所有节点值
const traversal = head => {
let current = head
while (current !== null) {
console.log(current.val)
current = current.next
}
}
// 计算链表长度
const getLen = head => {
let length = 0;
let current = head;
while (current !== null) {
length++;
current = current.next;
}
return length;
}
// 查找节点
const findNode = (head, target) => {
let current = head
while (current !== null) {
if (current.val === target) return current
current = current.next
}
return null
}
// 查找第K个节点(索引从0开始)
const findKNode = (head, k) => {
let current = head
let count = 0
while (current !== null && count < k) {
current = current.next
}
return current // 若 k 超出长度,current 会变为 null
}
插入节点
根据位置不同,插入分为「头部插入」「中间插入」「尾部插入」,核心是调整指针指向。
class ListNode {
val: number
next: ListNode | null
constructor(val?: number, next?: ListNode | null) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
}
// 头部插入
const insertAtHead = (head, val) => {
const newNode = new ListNode(val)
newNode.next = head
return newNode
}
// 尾部插入
const insertAtTail = (head, val) => {
const newNode = new ListNode(val)
if (!head) return newNode; // 若链表为空,新节点即为表头
const current = head
while (current.next !== null) {
current = current.next
}
current.next = newNode
return head
}
// 在第K个节点后插入
const insertAtK = (head, val, k) => {
const preNode = findKNode(head, k)
if (preNode) {
const newNode = new ListNode(val)
newNode.next = preNode.next
preNode.next = newNode
}
return head
}
删除节点
同样分「头部删除」「中间删除」「尾部删除」,需注意释放节点(避免内存泄漏,JS 由垃圾回收自动处理)
// 头部删除
const deleteAtHead = head => {
if (!head) return null
return head.next
}
// 尾部删除
const deleteAtTail = head => {
if (!head || !head.next) return null
let current = head
while(current.next.next) {
current = current.next
}
current.next = null
return head
}
// 中间删除
const deleteTarget = (head, target) => {
if (!head) return null
// 目标节点为第一个节点,直接返回下一节点作为head
if (head.val === target) return head.next
let current = head
// 遍历节点找到目标节点上一个节点
while(current.next && current.next.val !== target) {
current = current.next
}
if (current.next) current.next = current.next.next
return head
}
反转链表
将链表方向反转(如 1→2→3 变为 3→2→1),核心是调整每个节点的 next 指针指向其前驱节点。
// 反转链表
const reverseList = head => {
const prevNode = null // 前置节点
const current = head
while (current) {
const nextTemp = current.next // 暂存下一个节点
current.next = prevNode // 改变当前节点next指向prevNode
prevNode = current // 后移前置节点
current = nextTemp // 后移当前节点
}
return prevNode
}
树的基础算法
树的遍历
-
深度优先遍历(
DFS)优先沿深度访问节点,分为前序、中序、后序遍历(以根节点访问时机区分)。
// 深度优先遍历 // 前序 const preOrderDFS = (root, result = []) => { if (!root) return result result.push(root.val) preOrderDFS(root.left, result) preOrderDFS(root.right, result) return result } // 中序 const inOrderDFS = (root, result = []) => { if (!root) return result inOrderDFS(root.left, result) result.push(root.val) inOrderDFS(root.right, result) return result } // 后序 const postOrderDFS = (root, result = []) => { if (!root) return result inOrderDFS(root.left, result) inOrderDFS(root.right, result) result.push(root.val) return result } -
广度优先遍历(
BFS,层序遍历)// 广度优先遍历 const levelOrder = (root) => { const result = []; if (!root) return result; const queue = [root] // 初始根入遍历队列 // 循环至遍历队列为空 while (queue.length) { for (let i = 0; i < queue.length; i++) { const node = queue.shift() // 第一项出遍历队列 result.push(node.val) // 按左右顺序 将下一层树节点入遍历队列 node.left && queue.push(node.left) node.left && queue.push(node.right) } } return result }
176万+

被折叠的 条评论
为什么被折叠?



