目录
引言
在计算机科学中,数据结构和算法是构建高效软件系统的基础。数据结构提供了组织、管理和存储数据的方式
,而算法则是解决问题的一系列有序步骤
。了解和掌握它们对于任何软件开发者来说都是至关重要的。
正文开始
,如果觉得文章对您有帮助,请帮我三连+订阅,谢谢
💖💖💖
数据结构基础
定义
数据结构是计算机科学中存储、组织数据的方式,它使得我们可以高效地访问和修改数据。数据结构通常包括数据的逻辑关系,这些关系定义了数据元素之间的相互作用。
重要性
- 效率:选择合适的数据结构可以显著提高程序的执行效率。
- 可读性:良好的数据结构设计可以提高代码的可读性和可维护性。
- 功能:不同的数据结构支持不同的操作,选择正确的数据结构可以简化实现复杂功能的过程。
常见数据结构
- 数组:连续的内存空间,支持快速随机访问。
- 链表:由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。
- 栈:后进先出(LIFO)的数据结构。
- 堆(Heap):一种特殊的树形数据结构,满足堆性质:即父节点的值总是大于或小于它的子节点(最大堆或最小堆)。
- 队列:先进先出(FIFO)的数据结构。
- 哈希表:通过键值对存储数据,支持快速查找。
- 图(Graph):由顶点(或节点)和边组成,可以表示复杂的关系,如网络、路径等。
- 并查集(Disjoint Set or Union-Find): 用于处理一些不交集(Disjoint Sets)的合并及查询问题,常用于解决网络连通性问题。
- 树(Tree):由节点组成的层次结构,每个节点有零个或多个子节点,常用于表示具有层次关系的数据。
- 二叉树(Binary Tree): 每个节点最多有两个子节点(左子节点和右子节点)的树结构。
- 二叉搜索树(Binary Search Tree, BST): 一种特殊的二叉树,其中每个节点的左子树只包含小于当前节点的键,右子树只包含大于当前节点的键。
- 平衡树(Balanced Tree): 一种自平衡的二叉搜索树,如AVL树和红黑树,它们通过在插入和删除操作后调整树结构来保持树的平衡。
- 字典树(Trie): 用于检索字符串数据集中的键的数据结构,允许在时间复杂度为O(n)的情况下快速插入和查找字符串。
算法基础
定义
算法是解决特定问题的一系列计算步骤。一个好的算法应该具备正确性、可终止性、有穷性,并在资源使用上尽可能高效。
时间复杂度
时间复杂度是衡量算法执行时间的指标,通常用大O
符号表示。它描述了算法执行时间随输入规模增长的变化趋势。
- O(1):
常数
时间复杂度,与输入规模无关。 - O(log n):
对数
时间复杂度,常用于二分搜索。 - O(n):
线性
时间复杂度,操作数与输入规模成正比。 - O(n log n):
对数线性
时间复杂度,常用于快速排序等。 - O(n^2):
平方时间
复杂度,常见于简单排序算法。 - O(2^n):
指数时间复杂度
, 算法运行时间随输入规模呈指数增长,通常效率较低。 - O(n!):
阶乘时间复杂度
, 算法运行时间与输入规模的阶乘成正比,效率非常低,常见于某些排列问题。
空间复杂度
空间复杂度是衡量算法执行过程中所需存储空间的指标。它同样用大O符号表示,描述了算法占用空间随输入规模增长的变化趋势。
时间复杂度示例代码
以下代码将用 Javascript 语法演示
O(1) - 常数时间复杂度
基础代码,什么都没有
// 常数时间操作
function constantTimeOperation() {
let a = 1,
b = 2;
let c = a + b;
console.log("输出结果:", c);
return c;
}
O(log n) - 对数时间复杂度
binarySearch
函数要求参数数组是 已排序的,
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;
}
let arr = [1,3,5,7,9,11,13,15]
let index = binarySearch(arr,5)
console.log(`index: ${index}`)
O(n) - 线性时间复杂度
常见的for循环
function linearTimeOperation(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i]; // 线性时间操作
}
return sum;
}
let arr = [1,3,5,7,9,11,13,15]
let sum = linearTimeOperation(arr)
console.log(`sum: ${sum}`)
O(n log n) - 对数线性时间复杂度
典型快速排序算法
function quickSort(arr) {
if (arr.length < 2) {
return arr;
}
let pivot = arr[0];
let left = [];
let right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
O(n^2) - 平方时间复杂度
常见的冒泡算法
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
let arr = [1,8,3,2,12,6,11,4,15]
let res = bubbleSort(arr)
console.log(`res: ${res}`)
O(2^n) - 指数时间复杂度
简单的递归函数
function recursiveExponential(n) {
if (n === 0) {
return 1;
}
return recursiveExponential(n - 1) + recursiveExponential(n - 1);
}
O(n!) - 阶乘时间复杂度
常见结成算法
function factorial(n) {
if (n === 0 || n === 1) {
return 1;
}
return n * factorial(n - 1);
}
空间复杂度示例代码解释:
空间复杂度是指算法在执行过程中所需的存储空间,这包括输入数据所占的空间以及算法执行过程中临时占用的空间。
O(1) - 常数空间复杂度
常数空间复杂度意味着算法使用的空间不随输入规模的变化而变化。
function constantSpace() {
let result = 42; // 使用的额外空间是常数
return result;
}
O(log n) - 对数空间复杂度
对数空间复杂度通常与树结构的深度有关,例如二叉搜索树。
function logSpace(arr, low, high, target) {
if (high >= low) {
let mid = Math.floor((low + high) / 2);
if (arr[mid] === target) {
return [mid]; // 找到一个元素
} else if (arr[mid] > target) {
return logSpace(arr, low, mid - 1, target); // 递归搜索左半部分
} else {
return logSpace(arr, mid + 1, high, target); // 递归搜索右半部分
}
}
return []; // 未找到
}
O(n) - 线性空间复杂度
线性空间复杂度意味着算法使用的空间与输入规模成正比。
function linearSpace(arr) {
let newArr = new Array(arr.length); // 创建一个与输入数组等长的数组
for (let i = 0; i < arr.length; i++) {
newArr[i] = arr[i] * 2; // 复制并修改原数组的元素
}
return newArr;
}
O(n^2) - 平方空间复杂度
平方空间复杂度通常出现在使用二维数组存储数据时。
function squareSpace(rows, cols) {
let matrix = new Array(rows); // 创建一个行数组
for (let i = 0; i < rows; i++) {
matrix[i] = new Array(cols); // 每行创建一个列数组
for (let j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j; // 初始化矩阵元素
}
}
return matrix;
}
O(2^n) - 指数空间复杂度
指数空间复杂度通常出现在递归算法中,尤其是那些有大量重复计算的算法。
function exponentialSpace(n) {
if (n === 0) {
return [];
}
let subProblem1 = exponentialSpace(n - 1);
let subProblem2 = exponentialSpace(n - 1);
let combined = subProblem1.concat(subProblem2); // 合并两个子问题的解
return combined;
}
O(n!) - 阶乘空间复杂度
阶乘空间复杂度非常罕见,通常与某些特定的递归算法有关。
function factorialSpace(n) {
if (n === 0 || n === 1) {
return [1]; // 只有一个元素的数组
}
let subFactorial = factorialSpace(n - 1);
let newLevel = new Array(n); // 为当前层创建一个数组
for (let i = 0; i < n; i++) {
newLevel[i] = subFactorial.map(el => el * (n - i)); // 复制并修改下一层的元素
}
return subFactorial.concat(newLevel); // 合并前一层和当前层
}
总结
数据结构和算法是软件开发中的基石。掌握它们不仅能够帮助我们写出更高效的代码,还能提高我们解决问题的能力。通过实际的代码示例,我们可以更深入地理解每种数据结构和算法的工作原理和使用场景。随着技术的不断发展,新的数据结构和算法也在不断涌现,持续学习是每个程序员的必修课。