一、总体要求
1.模拟手机通信录管理系统
主要功能:
- 查看功能:选择此功能,列出手机通信录的分类,如A同事、B家人、C朋友、D同学等分类,但选中某分类时,显示出此类所有数据中的姓名和电话号码;
- 增加功能:录入新数据(包括姓名、电话号码、分类),当录入重复的姓名和电话号码时,提示结果错误并取消录入。
- 修改功能:对选择(选择的方式自定)的联系人,修改其通信录的相关信息,但如果修改姓名,不能跟其他已有的联系人的姓名不能重复。
- 删除功能:选择某个联系人的姓名,可对此人的相应数据进行删除。
- 其他功能:自行分析设计有特色效果的功能。(作为加分点)
- 要求:设计合理的数据结构,实现合理美观的操作界面。
2.最小生成树算法
设计算法实现给定网的最小生成树,如下网的最小生成树。
- 环境配置
Visual studio 2010
二、实验步骤
1. 模拟手机通信录管理系统
在设计模拟手机通信录管理系统时,要考虑数据结构的选取,还要注意对重复数据的处理。
(1)实验思想
a)整体代码通过四个数组实现;四种类型通过switch选择。
b)功能菜单如下:录入、输出、删除、修改、退出系统。
c) ①定义结构体phone_contacts//手机通讯录;
②定义四个结构体数组存储不同类型的人员信息;
③定义4个全局变量记录不同类型人员的数量。
d) ①信息录入时,如若发现姓名或者号码重复,则重新录入;
②输出时,通过switch可选择不同的方式,可以单独输出某一类型人员,也可以全部输出;
③删除时,通过数组中该元素后面元素依次向前移动的方式;
④ 修改时,通过访问数组进行元素替换。
(2)代码
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#define N 100
using namespace std;
struct phone_contacts//手机通讯录
{
char name[10];//姓名
int phone;//电话号码
};
struct phone_contacts AA[N],BB[N],CC[N],DD[N];//结构体对象数组:存不同类型人员的信息
int a=0,b=0,c=0,d=0;//全局变量记录不同类型人员的数量
int input() //信息录入:通过比对注意信息是否重复,若重复i--,重新循环录入
{
int i=0,j=0,y=0,m=0,k=0;
char x;
cout<<"请输入要增加的总人数:"<<endl;
cin>>j;
for(i=0;i<j;i++)
{ int n=1;
cout<<"输入第"<<i+1<<"个人员的信息:"<<endl;
cout<<"请选择新增人员的类型:A.同事 B.家人 C.朋友 D.同学:"<<endl;
cin>>x;
cout<<"请依次输入其姓名、电话号码:"<<endl;
switch(x)
{
case 'A':
{ n=1;
cin>>AA[a].name>>AA[a].phone;a++;
//cout<<"n1="<<n<<"i="<<i<<endl;
//cout<<"AA[a].name"<<AA[a].name<<endl;
if(n)
{ k=i;
for(m=0;m<i;m++)
{ //cout<<"AA[a].phone="<<AA[a-1].phone<<"AA[m].phone="<<AA[m].phone<<endl;
if(strcmp(AA[a-1].name,AA[m].name)==0)
{ cout<<"录入姓名重复,请重新输入。"<<endl;i--;a--;n=1;}
else if(AA[a-1].phone==AA[m].phone)
{ cout<<"录入电话号码重复,请重新输入。"<<endl;i--;a--;n=1;}
else
n=0;
}
}
// cout<<"n2="<<n<<"i="<<i<<endl;
if(n==0||k==0)
{ y=1;cout<<"增加成功!"<<endl<<endl;}
break;
}
case 'B':
{ n=1;
cin>>BB[b].name>>BB[b].phone;b++;
if(n)
{ k=i;
for(m=0;m<i;m++)
{ if(strcmp(BB[i].name,BB[m].name)==0)
{ cout<<"录入姓名重复,请重新输入。"<<endl;i--;b--;n=1;}
else if(BB[i].phone==BB[m].phone)
{ cout<<"录入电话号码重复,请重新输入。"<<endl;i--;b--;n=1;}
else
n=0;
}
}
if(n==0||k==0)
{ y=2;cout<<"增加成功!"<<endl<<endl;}
break;
}
case 'C':
{ n=1;
cin>>CC[c].name>>CC[c].phone;c++;
if(n)
{ k=i;
for(m=0;m<i;m++)
{ if(strcmp(CC[i].name,CC[m].name)==0)
{ cout<<"录入姓名重复,请重新输入。"<<endl;i--;c--;n=1;}
else if(AA[i].phone==AA[m].phone)
{ cout<<"录入电话号码重复,请重新输入。"<<endl;i--;c--;n=1;}
else
n=0;
}
}
if(n==0||k==0)
{ y=3;cout<<"增加成功!"<<endl<<endl;}
break;
}
case 'D':
{ n=1;
cin>>DD[d].name>>DD[d].phone;d++;
if(n)
{ k=i;
for(m=0;m<i;m++)
{ if(strcmp(DD[i].name,DD[m].name)==0)
{ cout<<"录入姓名重复,请重新输入。"<<endl;i--;d--;n=1;}
else if(AA[i].phone==AA[m].phone)
{ cout<<"录入电话号码重复,请重新输入。"<<endl;i--;d--;n=1;}
else
n=0;
}
}
if(n==0||k==0)
{ y=4;cout<<"增加成功!"<<endl<<endl;}
break;
}
}
}
return j;
}
void display() //输出信息:可选择输出一种类型,也可以选择输出全部
{
int i=0;
char x;
cout<<"请选择要显示的类型:A.同事 B.家人 C.朋友 D.同学 F.全部:"<<endl;
cin>>x;
switch(x)
{ case 'A':
{ for(i=0;i<a;i++)
{ cout<<"显示第"<<i+1<<"个同事信息"<<endl;
cout<<"姓名为:"<<AA[i].name<<" 电话号码为:"<<AA[i].phone<<endl;
}
break;
}
case 'B':
{ for(i=0;i<b;i++)
{ cout<<"显示第"<<i+1<<"个家人信息"<<endl;
cout<<"姓名为:"<<BB[i].name<<" 电话号码为:"<<BB[i].phone<<endl;
}
break;
}
case 'C':
{ for(i=0;i<c;i++)
{ cout<<"显示第"<<i+1<<"个朋友信息"<<endl;
cout<<"姓名为:"<<CC[i].name<<" 电话号码为:"<<CC[i].phone<<endl;
}
break;
}
case 'D':
{ for(i=0;i<d;i++)
{ cout<<"显示第"<<i+1<<"个同学信息"<<endl;
cout<<"姓名为:"<<DD[i].name<<" 电话号码为:"<<DD[i].phone<<endl;
}
break;
}
case 'F':
{ for(i=0;i<a;i++)
{ cout<<"A.同事 "<<"姓名为:"<<AA[i].name<<" 电话号码为:"<<AA[i].phone<<endl;}
for(i=0;i<b;i++)
{ cout<<"B.家人 "<<"姓名为:"<<BB[i].name<<" 电话号码为:"<<BB[i].phone<<endl;}
for(i=0;i<c;i++)
{ cout<<"C.朋友 "<<"姓名为:"<<CC[i].name<<" 电话号码为:"<<CC[i].phone<<endl;}
for(i=0;i<d;i++)
{ cout<<"D.同学 "<<"姓名为:"<<DD[i].name<<" 电话号码为:"<<DD[i].phone<<endl;}
break;
}
}
}
void delet() //信息删除:通过姓名比对在数组中找到相应位置删除元素,并将后面的元素依次向前移动
{
int i,k;
char x;char names[10];
cout<<"请输入要删除人员的类型:A.同事 B.家人 C.朋友 D.同学:"<<endl;
cin>>x;
cout<<"请输入要删除的人的姓名:"<<endl;
scanf("%s",names);
switch(x)
{
case 'A':
{ for(i=0;i<a;i++)
{ if(strcmp(names,AA[i].name)==0)
{ for(k=i;k<a;k++)
{ strcpy(AA[k].name,AA[k+1].name);
AA[k].phone=AA[k+1].phone;}//i后元素依次往前移动
}
}
cout<<"删除成功!"<<endl;
a--;break;
}
case 'B':
{ for(i=0;i<b;i++)
{ if(strcmp(names,BB[i].name)==0)
{ for(k=i;k<b;k++)
BB[k]=BB[k+1];//i后元素依次往前移动
}
}
cout<<"删除成功!"<<endl;
b--;break;
}
case 'C':
{ for(i=0;i<c;i++)
{ if(strcmp(names,CC[i].name)==0)
{ for(k=i;k<c;k++)
CC[k]=CC[k+1];//i后元素依次往前移动
}
}
cout<<"删除成功!"<<endl;
c--;break;
}
case 'D':
{ for(i=0;i<d;i++)
{ if(strcmp(names,DD[i].name)==0)
{ for(k=i;k<d;k++)
DD[k]=DD[k+1];//i后元素依次往前移动
}
}
cout<<"删除成功!"<<endl;
d--;break;
}
}
}
void change() //信息修改:通过姓名比对在数组中找到相应位置,替换掉原信息
{
int i,y;
char x;
char names[10];
cout<<"请选择修改人员的类型:A.同事 B.家人 C.朋友 D.同学:"<<endl;
cin>>x;
switch(x)
{
case 'A':
{ cout<<"请输入要修改人员的原姓名:"<<endl;
scanf("%s",names);
for(i=0;i<a;i++)
{ if(strcmp(AA[i].name,names)==0)
{ cout<<"请输入要修改的选项:1.姓名 2.电话号码:"<<endl;
cin>>y;
if(y==1)
{ cout<<"将姓名改为:"<<endl;
cin>>AA[i].name;
cout<<"修改成功!"<<endl;
}
else if(y==2)
{ cout<<"将电话号码改为:"<<endl;
cin>>AA[i].phone;
cout<<"修改成功!"<<endl;
}
}
}
break;
}
case 'B':
{ cout<<"请输入要修改人员的原姓名:"<<endl;
scanf("%s",names);
for(i=0;i<b;i++)
{ if(strcmp(BB[i].name,names)==0)
{ cout<<"请输入要修改的选项:1.姓名 2.电话号码:"<<endl;
cin>>y;
if(y==1)
{ cout<<"将姓名改为:"<<endl;
cin>>BB[i].name;
cout<<"修改成功!"<<endl;
}
else if(y==2)
{ cout<<"将电话号码改为:"<<endl;
cin>>BB[i].phone;
cout<<"修改成功!"<<endl;
}
}
}
break;
}
case 'C':
{ cout<<"请输入要修改人员的原姓名:"<<endl;
scanf("%s",names);
for(i=0;i<c;i++)
{ if(strcmp(CC[i].name,names)==0)
{ cout<<"请输入要修改的选项:1.姓名 2.电话号码:"<<endl;
cin>>y;
if(y==1)
{ cout<<"将姓名改为:"<<endl;
cin>>CC[i].name;
cout<<"修改成功!"<<endl;
}
else if(y==2)
{ cout<<"将电话号码改为:"<<endl;
cin>>CC[i].phone;
cout<<"修改成功!"<<endl;
}
}
}
break;
}
case 'D':
{ cout<<"请输入要修改人员的原姓名:"<<endl;
scanf("%s",names);
for(i=0;i<d;i++)
{ if(strcmp(DD[i].name,names)==0)
{ cout<<"请输入要修改的选项:1.姓名 2.电话号码:"<<endl;
cin>>y;
if(y==1)
{ cout<<"将姓名改为:"<<endl;
cin>>DD[i].name;
cout<<"修改成功!"<<endl;
}
else if(y==2)
{ cout<<"将电话号码改为:"<<endl;
cin>>DD[i].phone;
cout<<"修改成功!"<<endl;
}
}
}
break;
}
}
}
void menu() //主菜单
{
printf("\n-------------------------------\n");
printf("1.录入\n");
printf("2.输出\n");
printf("3.删除\n");
printf("4.修改\n");
printf("5.退出系统\n");
printf("-------------------------------\n");
}
int main() //主函数
{
int x;
int y;
int z=1;
while(z)
{
menu();
printf("请输入要执行功能的序号:\n");
scanf("%d",&x);
switch(x)
{
case 1:
{ y=input();
break;
}
case 2:
{ display();
break;
}
case 3:
{ delet();
break;
}
case 4:
{ change();
break;
}
case 5:
{
z=0;
printf("退出系统成功!感谢您的使用!\n");
break;
}
}
}
}
(3)运行结果
2. 最小生成树算法
最小生成树算法是图论中的一个经典问题,常用于网络设计、电路布线等领域。在设计算法时,我们需要考虑如何从一个给定的图中选择边,使得这些边构成的子图是一个无环连通图(即树),并且这些边的权值之和最小。
常见的最小生成树算法有Prim算法和Kruskal算法。Prim算法从任意一个顶点开始,每次选择一条与当前子图相连且权值最小的边,直到所有顶点都被包含在子图中。Kruskal算法则按照边的权值从小到大进行排序,然后依次选择边,但要求选择的边不能与已选择的边构成环。
在实现算法时,我们需要考虑如何高效地选择边和判断环。这可以通过使用并查集数据结构来实现。并查集可以快速地判断两个顶点是否属于同一个集合(即是否已经连通),从而避免形成环。
(1)实验思想
a)最小生成树的定义为:是原图的极小连通子图,包含图中所有结点,最重要的是保持图连通最少的边。
b)邻接矩阵存储两顶点的权值:
c)prim算法(普利姆算法):对图G(V,E)设置集合S,存放已访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。执行n次(n为顶点个数),直到集合S已包含所有顶点。Prim算法利用优先级队列来存储候选边,以便每次都能快速找到权重最小的边,这体现了贪心策略的思想。
(2)代码
#include "stdafx.h"
#include "stdafx.h"
#include <iostream>
#include <algorithm>
#define MAX 9999
using namespace std;
struct AMGraph
{
char *vertices; //顶点表,储存所有顶点元素
int *visited; //蓝白点数组,蓝点为1,白点为0;选择离白点最短路径的蓝点洗白
int **arcWeight; //邻接矩阵,存储两顶点间的权重
int currVex, currArc; //顶点数 、边数
};
int get_Index(const AMGraph &G, char u)
{
for(int i = 0; i < G.currVex; i++)
if(u == G.vertices[i])
return i;//获得顶点在顶点数组中的下标
return -1;
}
void createUND(AMGraph &G)//创建邻接矩阵
{
cout << "请输入图的顶点个数和边数:";
cin >> G.currVex >> G.currArc;
G.vertices = new char[G.currVex];
G.visited = new int[G.currVex];
cout << "请输入顶点的值(char类型):";
for(int i = 0; i < G.currVex; i++)
{ cin >> G.vertices[i]; }
//动态邻接矩阵开辟空间,同时进行初始化
G.arcWeight = new int*[G.currVex];
for(int i = 0; i < G.currVex; i++)
{
G.arcWeight[i] = new int[G.currVex];
for(int j = 0; j < G.currVex; j++)
if(i == j)
G.arcWeight[i][j] = 0; //邻接矩阵中主对角线全为0,即结点到自己的边初始化为0
else
G.arcWeight[i][j] = MAX; //除了上条语句情况其余邻接矩阵的位置的权值初始化为无穷大
}
fill(G.visited, G.visited+G.currVex, 1); //填充蓝白点数组,初始化每个顶点的标记都为1(即蓝点)
char v1, v2;
int i, j, power;
cout << "请依次输入两顶点值和权值:"<<endl;
for(int k = 0; k < G.currArc; k++)
{
cin >> v1 >> v2 >> power;
i = get_Index(G, v1);
j = get_Index(G, v2);
G.arcWeight[i][j] = power; //这里是 i 行 j 列
G.arcWeight[j][i] = power; //这里是 j 列 i 行
}
return ;
}
void Prim(AMGraph& G)
{
int Min[100];
fill(Min, Min+G.currVex, MAX);
Min[0] = MAX; // min[0]定义为无穷大才能实现每 i 轮的 Min 的初始化
int MST = 0;
cout << "洗白点的顺序:";
for(int i = 0; i < G.currVex; i++)
{ int w = 0;
for(int j = 0; j < G.currVex; j++)
if(G.visited[j] && Min[w] > Min[j])
{ //选择离白点最短路径的蓝点洗白,将该节点添加入生成树。
w = j;
}
G.visited[w] = 0; //洗白
cout << G.vertices[w] << " ";
if(w != 0) // 权值累加时排除刚开始节点的权值(刚开始的节点权值为无穷大)
MST += Min[w]; //权值累加
for(int k = 0; k < G.currVex; k++)
if(G.visited[k] && G.arcWeight[w][k] < Min[k])
{
Min[k] = G.arcWeight[w][k]; //更新每个蓝点到白点集合的最小值
}
}
cout << endl;
cout << "连通图最小边权总和:" << MST << endl;
}
int main()
{
AMGraph G; //构造图
createUND(G); //初始化无向图
Prim(G);
return 0;
}
(3)运行结果
三、总结
总结来说,本次实验不仅加深了我对数据结构和算法的理解,还提升了我解决实际问题的能力。通过设计和实现手机通信录管理系统,我学习了如何管理复杂的数据集。同时,通过实现最小生成树算法,我掌握了图论中的一个重要概念及其在实际中的应用;它告诉我们,在解决复杂问题时,我们可以通过选择最优的局部解来逼近全局最优解。这些经验对于我们未来的学习和工作都将是宝贵的财富。