2023/11/27-28 周一周二
GDPU数据结构实验十二 图的遍历及应用(Dijkstra)
【实验内容】
1.根据下图邻接矩阵,编程实现该图的深度与广度优先遍历算法,输出遍历序列。
2.单源节点最短路径问题
问题描述:求从有向图的某一结点出发到其余各结点的最短路径。
基本要求:
(1)有向图采用邻接矩阵表示。
(2)单源节点最短路径问题采用Dijkstra算法。
(3)输出有向图中从源结点A到其余各结点的最短路径和最短路径值。
参考界面如下图所示。
【题目分析】
- 由于使用C语言,bfs的队列需要使用头文件(C++有自带)。
- 有向图采用邻接矩阵表示,而无向图采用邻接表表示,menu值判断即可。
- 到每个点最短距离就是distance数组
- path数组是可以映射找到该点对应的最短路的,下标对应(=)顶点,元素对应(=)前一个顶点
【参考代码】
图的存储结构(邻接矩阵,邻接表)http://t.csdnimg.cn/0LzEE和之前的实验一样,只是多了内容而已
头文件的#pragma once语句是针对VS的
SequenceList.h文件
#pragma once
typedef struct
{
int list[MaxSize];
int size;
}SequenceList;
void ListInitialize(SequenceList *L)
{
L->size = 0;
}
int ListLength(SequenceList L)
{
return L.size;
}
int ListInsert(SequenceList *L,int i,ElemType e)
{
int j = 0;
if( L->size >= MaxSize)
{
printf("顺序表已满 \n");
return 0;
}
if(i < 0 || i > L->size)
{
printf("参数i不合法 \n");
return 0;
}
for(j = L->size; j > i; j--)
L->list[j] = L->list[j-1];
L->list[i] = e;
L->size++;
return 1;
}
void ListPrint(SequenceList L)
{
for(int i = 0; i < ListLength(L); i++)
{
printf("%d ", L.list[i]);//原来是printf没改
}
printf("\n");
}
int ListDelete(SequenceList *L, int e)
{
int i,j;
for( i=0; i<L->size; i++)
{
if( L->list[i] == e )
break;
}
if( i==L->size ) return 0;
for( j=i; j<=L->size; j++)//将第i个后的元素前移
{
L->list[j] = L->list[j+1];
}
L->size--;
return 1;
}
queue.h文件
#pragma once
//循环队列定义,队首指针和队尾指针
typedef struct
{
ElemType queue[MaxQueueSize];
int front;
int rear;
int count; //计数器
}Queue;
//初始化队列
void QueueInitiate(Queue *Q)
{
Q->count = 0;
Q->front = 0;
Q->rear = 0;
}
//检查队列是否为空
int QueueNotEmpty(Queue Q)
{
if(Q.count == 0)
return 0;
else
return 1;
}
//入队
int QueueAppend(Queue *Q, ElemType x)
{
if ( Q->count>0 && Q->rear == Q->front ) //检查是否申请成功
{
printf("队列已满无法插入!\n");
return 0;
}
else
{
Q->queue[Q->rear] = x;
Q->rear = ( Q->rear + 1 ) % MaxQueueSize;
Q->count++;
return 1;
}
}
//出队
int QueueDelete(Queue *Q, ElemType *x)
{
if (Q->count == 0) //队列中只有一个节点
{
printf("循环队列已空!\n");
return 0;
}
else
{
*x = Q->queue[Q->front];
Q->front = ( Q->front + 1 ) % MaxQueueSize;
Q->count--;
return 1;
}
}
//获取队头元素
int QueueGet(Queue *Q, ElemType *d)
{
if (Q->count == 0) //队列中只有一个节点
{
printf("循环队列已空!\n");
return 0;
}
else
{
*d = Q->queue[Q->front];
return 1;
}
}
AdjacencyList.h文件(主要用于输出邻接表)
#pragma once
//邻接表
typedef struct Node
{
int adjvex;
struct Node* next;
}Edge;
typedef struct
{
ElemType data;
int source;
Edge* adj; //邻接边的头指针
}AdjHeight;
typedef struct
{
AdjHeight a[MaxVertices];
int vertsNum; //顶点个数
int edgesNum; //边个数
}ListGraph;
// 初始化
void initiateAdj(ListGraph* G)
{
int i, j;
G->vertsNum = 0;
G->edgesNum = 0;
for (i = 0; i < MaxVertices; i++)
{
G->a[i].source = i;
G->a[i].adj = NULL;
}
}
//插入顶点
void insertV(ListGraph* G, int i, ElemType ver)
{
if (i >= 0 && i < MaxVertices)
{
G->a[i].data = ver;
G->vertsNum++;
}
else
printf("顶点越界");
}
//插入边
void insertEdge(ListGraph* G, int v1, int v2)
{
Edge* p;
if (v1 < 0 || v1 >= G->vertsNum || v2 < 0 || v2 >= G->vertsNum)
{
printf("参数v1或v2越界出错!");
return;
}
p = (Edge*)malloc(sizeof(Edge));
p->adjvex = v2;
p->next = G->a[v1].adj;
G->a[v1].adj = p;
G->edgesNum++;
}
//删除边
void deleteEdge(ListGraph* G, int v1, int v2)
{
Edge* curr, * pre;
if (v1 < 0 || v1 >= G->vertsNum || v2 < 0 || v2 >= G->vertsNum)
{
printf("参数v1或v2越界出错!");
return;
}
pre = NULL;
curr = G->a[v1].adj;
while (curr != NULL && curr->adjvex != v2)
{
pre = curr;
curr = curr->next;
}
if (curr != NULL && curr->adjvex == v2 && pre == NULL)
{
G->a[v1].adj = curr->next;
free(curr);
G->edgesNum--;
}
else
printf("边<v1,v2>不存在!");
}
//取第一个邻接顶点
int adjgetFirstVex(ListGraph G, int v)
{
Edge* p;
if (v < 0 || v > G.vertsNum)
{
printf("参数v1或v2越界出错!");
return 0;
}
p = G.a[v].adj;
if (p != NULL)
return p->adjvex;
else
return -1;
}
//取下一个邻接顶点
int adjGetNextVex(ListGraph G, int v1, const int v2)
{
Edge* p;
if (v1 < 0 || v1 > G.vertsNum || v2 < 0 || v2 > G.vertsNum)
{
printf("参数v1或v2越界出错!");
return 0;
}
p = G.a[v1].adj;
while (p != NULL)
{
if (p->adjvex != v2)
{
p = p->next;
continue;
}
else break;
}
p = p->next;
if (p != NULL) return p->adjvex;
else return -1;
}
//取消所有的链表。
void destroyAdj(ListGraph * G)
{
int i;
Edge * p, * q;
for (i = 0; i < G->vertsNum; i++)
{
p = G->a[i].adj;
while (p != NULL)
{
q = p->next;
free(p);
p = q;
}
}
}
Graph.h文件
#pragma once
#define MaxSize 100
#include "SequenceList.h"
typedef struct
{
SequenceList V;
int edge[100][100];
int edgesNum;
}MatrixGraph;
//初始化
void initiate(MatrixGraph* G, int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (i == j)
{
G->edge[i][j] = 0;
}
else
{
G->edge[i][j] = 10000;
}
}
}
G->edgesNum = 0;
ListInitialize(&G->V);
}
//void createGraph(MatrixGraph* G, ElemType v[], int n, RowColWeight E[], int e) {
// int i, k;
// initiate(G, n);
// for (i = 0; i < n; i++)
// {
// insertV(G, v[i]);
// }
// for (k = 0; k < e; k++)
// {
// insertEdge(G, E[k].row, E[k].col, E[k].weight);
// }
//}
//插入顶点
void insertV(MatrixGraph* G, ElemType ver)
{
ListInsert(&G->V, G->V.size, ver);
}
void insertEdge(MatrixGraph* G, int v1, int v2, int weight)
//在图G中插入边<v1,v2>,边<v1,v2>的权为weight
{
if (v1 < 0 || v1 >= G->V.size || v2 < 0 || v2 >= G->V.size)
{
printf("参数v1或v2越界出错!");
return ;
}
G->edge[v1][v2] = weight;
G->edgesNum++;
}
//删除边
void deleteEdge(MatrixGraph* G, int v1, int v2)
{
if (v1 < 0 || v1 >= G->V.size || v2 < 0 || v2 >= G->V.size || v1 == v2)
{
printf("参数v1或v2越界出错!");
return;
}
G->edge[v1][v2] = 10000;
G->edgesNum--;
}
//删除顶点
void deleteV(MatrixGraph* G, int v)
{
int n = ListLength(G->V), i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if ((i == v || j == v) && G->edge[i][j] > 0 && G->edge[i][j] < 10000)
G->edgesNum--;
}
}
for (i = v; i < n; i++)
{
for (j = v; j < n; j++)
G->edge[i][j] = G->edge[i + 1][j];
}
for (i = v; i < n; i++)
{
for (j = v; j < n; j++)
G->edge[i][j] = G->edge[i][j + 1];
}
ListDelete(&G->V, v);
}
//取第一个邻接顶点
int getFirstVex(MatrixGraph G, int v)
{
int col;
if (v < 0 || v >= G.V.size)
{
printf("参数v越界出错!");
return 1;
}
for (col = 0; col < G.V.size; col++)
{
if (G.edge[v][col] > 0 && G.edge[v][col] < 10000)
return col;
}
return -1;
}
//下一个邻接顶点
int getNextVex(MatrixGraph G, int v1, int v2)
{
int col;
if (v1 < 0 || v1 >= G.V.size || v2 < 0 || v2 >= G.V.size )
{
printf("参数v1或v2越界出错!");
return 1;
}
for (col = v2+1; col <= G.V.size; col++)
{
if (G.edge[v1][col] > 0 && G.edge[v1][col] < 10000)
return col;
}
return -1;
}
.c/.cpp文件
#include <stdio.h>
#include <malloc.h>
#define MaxSize 100
#define MaxVertices 100
#define MaxWeight 10000
#define MaxQueueSize 100
typedef char ElemType;
#include "Graph.h"
#include "AdjacencyList.h"
#include "queue.h"
typedef struct//有向图
{
int row;
int col;
int weight;
}RowColWeight;
typedef struct//无向图
{
int row;
int col;
}RowCol;
//邻接矩阵
void createGraph(MatrixGraph* G, ElemType v[], int n, RowColWeight E[], int e) {
int i, k;
initiate(G, n);
for (i = 0; i < n; i++)
{
insertV(G, v[i]);
}
for (k = 0; k < e; k++)
{
insertEdge(G, E[k].row, E[k].col, E[k].weight);
}
}
//邻接表
void createAdjGraph(ListGraph* G, ElemType v[], int n, RowCol d[], int e)
{
int i, k;
initiateAdj(G);
for (i = 0; i < n; i++)
{
insertV(G, i, v[i]);
}
for (k = 0; k < e; k++)
{
insertEdge(G, d[k].row, d[k].col);
}
}
//算法
void Dijkstra(MatrixGraph G, ElemType v0, int distance[], int path[])
//带权图G从下标v0顶点到其他顶点的最短距离distance和最短路径下标path
{
int n = G.V.size;
int* s = (int*)malloc(sizeof(int) * n);
int minDis, i, j, u;
//初始化
for (i = 0; i < n; i++)
{
distance[i] = G.edge[v0][i];
s[i] = 0;
if (i != v0 && distance[i] < MaxWeight)
path[i] = v0;
else
path[i] = -1;
}
s[v0] = 1; //标记顶点v0已从集合T加入到集合S中
//在当前还未找到最短路径的顶点集中选取具有最短距离的顶点u
for (i = 1; i < n; i++)
{
minDis = MaxWeight;
for (j = 0; j <= n; j++)
{
if (s[j] == 0 && distance[j] < minDis)
{
u = j;
minDis = distance[j];
}
}
if (minDis == MaxWeight)
return;
s[u] = 1; //标记顶点u已从集合T加入到集合S中
//修改从v0到其他顶点的最短距离和最短路径
for (j = 0; j < n; j++)
{
if (s[j] == 0 && G.edge[u][j] < MaxWeight
&& distance[u] + G.edge[u][j] < distance[j])
{
//顶点v0经顶点u到其他顶点的最短距离和最短路径
distance[j] = distance[u] + G.edge[u][j];
path[j] = u;
}
}
}
}
//采用邻接矩阵深搜
void DFS(MatrixGraph G, int v, int visited[])
{
int w;
printf("%c ", G.V.list[v]);
visited[v] = 1;
w = getFirstVex(G, v);
while (w != -1)
{
if (!visited[w])
DFS(G, w, visited);
w = getNextVex(G, v, w);
}
}
//采用邻接矩阵广搜
void BFS(MatrixGraph G, int v, int visited[])
{
ElemType u, w;
Queue q;
printf("%c ", G.V.list[v]);
visited[v] = 1;
QueueInitiate(&q);
QueueAppend(&q, v);
while (QueueNotEmpty(q))
{
QueueDelete(&q, &u);
w = getFirstVex(G, u);
while (w != -1)
{
if (!visited[w])
{
printf("%c ", G.V.list[w]);
visited[w] = 1;
QueueAppend(&q, w);
}
w = getNextVex(G, u, w);
}
}
}
int main()
{
printf("请选择生成:1.生成有向图 2.生成无向图(输入1或2)\n");
int menu;
scanf_s("%d", &menu);
MatrixGraph g;
ListGraph g1;
ElemType a[] = { 'A', 'B', 'C', 'D', 'E', 'F' };
RowColWeight rcw[] = { {0,2,5}, {0,3,30},{1,0,2},{1,4,8},{2,1,15}, {2,5,7},
{4,3,4},{5,3,10}, {5,4,18} };
RowCol rc[] = { {0,2}, {0,3},{1,0},{1,4},{2,1}, {2,5},
{4,3},{5,3}, {5,4} };
int n = 6, e = 9;
int i, j;
createGraph(&g, a, n, rcw, e);
if (menu == 1)
{
printf("顶点集合为:\n");
for (i = 0; i < g.V.size; i++)
{
printf("%c\t", g.V.list[i]);
}
printf("\n");
printf("权值集合为:\n");
for (i = 0; i < g.V.size; i++)
{
for (j = 0; j < g.V.size; j++)
printf("%5d ", g.edge[i][j]);
printf("\n");
}
}
if (menu == 2)
{
createAdjGraph(&g1, a, n, rc, 9);
Edge* p;
printf("顶点数为%d,边数为%d,邻接表如下所示:\n", g1.vertsNum, g1.edgesNum);
//输出顶点
for (i = 0; i < g1.vertsNum; i++)
{
printf("%c ", g1.a[i].data);
p = g1.a[i].adj;
while (p != NULL)
{
printf("-->%d ", p->adjvex);
p = p->next;
}
printf("\n");
}
}
printf("深度优先搜索序列为:");
int visited[100] = { 0 };
DFS(g, 0, visited);
printf("\n");
printf("广度优先搜索序列为:");
for (int i = 0; i < 100; i++) //重置0(漏了这步)
{
visited[i] = 0;
}
BFS(g, 0, visited);
int distance[6] = {0}, path[6] = {0};//初始化
Dijkstra(g, 0, distance, path);
printf("\n");
printf("从顶点到A到其他顶点的距离为:\n");
for (i = 0; i < 6; i++)
{
printf("到顶点%c的最短距离为:%d\n", a[i], distance[i]);
}
printf("path数组:\n");
for (i = 0; i < 6; i++)
{
printf("%d ", path[i]);
}
printf("\nA到其他点的最短路:\n");
int res[10] = {0};
for (i = 1; i < 6; i++)
{
int temp = i;
int k = 0;
while (path[temp] != -1)//到起始点就终止
{
res[k] = temp; //存储每个顶点的最短路径(映射关系弄错了)
//应该存储path的下标。而不是path数组的元素
temp = path[temp]; //顺藤摸瓜找顶点
k++;
}
res[k] = -1;//因为前面寻根的时候,到-1就终止了,起始点值设为-1
while (k>=0)
{
if (res[k] == -1)
printf("A ");//特判起始点
else
{
printf("%c ", a[res[k]]);
}
k--;
}
printf("\n");
for (j = 0; j < 10; j++) //重置0(漏了这步)
res[j] = 0;
}
}
【运行结果】
2023/11/29-12/3 周三到周日
部分背包问题(非0-1背包)
【题目描述】
【思路】
挑单价最贵的拿,要排倒序
【参考代码】
#include <iostream>
using namespace std;
#include <algorithm>
#include <iomanip>
struct gold
{
int weight; //金子的质量
int value; //金子的总价值
double price; //金子的单价,小数形式用double
}g[100];
bool cmp(gold a, gold b)
{
return a.price > b.price; //降序
}
int main()
{
int n, t;
int i = 0;
double total = 0;
cin >> n >> t;
for (i = 0; i < n; i++)
{
cin >> g[i].weight >> g[i].value;
g[i].price = (g[i].value * 1.0) / g[i].weight; //用小数计算
}
sort(g, g + n, cmp); //降序排序
i = 0; //重置i
while (t) //背包仍然有容量
{
if (t >= g[i].weight) //背包够大,装得下就装
{
t -= g[i].weight;
total += g[i].value;
}
else //装不下,挑最贵的拿,尽量装满
{
total += t * g[i].price;
break;
}
if (i == n - 1) //金子全部拿完了,n-1堆,结束
break;
i++;
}
cout << fixed << setprecision(2) << total;
//保留两位小数fixed << setprecision(2),setprecision的头文件:<iomanip>
}
【运行结果】
活动选择问题
【题目描述】
【思路】
结束时间最早活动优先选择,若两者时间相等,则最早开始优先。
【参考代码】
#include <iostream>
using namespace std;
#include <algorithm>
struct activity
{
int start;
int end;
}a[200];
//比较函数
bool cmp(activity x, activity y)
{
if (x.end == y.end)
return x.start < y.start;//不能加等于号,会报错
return x.end < y.end;
}
int main()
{
int n, i = 0;
//activity a[20];
cin >> n;
for (i = 0; i < n; i++)
{
cin >> a[i].start >> a[i].end;
}
sort(a, a + n, cmp);
int cnt = 1;//第一个活动已经选了,所以初始是1 (1,4)
int j = 0;
for (i = 1; i < n; i++)
{
if (a[j].end <= a[i].start)
{
cnt++;
j = i;
}
}
cout << cnt;
}
【输出结果】
区间覆盖问题
【题目描述】
题目链接:凌乱的yyy / 线段覆盖 - 洛谷
此问题的描述是:给定一个长度为m的区间,再给出n条线段的起点和终点(闭区间),
求最少使用多少条线段可以将整个区间完全覆盖。
【思路】
要解此题的完整步骤如下:
1.在所有待选择的区间里,剔除起点和终点在所求范围之外的区间。
2.将所有区间按起点进行排序。
3.默认选中第一个点,然后在挑选点的过程中,需要遵循以下原则:
(1)新区间的起点要小于等于当前区间的终点;
(2)新区间的终点要大于当前区间的起点。
4.循环重复步骤3,直到当前区间的终点值 >= 预期的终点值,结束寻找区间的过程
【参考代码】
#include <iostream>
using namespace std;
#include <algorithm>
struct section //区间
{
int start;
int end;
}a[1000000];
bool cmp(section x, section y)
{
return (x.end < y.end); //按右端点排序
}
int main()
{
int n, m;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i].start >> a[i].end;
}
sort(a, a + n, cmp);
int time = 0, ans = 0;
for (int i = 0; i < n; i++)
{
if (a[i].start >= time)
{
ans++;
time = a[i].end;
}
}
cout << ans;
return 0;
}
【输出结果】
小船过河问题
【题目描述】
题目链接:过河问题 - 洛谷
该问题的常见描述是:有n个人需要过河,只有一艘船,最多能乘2人,
船的运行速度为2人中较慢一人的速度,过去后还需一个人把船划回来,
把n个人运到对岸,最少需要多久。
【思路】
两种方法,较容易想到的是第一种方式:
第一种办法:先让1 2过去(2分钟),1回来(1分钟),1 5过去(5分钟),1回来(1分钟),
1 10再过去(10分钟),总共需要19分钟就可以让四个人都过去。
而正确答案是第二种办法:先让1 2过去(2分钟),1回来(1分钟),5 10过去(10分钟),
2回来(2分钟),1 2再过去(2分钟),总共需要17分钟就可以让四个人都过去。
本题的关键在于把最慢的和次慢的两个人运过河,因此只要大于四个人的情况下:
只需要将这两种方式这两个人运过河的时间做对比:
第一种:T4 + T1 + T3 + T1
第二种:T2 + T1 + T4 + T2
对比之后取短的就行
在人数 >= 4时,该问题的解决思路常常有两种:
1.最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,
然后次快的将船划回来。此时我们可以计算一下这个过程所花费的时间
(至于为什么要计算这个过程所花费的时间,因为当人数 >= 4时,
一直将这个过程重复下去即可)
2.最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,
然后最快的将船划回来。
【参考代码】
#include <iostream>
using namespace std;
int cross(int a[], int n)
{
sort(a, a + n);//排序之后最慢的在最后面
int time = 0;
//负责将最慢和次慢的人运过河
while (n > 3)
{//比较看谁更快一点
if ( (2 * a[0] + a[n - 2] + a[n - 1]) <= (2 * a[1] + a[0] + a[n - 1]) )
time = time + 2 * a[0] + a[n - 2] + a[n - 1];
else
time = time + 2 * a[1] + a[0] + a[n - 1];
n = n - 2;//运过去两个人(最慢和次慢)
}
if (n == 3)
time = time + a[0] + a[1] + a[n - 1];
if (n == 2)
time = time + a[n - 1];//回来接最后一个人回去
if (n == 1)
time = time + a[0];//自己回去
return time;
}
int main()
{
int n;//输入一共多少个人过河
cin >> n;
int a[100000] = { 0 };
for (int i = 0; i < n; i++)
cin >> a[i];//输入每个人过河的时间
cout << cross(a, n) << endl;
return 0;
}
【输出结果】