常见开发模式
瀑布模型(Waterfall Model):瀑布模型是一种线性的软件开发过程,包括需求分析、系统设计、实现、测试、部署和维护等阶段,每个阶段依次执行,前一个阶段完成后才能进行下一个阶段。瀑布模型适用于需求变更较少且项目范围明确的情况。
迭代开发模型(Iterative Development Model):迭代开发模型将软件开发过程分解为多个迭代周期,每个迭代周期都包括需求分析、设计、编码、测试和部署等阶段,每个迭代周期都会交付一部分功能。迭代开发模型适用于需求不断变化或者需要快速响应市场变化的情况。
增量开发模型(Incremental Development Model):增量开发模型是在已有的软件基础上逐步添加新的功能和特性,每次增加一个小的模块或者部分功能,逐步完善软件系统。增量开发模型适用于大型软件系统的开发,可以减少风险并提高可控性。
螺旋模型(Spiral Model):螺旋模型将软件开发过程分解为多个迭代的螺旋,每个螺旋代表一个迭代周期,包括计划、风险分析、开发和评审等阶段。螺旋模型适用于需要重点关注风险管理的项目,可以在迭代过程中及时发现和解决问题。
敏捷开发(Agile Development):敏捷开发是一种基于价值交付和持续反馈的软件开发方法,强调团队协作、自组织、快速迭代和灵活响应需求变化。常见的敏捷方法包括Scrum、Kanban、XP等。
DevOps:DevOps 是一种软件开发和运维的文化、实践和自动化工具的结合,旨在缩短软件开发周期、提高软件交付速度和质量、增强团队协作和沟通效率。
数据结构与算法知识
1.常见数据结构
数组(Array)
数组是一种线性数据结构,它由一组连续的内存空间组成,用来存储相同类型的数据。数组可以通过索引来访问元素,索引从0开始计数。
基本操作:
- 访问元素:通过索引访问数组中的元素。
- 插入元素:向数组中插入一个元素,通常需要移动后面的元素以腾出空间。
- 删除元素:从数组中删除一个元素,通常需要移动后面的元素以填补空缺。
- 更新元素:修改数组中指定索引的元素的值。
// 创建一个数组 let arr = [1, 2, 3, 4, 5];// 访问元素 console.log(arr[0]); // 输出:1// 插入元素 arr.push(6); // 在数组末尾插入元素 6 console.log(arr); // 输出:[1, 2, 3, 4, 5, 6]// 删除元素 arr.pop(); // 删除数组末尾的元素 console.log(arr); // 输出:[1, 2, 3, 4, 5]// 更新元素 arr[0] = 10; console.log(arr); // 输出:[10, 2, 3, 4, 5]
链表(Linked List)
链表是一种线性数据结构,它由一组节点(Node)组成,每个节点包含两部分:数据域(存储数据)和指针域(指向下一个节点)。链表中的节点通过指针相连,形成一条链式结构。
链表相对于数组来说,插入和删除节点的操作效率更高,但是查找节点的效率较低。链表适合频繁插入和删除操作的场景。
基本操作:
- 插入节点:在链表的任意位置插入一个新节点。
- 删除节点:从链表中删除指定位置的节点。
- 查找节点:在链表中查找指定值的节点。
- 遍历链表:遍历整个链表,访问每个节点。
// 定义链表节点类 class ListNode {constructor(val) {this.val = val; // 节点的值this.next = null; // 指向下一个节点的指针} }// 定义链表类 class LinkedList {constructor() {this.head = null; // 链表头节点}// 插入节点insert(val) {const newNode = new ListNode(val);if (!this.head) {this.head = newNode;} else {let current = this.head;while (current.next) {current = current.next;}current.next = newNode;}}// 删除节点delete(val) {if (!this.head) return;if (this.head.val === val) {this.head = this.head.next;return;}let prev = this.head;let current = this.head.next;while (current) {if (current.val === val) {prev.next = current.next;return;}prev = current;current = current.next;}}// 查找节点find(val) {let current = this.head;while (current) {if (current.val === val) {return current;}current = current.next;}return null;}// 遍历链表display() {let current = this.head;while (current) {console.log(current.val);current = current.next;}} }// 创建一个链表 const linkedList = new LinkedList(); linkedList.insert(1); linkedList.insert(2); linkedList.insert(3);// 遍历链表并输出节点值 linkedList.display(); // 输出:1 2 3// 删除节点值为 2 的节点 linkedList.delete(2);// 再次遍历链表并输出节点值 linkedList.display(); // 输出:1 3
栈(Stack)
栈是一种具有特定操作规则的线性数据结构,它的特点是后进先出(LIFO,Last In First Out)。栈可以用数组或链表实现。
栈常用于需要临时存储和回溯的场景,例如浏览器的前进和后退功能、表达式求值、函数调用等。
基本操作:
- 入栈:将元素压入栈顶。
- 出栈:从栈顶弹出元素。
- 查看栈顶元素:获取栈顶元素但不移除。
- 判断栈是否为空:判断栈中是否有元素。
- 获取栈的大小:获取栈中元素的数量。
数组实现栈
class Stack {constructor() {this.items = [];}// 入栈push(element) {this.items.push(element);}// 出栈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;} }// 创建一个栈实例 const stack = new Stack();// 入栈 stack.push(1); stack.push(2); stack.push(3);// 出栈 console.log(stack.pop()); // 输出:3 // 查看栈顶元素 console.log(stack.peek()); // 输出:2 // 获取栈的大小 console.log(stack.size()); // 输出:2 // 判断栈是否为空 console.log(stack.isEmpty()); // 输出:false
链表实现栈
class ListNode {constructor(val) {this.val = val;this.next = null;} }class Stack {constructor() {this.top = null; // 栈顶节点this.size = 0; // 栈的大小}// 入栈push(val) {const newNode = new ListNode(val);if (!this.top) {this.top = newNode;} else {newNode.next = this.top;this.top = newNode;}this.size++;}// 出栈pop() {if (this.isEmpty()) return null;const popped = this.top;this.top = this.top.next;popped.next = null;this.size--;return popped.val;}// 查看栈顶元素peek() {if (this.isEmpty()) return null;return this.top.val;}// 判断栈是否为空isEmpty() {return this.size === 0;}// 获取栈的大小getSize() {return this.size;} }// 创建一个栈实例 const stack = new Stack();// 入栈 stack.push(1); stack.push(2); stack.push(3);// 出栈 console.log(stack.pop()); // 输出:3 // 查看栈顶元素 console.log(stack.peek()); // 输出:2 // 获取栈的大小 console.log(stack.getSize()); // 输出:2 // 判断栈是否为空 console.log(stack.isEmpty()); // 输出:false
队列(Queue)
队列是一种具有特定操作规则的线性数据结构,它的特点是先进先出(FIFO,First In First Out)。队列可以用数组或链表实现。
队列常用于需要按顺序处理任务的场景,例如消息队列、广度优先搜索等。
基本操作:
- 入队:将元素加入队列尾部。
- 出队:从队列头部移除元素。
- 查看队首元素:获取队列头部的元素但不移除。
- 判断队列是否为空:判断队列中是否有元素。
- 获取队列的大小:获取队列中元素的数量。
class ListNode {constructor(val) {this.val = val;this.next = null;} }class Queue {constructor() {this.front = null; // 队头节点this.rear = null; // 队尾节点this.size = 0; // 队列的大小}// 入队enqueue(val) {const newNode = new ListNode(val);if (!this.rear) {this.front = newNode;this.rear = newNode;} else {this.rear.next = newNode;this.rear = newNode;}this.size++;}// 出队dequeue() {if (this.isEmpty()) return null;const dequeued = this.front;this.front = this.front.next;dequeued.next = null;this.size--;// 如果出队后队列为空,更新 rear 指针if (!this.front) {this.rear = null;}return dequeued.val;}// 查看队首元素peek() {if (this.isEmpty()) return null;return this.front.val;}// 判断队列是否为空isEmpty() {return this.size === 0;}// 获取队列的大小getSize() {return this.size;} }// 创建一个队列实例 const queue = new Queue();// 入队 queue.enqueue(1); queue.enqueue(2); queue.enqueue(3);// 出队 console.log(queue.dequeue()); // 输出:1 // 查看队首元素 console.log(queue.peek()); // 输出:2 // 获取队列的大小 console.log(queue.getSize()); // 输出:2 // 判断队列是否为空 console.log(queue.isEmpty()); // 输出:false
树(Tree):
二叉树、二叉搜索树、AVL 树、红黑树等
树是一种非线性数据结构,它由一组节点组成,节点之间通过边连接,形成层次关系。树中有一个特殊的节点称为根节点,除了根节点之外,每个节点都有一个父节点和零个或多个子节点。
树是一种非常重要的数据结构,常用于模拟层次结构的问题,例如文件系统、组织架构、HTML 文档等。二叉树是树的一种特殊形式,在算法和数据结构中应用广泛。
基本概念:
- 根节点:树的顶端节点,没有父节点。
- 父节点:每个节点都有一个父节点,除了根节点。
- 子节点:每个节点可以有零个或多个子节点。
- 叶子节点:没有子节点的节点称为叶子节点。
- 深度:从根节点到当前节点的唯一路径的长度称为节点的深度。
- 高度:从当前节点到最远叶子节点的路径的长度称为节点的高度。
常见类型:
- 二叉树(Binary Tree):每个节点最多有两个子节点的树。
- 二叉搜索树(Binary Search Tree,BST):一种特殊的二叉树,左子树上的所有节点的值都小于根节点的值,右子树上的所有节点的值都大于根节点的值。
class TreeNode {constructor(val) {this.val = val;this.left = null;this.right = null;} }// 创建一个二叉树 const root = new TreeNode(1); root.left = new TreeNode(2); root.right = new TreeNode(3); root.left.left = new TreeNode(4); root.left.right = new TreeNode(5);// 中序遍历二叉树(左-根-右) function inOrderTraversal(node) {if (!node) return;inOrderTraversal(node.left);console.log(node.val);inOrderTraversal(node.right); }console.log("Inorder traversal:"); inOrderTraversal(root);
堆(Heap)
堆是一种特殊的树形数据结构,通常实现为数组,并满足堆属性:对于堆中任意节点 i,其父节点的值小于或等于其子节点的值(最小堆),或者父节点的值大于或等于其子节点的值(最大堆)。
堆是一种非常重要的数据结构,它在各种算法和应用中都有广泛的应用,比如堆排序、优先队列、调度算法等。
基本概念:
- 最小堆(Min Heap):每个父节点的值都小于或等于其子节点的值。
- 最大堆(Max Heap):每个父节点的值都大于或等于其子节点的值。
- 堆的形状性质:堆是一个完全二叉树,即除了最后一层之外,每一层都是满的,且最后一层的节点都尽可能地靠左排列。
- 堆的堆序性质:堆中的每个节点都必须满足堆属性。
常见操作:
- 插入(Insertion):向堆中插入一个新元素。
- 删除最值(Extract-Min/Max):删除并返回最小(最大)值。
- 查找最值(Find-Min/Max):查找并返回最小(最大)值。
- 堆化(Heapify):将无序数组转换为堆结构。
- 堆排序(Heap Sort):利用堆实现的一种排序算法。
class MinHeap {constructor() {this.heap = [];}// 获取父节点索引getParentIndex(index) {return Math.floor((index - 1) / 2);}// 获取左子节点索引getLeftChildIndex(index) {return index * 2 + 1;}// 获取右子节点索引getRightChildIndex(index) {return index * 2 + 2;}// 上移操作(向上调整)siftUp(index) {let parentIndex = this.getParentIndex(index);while (index > 0 && this.heap[parentIndex] > this.heap[index]) {[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];index = parentIndex;parentIndex = this.getParentIndex(index);}}// 下移操作(向下调整)siftDown(index) {let minIndex = index;const leftChildIndex = this.getLeftChildIndex(index);const rightChildIndex = this.getRightChildIndex(index);if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] < this.heap[minIndex]) {minIndex = leftChildIndex;}if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] < this.heap[minIndex]) {minIndex = rightChildIndex;}if (minIndex !== index) {[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]];this.siftDown(minIndex);}}// 插入新元素insert(value) {this.heap.push(value);this.siftUp(this.heap.length - 1);}// 删除并返回最小值extractMin() {if (this.heap.length === 0) return null;if (this.heap.length === 1) return this.heap.pop();const min = this.heap[0];this.heap[0] = this.heap.pop();this.siftDown(0);return min;}// 获取最小值peek() {return this.heap.length > 0 ? this.heap[0] : null;}// 获取堆的大小size() {return this.heap.length;}// 判断堆是否为空isEmpty() {return this.heap.length === 0;} }// 创建一个最小堆实例 const minHeap = new MinHeap();// 插入元素 minHeap.insert(3); minHeap.insert(2); minHeap.insert(1);// 获取最小值 console.log(minHeap.peek()); // 输出:1// 删除并返回最小值 console.log(minHeap.extractMin()); // 输出:1
图(Graph):
有向图、无向图、加权图等
图是一种非线性数据结构,由一组节点(顶点)和一组边组成。图中的节点可以是任意对象,边则是连接节点的线段。图可以用来表示各种实体之间的关系,比如社交网络中的用户和好友关系、地图中的城市和道路关系等。
图是非常灵活和强大的数据结构,它在各种领域都有广泛的应用,例如网络路由、社交网络分析、推荐系统等。理解图的基本概念和常见算法对于解决与图相关的问题非常重要。
基本概念:
- 顶点(Vertex):图中的节点。
- 边(Edge):连接两个顶点的线段,可以是有向或无向的。
- 有向图(Directed Graph):图中的边有方向,即从一个顶点到另一个顶点有一个确定的方向。
- 无向图(Undirected Graph):图中的边没有方向,即从一个顶点到另一个顶点没有确定的方向。
- 权重(Weight):边上的值,用来表示连接两个顶点之间的距离、代价等信息。
- 路径(Path):顶点序列,按照边的连接顺序排列。
- 环(Cycle):至少有一个边的起始顶点和终止顶点相同的路径。
常见类型:
- 有向图:图中的边有方向。
- 无向图:图中的边没有方向。
- 加权图(Weighted Graph):图中的边带有权重。
- 稀疏图(Sparse Graph):图中的边相对较少。
- 稠密图(Dense Graph):图中的边相对较多。
class Graph {constructor() {this.vertices = []; // 顶点集合this.adjacencyList = new Map(); // 邻接表,用于存储顶点及其相邻顶点}// 添加顶点addVertex(vertex) {this.vertices.push(vertex);this.adjacencyList.set(vertex, []);}// 添加边(无向图)addEdge(v1, v2) {this.adjacencyList.get(v1).push(v2);this.adjacencyList.get(v2).push(v1);}// 添加边(有向图)addDirectedEdge(v1, v2) {this.adjacencyList.get(v1).push(v2);}// 获取相邻顶点列表getAdjacentVertices(vertex) {return this.adjacencyList.get(vertex);}// 打印图的邻接表printGraph() {for (let [vertex, neighbors] of this.adjacencyList) {console.log(`${vertex} -> ${neighbors.join(", ")}`);}} }// 创建一个无向图实例 const graph = new Graph(); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C");// 打印图的邻接表 console.log("Graph adjacency list:"); graph.printGraph();
哈希表(Hash Table)
哈希表是一种高效的数据结构,它通过哈希函数将键(key)映射到存储桶(bucket)中的索引,从而实现快速的查找、插入和删除操作。哈希表通常基于数组实现,每个存储桶可以存储一个或多个键值对。
哈希表是一种高效的数据结构,它在平均情况下具有常数时间复杂度的插入、查找和删除操作。哈希表被广泛应用于各种计算机科学领域,如数据库索引、缓存实现、编译器和解释器等。
基本概念:
- 哈希函数:将键映射到存储桶索引的函数。
- 存储桶:存储哈希表中的键值对的容器。
- 碰撞(Collision):当两个不同的键经过哈希函数映射到相同的存储桶时发生碰撞。
- 负载因子(Load Factor):哈希表中元素数量与存储桶数量的比值。
- 哈希冲突解决方法:开放定址法、链表法、再哈希法、建立公共溢出区等。
常见操作:
- 插入(Insertion):将键值对插入哈希表中。
- 查找(Lookup):根据键查找对应的值。
- 删除(Deletion):根据键删除键值对。
- 调整大小(Resizing):根据负载因子动态调整哈希表的大小。
class HashTable {constructor(size = 11) {this.size = size; // 哈希表的大小this.buckets = new Array(size).fill(null).map(() => []);}// 哈希函数:将键映射到存储桶索引hash(key) {let hash = 0;for (let i = 0; i < key.length; i++) {hash = (hash << 5) + key.charCodeAt(i);}return hash % this.size;}// 插入键值对set(key, value) {const index = this.hash(key);const bucket = this.buckets[index];for (let i = 0; i < bucket.length; i++) {if (bucket[i][0] === key) {// 如果键已经存在,则更新值bucket[i][1] = value;return;}}bucket.push([key, value]);}// 获取键对应的值get(key) {const index = this.hash(key);const bucket = this.buckets[index];for (let i = 0; i < bucket.length; i++) {if (bucket[i][0] === key) {return bucket[i][1];}}return undefined; // 键不存在}// 删除键值对delete(key) {const index = this.hash(key);const bucket = this.buckets[index];for (let i = 0; i < bucket.length; i++) {if (bucket[i][0] === key) {return bucket.splice(i, 1)[0][1];}}return undefined; // 键不存在} }// 创建一个哈希表实例 const hashTable = new HashTable();// 插入键值对 hashTable.set("apple", 10); hashTable.set("banana", 20); hashTable.set("orange", 30);// 获取键对应的值 console.log(hashTable.get("apple")); // 输出:10// 删除键值对 console.log(hashTable.delete("banana")); // 输出:20// 再次获取键对应的值 console.log(hashTable.get("banana")); // 输出:undefined
优先队列(Priority Queue)
优先队列是一种特殊的队列,每个元素都关联有一个优先级。具有高优先级的元素会先被取出。优先队列的实现可以基于各种数据结构,例如堆、有序数组等。
基本概念:
- 优先级:用于确定元素被取出的顺序,通常是一个可以比较的值。
- 插入:按照元素的优先级将元素插入队列中。
- 取出:从队列中移除并返回具有最高优先级的元素。
常见操作:
- 插入(Insertion):将元素插入队列中,并根据优先级进行调整。
- 取出最高优先级元素(Extract-Max/Min):从队列中移除并返回具有最高优先级的元素。
- 查看最高优先级元素(Peek-Max/Min):查看队列中具有最高优先级的元素,但不移除。
// 使用最小堆实现优先队列 class PriorityQueue {constructor() {this.heap = [];}// 插入元素insert(element, priority) {const node = { element, priority };this.heap.push(node);this.siftUp(this.heap.length - 1);}// 取出最高优先级元素extractMin() {if (this.heap.length === 0) return null;if (this.heap.length === 1) return this.heap.pop().element;const min = this.heap[0].element;this.heap[0] = this.heap.pop();this.siftDown(0);return min;}// 查看最高优先级元素peek() {return this.heap.length > 0 ? this.heap[0].element : null;}// 上移操作(向上调整)siftUp(index) {let parentIndex = Math.floor((index - 1) / 2);while (index > 0 && this.heap[parentIndex].priority > this.heap[index].priority) {[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];index = parentIndex;parentIndex = Math.floor((index - 1) / 2);}}// 下移操作(向下调整)siftDown(index) {let minIndex = index;const leftChildIndex = index * 2 + 1;const rightChildIndex = index * 2 + 2;if (leftChildIndex < this.heap.length && this.heap[leftChildIndex].priority < this.heap[minIndex].priority) {minIndex = leftChildIndex;}if (rightChildIndex < this.heap.length && this.heap[rightChildIndex].priority < this.heap[minIndex].priority) {minIndex = rightChildIndex;}if (minIndex !== index) {[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]];this.siftDown(minIndex);}} }// 创建一个优先队列实例 const priorityQueue = new PriorityQueue();// 插入元素 priorityQueue.insert("Task 1", 3); priorityQueue.insert("Task 2", 2); priorityQueue.insert("Task 3", 1);// 取出最高优先级元素 console.log(priorityQueue.extractMin()); // 输出:Task 3
字典树(Trie)
字典树(Trie)是一种树形数据结构,用于高效地存储和检索字符串集合。字典树的每个节点代表一个字符串的字符,从根节点到叶子节点的路径构成一个字符串。字典树常用于搜索引擎、拼写检查和字典等应用中。
字典树是一种非常高效的数据结构,特别适用于字符串的存储和检索。它的插入、搜索和删除操作的时间复杂度都是 O(m),其中 m 是字符串的长度。
基本概念:
- 根节点(Root):树的顶端节点,不包含任何字符,但是作为整个树的起点。
- 节点(Node):包含一个字符以及指向其子节点的指针的数据结构。
- 子节点(Child Node):一个节点的直接后继节点。
- 叶子节点(Leaf Node):没有子节点的节点,表示一个完整的字符串。
- 路径(Path):从根节点到任意节点的序列,表示一个字符串。
- 字符串集合:所有从根节点到叶子节点的路径所表示的字符串的集合。
基本操作:
- 插入(Insertion):将字符串插入字典树中。
- 搜索(Search):搜索字典树中是否存在某个字符串。
- 删除(Deletion):从字典树中删除某个字符串。
class TrieNode {constructor() {this.children = new Map(); // 使用 Map 存储子节点,键为字符,值为子节点this.isEndOfWord = false; // 标记是否是字符串的结束节点} }class Trie {constructor() {this.root = new TrieNode(); // 根节点}// 插入字符串insert(word) {let node = this.root;for (const char of word) {if (!node.children.has(char)) {node.children.set(char, new TrieNode());}node = node.children.get(char);}node.isEndOfWord = true; // 将最后一个节点标记为单词的结束节点}// 搜索字符串search(word) {let node = this.root;for (const char of word) {if (!node.children.has(char)) {return false; // 字符串不存在}node = node.children.get(char);}return node.isEndOfWord; // 判断最后一个节点是否是单词的结束节点}// 删除字符串delete(word) {this.deleteRecursive(this.root, word, 0);}// 递归删除字符串deleteRecursive(node, word, index) {if (index === word.length) {// 当 index 等于字符串长度时,表示找到了要删除的字符串if (!node.isEndOfWord) {return false; // 字符串不存在}node.isEndOfWord = false; // 取消最后一个节点的结束标记return node.children.size === 0; // 返回当前节点是否可以被删除}const char = word[index];if (!node.children.has(char)) {return false; // 字符串不存在}const shouldDeleteCurrentNode = this.deleteRecursive(node.children.get(char), word, index + 1);if (shouldDeleteCurrentNode) {node.children.delete(char); // 删除当前节点的子节点return node.children.size === 0; // 返回当前节点是否可以被删除}return false;} }// 创建一个 Trie 实例 const trie = new Trie();// 插入字符串 trie.insert("apple"); trie.insert("banana");// 搜索字符串 console.log(trie.search("apple")); // 输出:true console.log(trie.search("banana")); // 输出:true console.log(trie.search("orange")); // 输出:false// 删除字符串 trie.delete("banana"); console.log(trie.search("banana")); // 输出:false
并查集(Disjoint Set)
并查集(Disjoint Set)是一种数据结构,用于管理元素的分组以及在不相交的集合之间进行合并和查询。它通常用于解决一些集合的合并与查询问题,比如判断图中的连通分量、快速查找两个元素是否属于同一集合等。
基本概念:
- 集合(Set):由若干元素组成的集合。
- 并查集(Disjoint Set):由若干不相交的集合组成的数据结构,每个集合被称为一个组或者一个集合。
- 元素(Element):并查集中的每个对象,每个元素都属于一个集合。
- 代表元(Representative Element):每个集合中的一个元素被选为代表元,通常是集合中的某个元素。
- 合并(Union):将两个集合合并成一个集合。
- 查找(Find):确定一个元素属于哪个集合,通常找到其代表元。
基本操作:
- 初始化(MakeSet):将每个元素初始化为一个独立的集合。
- 查找(Find):找到元素所在的集合的代表元素。
- 合并(Union):将两个元素所在的集合合并成一个集合。
实现方式:
并查集的实现方式有多种,其中较为常见的是基于数组和基于树的实现方式。基于树的实现方式包括基于树的深度优化(按秩合并)和基于路径的优化(路径压缩)。
class DisjointSet {constructor(size) {this.parent = new Array(size); // 记录每个元素的父节点this.rank = new Array(size).fill(0); // 记录每个集合的秩(树的高度)for (let i = 0; i < size; i++) {this.parent[i] = i; // 初始化时,每个元素的父节点为自身}}// 查找元素所在集合的代表元素(根节点)find(x) {if (this.parent[x] !== x) {this.parent[x] = this.find(this.parent[x]); // 路径压缩,将 x 的父节点设为根节点}return this.parent[x];}// 合并两个元素所在的集合union(x, y) {const rootX = this.find(x);const rootY = this.find(y);if (rootX !== rootY) {// 按秩合并,将秩较小的树合并到秩较大的树上if (this.rank[rootX] < this.rank[rootY]) {this.parent[rootX] = rootY;} else if (this.rank[rootX] > this.rank[rootY]) {this.parent[rootY] = rootX;} else {this.parent[rootY] = rootX;this.rank[rootX]++; // 若两棵树的秩相同,则合并后新树的秩加一}}} }// 创建一个并查集实例 const disjointSet = new DisjointSet(5);// 合并集合 disjointSet.union(0, 1); disjointSet.union(2, 3); disjointSet.union(0, 4);// 查找元素所在集合的代表元素 console.log(disjointSet.find(1)); // 输出:0 console.log(disjointSet.find(2)); // 输出:2 console.log(disjointSet.find(3)); // 输出:2 console.log(disjointSet.find(4)); // 输出:0
AVL 树、红黑树:
AVL树和红黑树都是自平衡二叉搜索树,它们都能够在插入和删除操作后通过旋转等操作来保持树的平衡,从而保证了树的搜索、插入和删除等操作的时间复杂度都为 O(log n),其中 n 是树中节点的数量。
AVL树(Adelson-Velsky and Landis Tree)
AVL树是由两位苏联计算机科学家G.M. Adelson-Velsky和E.M. Landis于1962年提出的一种自平衡二叉搜索树。在AVL树中,任意节点的左右子树的高度差不超过1。
特点:
- AVL树是严格平衡的,任意节点的左右子树高度差不超过1。
- 插入和删除节点时,可能需要进行单旋转或双旋转等操作来保持树的平衡。
- AVL树的平衡性更强,适用于对搜索性能有严格要求的场景。
红黑树(Red-Black Tree)
红黑树是由Rudolf Bayer于1972年发明的,由Leo J. Guibas和Robert Sedgewick在1978年改进后广泛应用于计算机科学中。红黑树是一种近似平衡的二叉搜索树,它通过一些特定规则来保持树的大致平衡。
特点:
- 红黑树是近似平衡的,不像AVL树那样严格保持平衡。
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色的,叶子节点(NIL节点,空节点)是黑色的。
- 如果一个节点是红色的,则它的子节点都是黑色的(红黑树的第4条性质)。
- 从根节点到叶子节点的每条路径上,黑色节点的数量是相同的。
- 插入和删除节点时,通过颜色变换和旋转等操作来保持树的平衡。
应用场景:
- 红黑树的平衡性相对AVL树更弱一些,但是它在插入和删除操作上比AVL树更高效一些,因此在实际应用中更为常见。
- Java中的TreeMap和TreeSet、C++中的map和set等STL容器的底层实现就是红黑树。
总的来说,AVL树和红黑树都是非常重要的数据结构,在工程中应用广泛,根据具体场景的需求选择合适的树来使用
2.基本算法
- 排序算法:冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序等
链接:基本排序算法-CSDN博客
- 线性搜索、二分搜索
链接:基本搜索/查找算法-CSDN博客
- 递归和回溯算法
链接:递归和回溯算法使用-CSDN博客
- 动态规划算法
链接:动态规划问题-CSDN博客
- 贪心算法
链接:贪心算法相关-CSDN博客
- 分治算法
链接:分治算法相关-CSDN博客
- 深度优先搜索(DFS)和广度优先搜索(BFS)
链接:基本搜索/查找算法-CSDN博客
3.高阶数据结构与算法(难!!!!)
- 平衡树:AVL 树、红黑树、B 树、B+ 树
链接:平衡树:AVL 树、红黑树、B 树、B+ 树-CSDN博客
- 图的搜索算法:Dijkstra 算法、Bellman-Ford 算法、Floyd-Warshall 算法
- 最短路径算法:Dijkstra 算法、Bellman-Ford 算法、Floyd-Warshall 算法
链接:图的搜索算法:Dijkstra 算法、Bellman-Ford 算法、Floyd-Warshall 算法-CSDN博客
- 字符串匹配算法:暴力匹配、KMP 算法、Boyer-Moore 算法、Rabin-Karp 算法
链接:字符串匹配算法
- 最小生成树算法:Prim 算法、Kruskal 算法
链接:最小生成树算法:Prim 算法、Kruskal 算法-CSDN博客
- 负载均衡算法:轮询、加权轮询、哈希、最小连接数等