学校的数据结构课程设计实习
要求从下列五项中每一项选择一个题目完成
(一)线性结构(链表)题目
(二)栈和队列题目
(三)树型结构题目
(四)图型结构题目
(五)查找、排序、文件
12.14
在(一)线性结构(链表)题目选择了【5、约瑟夫环 】
5、joseph环
任务:编号是1,2,…,n的n个人按照顺时针方向围坐一圈,每个人只有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个仍开始顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止。设计一个程序来求出出列顺序。
要求:利用单向循环链表存储结构模拟此过程,按照出列的顺序输出各个人的编号。
测试数据:m的初值为20,n=7 ,7个人的密码依次为3,1,7,2,4,7,4,首先m=6,则正确的输出是什么?
要求:输入数据:建立输入处理输入数据,输入m的初值,n ,输入每个人的密码,建立单循环链表。
输出形式:建立一个输出函数,将正确的输出序列
数据结构:
typedef struct Node
{
int data;
int password;
struct Node *next;
}Node, *LinkList;
基本操作:初始化单链表;给每个人赋密码;确定需要处理的人数;确定开始的上限值;得到正确的顺序;输出结果。
思想:这个题其实是标准的约瑟夫环的进阶版,每一次要报数的截点是不一样的,依上一个报数剔除的节点的密码而定,但是整体思想不变,不过题目要求使用链表结构,所以还是先按照要求建立好循环链表,不过在建立链表的过程中有一个技巧,就是返回的节点应该是建立时的最后一个节点,也就是准备报数节点的前一个,还有就是在报数的过程中,当然要进行循环操作,循环的层数就根据每次的m,但是循环m-1次,返回的依旧是要剔除的前一个节点,这样会非常方便,可以直接剔除它的下一个节点,这样它本身又正好是下一个要报数的前一个节点,正好满足循环条件,而且巧妙的避开了如果m=1情况下的特判情况。
代码:
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
int password;
struct Node *next;
}Node, *LinkList;
Node* createlink(int n)
{
Node *head,*p1,*p2;
head = new(Node);
head->next = NULL;
p1 = head;
int x;
for(int i = 1; i < n; i++)
{
cin >> x;
p1->data = i;
p1->password = x;
p2 = new(Node);
p2->next=NULL;
p1->next=p2;
p1 = p2;
p2 = p2->next;
}
cin >> x;
p1->data = n;
p1->password = x;
p1->next = head;//连接成环!
return p1;//返回最后一个节点,方便报数
}
Node* textlink(Node* head, int m)
{
Node* vis = head;
for(int i = 1; i < m; i++)//从开始报数的前一个节点开始加1,然后返回要剔除的前一个节点
{
vis = vis->next;
}
return vis;
}
int main()
{
int n;
int m = 20;//m初值设为20
int n_count;
Node* head;
cin >> n;
n_count = n;//用来记录链表中的节点数,判断是否为空
head = createlink(n);//创建初始环
cin >> m;//输入m
while(n_count>1)
{
head = textlink(head, m);
Node* printlink = head->next;
head->next = printlink->next;
cout << printlink->data<<" ";
n_count--;
m = printlink->password;
//head = head->next;
}
cout << head->data <<endl;
//测试输出
/*Node* temp;
temp = head;
for(int i = 1; i <= n; i++)
{
cout << temp->data << " " << temp->password << endl;
temp = temp->next;
}*/
return 0;
}
/*
7 6
3 1 7 2 4 7 4
*/
12.15
在(三)树型结构题目里选了【1、二叉树的构造】
1、二叉树的构造
任务:已知二叉树的层序和中序遍历序列,或已知二叉树的先序序列、中序序列,试编写算法建立该二叉树( 用递归或非递归的方法都可以)。
要求:能够输入树的各个结点,并能够输出用不同方法遍历的遍历序列;分别建立建立二叉树存储结构的的输入函数、输出层序遍历序列的函数、输出先序遍历序列的函数;
ps:眼瞎看错题干了,所以最终写了三种情况,分别是按照先序中序,后续中序,层序中序分别建立二叉树,以及先序输出和层序输出。
思路:
先序中序和后续中序的思想差不多,先序的第一个为根,后续的最后一个为根,先找到根在中序中的位置,它左边的都是其左子树,右边的都是其右子树。而对于先序来说,他的先序序列下一位一定是它的左儿子,如果其有右儿子,那一定是向右数其左子树的个数,这个个数可以通过中序序列计算出来,对后序序列同理,它的前一位一定是其右儿子,左儿子如果存在就是向左数其右儿子个数,按照这样的规律递归,就可以建立出二叉树。
而层序中序就不一样了,不过同样有规律,这里可以参见这篇博客:根据中序和层序建立二叉树
参考上面的这篇博客的方法,可以用深搜的思想,递归建立二叉树,详细见代码。
至于先序输出就简单了,同样递归输出就可以了
不过层序输出,如果可以STL的话当然容易,队列即可,但是课程设计貌似不允许STL,所以要手写栈,这里借鉴了朋友的手写栈。
就这样!整体代码如下:
#include <iostream>
#include <cstdio>
#include <malloc.h>
using namespace std;
struct node
{
char data;
node* l_son;
node* r_son;
};
char pre[100];//先序序列
char in[100];//中序序列
char post[100];//后序序列
char ord[100];//层序序列
int n;
bool flag = false;
//手写队列
typedef struct Queue
{
node *head;
node *tail;
int length;
}Queue;
int InitQueue(Queue &Q)
{
Q.head=(node*)malloc(n*sizeof(node));
if(!Q.head)
return 0;
Q.tail=Q.head;
Q.length=n;
return 1;
}
bool EmptyQueue(Queue Q)
{
if(Q.tail==Q.head)
return 1;
else return 0;
}
int InQueue(Queue &Q,struct node p)
{
if((Q.tail-Q.head+n)%n==n-1)
return 0;
*Q.tail=p;
Q.tail++;
return 1;
}
int OutQueue(Queue &Q,struct node &p)
{
if(Q.head==Q.tail)
return 0;
p=*Q.head;
Q.head++;
return 1;
}
//根据先序和中序序列构造二叉树
node* createTree_baseon_pre_in(int preindex, int instart, int inend)
{
node *root = NULL;
int mid = -1;
for(int i = instart; i <= inend; i++)
{
if(pre[preindex] == in[i])
{
mid = i;
break;
}
}
if(mid != -1)
{
root = new(node);
root->data = pre[preindex];
root->l_son = createTree_baseon_pre_in(preindex+1,instart,mid-1);
root->r_son = createTree_baseon_pre_in(preindex+mid-instart+1,mid+1,inend);
}
return root;
}
//根据后序和中序序列构造二叉树
node* createTree_baseon_post_in(int postindex, int instart, int inend)
{
node* root = NULL;
int mid =-1;
for(int i = instart; i <= inend; i++)
{
if(post[postindex] == in[i])
{
mid = i;
break;
}
}
if(mid != -1)
{
root = new(node);
root->data = post[postindex];
root->l_son = createTree_baseon_post_in(postindex-(inend-mid)-1,instart,mid-1);
root->r_son = createTree_baseon_post_in(postindex-1,mid+1,inend);
}
return root;
}
//返回中序序列中的对应下标
int count_node(node* p)
{
for(int i = 0; i < n ;i++)
{
if(p->data == in[i])
return i;
}
return -1;
}
//深搜递归建立二叉树
void dfs(node* p, node* root)
{
int i,j;
i = count_node(p);
j = count_node(root);
if(i < j)
{
if(root->l_son==NULL)
{
root->l_son = p;
}
else
dfs(p,root->l_son);
}
else if(i > j)
{
if(root->r_son==NULL)
{
root->r_son = p;
}
else
dfs(p, root->r_son);
}
}
//按照层序和中序构造二叉树
node* createTree_baseon_ord_in(int n)
{
node* root =NULL;
root = new(node);
root->l_son = NULL;
root->r_son = NULL;
root->data = ord[0];
for(int i = 1; i < n ; i++)
{
node* p =new(node);
p->l_son = NULL;
p->r_son = NULL;
p->data = ord[i];
dfs(p, root);
}
return root;
}
//先序遍历二叉树
void pre_traversal(node* root)
{
if(root == NULL)
{
return;
}
if(flag)
printf(" ");
printf("%c",root->data);
flag = true;
pre_traversal(root->l_son);
pre_traversal(root->r_son);
}
//层序遍历二叉树
void ord_traversal(node* root)
{
node p;
p.data = '0';
p.l_son = NULL;
p.r_son = NULL;
Queue Q;
InitQueue(Q);
InQueue(Q, *root);
while(!EmptyQueue(Q))
{
OutQueue(Q, p);
printf("%c ",p.data);
if(p.l_son)
InQueue(Q,*p.l_son);
if(p.r_son)
InQueue(Q,*p.r_son);
}
}
int main()
{
while(1)
{
node* root = NULL;
printf("请输入二叉树节点个数(输入0,则退出系统):\n");
cin >> n;
if(n == 0)
{
printf("已经为您退出系统!\n");
break;
}
printf("利用先序和中序建立二叉树请输入1\n");
printf("利用后序和中序建立二叉树请输入2\n");
printf("利用层序和中序建立二叉树请输入3\n");
char input;
cin >> input;
while(1)
{
if(input=='1'||input=='2'||input=='3')
break;
else
{
printf("输入不包含1或2或3,请重试\n");
cin >> input;
}
}
if(input == '1')
{
printf("请输入先序序列:\n");
for(int i = 0; i < n; i++)
{
cin >> pre[i];
}
printf("请输入中序序列:\n");
for(int i = 0; i < n; i++)
{
cin >> in[i];
}
root = createTree_baseon_pre_in(0,0,n-1);
printf("二叉树建立成功\n");
}
else if(input == '2')
{
printf("请输入后序序列:\n");
for(int i = 0; i < n; i++)
{
cin >> post[i];
}
printf("请输入中序序列:\n");
for(int i = 0; i < n; i++)
{
cin >> in[i];
}
root = createTree_baseon_post_in(n-1,0,n-1);
printf("二叉树建立成功\n");
}
else if(input == '3')
{
printf("请输入层序序列:\n");
for(int i = 0; i < n; i++)
{
cin >> ord[i];
}
printf("请输入中序序列:\n");
for(int i = 0; i < n; i++)
{
cin >> in[i];
}
root = createTree_baseon_ord_in(n);
printf("二叉树建立成功\n");
}
printf("层序遍历请输入0,先序遍历请输入1\n");
cin >> input;
while(1)
{
if(input=='0'||input=='1')
break;
else
{
printf("输入不包含0或1,请重试\n");
cin >> input;
}
}
if(input == '1')
{
printf("先序遍历结果如下:\n");
pre_traversal(root);
cout << endl;
}
else if(input == '0')
{
printf("层序遍历结果如下:\n");
ord_traversal(root);
cout << endl;
}
}
return 0;
}
/*
个数:7
先序:ABDECFG
中序:DBEAFCG
后序:DEBFGCA
层序:ABCDEFG
*/
12.17
在(二)栈和队列题目里选了【3、停车场管理】
3、停车场管理
设停车场(如下图1所示)内只有一个可停放几量汽车的狭长通道,且只有一个大门可供汽车进出。汽车在停车场内按车辆到达时的先后顺序,依次由北向南排列(大门在最南端,最先到达的第一辆车停放在车场的最北端),若车场内已经停满几量汽车,则后来的汽车只能在门外的便道上等候,一旦停车场内有车开走,则排在便道上的第一辆汽车即可开入;当停车场内某车辆要离开时,由于停车场是狭长的通道,在它之后开入车场的车辆必须先退出车场为它让路,待该车辆开出大门外后,为它让路的车辆再按原次序进入车场。在这里假设汽车不能从便道上开走。试设计一个停车场管理程序(这里只是一个假想的停车场管理,并不代表实际的停车场管理)。
分析:汽车在停车场内进出是按照栈的运算方式来实现的,先到的先进停车场;停车场的汽车离开停车场时,汽车场内其它汽车为该辆汽车让路,也是按栈的方式进行;汽车在便道上等候是按队列的方式进行的。因此,将停车场设计成一个栈,汽车让路也需要另一个栈来协助完成,汽车进出便道用队列来实现。
本设计,栈采用顺序栈结构,队列用链式存储结构。
存储结构定义如下:
#define stacksize 10
typedef struct sqstack
{
int data[stacksize];
int top;
} SqStackTp;
typedef struct linked_queue
{
int data;
struct linked_queue * next;
}LqueueTp;
typedef struct
{
LqueueTp *front , *rear ;
} QueptrTp;
停车场管理的算法描述如下:
1)接受命令和车号,若是汽车要进停车场,先判断停车场栈是否满,若不满,则汽车入栈,否则汽车进入便道队列等候。
2)若是汽车要离开停车场,为给汽车让路,将停车场栈上若干辆汽车入临时栈,等这辆车出停车场后,临时栈中的汽车出栈,在回到停车场栈,然后看便道队列是否为空,若不空则说明有汽车等候,从队头取出汽车号,让该车进入停车场栈。
3)重复1),2)直到为退出命令(车号为0或负数)。
思路:根据对题干的分析,其实已经非常清晰,我们需要两个栈一个用于模拟停车场,另一个模拟临时停车场,还需要一个队列模拟便道
操作时:有车要停进来时停车场栈不满则直接入栈,满的话就暂时入队;有车要离开时,对停车场栈顶一个一个筛查,不是对应车就先入临时栈,找到对应车出栈,再把临时栈中的车入会停车场栈,然后判断如果目前停车场栈不满的话,用便道队列中的车补齐,直到收到退出指令。
#include <iostream>
#include <stdio.h>
#define stacksize 5
using namespace std;
typedef struct sqstack
{
int data[stacksize];
int top;
}SqStackTp;
typedef struct linkedqueue
{
int data;
struct linkedqueue *next;
}LqueueTp;
typedef struct
{
LqueueTp *head, *rear;
}QueptrTp;
//栈操作
void initstack(SqStackTp &S)//初始化栈
{
S.top = 0;
}
void Sq_push(SqStackTp &S,int num)//把元素num压入栈
{
S.top++;
S.data[S.top] = num;
}
int Sq_pop(SqStackTp &S)//把元素num弹出栈
{
if(S.top > 0)
{
int num = S.data[S.top];
S.top--;
return num;
}
else
return 0;
}
//队列基本操作
void initqueue(QueptrTp &Q)//初始化空队列
{
Q.head = Q.rear = new(LqueueTp);
Q.head->next = NULL;
}
void Qu_push(QueptrTp &Q, int num)//把元素插入队列
{
linkedqueue *q;
q = new(LqueueTp);
q->next=NULL;
q->data = num;
Q.rear->next = q;
Q.rear = q;
}
int Qu_pop(QueptrTp &Q)//把元素弹出队列
{
if(Q.head == Q.rear)
return 0;
else
{
linkedqueue *q;
q = Q.head->next;
Q.head->next = q->next;
if(Q.head->next == NULL)
Q.head = Q.rear;
return q->data;
}
}
void in_Sq(SqStackTp &S,QueptrTp &Q)//处理进入停车场的车
{
int number;
cout << "******请输入将要进入的车的车牌:" << endl;
cin >> number;
if(S.top < stacksize-1)
{
Sq_push(S,number);
cout << "******该车已经进入停车场------------******" << endl;
}
else
{
Qu_push(Q,number);
cout << "******停车场已满,该车已进入便道----******" <<endl;
}
}
void Qu_Sq(SqStackTp &S,QueptrTp &Q)//如果停车场未满而便道上还有车的情况
{
while(S.top<stacksize-1)
{
if(Q.head!=Q.rear)
{
int num = Qu_pop(Q);
Sq_push(S,num);
}
else
break;//如果便道没有的话,要退出啊
}
}
void out_Sq(SqStackTp &S1,SqStackTp &S2,QueptrTp &Q)//处理离开的车
{
if(S1.top <= 0)
{
cout << "******目前停车场里并没有车辆--------******" <<endl;
return ;
}
int number,num;
cout << "******请输入将要离开的车的车牌:" << endl;
cin >> number ;
int flag = 0;//判断停车场里是否有要离开的车
while(S1.top>0)
{
num = Sq_pop(S1);
if(num == number)
{
flag = 1;
break;
}
else
{
Sq_push(S2,num);
}
}
while(S2.top > 0)
{
num = Sq_pop(S2);
Sq_push(S1,num);
}
Qu_Sq(S1,Q);
if(flag == 1)
cout << "******该车已经离开停车场------------******" << endl;
else
cout << "******未在停车场内找到该车----------******" << endl;
}
void display_num(SqStackTp &S,QueptrTp &Q)
{
if(S.top>0)
{
cout << "******目前停车场里的车分别是:" <<endl;
for(int i=1;i<=S.top;i++)
{
cout << S.data[i] <<endl;
}
}
else
{
cout << "******目前停车场里并没有车辆--------******" <<endl;
}
if(Q.head!=Q.rear)
{
cout << "******目前便道上的车分别是:" <<endl;
linkedqueue *temp;
temp = Q.head->next;
while(temp !=Q.rear)
{
cout << temp->data << endl;
temp = temp->next;
}
cout << temp->data <<endl;
}
else
{
cout << "******目前便道里并没有车辆----------******" <<endl;
}
}
void menu()
{
cout << "******A:进入停车场-----------------******" <<endl;
cout << "******B:离开停车场-----------------******" <<endl;
cout << "******C:显示目前停车场和便道上的车-******" <<endl;
cout << "******X:退出系统-------------------******" <<endl;
}
int main()
{
SqStackTp S1,S2;
initstack(S1);
initstack(S2);
QueptrTp Q;
initqueue(Q);
cout << "******这是一个简易的停车场管理系统!******" <<endl;
cout << "******下面你可以进行以下选项: ******"<< endl;
cout << "******A:进入停车场-----------------******" <<endl;
cout << "******B:离开停车场-----------------******" <<endl;
cout << "******C:显示目前停车场和便道上的车-******" <<endl;
cout << "******X:退出系统-------------------******" <<endl;
char ord;
while(cin >> ord)
{
fflush(stdin);// 防止错误输入
//cout << ord[0];
if(ord == 'A')
{
in_Sq(S1,Q);
cout << endl;
cout << "******请继续选择:" <<endl;
menu();
}
else if(ord == 'B')
{
out_Sq(S1,S2,Q);
cout << endl;
cout << "******请继续选择:" <<endl;
menu();
}
else if(ord == 'X')
{
cout << "******感谢使用本系统!!!----------******" << endl;
break;
}
else if(ord == 'C')
{
display_num(S1,Q);
cout << endl;
cout << "******请继续选择:" <<endl;
menu();
}
else
{
cout << "******错误输入,请重试!----******" <<endl;
cout << endl;
menu();
}
}
return 0;
}
12.17
在(四)图型结构题目里选了【*7、行车路线】(必选)
7、行车路线
小明和小芳出去乡村玩,小明负责开车,小芳来导航。
小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走s公里小明会增加s2的疲劳度。
例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为(2+2)2+2+22=16+2+4=22。
现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。
输入格式
输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口由1至n编号,小明需要开车从1号路口到n号路口。
接下来m行描述道路,每行包含四个整数t, a, b, c,表示一条类型为t,连接a与b两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1号路口和n号路口是连通的。
输出格式
输出一个整数,表示最优路线下小明的疲劳度。
样例输入
6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
样例输出
76
样例说明
从1走小道到2,再走小道到3,疲劳度为52=25;然后从3走大道经过4到达5,疲劳度为20+30=50;最后从5走小道到6,疲劳度为1。总共为76。
数据规模和约定
对于30%的评测用例,1≤n≤8,1≤m≤10;
对于另外20%的评测用例,不存在小道;
对于另外20%的评测用例,所有的小道不相交;
对于所有评测用例,1≤n≤500,1≤m≤105,1≤a, b≤n,t是0或1,c≤105。保证答案不超过106。
思路:
整体思想是应用------迪杰斯特拉算法。
分为四种情况
1.大路+大路:两次长度相加
2.大路+小路:加上小路长度的平方
3.小路+大路:直接加大路
4.小路+小路:这个比较特殊,要在目前记录长度的基础上,减掉之前连续走的小路和的平方,再加上目前所有小路和的平方即可。
具体在代码里有注释
代码:
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define MAXSIZE 505
const int INF = 0x3f3f3f3f;
const int NINF = 0xc0c0c0c0;
//1061109567, -1061109567
using namespace std;
long long big[MAXSIZE][MAXSIZE], small[MAXSIZE][MAXSIZE]; //大路小路
int vis_big[MAXSIZE], vis_small[MAXSIZE]; //最短路径顶点集
long long small_r[MAXSIZE], big_r[MAXSIZE]; //记录v0到各个顶点走大路或者走小路的最短路径长度---权值
long long path[MAXSIZE]; //记录顶点v之前正在连续走的小路
long long n, m;
int visited[100000]; //访问记录数组
long long Shortest_Path(long long v0)
{//用Dijkstra算法求无向网G的v0到其余定点的最短路径,并输出最短路径长度
for(long long i = 0; i < n; i++)
{
small_r[i] = small[v0][i];
big_r[i] = big[v0][i];
if(small_r[i] < INF)
{
path[i] = small_r[i];
small_r [i] *= small_r[i];
}
else
path[i] = INF;
}
vis_big[v0] = vis_small[v0] = 1; // v0加入顶点集
/*-----------------------------初始化完成-----------------------------*/
while(1)
{
long long min_r = INF, flag = 0, v = -1; //小路:flag = 1;大路:flag = 0
for(long long j = 0; j < n; j++) //找本次遍历的最小路径
{
if(!vis_big[j] && big_r[j] < min_r)
{
flag = 0;
v = j;
min_r = big_r[j];
}
if(!vis_small[j] && small_r[j] < min_r)
{
flag = 1;
v = j;
min_r = small_r[j];
}
}
if(v == -1) // 说明遍历结束
break;
//否则加入顶点集
if(flag == 1)
{
vis_small[v] = 1;
}
else
vis_big[v] = 1;
for(long long j = 0; j < n; j++) // 更新v0到集合v-vis上所有定点的最短路径长度
{
if(!vis_small[j] && small[v][j] < INF)
{
if(flag == 1) //pre_v到v之间是small road
{
long long temp = small_r[v] - path[v]*path[v] + (path[v] + small[v][j])*(path[v] + small[v][j]);
if(small_r[j] > temp || (small_r[j] == temp&&path[j] > (path[v] + small[v][j])))
{
small_r[j] = temp;
path[j] = path[v] + small[v][j];
}
}
else //pre_v到v之间是big road
{
long long temp = big_r[v] + small[v][j]*small[v][j];
if(small_r[j] > temp || (small_r[j] == temp && path[j] > small[v][j]))
{
small_r[j] = temp;
path[j] = small[v][j];
}
}
}
if(!vis_big[j] && big[v][j] < INF)
{
if(flag == 1)
{
long long temp = small_r[v] + big[v][j];
big_r[j] = min(temp, big_r[j]);
}
else
{
long long temp = big_r[v] + big[v][j];
big_r[j] = min(temp, big_r[j]);
}
}
}
}
return min(big_r[n-1], small_r[n-1]);
}
void ming_fang()
{
long long t ,a, b, c;
cout << "please input n(point number) & m(road number):" << endl;
cin >> n >> m;
for(long long i = 0; i < n; i++)
{
for(long long j = 0; j <= i; j++)
{
big[i][j] = big[j][i] = INF;
small[i][j] = small[j][i] = INF;
}
}
memset(vis_big, 0, sizeof(vis_big)); //初始化
memset(vis_small, 0, sizeof(vis_small));
cout << "please inpute t(1:small_road 0:big_road) & a(start) & b(end) & c(weight):" <<endl;
for(int i = 0; i < m; i++)
{
cin >> t >> a >> b >> c;
a--;
b--;
if(t == 1)
small[a][b] = small[b][a] = min(c, small[a][b]);
else
big[a][b] = big[b][a] = min(c, big[a][b]);
}
long long best_length = Shortest_Path(0);
cout << "the shortest path length is: " << best_length <<endl;
}
int main()
{
while(1)
{
ming_fang();
}
return 0;
}
/*
test data:
6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
输出76
6 7
0 1 2 2
0 2 3 3
0 3 4 3
0 4 5 1
0 1 4 6
0 1 5 8
0 5 6 1
输出8
6 5
1 1 2 2
1 2 3 3
1 3 4 3
1 4 5 1
1 5 6 1
输出100
*/
12.18
在(五)查找、排序、文件题目里选了【3、散列文件的插入、删除和查找】
3、散列文件的插入、删除和查找
功能要求:
(1)初始化三列文件;
(2)向散列文件中插入一个元素;
(3)从散列文件中删除一个元素;
(4)从散列文件中查找一个元素。
散列文件通常采用链接法处理冲突。
散列文件中每个节点的类型定义为:
Struct FLNode
{ //散列主文件中的节点类型
ElemType data ; //值域
Int next; //指向下一个节点的指针域
};
分析
-
数据结构:节点和题目一样,数据域包含关键字key和其余信息rest,指针域next是int型,就是向散列主文件中存储元素数组的下标,next == -1 表示空。
-
在散列主文件中,以线性数组存储散列表元素。
-
散列主文件存储元素,索引文件存储索引地址。
-
查找、删除、增加、初始化等操作都用了两个函数。
-
宏定义 N 为散列地址长度。
-
向单链表插入采用头插。
-
N 号地址不存放数据,删除后的节点放入N号节点。
-
使用二进制存取文件,使磁盘中的数据和内存中存储形式相同,方便操作。
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
#define N 13
//定义散列表长度
typedef int KeyType;
//定义关键字类型,整型
struct ElemType //元素类型
{
KeyType key; //关键字符
char rest[10]; //其他域,假定用字符数组
};
struct FLNODE //索引文件中的结点类型
{
struct ElemType data; //值域
int next; //指向下一个结点的指针域
};
const int b1 = sizeof(KeyType) ; //用全局常量b1保存索引表文件中的记录(元素)长度
const int b2 = sizeof(struct FLNODE) ; //用全局常量b2保存索引主文件中的记录(结点)长度
//定义散列主文件和索引表文件的名字
char filename[] = "Hash" ; //散列主文件名
char Iname[] = "HashIndex"; //索引表文件名
//建立并初始化散列表文件
void InitHashFile(char *fname)
{
char ch;
printf("确定要重新初始化散列文件(Y/N)?");
getchar();
scanf("%c",&ch);
if(ch == 'Y')
{
printf("---重新初始化散列文件完毕!---\n");
}
else
{
printf("---未执行重建散列文件操作!---\n");
return ;
}
/*------确认完毕,开始初始化------*/
int i;
int *A;
//以读写方式新建二进制散列文件
FILE *fp, *fp2;
fp = fopen(fname, "wb+");
fp2 = fopen(Iname, "wb+");
if(fp == NULL || fp2 == NULL)
{
printf("打开文件失败!\n");
exit(1);
}
//动态分配具有N+1个整型储存空间的数组A
A = (int*)calloc(N + 1, b1);
if(A == NULL)
{
printf("内存申请失败!\n");
exit(1);
}
//给数组A中的每个元素赋初值-1,表示空指针
for(i = 0; i < N + 1; i++)
{
A[i] = -1;
}
//初始化散列文件
fwrite((char*)A, (N + 1)*b1, 1, fp);
fwrite((char*)A, (N + 1)*b1, 1, fp2);
//删除数组A
free(A);
//关闭fp对应文件
fclose(fp);
fclose(fp2);
}
//检测是否存在散列文件
void existHash(char *fname)
{
char bcreate;
char filename[128];
FILE *fp, *fp2;
fp = fopen(fname, "rb");
fp2 = fopen(Iname, "rb");
if(fp == NULL||fp2 == NULL)
{
printf("---散列文件不存在,是否重新建立散列文件(Y:重新建立/N:打开其他散列文件)?---\n");
scanf("%c", &bcreate);
if(bcreate == 'Y')
{
InitHashFile(fname);
printf("---新建散列文件完毕!---\n");
}
else
{
printf("请输入散列文件路径:\n");
scanf("%s", filename);
strcpy(fname, filename);
existHash(fname);
}
}
else
{
fclose(fp);
fclose(fp2);
}
}
//把元素x插入到散列文件中
void HFInsertOne(char *fname, struct ElemType x)
{
int p;
int len; //文件尾结点位置序号
int *A;
int d;
struct FLNODE temp;
struct FLNODE pn;
//以读写和不新建方式打开散列文件
FILE *fp, *fp2;
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(fp == NULL)
{
printf("打开文件失败!\n");
exit(1);
}
//动态分配具有N + 1 个整型存储空间的数组
A = (int *)calloc(N + 1, b1);
if(!A)
{
printf("内存申请失败!\n");
exit(1);
}
//将索引文件的表头读入到数组A中
fread((char*)A, (N + 1)*b1, 1, fp2);
//以关键字x.key计算x的散列地址,采用除留取余法
d = x.key % N;
//以x和A[d]的值构成待插入散列文件的内存节点temp
temp.data = x;
temp.next = A[d];
//将temp结点的值写入到散列文件中,并连接到散列文件表头
//下表d单链表的表头
if(A[N] == -1)
{
//将文件指针移至文件尾
fseek(fp, 0L, 2);
//计算出文件尾的结点位置序号
len = (ftell(fp) - (N+1)*b1)/b2;
//将temp结点的值写入文件尾
fwrite((char*)&temp, b2, 1,fp);
//使A[d]指向新插入的结点
A[d] = len;
}
else
{
//p指向空闲单链表的表头结点
p = A[N];
//使空闲单链表的表头指针指向其下一个节点
fseek(fp, b1 * (N+1) + p*b2, 0);
fread((char*)&pn, b2, 1, fp);
A[N] = pn.next;
//使temp的值写入到p位置的结点上
fseek(fp, -b2, 1);
fwrite((char*)&temp, b2, 1, fp);
//使A[p]指向新插入的p结点
A[d] = p;
}
//将数组A中的全部内容写回到索引文件的表头中
fseek(fp,0L,0);
fseek(fp2,0L,0);
fwrite((char*)A,b1 * (N+1), 1, fp2);
//删除动态数组A和关闭散列文件
free(A);
fclose(fp);
fclose(fp2);
}
//从散列文件中删除关键字尾x.key的元素,并由x带回该
//元素,若删除成功则返回1,否则返回0
int HFDelete(char *fname, struct ElemType *x)
{
struct FLNODE tp, tq;
int p, q;
int *A;
int d;
FILE *fp, *fp2;
//打开散列文件
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(fp == NULL||fp2 == NULL)
{
printf("打开文件失败!\n");
exit(1);
}
//申请动态数组A
A = (int *)calloc(N + 1,b1);
if(A == NULL)
{
printf("内存申请失败!\n");
exit(1);
}
//将索引文件表头读入到数组A中
fread((char*)A, (N+1)*b1, 1, fp2);
//计算散列地址d
d = x->key % N;
p = A[d];
while(p != -1)
{
fseek(fp, (N+1)*b1+p*b2, 0);
fread((char*)&tp, b2, 1, fp);
if(tp.data.key == x->key)
{
//将删除结点的元素值赋给x带回
*x = tp.data;
//从单链表中删除p结点
if(p == A[d])
{
A[d] = tp.next;
}
else
{
tq.next = tp.next;
fseek(fp, (N+1)*b1+q*b2, 0);
fwrite((char*)&tq, b2, 1, fp);
}
//将p结点链接到空闲单链表的表头
tp.next = A[N];
fseek(fp, (N+1)*b1+p*b2, 0);
fwrite((char*)&tq, b2, 1, fp);
A[N] = p;
//结束while循环
break;
}
else
{
//使p指针的值赋给q,tp结点的值赋值给tq结点
q = p;
tq = tp;
//p指向单链表中的下一个节点
p = tp.next;
}//if分支结束
}//while循环结束
//将索引文件的表头重新写入索引文件中
fseek(fp,0L,0);
fseek(fp2,0L,0);
fwrite((char*)A,(N+1)*b1, 1, fp2);
//释放A数组申请的空间
free(A);
//关闭散列文件
fclose(fp);
fclose(fp2);
if(p == -1)
{
return 0;//没有找到要删除的结点
}
else
{
return 1;//成功删除要删除的结点
}
}
//从散列文件中查找关键字为x.key的元素,并由x带回
//元素,若查找成功则返回1,否则返回0
int HFSearch(char *fname, struct ElemType *x)
{
int d;
int p;
int *A;
struct FLNODE temp;
//以读写方式打开散列文件
FILE *fp, *fp2;
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(fp == NULL||fp2 == NULL)
{
printf("打开文件失败!\n");
exit(1);
}
//申请动态数组A
A = (int *)calloc(N+1, b1);
if(NULL == A)
{
printf("内存申请失败!\n");
exit(1);
}
fread((char*)A, (N+1)*b1, 1, fp2);
d = x->key % N;
//取出地址为d的单链表的表头指针(指向该地址的第一个存储元素)
p = A[d];
//从d点链表中查找关键字为x.key的元素
while(p != -1)
{
fseek(fp,(N+1)*b1+p*b2,0);//在文件中定位
fread((char*)&temp,b2,1,fp);
if(temp.data.key == x->key)
{
*x = temp.data;//被查找到的元素由x带回
break;
}
else
{
p = temp.next;//把节点指针移到下一个节点
}
}
//释放A数组申请的内存空间
free(A);
//关闭文件
fclose(fp);
fclose(fp2);
if(p == -1)
{
return 0;
}
else
{
return 1;
}
}
//顺序打印出散列文件中的每一个单链表中的每个节点位置序号及元素值
void HFPrint(char *fname)
{
int i;
int p;
int *A;
struct FLNODE pn;
//以读写方式打开散列文件
FILE *fp, *fp2;
fp = fopen(fname, "rb+");
fp2 = fopen(Iname, "rb+");
if(fp == NULL||fp2 == NULL)
{
printf("打开文件失败!\n");
exit(1);
}
//申请动态数组A
A = (int *)calloc(N+1,b1);
if(A ==NULL)
{
printf("内存申请失败!\n");
exit(1);
}
fread((char*)A, b1, N+1, fp2);
for(i = 0; i < N + 1; i++)
{
printf("%d:",i);
p = A[i];
while(p !=-1)
{
fseek(fp, (N+1)*b1+p*b2,0);//修改文件指针位置
fread((char*)&pn, b2,1,fp);//从文件中读取节点
printf("%d->%d(%s) ",p,pn.data.key,pn.data.rest);
p = pn.next;
}
printf("\n");
}
free(A);
fclose(fp);
free(fp2);
}
void Insert(char filename[])
{
struct ElemType x;
printf("输入待插入元素x的值(一个整数(关键字)和一个字符串(附加信息,长度小于10)):\n");
scanf("%d",&x.key);
scanf("%s",x.rest);
if(!HFSearch(filename,&x))
HFInsertOne(filename,x);
else
{
printf("该值在哈希表已存在");
}
}
void Delete(char filename[])
{
struct ElemType x;
//定义tag用于保存或查找函数的返回值
int tag;
printf("输入待删除元素x的关键字:");
scanf("%d", &x.key);
tag = HFDelete(filename,&x);
if(tag == 1)
{
printf("---删除成功!%d %s ---\n",x.key,x.rest);
}
else
{
printf("---删除失败---\n");
}
}
void Search(char filename[])
{
struct ElemType x;
int tag;
printf("请输入待查找的元素x的关键字:\n");
scanf("%d", &x.key);
tag = HFSearch(filename, &x);
if(tag == 1)
{
printf("---查找成功!%d %s ---\n",x.key,x.rest);
}
else
{
printf("查找失败!\n");
}
}
void start()
{// 开始
int number; //选择的功能号表
//检测散列文件是否存在
existHash(filename);
while(1)
{
// print meum
printf("\n");
printf("\t+----------------------------------+\n");
printf("\t|----------- 散列文件 -------------|\n");
printf("\t|--1-- 初始化散列文件 -------------|\n");
printf("\t|--2-- 向散列文件中插入一个元素 ---|\n");
printf("\t|--3-- 从散列文件中删除一个元素 ---|\n");
printf("\t|--4-- 从散列文件中查找一个元素 ---|\n");
printf("\t|--5-- 打印散列文件 ---------------|\n");
printf("\t|--0-- 结束运行 -------------------|\n");
printf("\t+----------------------------------+\n");
printf("*--请输入你的选择(0-5):");
scanf("%d", &number);
//fflush(stdin);// 防止错误输入
switch(number)
{
case 0:
return ;
case 1:
//初始化散列文件
InitHashFile(filename);
break;
case 2:
//向散列文件中插入一个元素
Insert(filename);
break;
case 3:
//从散列文件中删除一个元素
Delete(filename);
break;
case 4:
//从散列文件中查找一个元素
Search(filename);
break;
case 5:
//打印散列文件
HFPrint(filename);
break;
default:
printf("\n--- 输入功能号表错误 ---\n");
break;
} //switch结束
//system("pause"); 不能使用 test data 连续输入
printf("请按任意键继续. . .");
}
}
int main()
{
start();
return 0;
}
/*
test data:
Insert:
2
10 a
2
11 b
2
12 c
2
13 d
2
14 e
2
15 f
2
16 g
2
17 h
2
18 i
2
19 j
2
20 k
2
21 l
2
22 m
2
23 n
2
24 o
2
25 p
*/
//感谢大佬
—哈希表说明:
例如:0:3->13(d)
0:为地址,13%13后是0,故该节点插入位置为0到地址;
3:为插入顺序,类似数据库中的id;
13:是key关键字;
d:是剩余信息;