第八周代码(图 + Dijkstra + DFS + BFS)

2023/11/27-28        周一周二

GDPU数据结构实验十二 图的遍历及应用(Dijkstra)

【实验内容】

1.根据下图邻接矩阵,编程实现该图的深度与广度优先遍历算法,输出遍历序列。

2.单源节点最短路径问题

问题描述:求从有向图的某一结点出发到其余各结点的最短路径。

基本要求:

(1)有向图采用邻接矩阵表示。

(2)单源节点最短路径问题采用Dijkstra算法。

(3)输出有向图中从源结点A到其余各结点的最短路径和最短路径值。

参考界面如下图所示。

【题目分析】

  • 由于使用C语言,bfs的队列需要使用头文件(C++有自带)。
  • 有向图采用邻接矩阵表示,而无向图采用邻接表表示,menu值判断即可。
  • 到每个点最短距离就是distance数组
  • path数组是可以映射找到该点对应的最短路的,下标对应(=)顶点,元素对应(=)前一个顶点

【参考代码】

图的存储结构(邻接矩阵,邻接表)icon-default.png?t=N7T8http://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背包)

【题目描述】

【深基12.例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>
}

【运行结果】

活动选择问题

【题目描述】

http://t.csdnimg.cn/WOOLN

【思路】

结束时间最早活动优先选择,若两者时间相等,则最早开始优先。

【参考代码】

#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;
}

【输出结果】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值