自己在秋招过程中遇到的数据结构与算法方面的面试题
vector是⼀种序列式容器,与array唯⼀差别就是对于空间运⽤的灵活性
array占⽤的是静态空间,⼀旦配置了就不可以改变⼤⼩,如果遇到空间不⾜的情况还要⾃⾏创建更⼤的空间,并⼿动将数据拷⻉到新的空间中,再把原来的空间释放。
vector则使⽤灵活的动态空间配置,维护⼀块连续的线性空间,在空间不⾜时,可以⾃动扩展空间容纳新元素,做到按需供给。其在扩充空间的过程中仍然需要经历: 重新配置空间,移动数据,释放原空间等操作。
动态扩容的规则:以原⼤⼩的两倍配置另外⼀块较⼤的空间(或者旧⻓度+新增元素的个数)
Vector扩容倍数与平台有关,在Win + VS 下是 1.5倍,在 Linux + GCC 下是 2 倍
频繁对vector调⽤push_back()对性能是有影响的,这是因为每插⼊⼀个元素,如果空间够⽤的话还能直接插⼊,若空间不够⽤,则需要重新配置空间,移动数据,释放原空间等操作,对程序性能会造成⼀定的影响
//定义一个单链表
struct ListNode {
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
//定义一个二叉树
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
在STL(标准模板库)中,unordered_map和map是两种不同的关联容器,它们在实现和特性上有一些区别。
当unordered_map中的元素比较多时,特定的哈希桶可能会包含较多的元素,形成一个链表。这种情况被称为哈希冲突。哈希冲突发生时,哈希函数将不同的键映射到相同的哈希桶,导致链表的长度增长。
哈希冲突会对unordered_map的性能产生一定的影响。当链表长度较长时,查找操作的平均时间复杂度可能会增加,因为需要遍历链表来找到对应的键值对。最坏情况下,哈希冲突可能导致查找操作的时间复杂度接近O(n),其中n是unordered_map中元素的数量。
C++标准库在unordered_map中使用了一种称为“链地址法”的方法来处理哈希冲突。当链表长度超过一定阈值时,会将链表转换为更高效的数据结构,如平衡二叉树或跳表,以减少查找的时间复杂度。
尽管如此,当unordered_map中的元素数量较大时,仍然需要谨慎选择合适的哈希函数,以及调整哈希桶的数量和负载因子,以平衡性能和空间的消耗。
如果对于大规模的数据集和高性能要求,可以考虑使用其他的哈希表实现,如Google的Dense Hash Map或Facebook的Folly库中的AtomicHashMap,它们提供了更高效的哈希表实现来处理大规模的数据。
优先队列(Priority Queue)是一种特殊类型的队列,其中每个元素都关联有一个优先级值。优先队列允许高优先级的元素优先于低优先级的元素被访问和处理。它可以用于以下几个方面:
在STL中,map
底层一般使用红黑树(Red-Black Tree)实现。
红黑树是一种自平衡的二叉搜索树,它通过在节点上添加额外的颜色属性,并通过一组规则来保持树的平衡性。这些规则确保了红黑树的高度相对平衡,使得在最坏情况下的操作时间复杂度为O(log n)。
为什么红黑树是相对平衡的呢?这是因为红黑树满足以下性质:
map
,则会得到更严格的平衡性,但付出的代价是在插入和删除操作时需要更多的旋转操作来保持平衡。AVL树要求任何节点的左子树和右子树的高度之差(平衡因子)不超过1,因此在插入新节点时可能需要进行更多的旋转操作,使树保持平衡。尽管AVL树的查找操作稍微快一些,但由于更多的旋转操作,插入和删除的性能相对较低。vector:在内存空间连续分布,支持下标随机访问,查询时间复杂度O(1),插入和删除时间复杂度O(n),迭代器可能会在插入或删除后失效,因为可能会重新分配到新的内存位置
list:在内存空间是节点相连方式,需要通过迭代器遍历,查询时间复杂度O(n),插入和删除时间复杂度O(1),迭代器是稳定的,插入删除不会影响,每个元素需要额外的指针,内存占用
1)分配一块更大的内存空间,通常是2倍
2)将原来空间的元素全部拷贝到新的内存空间
3)释放原来的内存空间
在 C++ 中,std::vector
是一个动态数组,它使用连续的内存块来存储元素。当创建 std::vector
并添加元素时,它会动态地分配和管理内存。下面是 std::vector
内存分配的一般过程:
std::vector
对象时,它不会分配任何内存。只有在开始添加元素时,才会进行初始分配。这通常发生在第一个元素被插入时。std::vector
会根据需要动态地分配一块连续的内存空间。它使用动态内存分配函数(如 new
或 malloc
)来分配内存。分配的内存块的大小通常会根据实现细节和策略来确定,但至少足够存储当前的元素。std::vector
中的元素数量达到当前内存块的容量时,需要进行扩容。此时,std::vector
会分配一块更大的内存空间,并将现有元素复制到新的内存块中。这涉及到分配更大的内存块,并将旧数据复制到新的内存块中,然后释放旧内存块。std::vector
对象被销毁或显式调用 clear()
函数时,它会释放所分配的内存,确保没有内存泄漏。释放内存通常使用动态内存释放函数(如 delete
或 free
)来完成。std::vector
的内存分配可能会引起元素的复制和内存的重新分配,因此在对大量元素进行插入或删除操作时,性能开销可能会增加。为了避免频繁的内存分配和复制,可以通过使用 **reserve()**
函数预留足够的内存空间,以减少重新分配的次数。std::vector
的 reserve()
和 resize()
是用于调整容器的容量和大小的函数,它们具有不同的作用和行为:
reserve(n)
:reserve()
函数用于预留足够的内存空间,以容纳至少 n
个元素,但不改变实际的元素数量(大小)。它会在不触发元素复制或构造的情况下,仅仅分配足够的内存,以提前为未来的元素添加操作做准备。reserve()
不会改变 size()
,即元素的实际数量不变。std::vector<int> vec;
vec.reserve(10); // 预留至少能容纳10个元素的内存空间,但元素数量仍为0
resize(n)
:resize()
函数用于改变 std::vector
的大小(元素数量),并相应地分配或释放内存空间。如果 n
大于当前的 size()
,则会在尾部添加默认构造的元素,使得 size()
等于 n
。如果 n
小于当前的 size()
,则会删除尾部的元素,使得 size()
等于 n
。std::vector<int> vec;
vec.resize(5); // 改变元素数量为5,新增的元素使用默认构造
vec.resize(3); // 改变元素数量为3,删除尾部的元素
总结:
reserve()
用于预留足够的内存空间,不改变实际的元素数量。resize()
用于改变 std::vector
的大小(元素数量),并相应地分配或释放内存空间。std::vector
分配的内存空间大小,可以容纳的最大元素数量。大小(size)指的是 std::vector
实际包含的元素数量。因此,容量和大小是两个不同的概念,reserve()
和 resize()
分别用于调整容量和大小。std::vector
的内存会在对象销毁时自动释放,或者在调用 clear()
函数时进行释放。
std::vector
对象超出其作用域或被显式销毁时,其析构函数会被调用,从而释放所分配的内存。{
std::vector<int> vec; // 创建 std::vector 对象
// 使用 vec ...
} // vec 超出作用域,其析构函数会被调用,释放内存
clear()
函数:clear()
函数用于清空 std::vector
中的所有元素,并释放相应的内存。调用 clear()
后,std::vector
的大小变为 0,但其容量并不改变,内存并未立即释放。
```cpp
std::vector<int> vec;
// 向 vec 添加元素 ...
vec.clear(); // 清空元素,但内存空间仍保留,容量不变
注意,调用 `clear()` 并不会立即释放内存,而是将 `std::vector` 的大小设置为 0,但保留已分配的内存空间。这样做是为了避免频繁的内存分配和释放,以提高性能。
如果希望释放内存并将容量设置为0,可以使用 `std::vector` 的 `shrink_to_fit()` 函数,它会要求释放未使用的内存空间,并将容量调整为与实际大小相匹配。
```cpp
std::vector vec;
// 向 vec 添加元素 ...
vec.clear(); // 清空元素,但内存空间保留
vec.shrink_to_fit(); // 释放未使用的内存空间,容量变为0
总结:
std::vector
的内存会在对象销毁时自动释放。clear()
函数用于清空元素,但并不立即释放内存。**shrink_to_fit()
** 函数。当使用 emplace_back 向容器中插入元素时,如果内存区域被占用(例如,容器的内部存储空间已满),容器会自动进行内存重新分配以容纳新元素。这个过程是由容器自动处理的,你不需要额外的操作。
具体地,当容器的内部存储空间不足时,容器会执行以下操作:
在处理一个 vector 列表时,如果使用迭代器进行元素删除操作,只有被删除的元素之后的迭代器会失效。被删除元素之前的迭代器仍然有效。
为了避免迭代器失效的问题,你可以使用 erase函数,该函数接受一个迭代器参数并在删除元素后返回一个指向下一个元素的迭代器。
进行扩容时,迭代器可能会失效。这是因为在扩容过程中,vector可能会重新分配内存并将现有的元素复制到新的内存位置。这导致之前获取的迭代器指向的是旧的内存位置,因此它们会失效。
当 vector 的大小超过其容量时,会触发重新分配内存的过程。这通常涉及将元素复制到一个新的内存位置,并释放旧的内存。在这个过程中,迭代器指向旧的内存位置,但复制后的元素存储在新的内存位置上。
为了避免迭代器失效的问题,可以使用索引来代替迭代器,或者在扩容之后重新获取迭代器。
list进行插入或删除操作时,迭代器不会失效。这是因为 list是一个双向链表的容器,其内部结构允许在插入或删除元素时保持现有的迭代器的有效性。
list的迭代器是以节点为基础的,每个节点存储一个元素,并包含指向前一个节点和后一个节点的指针。当进行插入或删除操作时,list可以通过重新链接节点来维护迭代器的有效性,而不需要重新分配内存或复制元素。
与 vector 不同,list的迭代器在插入和删除操作后仍然保持有效。这使得 list在需要频繁插入和删除元素的场景中更加适用。
如果是单向链表(单链表),则在删除节点之后,节点的迭代器会失效,因为单向链表的节点只包含指向下一个节点的指针,无法回溯到前一个节点。
当删除单向链表中的节点时,需要特别注意迭代器的失效问题。如果要删除当前节点,需要在删除之前记录下一个节点的指针,然后删除当前节点,并使用记录的下一个节点的指针更新迭代器。
需要注意的是,由于单向链表只有指向下一个节点的指针,无法回溯到前一个节点**,因此删除当前节点后,该节点之后的迭代器都会失效**。
优先级队列(Priority Queue)是一种特殊类型的队列,其中每个元素都具有与之关联的优先级值。这使得优先级队列能够以特定顺序处理元素,通常按照优先级从高到低或从低到高的顺序。
以下是关于优先级队列的一些关键特点和用途:
std::priority_queue
类模板,它是一个优先级队列的标准实现,可以用于各种应用中。你可以使用 push
方法插入元素,使用 top
方法访问队首元素,以及使用 pop
方法删除队首元素。std::priority_queue
创建一个升序的整数优先级队列:#include
#include
int main() {
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;
pq.push(3);
pq.push(1);
pq.push(4);
while (!pq.empty()) {
std::cout << pq.top() << " ";
pq.pop();
}
return 0;
}
n代表容器元素数量
二叉树
二叉搜索树
AVL树(平衡二叉搜索树)
红黑树
都是关联式容器,底层数据结构都是红黑树
set存储不重复的元素
map存储键值对,通常用于建立一 一对应的关系
最简单的实现就是数组,通过下标访问元素
其实hashtable就是通过哈希function将关键字映射到数组中的位置
使得散列表能够在O(1)时间复杂度内进行数据的插入、删除和查找操作
不同关键字映射到相同的哈希码
线性探测法:发生冲突寻找下一个可用位置,直到找到空槽
拉链法:每个数组位置维护一个链表,映射到同一位置的存储在链表上
不是指在内存中的排列方式,是数据元素是否按照顺序的方式排列
线性:数组,链表(逻辑线性),栈,队列
非线性:没有严格的顺序关系,树,图,堆
n代表容器元素数量
二叉树
二叉搜索树
AVL树(平衡二叉搜索树)
红黑树
都是关联式容器,底层数据结构都是红黑树
set存储不重复的元素
map存储键值对,通常用于建立一 一对应的关系
最简单的实现就是数组,通过下标访问元素
其实hashtable就是通过哈希function将关键字映射到数组中的位置
使得散列表能够在O(1)时间复杂度内进行数据的插入、删除和查找操作
哈希表在处理碰撞问题时有几种常见的策略:
最差情况下,如果哈希函数的质量较差或者哈希表的负载因子过高,导致冲突频繁发生,哈希表的性能可能会退化到O(n)的复杂度,其中n为哈希表中元素的数量。这是因为每次查找或插入时都需要遍历链表或进行多次探测,直到找到合适的位置或者遍历完所有元素。
为了避免哈希表退化到最差情况,可以采取以下策略:
总结起来,处理哈希表碰撞问题的关键是选择合适的解决策略,并合理调整哈希函数和哈希表参数,以在大多数情况下保持较好的性能。
不是指在内存中的排列方式,是数据元素是否按照顺序的方式排列
线性:数组,链表(逻辑线性),栈,队列
非线性:没有严格的顺序关系,树,图,堆
:::info
时间复杂度最好情况 最坏情况 空间复杂度 是否稳定 是否基于比较 排序方式
冒泡 O(n) O(n2) O(1) 稳定 是 内部
插入 O(n) O(n2) O(1) 稳定 是 内部
选择 O(n2) O(n2) O(1) 不稳定 是 内部
快排 O(nlogn) O(n2) O(logn) 不稳定 是 内部
堆排 O(nlogn) O(nlogn) O(1) 不稳定 是 内部
归并 O(nlogn) O(nlogn) O(n) 稳定 是 外部
希尔 O(nlog2n) O(nlog2n) O(1) 不稳定 是 内部
桶 O(n+k) O(n+k) O(k) 稳定 否 外部
计数 O(n+k) O(n2) O(n+k) 稳定 否 外部
基数 O(nk) O(nk) O(n+k) 稳定 否 外部
:::
要稳定就用归并
要节省空间就用堆排
内部排序:内存中对小规模数据进行排序,效率高
外部排序:大规模数据集,数据分块,适应内存大小,每个块排序,涉及磁盘的使用,效率低
比较相邻元素的大小
void bubbleSort(vector<int>& nums) {
int n = nums.size();
bool flag = false;
for (int i = 0; i < n - 1; ++i) {//i = 0 起,循环了n - 1趟,更符合规范理解
//for (int i = 0; i < n; ++i) {//i = 0 起,循环了n 趟,不影响结果
flag = false;
for (int j = 0; j < n - 1 - i; ++j) {
if (nums[j] > nums[j + 1]) {
//某⼀趟排序中,只要发⽣⼀次元素交换,flag就从false变为了true
//也即表示这⼀趟排序还不能确定所剩待排序列是否已经有序,应继续下⼀趟循环
swap(nums[j], nums[j + 1]);
flag = true;
}
}
//但若某⼀趟中⼀次元素交换都没有,即依然为flag = false
//那么表明所剩待排序列已经有序
//不必再进⾏趟数⽐,外层循环应该结束,即此时if (!flag) break; 跳出循环
if (!flag) { break; }
}
}
将元素插入到已排序的部分中,通过比较找到正确位置
分治策略,比较和合并
通过多次选择未排序部分的最小元素来排序
插入排序的改进,跳跃式接近
分治策略,将元素分为较小和较大的两部分
构建二叉堆,反复删除最大元素
数量比较少的时候用的插入,数量多的话用的是快排,递归深度达到某个阈值用堆排
//把数组排成最小的数
/*对vector容器内的数据进行排序,按照 将a和b转为string后
若 a+b
class Solution {
public:
static bool cmp(int a,int b){
string A="",B="";
A+=to_string(a);
A+=to_string(b);
B+=to_string(b);
B+=to_string(a);
return A<B;
}
string PrintMinNumber(vector<int> numbers) {
string answer="";
sort(numbers.begin(),numbers.end(),cmp);
for(int i=0;i<numbers.size();i++){
answer+=to_string(numbers[i]);
}
return answer;
}
};
//法二
/**string minNumber(vector& nums) {
vector temp;
for (auto num : nums) {
temp.push_back(to_string(num));
}
sort(temp.begin(), temp.end(), [](const string& a, const string& b) { return a + b < b + a; });
string result;
for (auto& t : temp) {
result += t;
}
return result;
}*/
sort中的比较函数compare要声明为静态成员函数或全局函数,不能作为普通成员函数,否则会报错。 因为:非静态成员函数是依赖于具体对象的,而std::sort这类函数是全局的,因此无法再sort中调用非静态成员函数。静态成员函数或者全局函数是不依赖于具体对象的, 可以独立访问,无须创建任何对象实例就可以访问。同时静态成员函数不可以调用类的非静态成员。
在实际实现中,sort函数还有一些优化,例如:
默认它的分布是比较随机的那种分布,然后快排在比较随机的分布上,表现的比较好,速度比较快
1、选取第⼀个数为基准
2、将⽐基准⼩的数交换到前⾯,⽐基准⼤的数交换到后⾯
3、对左右区间重复第⼆步,直到各区间只有⼀个数
从数组中选择⼀个元素,把这个元素称之为中轴元素吧,然后把数组中所有⼩于中轴元素的元素放在其左边,所有⼤于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们⽆需再移动中轴元素的位置。 从中轴元素那⾥开始把⼤的数组切割成两个⼩的数组(两个数组都不包含中轴元素),接着我们通过递归的⽅式,让 中轴元素左边的数组和右边的数组也᯿复同样的操作,直到数组的⼤⼩为1,此时每个元素都处于有序的位置
void quickSort(vector<int>& a, int low, int high) {
//结束标志
if (low >= high) return;
int last = high; // ⾼位下标
int first=low;
int key = a[first]; // 设第⼀个为基准
while (first < last){
// 从后往前⾛,将⽐第⼀个⼩的移到前⾯
while (first < last && a[last] > key){
last--;
}
if (first < last) a[first++] = a[last];
//从前往后⾛, 将⽐第⼀个⼤的移到后⾯
while (first < last && a[first] <= key){
first++;
}
if (first < last) a[last--] = a[first];
}
a[first] = key;
// 前半递归
quickSort(a, low, first - 1);
// 后半递归
quickSort(a, first + 1, high);
}
//调用
quickSort(A, 0,A.size()-1);
for (auto a : A) {
cout << a << endl;
}
#include
#include
using namespace std;
void print_nums(vector<int>& nums) {
for (auto& i : nums) {
cout << i << " ";
}
}
void quick_sort(vector<int>&nums,int left,int right) {
if (left >= right) return;
int low = left;
int high = right;
int val=nums[low];
cout << low << " " << high << " " << val << endl;
print_nums(nums);
cout << endl;
while (low < high) {
while (low < high && nums[high]>val) {
high--;
}
if (low < high) {
nums[low++] = nums[high];
}
while (low < high && nums[low] <= val) {
low++;
}
if (low < high) {
nums[high--] = nums[low];
}
}
nums[low]=val;
cout << "the index " << low << " value " << val << endl;
quick_sort(nums,left,low-1);
quick_sort(nums,low + 1, right);
}
int main() {
vector<int>nums{ 1,5,3,7,2,6,9,8 };
quick_sort(nums, 0, nums.size() - 1);
cout << "快速排序后" << endl;
print_nums(nums);
return 0;
}
将⼀个⼤的⽆序数组有序,我们可以把⼤的数组分成两个,然后对这两个数组分别进⾏排序,之后在把这两个数组 合并成⼀个有序的数组。由于两个⼩的数组都是有序的,所以在合并的时候是很快的。 通过递归的⽅式将⼤的数组⼀直分割,直到数组的⼤⼩为 1,此时只有⼀个元素,那么该数组就是有序的了,之后 再把两个数组⼤⼩为1的合并成⼀个⼤⼩为2的,再把两个⼤⼩为2的合并成4的 … 直到全部⼩的数组合并起来
归并排序是建⽴在归并操作上的⼀种有效的排序算法。该算法是采⽤分治法(Divide and Conquer)的⼀个⾮常典 型的应⽤。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为2-路归并。
1、把⻓度为n的输⼊序列分成两个⻓度为n/2的⼦序列;
2、对这两个⼦序列分别采⽤归并排序;
3、 将两个排序好的⼦序列合并成⼀个最终的排序序列。
比如将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8]
void mergeSortCore(vector<int>& data, vector<int>& dataTemp, int low, int high) {
if (low >= high) return;
int len = high - low, mid = low + len / 2;
int start1 = low, end1 = mid, start2 = mid + 1, end2 = high;
mergeSortCore(data, dataTemp, start1, end1);
mergeSortCore(data, dataTemp, start2, end2);
int index = low;
while (start1 <= end1 && start2 <= end2) {
dataTemp[index++] = data[start1] < data[start2] ? data[start1++] : data[start2++];
}
while (start1 <= end1) {
dataTemp[index++] = data[start1++];
}
while (start2 <= end2) {
dataTemp[index++] = data[start2++];
}
for (index = low; index <= high; ++index) {
data[index] = dataTemp[index];
}
}
void mergeSort(vector<int>& data) {
int len = data.size();
vector<int> dataTemp(len, 0);
mergeSortCore(data, dataTemp, 0, len - 1);
}
#include
#include
using namespace std;
void print_nums(vector<int>& nums) {
for (auto& i : nums) {
cout << i << " ";
}
}
void merge_sort(vector<int>&nums, vector<int>&temp, int low, int high) {
if (low >= high) return;
int mid = low + (high - low) / 2;
int start1 = low, end1 = mid;
int start2 = mid+1, end2 = high;
merge_sort(nums, temp, start1,end1);
merge_sort(nums, temp, start2,end2);
int index = low;
while (start1 <= end1 && start2 <= end2) {
temp[index++] = nums[start1] < nums[start2] ? nums[start1++] : nums[start2++];
}
while (start1 <= end1) {
temp[index++] = nums[start1++];
}
while (start2 <= end2) {
temp[index++] = nums[start2++];
}
for (index = low; index <= high; index++) {
nums[index] = temp[index];
}
}
int main() {
vector<int>nums{ 1,5,3,7,2,6,9,8 };
vector<int>temp(nums.size(), 0);
merge_sort(nums,temp, 0, nums.size() - 1);
cout << "归并排序后" << endl;
print_nums(nums);
return 0;
}
大根堆:每个节点的值都大于或者等于他的左右孩子节点的值
小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值
注意:升序用大根堆,降序就用小根堆(默认为升序)
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
void HeapAdjust(int* arr, int start, int end){
int tmp = arr[start];
for (int i = 2 * start + 1; i <= end; i = i * 2 + 1){
//有右孩子并且左孩子小于右孩子
if (i < end&& arr[i] < arr[i + 1]){
i++;
}
//i一定是左右孩子的最大值
if (arr[i] > tmp){
arr[start] = arr[i];
start = i;
}
else{
break;
}
}
arr[start] = tmp;
}
void HeapSort(int* arr, int len){
//第一次建立大根堆,从后往前依次调整
for(int i=(len-1-1)/2;i>=0;i--){
HeapAdjust(arr, i, len - 1);
}
//每次将根和待排序的最后一次交换,然后在调整
int tmp;
for (int i = 0; i < len - 1; i++){
tmp = arr[0];
arr[0] = arr[len - 1-i];
arr[len - 1 - i] = tmp;
HeapAdjust(arr, 0, len - 1-i- 1);
}
}
int main(){
int arr[] = { 9,5,6,3,5,3,1,0,96,66 };
HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
printf("排序后为:");
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){
cout<<arr[i]<<" ";
}
return 0;
}
拓扑排序是一种对有向无环图(DAG,Directed Acyclic Graph)进行排序的算法。在拓扑排序中,图中的每个节点代表一个任务,而图中的有向边则表示任务之间的依赖关系。拓扑排序的目标是找到一种排列,使得所有的任务都按照依赖关系的方向进行排序。
拓扑排序的主要特点是:如果图中存在一条从节点A到节点B的有向边,那么在排序中节点A一定出现在节点B的前面。
拓扑排序广泛应用于任务调度、编译器优化、依赖关系分析等领域。例如,如果你有一系列任务,每个任务都有一些依赖关系,拓扑排序可以帮助确定任务的执行顺序,确保满足所有的依赖关系。
在编译器中,源代码中的函数或模块之间存在依赖关系,拓扑排序可以确定编译的顺序,确保每个模块在被调用之前已经被编译。