基础数据结构
数组
理解与实现:
- 数组是一种线性数据结构,它包含相同类型的一组有序元素,通过下标(索引)访问。
- 数组在内存中是连续存储的,这使得随机访问时间复杂度为O(1),即通过下标直接寻址即可获取元素。
- 数组的大小在创建时固定,扩容通常需要重新分配内存并复制原有元素,代价较大。
- 实现上,数组通常是一个指针加上长度信息,指针指向数组首地址。插入、删除操作可能涉及元素移动。
链表(单链表、双链表)
理解与实现:
- 链表由一系列节点组成,每个节点包含数据域和指针域(单链表只有一个指向前一个节点的指针,双链表有两个指向前一个节点和后一个节点的指针)。
- 单链表只能从头节点开始遍历,而双链表可以从任意节点双向遍历。
- 链表不是连续存储,插入和删除操作只需改变相应节点的指针,时间复杂度为O(1)(不考虑节点查找时间)。
- 实现上,链表需要定义节点结构,并实现节点的创建、插入、删除、遍历等操作。
队列
理解与实现:
- 队列遵循“先进先出”(FIFO)原则,通常包含两个操作:入队(enqueue)和出队(dequeue)。
- 可以用数组或链表实现。数组实现时要考虑循环利用空间以避免频繁扩容,链表实现则无需考虑容量限制。
- 实现上,队列类需包含队首指针、队尾指针以及元素数量,提供入队(在队尾添加元素)、出队(返回并移除队首元素)以及判断队空、队满等方法。
栈
理解与实现:
- 栈遵循“后进先出”(LIFO)原则,主要操作为压栈(push)和弹栈(pop)。
- 同样可以用数组或链表实现。数组实现同样要考虑循环利用空间,链表实现则更灵活。
- 实现上,栈类需包含栈顶指针(数组实现时为栈顶索引)以及元素数量,提供压栈(在栈顶添加元素)、弹栈(返回并移除栈顶元素)以及判断栈空等方法。
哈希表
理解与实现:
- 哈希表是一种基于关键字直接访问的数据结构,通过哈希函数将关键字映射到表中的一个位置(槽位),实现近乎常数时间的查找、插入和删除。
- 解决哈希冲突通常采用开放寻址法或链地址法。开放寻址法通过探测下一个空槽位存放冲突元素,链地址法则在每个槽位处维护一个链表(或桶)。
- 实现上,哈希表类需包含哈希函数、冲突解决策略以及内部存储结构(数组或链表)。提供插入、删除、查找、扩容等方法。
树(二叉树、平衡树、查找树)
理解与实现:
- 二叉树:每个节点最多有两个子节点,分别为左子节点和右子节点。常见的二叉树有完全二叉树、满二叉树、二叉搜索树等。
- 平衡树:是一种特殊的二叉树,如AVL树、红黑树,保证树的高度大致平衡,从而确保查找、插入、删除等操作的时间复杂度接近O(log n)。
- 查找树:如二叉搜索树(BST),每个节点的值大于其左子树的所有节点值,小于其右子树的所有节点值,便于快速查找、插入和删除。
- 实现上,树结构需定义节点类,包含数据、左右子节点指针等字段。树类提供插入、删除、查找(递归或迭代)、遍历(前序、中序、后序、层序)等方法。平衡树还需额外实现保持平衡的旋转操作。
排序算法
理解与实现:
- 冒泡排序:通过重复遍历待排序列表,比较相邻元素并交换位置,直到没有元素再需要交换。时间复杂度为O(n^2),空间复杂度为O(1)。
- 插入排序:将未排序元素逐个插入已排序序列合适的位置。对于部分有序数据表现良好。时间复杂度为O(n^2),空间复杂度为O(1)。
- 选择排序:每一趟从待排序序列中选出最小(或最大)元素,放到已排序序列的末尾。时间复杂度为O(n^2),空间复杂度为O(1)。
- 快速排序:采用分治法,选取一个基准元素,将数组划分为两部分,左边小于基准,右边大于基准,递归对这两部分进行快速排序。理想情况下时间复杂度为O(n
log n),最坏情况为O(n^2),空间复杂度取决于递归栈深度,平均为O(log n)。 - 归并排序:将待排序序列分解成两半,分别排序后再合并。始终为O(n log n)时间复杂度,但需要额外空间O(n)进行合并。
查找算法
理解与实现:
- 线性查找:从列表的第一个元素开始,依次与目标值比较,直到找到目标或遍历完列表。时间复杂度为O(n),空间复杂度为O(1)。
- 二分查找:在有序列表中,从中间元素开始比较,若目标值大于中间元素则在右半区间查找,否则在左半区间查找,不断缩小范围直至找到或确定不存在。时间复杂度为O(log
n),空间复杂度为O(1)(忽略递归调用开销)。 - 哈希查找:通过哈希函数将目标值映射到哈希表的槽位,直接访问该位置上的元素。理想情况下时间复杂度为O(1),最坏情况(所有元素哈希冲突)为O(n),空间复杂度取决于哈希表大小。
以上数据结构和算法均需理解其原理、适用场景以及优缺点,并具备手写实现代码的能力。在实际项目中,可根据数据特性和性能需求选择合适的数据结构和算法。