1.数组与链表的区别与优缺点分别是什么?
•数组优点:索引访问速度快(O(1)复杂度),适合于快速随机访问。
•数组缺点:插入和删除元素效率较低(需要移动后续元素,平均复杂度O(n));大小固定,扩容成本高。
•链表优点:插入和删除元素效率较高(只需要修改指针,平均复杂度O(1)),空间利用率相对灵活。
•链表缺点:无法进行高效的随机访问(访问某一位置需要从头开始遍历,平均复杂度O(n))。
数组与链表的增删改查操作在不同数据结构中有不同的实现方式,下面分别给出数组与单链表的基础操作实现示例(以C++为例):
数组的操作:
#include <iostream>
using namespace std;
const int MAX_SIZE = 100; // 假设数组的最大容量为100
int arr[MAX_SIZE];
int size = 0; // 记录数组中元素的实际数量
// 增加操作(数组末尾添加元素)
void arrayAppend(int value) {
if (size < MAX_SIZE) {
arr[size++] = value;
} else {
cout << "数组已满,无法添加新元素!\n";
}
}
// 删除操作(假设数组是动态有序数组,删除指定值的元素)
bool arrayRemove(int value) {
for (int i = 0; i < size; ++i) {
if (arr[i] == value) {
for (int j = i; j < size - 1; ++j) {
arr[j] = arr[j + 1]; // 移动元素
}
--size;
return true;
}
}
return false; // 如果没找到值,则返回false
}
// 修改操作(更改指定索引处的元素值)
void arrayUpdate(int index, int newValue) {
if (index >= 0 && index < size) {
arr[index] = newValue;
} else {
cout << "索引越界,无法修改元素!\n";
}
}
// 查找操作(查找并返回指定值的索引)
int arrayFind(int value) {
for (int i = 0; i < size; ++i) {
if (arr[i] == value) {
return i;
}
}
return -1; // 如果没找到值,则返回-1
}
// 示例:
void runArrayOps() {
arrayAppend(5);
arrayAppend(10);
arrayAppend(15);
cout << "查找元素10的索引:" << arrayFind(10) << endl;
arrayUpdate(1, 12); // 将索引1处的元素改为12
arrayRemove(12); // 删除值为12的元素
}
int main() {
runArrayOps();
return 0;
}
单链表的操作:
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
class LinkedList {
private:
ListNode* head;
public:
LinkedList() : head(nullptr) {}
// 增加操作(链表末尾添加元素)
void listAppend(int value) {
ListNode* newNode = new ListNode(value);
if (!head) {
head = newNode;
} else {
ListNode* temp = head;
while (temp->next) {
temp = temp->next;
}
temp->next = newNode;
}
}
// 删除操作(删除值为指定值的第一个节点)
bool listRemove(int value) {
ListNode* temp = head, *prev = nullptr;
if (temp != nullptr && temp->val == value) {
head = temp->next;
delete temp;
return true;
}
while (temp != nullptr && temp->val != value) {
prev = temp;
temp = temp->next;
}
if (temp != nullptr) {
prev->next = temp->next;
delete temp;
return true;
}
return false; // 如果没找到值,则返回false
}
// 修改操作(更改值为指定值的节点值)
void listUpdate(int oldValue, int newValue) {
ListNode* temp = head;
while (temp != nullptr) {
if (temp->val == oldValue) {
temp->val = newValue;
return;
}
temp = temp->next;
}
cout << "链表中不存在值为" << oldValue << "的节点,无法修改!\n";
}
// 查找操作(查找并返回值为指定值的节点)
ListNode* listFind(int value) {
ListNode* temp = head;
while (temp != nullptr) {
if (temp->val == value) {
return temp;
}
temp = temp->next;
}
return nullptr; // 如果没找到值,则返回nullptr
}
// 示例:
void runListOps() {
listAppend(5);
listAppend(10);
listAppend(15);
ListNode* foundNode = listFind(10);
if (foundNode) {
cout << "查找元素10的节点:" << foundNode->val << endl;
}
listUpdate(10, 12); // 将值为10的节点改为12
listRemove(12); // 删除值为12的节点
}
~LinkedList() {
ListNode* temp = head;
while (temp) {
ListNode* toDelete = temp;
temp = temp->next;
delete toDelete;
}
}
};
int main() {
LinkedList linkedList;
linkedList.runListOps();
return 0;
}
2. 什么是栈和队列?它们的特点和应用场景分别是什么?
•栈(Stack):后进先出(Last In First Out, LIFO)的数据结构,特点是只能在一端进行插入和删除操作。应用场景包括函数调用栈、表达式求值、括号匹配等。
•队列(Queue):先进先出(First In First Out, FIFO)的数据结构,特点是在一端进行插入(enqueue),在另一端进行删除(dequeue)。应用场景包括任务调度、消息队列、广度优先搜索等。
3. 二叉树和平衡二叉树的区别是什么? AVL树和红黑树有什么特点?
•二叉树:每个节点最多有两个子节点,没有特定的高度平衡要求。
•平衡二叉树(如AVL树和红黑树):在每次插入或删除节点后,通过旋转等操作保持树的左右两个子树的高度差不超过1,以确保查找、插入、删除等操作的时间复杂度接近O(log n)。
•AVL树:严格的平衡二叉搜索树,高度平衡条件是任意节点的左右子树高度差不超过1。
•红黑树:一种弱平衡二叉搜索树,通过颜色属性(红色和黑色)保证大致平衡,它的旋转次数通常少于AVL树,插入和删除操作的效率相对较高。
4. 哈希表是如何工作的?哈希冲突如何解决?
•哈希表通过哈希函数将键映射到一个固定大小的数组的索引上,通过这个索引快速访问数据。哈希函数的目标是让键均匀分布,以减少冲突。
•哈希冲突解决方法主要有开放寻址法(如线性探测、二次探测等)和链地址法(如拉链法,每个槽位存放一个链表,冲突的键被链接在同一链表中)。
5. 什么是图的深度优先搜索(DFS)和广度优先搜索(BFS)?
•深度优先搜索(DFS)是一种沿着树的深度遍历的策略,尽可能深地搜索树的分支,直到到达叶子节点或无法继续前进为止,然后回溯到上一个节点继续探索其他分支。
•广度优先搜索(BFS)是从根节点开始,按照层级逐层访问节点,同一层级的节点按顺序访问,通常使用队列实现。
6. 什么是堆?最大堆和最小堆的区别是什么?
•堆是一种特殊的树形数据结构,每个节点的值都大于或小于其子节点的值。
堆有两种形式:
•最大堆:父节点的值总是大于或等于其子节点的值。
•最小堆:父节点的值总是小于或等于其子节点的值。•堆常用于实现优先队列,例如在Top K问题、堆排序算法中广泛应用。
7.什么是数据结构?
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。结构包括逻辑结构和物理结构。
数据的逻辑结构包括4种
(1)集合:数据元素之间除了有相同的数据类型再没有其他的关系
(2)线性结构:数据元素之间是一对一的关系 ——线性表、栈、队列
(3)树形结构:数据元素之间是一对多的关系
(4)图状结构:数据元素之间是多对多的关系。
物理结构包括顺序存储结构和链式存储结构。
8.栈和队列的区别和应用场景分别是什么?
•栈(Stack):栈是一种遵循“后进先出”(Last-In-First-Out,LIFO)原则的数据结构。插入操作通常称为“压栈”或“入栈”,删除操作称为“弹栈”或“出栈”。栈的应用场景包括但不限于:函数调用栈(存储函数调用的上下文信息)、括号匹配问题、深度优先搜索算法等。
•队列(Queue):队列遵循“先进先出”(First-In-First-Out,FIFO)原则。插入操作通常称为“入队”,删除操作称为“出队”。队列的应用场景包括但不限于:任务调度(按照任务到达的顺序执行)、消息队列(按照消息发送的顺序传递)、广度优先搜索算法等。
1.栈的基本操作(Python实现):
class Queue:
def __init__(self):
self.queue = []
def enqueue(self, item):
"""入队操作"""
self.queue.append(item)
def dequeue(self):
"""出队操作"""
if not self.is_empty():
return self.queue.pop(0)
else:
return None
def is_empty(self):
"""判断队列是否为空"""
return len(self.queue) == 0
# 示例
q = Queue()
q.enqueue(1)
q.enqueue(2)
print(q.dequeue()) # 输出:1
2.队列的基本操作(Python实现)
class Queue:
def __init__(self):
self.queue = []
def enqueue(self, item):
"""入队操作"""
self.queue.append(item)
def dequeue(self):
"""出队操作"""
if not self.is_empty():
return self.queue.pop(0)
else:
return None
def is_empty(self):
"""判断队列是否为空"""
return len(self.queue) == 0
# 示例
q = Queue()
q.enqueue(1)
q.enqueue(2)
print(q.dequeue()) # 输出:1
9. 什么是二叉搜索树?它的性质是什么?如何实现查找、插入和删除操作?
•二叉搜索树(BST)是一种特殊的二叉树,其性质为:对于树中的每一个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。查找操作可通过递归或迭代方式进行,从根节点开始比较,向左或向右子树移动直到找到目标值或查找到空节点。
插入操作需要找到合适的插入位置(即比插入值大的节点的左子树为空的位置或插入值小于根节点的左边界),然后创建新节点并连接到合适位置。删除操作较为复杂,需要考虑删除节点是叶节点、只有一个子节点还是有两个子节点的情况,分别采用不同的策略进行删除并保持BST的性质。
10. 如何实现LRU缓存淘汰策略?
•实现LRU(Least Recently Used)缓存淘汰策略可以采用哈希表和双向链表结合的方式。哈希表用于存储键值对,链表用于按访问顺序维护键。当新的键值对加入缓存时,将其放到链表头部,并在哈希表中更新其位置。当缓存满时,删除链表末尾的节点(这是最近最少使用的键),同时从哈希表中移除。
11. 图的邻接矩阵和邻接表分别是怎样的?
•邻接矩阵:是一个二维数组,数组的行和列对应图中的顶点,数组中的元素表示对应顶点之间的连接情况,若存在边则值为1(或边的权重),否则为0。邻接矩阵的空间复杂度为O(V^2),适合稠密图。
•邻接表:是每个顶点关联一个链表,链表中的节点表示与该顶点相连的其他顶点。邻接表可以进一步优化为邻接多重表(每个顶点关联一个双向链表,存储所有相连的顶点)或稀疏矩阵(仅存储非零元素的三元组)。邻接表的空间复杂度为O(E+V),适合稀疏图。
以下是使用邻接矩阵表示无向图的简单Python实现,包括创建图、添加边以及遍历图的功能:
class Graph:
def __init__(self, vertices):
"""
初始化一个空的邻接矩阵来表示图
:param vertices: 图中的顶点数
"""
self.vertices = vertices
self.graph = [[0] * vertices for _ in range(vertices)]
def add_edge(self, v1, v2):
"""
添加一条无向边
:param v1: 边的第一个顶点
:param v2: 边的第二个顶点
"""
if v1 >= self.vertices or v2 >= self.vertices:
raise IndexError("Vertex out of range")
self.graph[v1][v2] = 1
self.graph[v2][v1] = 1 # 因为是无向图,所以两个方向都要标记为1
def display_graph(self):
"""
显示当前图的邻接矩阵表示
"""
print("Adjacency Matrix:")
for row in self.graph:
print(row)
def bfs(self, start_vertex):
"""
使用广度优先搜索遍历图
:param start_vertex: 开始搜索的起始顶点
"""
visited = [False] * self.vertices
queue = []
visited[start_vertex] = True
queue.append(start_vertex)
while queue:
vertex = queue.pop(0)
print(vertex, end=" ")
for i in range(self.vertices):
if self.graph[vertex][i] and not visited[i]:
visited[i] = True
queue.append(i)
# 示例
g = Graph(5)
g.add_edge(0, 1)
g.add_edge(0, 4)
g.add_edge(1, 2)
g.add_edge(1, 3)
g.add_edge(1, 4)
g.add_edge(2, 3)
g.display_graph()
print("\nBFS from vertex 0:")
g.bfs(0)
在这个例子中,Graph 类通过邻接矩阵 graph 来存储图的信息,并提供了添加边的方法 add_edge() 和广度优先搜索(BFS)的实现 bfs()。请注意,这里的邻接矩阵采用的是稀疏矩阵的形式,即用1表示相邻,0表示不相邻。如果要表示带权重的图,可以将矩阵中的值替换为对应的权重。
12. Dijkstra算法是什么,它解决了什么问题?
•Dijkstra算法是荷兰计算机科学家艾兹格·迪科斯彻发明的一种单源最短路径算法,用于在一个有权重的有向图或无向图中寻找从单一顶点到其余所有顶点的最短路径。该算法通过逐步放松边的权值,始终保持从起始顶点出发到每个顶点的已知最短路径,并在每一步选取当前未确定最短路径的顶点中具有已知最短路径的顶点,对其进行松弛操作。
13. 贪心算法是什么?举例说明其应用场景。
•贪心算法是一种在每一步选择中都采取在当前状态下看起来最优(局部最优)的解决方案,期望以此达到全局最优解的策略。贪心算法并不一定总能得到全局最优解,但在某些特定条件下可以。例如在赫夫曼编码中,我们不断选择当前权值最小的两个结点合并,生成新的结点并将权值累加,最终构建一棵赫夫曼树,用于压缩数据传输。在这个过程中,每一步都选择了局部最优解(即最小权值结点),最终得到了全局最优的赫夫曼编码方案。
以下是一个简单的贪心算法编程示例——硬币找零问题,目标是使用最少数量的面额硬币组合成指定金额。
# Python 示例:硬币找零问题(贪心算法)
# 假设有以下面额的硬币可供选择:[1, 5, 10, 25]
def coin_change(coins, amount):
"""
coins: 可用硬币面额列表(假设列表已按降序排列)
amount: 需要找零的金额
"""
result = [] # 存放结果,即找零所需的硬币列表
coins_used = {} # 记录每个硬币面额使用的数量
for coin in coins:
while amount >= coin:
# 贪心选择:尽可能多地使用当前面额硬币
amount -= coin
if coin in coins_used:
coins_used[coin] += 1
else:
coins_used[coin] = 1
# 将使用的硬币数量转换为列表形式
for coin, count in coins_used.items():
for _ in range(count):
result.append(coin)
return result, amount # 返回找零硬币列表及剩余未找零的金额(理论上应为0)
# 示例
coins = [1, 5, 10, 25]
amount = 36
result, remaining = coin_change(coins, amount)
print(f"找零硬币:{result}, 剩余金额:{remaining}")