数据结构—排序(斐波那契数列,冒泡,选择,插入,快速,归并,图,广度优先算法)
目录
一 斐波那契数列(递归算法)
定义
原理
二 冒泡排序
定义
排序思路
函数原型
参数详解:
算法分析:
1. 使用函数库的qsort函数
2. 自定义冒泡排序
三 选择排序
定义
排序思路
四 插入排序
定义
排序思路
五 快速排序
定义
排序思路
六 归并排序
定义
排序思路
详细排序思路:
详细过程思路:
排序思路
七 广度优先算法
1. 定义
1. 是什么?
2. 为什么要学BFS?
2. BFS的详细思路与过程
1. 核心数据结构:队列(Queue)
2. 算法步骤分解(以树为例)
步骤1:初始化
步骤2:处理队列中的节点
步骤3:继续处理队列
步骤4:重复直到队列为空
八 图
一、图的定义
🌰 类比举例:
二、图的组成与关键术语
1. 图的分类
2. 核心术语
三、图的存储方式
1. 邻接矩阵(Adjacency Matrix)
2. 邻接表(Adjacency List)
四、图的遍历算法
1. 深度优先搜索(DFS, Depth-First Search)
2. 广度优先搜索(BFS, Breadth-First Search)
五、图的应用场景
六、代码实战:图的完整实现(邻接表)
七、总结
九 总结
一 斐波那契数列(递归算法)
定义
斐波那契数列是一个自然数序列,从0和1开始,后续每个数都是前两个数之和:
F(0)=0,F(1)=1,F(n)=F(n−1)+F(n−2)(n≥2)F(0)=0,F(1)=1,F(n)=F(n−1)+F(n−2)(n≥2)
原理
递归思想:将问题分解为更小的同类问题。
动态规划优化:避免重复计算,提高效率。
#include <stdio.h>
#include <stdlib.h>
/*
斐波那契数列(Fibonacci sequence),又称黄金分割数列 [1],
因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称“兔子数列”,
其数值为:0、1、1、2、3、5、8、13、21、34……
在数学上,这一数列以如下递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)。
*/
//使用递归算法,实现斐波那契数列(Fibonacci sequence)
int fibonacci(int n);int main(int argc, char const *argv[])
{//打印斐波那契数列(Fibonacci sequence)printf("请输入需要打印de斐波那契数列(Fibonacci sequence)数量(正整数):\n");int n;scanf("%d",&n);printf("斐波那契数列(Fibonacci sequence):\n");for (int i = 0; i < n; i++){printf("%d ",fibonacci(i));}printf("\n");return 0;
}//使用递归算法,实现斐波那契数列(Fibonacci sequence)
int fibonacci(int n){if (n<0){printf("斐波那契数列没有负数....\n");exit(EXIT_FAILURE);//异常退出}else if (n<=1){return n;//结束条件}else{//递归调用自身,实现实现斐波那契数列(Fibonacci sequence)return fibonacci(n-1)+fibonacci(n-2);}}
二 冒泡排序
java 冒泡排序 包括(超级简单,简单,一般)_java冒泡排序-CSDN博客
java的冒泡排序
冒泡排序:
一种排序的方式,对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有的数据按照要求完成排序.定义
通过重复交换相邻的逆序元素,将最大元素逐步“冒泡”到数组末尾。头文件:stdlib.h
排序思路
外层循环:控制遍历轮数(每轮确定一个最大元素的位置)。
内层循环:遍历未排序部分,比较相邻元素并交换。
外层循环控制需要进行的排序轮数,每轮将最大的元素“冒泡”到数组的末尾;
内存循环则负责逐个比较相邻的元素,并在必要时交换它们的位置;
每次循环将最大的元素移动到右侧,直到整个数组排序完成
函数原型
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *) );
参数详解:
参数名 类型 描述 base void*
指向待排序数组首元素的指针。 nitems size_t
数组中待排序的元素数量。 size size_t
每个元素的大小(字节数),通常用 sizeof(元素类型)
获取。compar int (*)(const void*, const void*)
比较函数指针,需自定义逻辑:
- 返回值<0
:第一个元素较小;
- 返回值=0
:两元素相等;
- 返回值>0
:第一个元素较大。算法分析:
时间复杂度:
最好情况:O(n^2)
最坏情况:O(n^2)
平均情况:O(n^2)
空间复杂度:只有一个额外变量
最好情况:O(1)
最坏情况:O(1)
平均情况:O(1)
稳定性(相等元素的顺序):冒泡排序是稳定的排序算法,相等元素不会被打乱顺序
尽管冒泡排序简单易懂,但是效率低下,通常不适用于大规模数据的排序
1. 使用函数库的qsort函数
#include <stdio.h>
#include <stdlib.h>
/*
二、C语言有自有的qsort函数。
头文件:stdlib.h
原型: void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *));参数:1. void *_Basebase: 指向待排序数组的第一个元素的指针。2. size_t _NumOfElements数组中的元素数量。3. size_t _SizeOfElements数组中每个元素的大小(以字节为单位)。4.int(__cdecl * _PtFuncCompare)(const void *, const void *)compar: 比较函数的指针,该函数用于比较两个元素。比较函数应当返回一个整数,表示比较结果:小于零:表示第一个元素小于第二个元素。等于零:表示两个元素相等。大于零:表示第一个元素大于第二个元素。5.无返回值
*/
// 定义一个包含五个整数的数组
int values[] = {88, 56, 100, 2, 25};// 比较函数,用于比较两个整数,返回值为两值之差
int cmpfunc(const void *a, const void *b)
{return (*(int *)a - *(int *)b);
}int main()
{int n;// 输出排序之前的数组内容printf("排序之前的列表:\n");// 遍历输出values的值,此处使用的是int n 我认为不妥,应该define一个自动长度for (n = 0; n < 5; n++){printf("%d ", values[n]);}// qsort其实是在库中声名的一个函数,并没有定义其内容// 使用 qsort 函数对数组进行排序qsort(values, 5, sizeof(int), cmpfunc);// 输出排序之后的数组内容printf("\n排序之后的列表:\n");for (n = 0; n < 5; n++){printf("%d ", values[n]);}return 0;
}
/*
GitHub Copilot:你的观察是正确的:在 stdlib.h 头文件中,qsort 只是进行了声明(即函数原型),并没有给出具体的实现(定义)。例如:为什么你能直接调用 qsort?
这是因为:声明告诉编译器有这样一个函数,参数和返回值是什么。
定义(实现)实际代码并不在头文件里,而是在对应的C标准库实现(如 Windows 下的 msvcrt.dll 或 ucrtbase.dll,Linux 下的 libc.so)的源代码中。
链接时,编译器会把你的 qsort 调用链接到C运行库(CRT)中已经实现好的 qsort 函数。
具体实现在哪里?
在 Windows 下,qsort 的实现通常在 msvcrt.dll 或 ucrtbase.dll 这样的动态链接库(DLL)里。
你用 MinGW 或 MSVC 编译时,链接器会自动把你的程序和这些库链接起来。
你可以在 MinGW 的源码包或微软的 CRT 源码中找到 qsort 的具体实现(如 qsort.c),但普通用户只需要声明和链接库即可使用。
总结
头文件只声明了 qsort,没有实现。
实现代码在C运行库的源代码或二进制库(DLL、.lib等)中。
链接器负责把你的调用和库里的实现关联起来,所以你能直接用。
如果你想看具体实现,可以去查找 MinGW 或 MSVC 的 CRT 源码包,搜索 qsort.c 文件。
*/
2. 自定义冒泡排序
#include <stdio.h>
#define LENGTH(array) (sizeof(array)/sizeof(array[0]))/*
冒泡排序:冒泡排序是一种简单的排序算法,它重复地遍历待排序的列表,一次比较两个相邻的元素,如果它们的顺序错误(从小到大/从大到小)就把它们交换过来;实现:
1.外层循环控制需要进行的排序轮数,每轮将最大的元素“冒泡”到数组的末尾;
2.内存循环则负责逐个比较相邻的元素,并在必要时交换它们的位置;
3.每次循环将最大的元素移动到右侧,直到整个数组排序完成算法分析:时间复杂度: 最好情况:O(n^2)最坏情况:O(n^2)平均情况:O(n^2)空间复杂度:只有一个额外变量最好情况:O(1)最坏情况:O(1)平均情况:O(1)稳定性(相等元素的顺序):冒泡排序是稳定的排序算法,相等元素不会被打乱顺序尽管冒泡排序简单易懂,但是效率低下,通常不适用于大规模数据的排序*///冒泡排序
void bubble_sort(int arr[],int n);//打印数组
void print_array(int arr[],int n);int main(int argc, char const *argv[])
{int arr[]={66,77,88,41,11,1,2,5,3,69,7,4,6,6,9,65,5,65,66,5};printf("排序前:\n");print_array(arr,LENGTH(arr));printf("bubble_sort 冒泡排序:\n");bubble_sort(arr,LENGTH(arr));printf("排序后:\n");print_array(arr,LENGTH(arr));return 0;
}//冒泡排序
void bubble_sort(int arr[],int n){//三个临时变量int i,j,temp;//1.外层循环控制需要进行的排序轮数,每轮将最大的元素“冒泡”到数组的末尾;for ( i = 0; i < n-1; i++){//2.内存循环则负责逐个比较相邻的元素,并在必要时交换它们的位置;for ( j = 0; j < n-1-i; j++){//3.每次循环将最大的元素移动到右侧,直到整个数组排序完成if (arr[j]>arr[j+1]){temp=arr[j];arr[j]=arr[j+1];arr[j+1]=temp;}}}
}//打印数组
void print_array(int arr[],int n){for (int i = 0; i < n; i++){printf("%d ",arr[i]);}printf("\n");
}
三 选择排序
定义
首先,在待排序序列中定位最小(或最大)元素,将其与序列首元素交换位置;随后,在剩余未排序元素中继续寻找最小(或最大)元素,并与未排序部分的首元素交换。这一过程循环进行,直至所有元素有序排列。
在每次迭代中,算法都会确定未排序部分的最小(或最大)元素,并将其添加到已排序序列的末尾,从而逐步构建有序序列。
排序思路
外层循环:遍历数组,确定当前位置应放置的最小元素。
内层循环:在未排序部分中找到最小值索引。
交换:将最小值与当前位置元素交换。
#include <stdio.h>
#define LENGTH(array) (sizeof(array)/sizeof(array[0]))/*
选择排序:选择排序是一种简单直观的排序算法,它首先在待排序序列中找到最小(最大)元素,然后将其放置在已排序序列的末尾,然后再从剩余未排序元素中继续选择最小(或最大)元素,依次类推,直到所有元素都排序完成。每次循环会找到未排序部分最小(最大)元素,放到已排序部分的末尾;实现:1.外层循环控制待排序部分的起始位置2.内层循环直到未排序部分最小元素(或者最大)的位置min_index3.找到最小元素后,将其与当前未排序部分的第一个元素交换算法分析:时间复杂度: 最好情况:O(n^2)最坏情况:O(n^2)平均情况:O(n^2)空间复杂度:只有一个额外变量最好情况:O(1)最坏情况:O(1)平均情况:O(1)稳定性(相等元素的顺序):选择排序是不稳定的排序算法,相等元素会被打乱顺序尽管选择排序简单易懂,但是效率低下,通常不适用于大规模数据的排序*///打印数组
void print_array(int arr[],int n);//选择排序
void selection_sort(int arr[],int n);int main(int argc, char const *argv[])
{int arr[]={66,77,88,41,11,1,2,5,3,69,7,4,6,6,9,65,5,65,66,5};printf("排序前:\n");print_array(arr,LENGTH(arr));printf("selection_sort 选择排序:\n");selection_sort(arr,LENGTH(arr));printf("排序后:\n");print_array(arr,LENGTH(arr));return 0;
}//打印数组
void print_array(int arr[],int n){for (int i = 0; i < n; i++){printf("%d ",arr[i]);}printf("\n");
}//选择排序
void selection_sort(int arr[],int n){int i,j,min_index,temp;for ( i = 0; i < n-1; i++){//找到未排序部分最小的元素//假如待排序第一个是最小的min_index=i;for ( j = i+1; j < n; j++){if (arr[j]<arr[min_index]){min_index=j;}}//循环结束,找到当前轮最小的index//找到最小元素后,将其与当前未排序部分的第一个元素交换temp=arr[min_index];arr[min_index]=arr[i];arr[i]=temp;}
}
四 插入排序
插入排序:
插入排序是一种简单直观的排序算法,它的工作方式类似于按顺序将扑克牌插入到手中的牌中。
插入排序每次从未排序部分取出一个元素,将它插入已排序部分的正确位置。
每次循环将未排序部分的第一个元素插入到已排序部分的正确位置,直到整个数组排序完成。
定义
将未排序元素逐个插入到已排序部分的正确位置。
排序思路
外层循环:遍历未排序元素(从第二个元素开始)。
内层循环:将当前元素与已排序部分从后向前比较,找到插入位置。
#include <stdio.h>#define LENGTH(array) (sizeof(array)/sizeof(array[0]))/*
插入排序:插入排序是一种简单直观的排序算法,它的工作方式类似于按顺序将扑克牌插入到手中的牌中。插入排序每次从未排序部分取出一个元素,将它插入已排序部分的正确位置。每次循环将未排序部分的第一个元素插入到已排序部分的正确位置,直到整个数组排序完成。插入排序的实现:
1.外层循环从第二个元素开始(即index为1),将每个元素插入到前面已排序的部分中
2.内层循环将当前元素与前面已排序部分的元素进行比较,并将比当前元素大的元素后移一位,直到找到合适的位置插入当前元素算法分析:时间复杂度: 最好情况:O(n^2)最坏情况:O(n^2)平均情况:O(n^2)空间复杂度:只有一个额外变量最好情况:O(1)最坏情况:O(1)平均情况:O(1)稳定性(相等元素的顺序):插入排序是稳定的排序算法,相等元素不会被打乱顺序尽管插入排序简单易懂,但是效率低下,通常不适用于大规模数据的排序
*///插入排序
void insertion_sort(int arr[],int n);//打印数组
void print_array(int arr[],int n);int main(int argc, char const *argv[])
{int arr[]={66,77,88,41,11,1,2,5,3,69,7,4,6,6,9,65,5,65,66,5};printf("排序前:\n");print_array(arr,LENGTH(arr));printf("insertion_sort 插入排序:\n");insertion_sort(arr,LENGTH(arr));printf("排序后:\n");print_array(arr,LENGTH(arr));return 0;
}//打印数组
void print_array(int arr[],int n){for (int i = 0; i < n; i++){printf("%d ",arr[i]);}printf("\n");
}//插入排序
void insertion_sort(int arr[],int n){int i,j,key;//1.外层循环从第二个元素开始(即index为1),将每个元素插入到前面已排序的部分中for ( i = 1; i < n; i++){key=arr[i];j=i-1;//2.内层循环将当前元素与前面已排序部分的元素进行比较,并将比当前元素大的元素后移一位,直到找到合适的位置插入当前元素while (j>=0 && arr[j]>key){arr[j+1]=arr[j];j--;}//找到合适的位置插入当前元素arr[j+1]=key;}
}
五 快速排序
快速排序:
快速排序是一种分治算法。它选择一个元素作为“枢轴”(pivot),
将数组分成两部分,小于枢轴的元素放在枢轴的左边,大于枢轴的元素放在枢轴的右边,
然后对两部分递归进行快速排序。
定义
采用分治策略,选择一个基准元素,将数组分为左右两部分(左半部分≤基准,右半部分≥基准),递归排序子数组。
排序思路
基准选择:通常选第一个或中间元素。
分区操作:将数组分为左右两部分。
递归排序:对左右子数组重复上述步骤。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{// 1. 首先,我们需要一个快速排序的函数声明void quick_sort(int arr[], int left, int right);// 2. 还需要一个打印数组的函数声明,方便观察排序前后的结果void print_array(int arr[], int size);// 3. 定义一个待排序的数组int arr[] = {34, 7, 23, 32, 5, 62, 32, 2, 78, 1};int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度// 4. 打印排序前的数组printf("排序前的数组: ");print_array(arr, n);// 5. 调用快速排序函数,对整个数组进行排序quick_sort(arr, 0, n - 1);// 6. 打印排序后的数组printf("排序后的数组: ");print_array(arr, n);return 0;
}// 快速排序的实现
// 参数说明:arr为待排序数组,left为排序区间左端点,right为右端点
void quick_sort(int arr[], int left, int right)
{// 递归终止条件:当区间内只有一个或没有元素时,直接返回if (left >= right){return;}// 选择基准值,这里选择区间最左边的元素int pivot = arr[left];int i = left;int j = right;// 下面进行一趟排序,将比pivot小的放左边,大的放右边while (i < j){// 从右往左找第一个小于pivot的元素while (i < j && arr[j] >= pivot){j--;}if (i < j){arr[i] = arr[j]; // 将小于pivot的元素填到左边的坑i++;}// 从左往右找第一个大于pivot的元素while (i < j && arr[i] <= pivot){i++;}if (i < j){arr[j] = arr[i]; // 将大于pivot的元素填到右边的坑j--;}}// 最后将基准值归位arr[i] = pivot;// 递归排序基准值左边和右边的子区间quick_sort(arr, left, i - 1);quick_sort(arr, i + 1, right);
}// 打印数组的函数实现
void print_array(int arr[], int size)
{for (int i = 0; i < size; i++){printf("%d ", arr[i]);}printf("\n");
}
六 归并排序
定义
采用分治策略,将数组递归分为两半,分别排序后合并。然后将排好序的子数组合并成一个有序的大数组。
排序思路
分解:将数组递归分为两半,直到子数组长度为1。
合并:将两个有序子数组合并为一个有序数组。
详细排序思路:
1. 如果数组长度为1,说明已经有序,直接返回。
2. 将数组从中间分成左右两个部分。
3. 对左半部分递归进行归并排序。
4. 对右半部分递归进行归并排序。
5. 合并两个有序的子数组,得到一个有序的大数组。
详细过程思路:
排序思路
分解:将数组递归分为两半,直到子数组长度为1。
合并:将两个有序子数组合并为一个有序数组。
递归拆分:不断将数组对半分,直到每个子数组只剩下一个元素。
合并过程:将两个有序的子数组合并成一个有序数组。合并时,分别用指针指向两个子数组的起始位置,比较指针所指元素的大小,将较小的元素放入临时数组,然后指针后移,直到有一个子数组元素全部放入临时数组,再将另一个子数组剩余的元素全部放入临时数组。
最后将临时数组的内容复制回原数组。
#include <stdio.h>
#include <stdlib.h>
#define LENGTH(array) (sizeof(array) / sizeof(array[0]))/*
/*
归并排序(Merge Sort)是一种分治法(Divide and Conquer)的典型应用。
它的基本思想是:将一个大的无序数组递归地分成两个子数组,分别对这两个子数组进行排序,
然后将排好序的子数组合并成一个有序的大数组。详细排序思路:
1. 如果数组长度为1,说明已经有序,直接返回。
2. 将数组从中间分成左右两个部分。
3. 对左半部分递归进行归并排序。
4. 对右半部分递归进行归并排序。
5. 合并两个有序的子数组,得到一个有序的大数组。详细过程思路:
- 递归拆分:不断将数组对半分,直到每个子数组只剩下一个元素。
- 合并过程:将两个有序的子数组合并成一个有序数组。合并时,分别用指针指向两个子数组的起始位置,比较指针所指元素的大小,将较小的元素放入临时数组,然后指针后移,直到有一个子数组元素全部放入临时数组,再将另一个子数组剩余的元素全部放入临时数组。
- 最后将临时数组的内容复制回原数组。代码实现如下:
*/// 合并两个有序子数组的函数
void merge(int arr[], int left, int mid, int right)
{int n1 = mid - left + 1; // 左子数组长度int n2 = right - mid; // 右子数组长度// 创建临时数组int *L = (int *)malloc(n1 * sizeof(int));int *R = (int *)malloc(n2 * sizeof(int));// 拷贝数据到临时数组L和Rfor (int i = 0; i < n1; i++)L[i] = arr[left + i];for (int j = 0; j < n2; j++)R[j] = arr[mid + 1 + j];// 合并临时数组到原数组arrint i = 0, j = 0, k = left;while (i < n1 && j < n2){if (L[i] <= R[j]){arr[k++] = L[i++];}else{arr[k++] = R[j++];}}// 将L剩余元素复制到arrwhile (i < n1){arr[k++] = L[i++];}// 将R剩余元素复制到arrwhile (j < n2){arr[k++] = R[j++];}// 释放临时数组free(L);free(R);
}// 归并排序的递归函数
void mergeSort(int arr[], int left, int right)
{if (left < right){int mid = left + (right - left) / 2; // 防止溢出// 对左半部分排序mergeSort(arr, left, mid);// 对右半部分排序mergeSort(arr, mid + 1, right);// 合并两个有序子数组merge(arr, left, mid, right);}
}
int main(int argc, char const *argv[])
{int a[] = {5, 9, 55, 48, 6, 226, 48, 2, 3, 6, 4, 959, 262};// 数组a的长度int n = LENGTH(a);printf("排序前: ");// 遍历一下原数组for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");// 调用归并排序// 0 表示数组的起始下标// n-1 表示数组的结束下标(最后一个元素)// 这样可以让归并排序处理整个数组mergeSort(a, 0, n - 1);printf("排序后: ");for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");return 0;
}
七 广度优先算法
1. 定义
1. 是什么?
-
广度优先算法是一种用于遍历或搜索树(Tree)或图(Graph)的算法。
-
核心思想:逐层访问节点,先访问离起点最近的节点,再访问更远的节点。
-
类比理解:像“水波纹扩散”一样,从起点开始,一圈一圈向外探索。
2. 为什么要学BFS?
-
解决最短路径问题(如迷宫最短出口)。
-
社交网络中查找“二度人脉”。
-
网络爬虫按层级抓取网页。
2. BFS的详细思路与过程
1. 核心数据结构:队列(Queue)
-
队列特性:先进先出(FIFO),保证按层级顺序处理节点。
-
操作:
-
入队(Enqueue):将未访问的相邻节点加入队列。
-
出队(Dequeue):处理当前节点。
-
2. 算法步骤分解(以树为例)
假设有一棵树如下:
A/ \B C/ \ \D E F
步骤1:初始化
-
创建一个空队列。
-
将起点(根节点A)标记为已访问并入队。
队列:[A]
已访问:{A}
步骤2:处理队列中的节点
-
出队A,打印A。
-
访问A的子节点B和C,标记为已访问并入队。
队列:[B, C]
已访问:{A, B, C}
输出:A
步骤3:继续处理队列
-
出队B,打印B。
-
访问B的子节点D和E,入队。
队列:[C, D, E]
已访问:{A, B, C, D, E}
输出:A B
步骤4:重复直到队列为空
-
出队C,打印C。
-
访问C的子节点F,入队。
队列:[D, E, F]
输出:A B C
-
出队D(无子节点),出队E(无子节点),出队F(无子节点)。
最终输出:A B C D E F
八 图
一、图的定义
图(Graph) 是一种由 节点(Vertex) 和 边(Edge) 组成的非线性数据结构,用于表示对象之间的关系。
-
节点(或顶点):表示实体(如城市、用户、网页等)。
-
边:表示节点之间的连接关系(如道路、社交关系、超链接等)。
🌰 类比举例:
-
社交网络:用户是节点,好友关系是边。
-
地图导航:城市是节点,公路是边。
-
网页链接:网页是节点,超链接是边。
二、图的组成与关键术语
1. 图的分类
类型 | 描述 |
---|---|
无向图 | 边没有方向,表示双向关系(如微信好友)。 |
有向图 | 边有方向,表示单向关系(如微博关注)。 |
加权图 | 边带有权重(如地图中道路的长度或耗时)。 |
无权图 | 边无权重,仅表示是否存在连接。 |
2. 核心术语
术语 | 描述 |
---|---|
度(Degree) | 节点连接的边数(有向图中分 入度 和 出度)。 |
路径(Path) | 从一个节点到另一节点经过的边序列。 |
环(Cycle) | 起点和终点相同的路径。 |
连通图 | 任意两个节点间都有路径。 |
子图 | 从原图中选取部分节点和边组成的图。 |
三、图的存储方式
1. 邻接矩阵(Adjacency Matrix)
-
定义:用二维数组表示节点之间的连接关系。
-
示例:节点数为
n
,矩阵大小为n×n
,matrix[i][j] = 1
表示节点i
和j
相连。
// 邻接矩阵示例(无向图)
int graph[4][4] = {{0, 1, 1, 0},{1, 0, 0, 1},{1, 0, 0, 1},{0, 1, 1, 0}
};
-
特点:
-
适合稠密图(边多)。
-
查询快(O(1)),但空间占用高(O(n²))。
-
2. 邻接表(Adjacency List)
-
定义:用链表或数组的数组存储每个节点的邻接节点。
-
示例:节点
0
的邻居是1
和2
,节点1
的邻居是0
和3
。
// 邻接表示例(C语言)
typedef struct Node {int vertex;struct Node* next;
} Node;Node* graph[4] = {NULL, NULL, NULL, NULL
};// 添加边(无向图)
void addEdge(int src, int dest) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->vertex = dest;newNode->next = graph[src];graph[src] = newNode;// 无向图需双向添加newNode = (Node*)malloc(sizeof(Node));newNode->vertex = src;newNode->next = graph[dest];graph[dest] = newNode;
}
-
特点:
-
适合稀疏图(边少)。
-
空间占用低(O(n + e)),查询邻居需遍历链表(O(degree))。
-
四、图的遍历算法
1. 深度优先搜索(DFS, Depth-First Search)
-
核心思想:沿一条路径深入到底,再回溯探索其他分支。
-
应用场景:路径查找、拓扑排序、检测环。
// DFS递归实现
void DFS(int v, bool visited[], Node* graph[]) {visited[v] = true;printf("%d ", v);Node* adjNode = graph[v];while (adjNode != NULL) {int neighbor = adjNode->vertex;if (!visited[neighbor]) {DFS(neighbor, visited, graph);}adjNode = adjNode->next;}
}
2. 广度优先搜索(BFS, Breadth-First Search)
-
核心思想:逐层遍历,先访问离起点最近的节点。
-
应用场景:最短路径(无权图)、社交网络中的层级关系。
// BFS队列实现
void BFS(int start, Node* graph[], int n) {bool visited[n];for (int i = 0; i < n; i++) visited[i] = false;Queue* q = createQueue();enqueue(q, start);visited[start] = true;while (!isEmpty(q)) {int v = dequeue(q);printf("%d ", v);Node* adjNode = graph[v];while (adjNode != NULL) {int neighbor = adjNode->vertex;if (!visited[neighbor]) {visited[neighbor] = true;enqueue(q, neighbor);}adjNode = adjNode->next;}}
}
五、图的应用场景
领域 | 具体应用 |
---|---|
社交网络 | 用户关系分析、推荐系统(如共同好友)。 |
交通导航 | 最短路径算法(Dijkstra、Floyd-Warshall)。 |
网络爬虫 | 网页链接分析(PageRank算法)。 |
电路设计 | 电路节点连接检测、电流路径规划。 |
生物信息学 | 蛋白质相互作用网络、基因调控网络。 |
六、代码实战:图的完整实现(邻接表)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>// 定义邻接表节点
typedef struct Node {int vertex;struct Node* next;
} Node;// 创建图(邻接表)
Node** createGraph(int vertices) {Node** graph = (Node**)malloc(vertices * sizeof(Node*));for (int i = 0; i < vertices; i++) {graph[i] = NULL;}return graph;
}// 添加边(无向图)
void addEdge(Node** graph, int src, int dest) {// 添加src→dest的边Node* newNode = (Node*)malloc(sizeof(Node));newNode->vertex = dest;newNode->next = graph[src];graph[src] = newNode;// 添加dest→src的边(无向图)newNode = (Node*)malloc(sizeof(Node));newNode->vertex = src;newNode->next = graph[dest];graph[dest] = newNode;
}// 打印图
void printGraph(Node** graph, int vertices) {for (int i = 0; i < vertices; i++) {Node* temp = graph[i];printf("节点 %d 的邻居:", i);while (temp) {printf("%d ", temp->vertex);temp = temp->next;}printf("\n");}
}int main() {int vertices = 4;Node** graph = createGraph(vertices);addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 3);addEdge(graph, 2, 3);printGraph(graph, vertices);return 0;
}
输出:
节点 0 的邻居:2 1
节点 1 的邻居:3 0
节点 2 的邻居:3 0
节点 3 的邻居:2 1
七、总结
图是描述复杂关系的核心数据结构,广泛应用于现实场景。
-
学习重点:掌握图的存储方式(邻接矩阵 vs 邻接表)、遍历算法(DFS/BFS)、经典问题(最短路径、最小生成树)。
-
进阶方向:学习 Dijkstra、Prim、Kruskal 等算法,探索图数据库(如 Neo4j)。
如果有具体问题(如“如何实现最短路径算法”),欢迎继续提问! 🚀
九 总结
排序算法 | 平均 $T(n)$ | 最坏 $T(n)$ | 最好 $T(n)$ | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
选择排序 | $O(n^2)$ | $O(n^2)$ | $O(n^2)$ | $O(1)$ | 不稳定 |
插入排序 | $O(n^2)$ | $O(n^2)$ | $O(n)$ | $O(1)$ | 稳定 |
希尔排序 | $O(n^{1.3})$ | $O(n^2)$ | $O(n)$ | $O(1)$ | 不稳定 |
冒泡排序 | $O(n^2)$ | $O(n^2)$ | $O(n)$ | $O(1)$ | 稳定 |
快速排序 | $O(n\log_2n)$ | $O(n^2)$ | $O(n\log_2n)$ | $O(n\log_2n)$ | 不稳定 |
在此理清以下代码思路,我认为还是先理解逻辑结构,在动手去写代码会更好些,本章代码实在是多,我暂时无法理解,希望之后的时间会经常来看看。
相关文章:
数据结构—排序(斐波那契数列,冒泡,选择,插入,快速,归并,图,广度优先算法)
目录 一 斐波那契数列(递归算法) 定义 原理 二 冒泡排序 定义 排序思路 函数原型 参数详解: 算法分析: 1. 使用函数库的qsort函数 2. 自定义冒泡排序 三 选择排序 定义 排序思路 四 插入排序 定义 排序思路 五 快速…...
NetSuite CSV导入Item Fulfillment的功能测试
上一篇我们说过如何通过CSV导入更新IF上的Department/Class信息,这篇是来测试一下如果SO在Pending Fulfillment的状态下通过CSV导入IF,这个新版本的一个功能,刚好将测试的过程与结果与大家分享~ 准备文件 External ID是外部ID; …...
网络原理 | 网络基础概念复习
目录 网络中的重要概念 IP地址 端口号 协议 五元组 协议分层 OSI七层网络模型 TCP/IP 五层(四层)模型 网络设备所在的分层 封装和分用 网络中的重要概念 IP地址 IP地址主要用于标识网络主机、其他网络设备的网络地址。在网络数据传输中&#…...
Vsan数据恢复——Vsan上虚拟机不可用,虚拟机组件信息破坏的数据恢复
Vsan数据恢复环境: 一台采用VsSAN分布式文件系统的存储设备由于未知原因关机重启。管理员发现上层的虚拟机不可用,存储内的数据丢失。 Vsan数据恢复过程: 1、将故障存储设备断电,将存储内的硬盘编号后取出。硬件工程师检测后没有发…...
V837s-LAN8720A网口phy芯片调试
目录 前言 一、LAN8720A 芯片概述 二、硬件连接 三、设备树配置 四、内核配置 五、网口调试 总结 前言 在嵌入式系统开发中,网络连接是至关重要的一部分。v837s开发板搭载了LAN8720A系列的网口PHY芯片,用于实现以太网连接。在开发过程中,对于网口的稳定性和性能的调试至…...
C++(12):using声明
目录 一、定义 二、核心用法示例 示例 1:单独引入 std::string 和 std::coun 示例 2:在局部作用域中使用 using 声明 三、对比 using namespace std(不推荐) 四、关键注意事项 1. 名称冲突问题 2. 作用域规则 3. 头文件中的陷阱 五、最佳实践总结 六、完整安全示…...
Xinference 命令大全:从模型部署到管理
Xinference 是一个高性能、分布式的模型推理框架,支持多种大语言模型(LLM)、嵌入模型(Embedding)和图像生成模型。本文将详细介绍 Xinference 的常用命令,涵盖模型启动、管理、监控及 API 调用,帮助你快速掌握其核心功能。 1. 安装与启动 Xinference 1.1 安装 Xinferen…...
如何在线免费压缩PDF文档?
PDF文件太大,通常是因为内部嵌入字体和图片。怎么才能将文件大小减减肥呢,主要有降低图片清晰度和去除相关字体两个方向来实现文档效果。接下来介绍三个免费压缩PDF实用工具。 (一)iLoveOFD在线转换工具 iLoveOFD在线转换工具&a…...
在Rocky Linux 9.5上部署MongoDB 8.0.9:从安装到认证的完整指南
mongodb 的部署 #安装依赖 yum -y install libcurl openssl #安装mongodb yum -y install https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/RPMS/mongodb-org-server-8.0.9-1.el9.x86_64.rpm #启动服务 systemctl start mongod.service && system…...
Unix Bourne Shell
本文来源 : 腾讯元宝 Unix Bourne Shell(简称sh)是Unix系统中最经典的命令行解释器(shell),由Stephen Bourne于1977年在贝尔实验室开发,并成为后续众多shell(如bash、ksh等ÿ…...
如何在 AWS 上构建支持 AVIF 的前端图片优化方案
一、为什么使用 AVIF 图片格式? 优势点 说明 高压缩率 在相似质量下,AVIF 文件比 JPEG/PNG/WebP 更小,能有效节省带宽和存储空间。 更高画质 即使在低码率下也能保持清晰细节,减少压缩带来的马赛克或模糊问题。 支持透明度 …...
Linux系统进行环境开发环境配置
一. 使用fishros(鱼香肉丝)配置开发环境 对于初学者来说,最难的关卡莫非是开发环境的的搭建,特别是在Ubuntu系统上ROS系统安装时后出现的各种报错以及失败,本篇博客讲述了ROS系统的一键安装过程,适用于18.04及以后的Ubuntu系统版本…...
前端npm的核心作用与使用详解
一、npm是什么? npm(Node Package Manager) 是 Node.js 的默认包管理工具,也是全球最大的开源代码库生态系统。虽然它最初是为 Node.js 后端服务设计的,但如今在前端开发中已成为不可或缺的基础设施。通过npm,开发者可以轻松安装、管理和共享代码模块。 特性: 依赖管理…...
软考软件评测师——软件工程之系统维护
一、系统质量属性 可维护性 衡量软件系统适应修改的难易程度,包括修复缺陷、扩展功能或调整规模的效率。计算公式为:系统可用时间占比 1/(1平均修复时间),其中平均修复时间(MTTR)指排除故障所需的平均耗时。 可靠性 vs 可用性 可靠性&…...
CSRF攻击 + 观测iframe加载时间利用时间响应差异侧信道攻击 -- reelfreaks DefCamp 2024
参考: https://0x90r00t.com/2024/09/30/3708/ 题目信息 有些事情最好还是保持低调。当然,除非你是个真正的怪胎。 注意:该网站通过HTTPS提供服务 标志格式:DCTF{}题目实现了一个类似视频网站的东西 在其提供的数据库中…...
火山RTC 8 SDK集成进项目中
一、SDK 集成预备工作 1、SDK下载 https://www.volcengine.com/docs/6348/75707 2、解压后 3、放在自己项目中的位置 1)、include 2)、lib 3)、dll 暂时,只需要VolcEngineRTC.dll RTCFFmpeg.dll openh264-4.dll, 放在intLive2…...
spring boot Controller 和 RestController 的区别
spring boot Controller 和 RestController 的区别 5.3.1常用注解 Spring MVC控制器中常使用的注解有如下几种。 Controller Controller 标记在类上。使用Controller 标记的类表示是Spring MVC的Controller对象。分发处理器将会扫描使用了该注解的类,并检测其中的…...
mavgenerate 在 win11 下环境搭建注意问题
开发随笔 mavgenerate 是mavlink配套的协议生成工具,mavgenerate 在 win11 下环境搭建注意问题: 1、Python 就使用文件包当中的版本,由于python 版本能与 future 及 pip 之间存在特定的组合关系,故不推荐下载使用最新版本 2、安…...
SSM项目集成redis、Linux服务器安装redis
在SSM(Spring Spring MVC MyBatis)项目中引入Redis主要分为以下步骤,确保配置正确并能在业务中灵活使用: 1. 添加Redis依赖 在Maven的pom.xml中添加Spring Data Redis和Jedis(或Lettuce)依赖&#…...
sqli-labs靶场第七关——文件导出注入
一:目标 通过sql注入将php代码写入网站目录,通过这个php文件执行命令 二:确认前置条件 %secure_file_priv% 首先我们需要Mysql是否允许导出文件 先尝试在网页中sql注入,检查导出权限 ?id1)) union select 1,secure_file_pr…...
python使用matplotlib无法显示中文字体报错
python使用matplotlib字体报错 当我们使用python使用matplotlib总是出现报错,图片中文变成方框 findfont: Font family WenQuanYi Micro Hei not found. findfont: Font family Heiti TC not found. findfont: Font family [SimHei] not found. Falling back to De…...
VTEP是什么
VTEP(VXLAN Tunnel Endpoint,VXLAN 隧道端点)是 VXLAN(Virtual Extensible LAN)网络中的关键组件,用于处理 VXLAN 流量的封装和解封装。以下以可读的 Markdown 格式详细解释 VTEP 的定义、功能、实现方式以…...
React Native简介
React Native 是由 Meta(原 Facebook)开源的跨平台移动应用开发框架,基于 React 和 JavaScript,允许开发者使用同一套代码库构建 iOS 和 Android 原生应用。通过 JavaScript 调用原生组件实现高性能渲染。 跨平台开发 共享 80%-9…...
边缘计算模块
本文来源 :腾讯元宝 边缘计算模块是一种部署在网络边缘(靠近数据源)的集成化硬件/软件设备,用于实时处理本地数据,减少云端依赖,提升响应速度与安全性。以下是其核心要点: 1. 核心组成 …...
策略模式-枚举实现
策略模式的实现方法有很多,可以通过策略类if,else实现。下面是用枚举类实现策略模式的方法。 定义一个枚举类,枚举类有抽象方法,每个枚举都实现抽象方法。这个策略,实现方法是工具类的很实现,代码简单好理解 枚举实现…...
C++算法(22):二维数组参数传递,从内存模型到高效实践
引言 在C程序设计中,二维数组的参数传递是许多开发者面临的棘手问题。不同于一维数组的相对简单性,二维数组在内存结构、类型系统和参数传递机制上都存在独特特性。本文将深入探讨静态数组、动态数组以及STL容器三种实现方式,通过底层原理分…...
LeetCode LCR 015. 找到字符串中所有字母异位词 (Java)
LCR 015. 找到字符串中所有字母异位词 题目描述 给定两个字符串 s 和 p,要求找到 s 中所有是 p 的变位词(字母相同但排列不同)的子串,并返回这些子串的起始索引。例如: 输入 s "cbaebabacd", p "a…...
幼儿学前教育答辩词答辩技巧问题答辩自述稿
### 📘《幼儿园大班活动开展存在的问题及解决策略》📝 我的论文题目是《幼儿园大班活动开展存在的问题及解决策略》📖。我将从论文框架、研究内容、需要解决的问题、研究结论这四部分来阐述我的论文📝。 论文框架由绪论Ǵ…...
双目立体视觉
文章目录 1,前言2,原理3,组成部分3.1,数字图像采集。3.2 ,相机标定。3.3,图像预处理与特征提取。3.4 ,图像校正。3.5 ,立体匹配。3.6 ,三维重建。 4,主要的算…...
机器人弧焊二八混合气体节约
焊接技术在现代工业生产中作为关键环节之一,其效率和成本直接影响到整个制造流程的经济性与环保性。近年来,随着节能减排理念深入人心,各行业都在积极探索绿色制造方案。在焊接领域,二八混合气体的应用结合WGFACS智能流量调节系统…...
Linux进程通讯和原子性
在Linux系统中,进程间通信(IPC)和原子性是并发编程中的核心问题。以下是对这些概念的详细分步解释: 一、进程间通信(IPC)方法 1. 管道(Pipe) 匿名管道:用于父子进程等有…...
深度学习之用CelebA_Spoof数据集搭建一个活体检测-一些模型训练中的改动带来的改善
实验背景 在前面的深度学习之用CelebA_Spoof数据集搭建一个活体检测-模型搭建和训练,我们基于CelebA_Spoof数据集构建了一个用SqueezeNe框架进行训练的活体2D模型,采用了蒸馏法进行了一些简单的工作。在前面提供的训练参数中,主要用了以下几…...
Oracle APEX IR报表列宽调整
1. 问题:如何调整Oracle APEX IR报表列宽 1-1. 防止因标题长而数据短,导致标题行的文字都立起来了,不好看。 1-2. 防止因数据太长而且中间还没有空格,把列撑开的太宽也不换行,不好看。 2. 解决办法 针对如上问题解…...
6大核心记忆方法
以下是结合脑科学原理和高效学习策略总结的 6大核心记忆方法,帮助你摆脱“学完就忘”的困境: 一、间隔重复与分散学习 遵循艾宾浩斯遗忘曲线:学习后20分钟遗忘58%,1天后遗忘66%。通过设定复习节点(如学后1天、3天、1周…...
conda更换清华源
1、概览 anaconda更换速度更快、更稳定的下载源,在linux环境测试通过。 2、conda源查看 在修改之前可以查看下现有conda源是什么,查看conda配置信息,如下: cat ~/.condarc 可以看到你的conda源,以我的conda源举例&am…...
5月15日星期四今日早报简报微语报早读
5月15日星期四,农历四月十八,早报#微语早读。 1、中国至越南河内国际道路运输线路正式开通; 2、免签国1,中乌(兹别克斯坦)互免签证协定6月生效; 3、杭州“放大招”支持足球发展:足…...
网络损伤仪功能介绍与应用场景剖析
以下是关于 网络损伤仪(Network Impairment Emulator) 的核心功能介绍及其应用场景的详细说明: 一、网络损伤仪的核心功能 带宽限制(Bandwidth Throttling) 模拟不同网络带宽(如从1Mbps到10Gbps)…...
超时检测机制和心跳包机制(Heartbeat)
一、超时检测机制 1. I/O 函数超时设置 1.1 select/poll/epoll 的超时参数 select c struct timeval timeout {3, 0}; // 3秒超时 int n select(maxfd1, &readfds, NULL, NULL, &timeout); if (n 0) printf("select timeout\n"); // 超时无事件poll c …...
经典卷积神经网络
目录 经典卷积神经网络 一、卷积神经网络基础回顾 二、LeNet:开启 CNN 先河 三、AlexNet:突破性进展 四、ZFNet:继承与优化 五、GoogLeNet:引入 Inception 模块 六、VggNet:深度与简单结构的融合 七、ResNet&a…...
Reactor模型详解与C++实现
Reactor模型详解与C实现 一、Reactor模型核心思想 Reactor模式是一种事件驱动的并发处理模型,核心通过同步I/O多路复用实现对多个I/O源的监听,当有事件触发时,派发给对应处理器进行非阻塞处理。 关键特征: 非阻塞I/Oÿ…...
观测云产品更新 | 安全监测、事件中心、仪表板AI智能分析等
观测云更新 安全监测 新增 SIEM 功能模块:实时分析企业各类系统(如服务器、应用、网络设备)的日志和事件数据,自动发现潜在威胁,帮助团队迅速定位异常,充分发挥安全监控中枢的作用。 注意:目…...
【HTML】个人博客页面
目录 页面视图编辑 页面代码 解释: HTML (<body>): 使用了更加语义化的HTML5标签,例如<header>, <main>, <article>, <footer>。文章列表使用了<article>包裹,结构清晰。添加了分页导航。使用了Font…...
OrangePi Zero 3学习笔记(Android篇)10 - SPI和从设备
目录 1. 配置内核 2. 修改设备数 3. 修改权限 4. 验证 Zero 3的板子有2个SPI Master接口,其中SPI0接的是板载16MB大小的SPI Nor Flash,SPI1则是导出到26pin的接口上。 spi和i2c有点不同,spi是直接生成spi虚拟设备,所以在dev里…...
《Java 大视界——Java 大数据在智能电网分布式能源协同调度中的应用与挑战》
随着风电、光伏等分布式能源大规模接入电网,传统调度系统面临数据规模激增、响应延迟显著、多源异构数据融合困难等核心问题。本文聚焦Java生态下的大数据技术体系,深入探讨其在智能电网实时监测、负荷预测、资源优化配置等场景中的落地实践。通过分析Sp…...
基于正点原子探索者开发板的简易音乐播放器
1、概述 本次实验的名称叫做“基于正点原子探索者开发板的简易音乐播放器”。本实验的功能框图如下: 从图上我们可以清晰的看到本实验所需的实现的功能、以及每个功能需要怎么实现。 这次实验使用的是正点原子的探索者开发板,此开发板采用的MCU是STM32F4…...
【CF】Day59——Codeforces Round 914 (Div. 2) D
D. Set To Max 题目: Easy 思路: 简单题 由于题目的数据给的很小,所以我们可以用 n 的复杂度过,那我们来观察一下我们应该怎么操作 显然,如果 a[i] > b[i] 时是无法构造的,同时 a[i] b[i] 时就不用管…...
【Linux专栏】Linux进程间关系和守护进程
文章目录 1、进程间关系1.1 进程组1.2 组长进程 2、会话?2.1 查看会话2.2 创建会话 3、控制终端4、作业控制4.1 前台/后台进程 5、守护进程5.1 如何创建守护进程?5.2 杀掉守护进程 1、进程间关系 主要描述两个名称概念:即进程组和组长进程。…...
AutoVACUUM (PostgreSQL) 与 DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC (Oracle) 对比
AutoVACUUM (PostgreSQL) 与 DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC (Oracle) 对比 核心功能对比 特性PostgreSQL AutoVACUUMOracle GATHER_DATABASE_STATS_JOB_PROC主要目的空间回收 统计信息更新仅优化器统计信息收集底层机制MVCC(多版本并发控制)维护CBO(基于成本的…...
go依赖查询工具之godepgraph(分析main.go的依赖树)
文章目录 go依赖查询工具之godepgraph(分析main.go的依赖树)什么是服务间的隐式耦合?分析main.go的依赖树方法1. godepgraph (配合 Graphviz 可视化) - 最直观【推荐】方法2. go list go依赖查询工具之godepgraph(分析main.go的依…...
市场差分探头信号输出形式的一些因素考量
在5G/6G通信、新能源汽车电控等高频测量场景中,差分探头的信号输出架构直接影响测试系统的信噪比与可靠性。根据IEEE仪器与测量协会2024年度报告,单端输出方案的市场渗透率较2020年提升42%,这一趋势背后蕴含着深刻的技术变革逻辑。 一、核心…...