《软件技术基础》课程的第二章《数据结构》的习题解析,分为5个编程小作业和3个项目。word版有空的时候会上传到Github上,敬请期待。
编程作业
作业1
设顺序表L是一个递增有序表,试写一算法,将元素x插入L中,并使L仍是一个有序表。
作业2
已知某链接存储的数据序列的第一个数据的地址为FIR,结点的结构为{key,data,next},请编写一算法,在该数据序列中确定关键字值为def的数据是否存在,若存在,返回数据所在结点的地址,否则,返回null。
作业3
假设以双亲表示法作树的存储结构,写出双亲表示的类型说明,并编写求给定的树(结点数为n)的深度的算法。
思路:以双亲表示法作树的存储结构,对每一个结点,找其双亲,直至根结点,即可得到它的层次。树的深度即所有结点的最大层次。
代码:
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define max 100
//双亲表示法
typedef struct Node
{
int data;
int parent;
}PTNode;
typedef struct
{
PTNode nodes[max];
int n;
}PTree;
int GetDepth(PTree t)
{
int maxdepth=0,f,temp;
for(int i=1;i<=t.n;i++)
{
temp=0;
f=i-1;
while(f>=0)
{
temp++;
f=t.nodes[f].parent;
}
if(temp>maxdepth) maxdepth=temp;
return maxdepth;
//return maxdepth=temp>maxdepth?temp:maxdepth;
}
}
int main()
{
int x;
PTree t;
cin>>x;
t.n=x;
for(int i=0;i<t.n;i++)
{
int data,parent;
cin>>data>>parent;
t.nodes[i].data=data;
t.nodes[i].parent=parent;
}
cout<<GetDepth(t)<<endl;
return 0;
}
作业4
假设一个有向图G采用邻接矩阵存储,分别设计实现以下要求的算法:
求出图G中每个顶点的入度
求出图G中每个顶点的出度
求出图G中出度最大的一个顶点,并输出该顶点编号。
计算图G中出度为0的顶点数
判断图G中是否存在边<i,j>
思路:用一个二维数组graph[][]存储这个邻接矩阵,由于矩阵中只存在0和1,所以点i的入度=i列元素之和;点i的出度=i行元素之和。在求每个点的出度时,设一个max_outdegree_point来记录出度最大的点的编号,设一个zero_outdegree_count记录出度为0的顶点数。若存在边<i,j>,则graph[i][j]=1,否则为0,据此来判断图G中是否存在边<i,j>。
输入:
第一行输入两个数num,n。num表示图G有num个点,n表示下面要输入n行信息。输入保证i,j<=num。
(n,m<=1000)
接下来的n行,每行输入两个数字i,j,表示图G有一条由点i指向点j的有向边。
接着输入一个数m,m表示要询问m次图G中是否存在边<i,j>。
再接下来的m行,每行输入两个数字i,j,表示询问图G中是否存在边<i,j>。
输入保证i,j<=num。
注:n,m<=1000
输出:
输出图G中每个顶点的入度;
输出图G中每个顶点的出度;
输出图G中出度最大的顶点的编号;
输出图G中出度为0的顶点数;
后面对于每次询问,若存在该边,则输出yes;否则输出no。
示例图G:
程序截图:
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define max 1005
int graph[max][max]={0};//初始化
int num,n,m;
int max_outdegree_point,max_outdegree=0,zero_outdegree_count=0;
//求每个点的入度
void GetIndegree()
{
for(int j=1;j<=n;j++)
{
int indegree=0;
for(int i=1;i<=n;i++)
{
indegree+=graph[i][j];
}
printf("点%d的入度为%d。\n",j,indegree);
}
}
//求每个点的出度
void GetOutdegree()
{
for(int i=1;i<=n;i++)
{
int outdegree=0;
for(int j=1;j<=n;j++)
{
outdegree+=graph[i][j];
}
//求出度最大的点的编号
if(max_outdegree<outdegree)
{
max_outdegree=outdegree;
max_outdegree_point=i;
}
//记录出度为0的顶点数
if(!outdegree) zero_outdegree_count++;
printf("点%d的出度为%d。\n",i,outdegree);
}
}
//询问函数
bool find(int x,int y)
{
if(graph[x][y]) return true;
else return false;
}
int main()
{
cin>>num>>n;
for(int i=0;i<n;i++)
{
int x,y;
cin>>x>>y;
graph[x][y]=1;
}
//邻接矩阵完成建立
GetIndegree();
GetOutdegree();
printf("出度最大的顶点的编号为%d。\n",max_outdegree_point);
printf("出度为0的顶点数为%d。\n",zero_outdegree_count);
//查询
cin>>m;
for(int i=0;i<m;i++)
{
int x,y;
cin>>x>>y;
if(find(x,y)) cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
return 0;
}
作业5
设计算法,打印连通图G中每个顶点一次且仅一次,并要求打印次序满足以下条件:距离顶点v0近的顶点先于距离远的顶点(以边数为单位)。
思路:假设图G是无向图,建立好无向图后bfs(相当于二叉树的层序遍历,就是一一个点为中心,遍历他周围的节点)就能按距离顶点近的顶点先于距离远的顶点的次序打印连通图G中每个顶点一次且仅一次。
输入:
第一行输入两个数n,m,n表示无向图的点的数量,m表示输入m条边的信息。
接下来的m行,每行输入i和j,表示点i和点j之间有一条无向边。
输出:
输出为一行,按距离顶点近的顶点先于距离远的顶点的次序打印连通图G中每个顶点一次且仅一次,点与点之间用空格分隔。
算法:
用q[]数组来存储bfs过程中的经过的点的顺序,用visit[]数组来记录是否访问过点,visit[i]=1表示访问过,否则没有。用head,tail配合q[]数组来模拟一个队列。从head处开始循环,将未经过的点进队。最后遍历输出q[]数组。
示例图G:
程序截图:
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f;
#define max 101
int graph[max][max],visit[max],q[max];
int n,m;
int main()
{
memset(visit,0,sizeof(visit)); //初始化标记数组
cin>>n>>m;
//初始化无向图
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
if(i==j) graph[i][j]=0;
else graph[i][j]=INF;
}
//建立无向图
while(m--)
{
int u,v;
cin>>u>>v;
graph[v][u]=1;
graph[u][v]=1;
}
//初始化队列
memset(q,0,sizeof(q));
int head=1,tail=1,temp;
q[tail]=1;
tail++;
visit[1]=1;
while(head<tail)
{
temp=q[head];
for(int i=1;i<=n;i++)
{
//将未走过的节点进队
if(!visit[i] && graph[temp][i]==1)
{
visit[i]=1;
q[tail]=i;
tail++;
}
//结束条件
if(tail>n) break;
}
head++;
}
//遍历输出
for(int i=1;i<tail;i++) printf("%d ",q[i]);
return 0;
}
项目1:约瑟夫游戏
项目1:编程实现以下实验项目
约瑟夫生死游戏
每30个乘客同乘一艘船,因为严重超载,加上风高浪大,危险万分,因此船长告诉乘客,只有将全船一半乘客投入海中,其余人才能幸免于难。无奈,大家只得同意这种办法,并议定30个人围成一圈,由第1个人数起,依次报数,数到第9人,便把他投入大海中,然后再从他的下一个人数起,数到第9人,再将他扔到大海中,如此循环地进行,直到剩下15个乘客为止。问哪些位置是将被扔下大海的位置。
思路:
- 模拟:按照题意去做,用一个visit数组记录下已经出队了的人,visit初始全部赋值为false。
- 链表:构建一个链表,使整个游戏在链表中运行,包含删除节点、移动节点等功能,尽量在节点被删除时不必移动很多节点。输入n的值来构建一个具体的链表,对删除了节点后的链表进行重连,使循环能够继续下去,输入间隔值x使间隔确定,输入y来中止循环,最终输出所有被删除节点的序号。
算法分析与设计:
模拟:循环y次,每次循环里将一个原本为false的visit元素修改为true,最终输出visit元素为true的下标。代码如下:
链表:构建一个循环链表,读入完数据后要将头和尾相连。代码如下:
数到x时删除节点,重新连接前后结点,不然的话它就会变成野指针,代码如下:
数据结构设计:
模拟:一个visit数组:
链表:一个结构体Node:
系统实现:
测试结果:
模拟:
链表:
完整代码:
模拟:
#include<cstdio>
using namespace std;
int main()
{
int n,x,y,s=0;
scanf("%d%d%d",&n,&x,&y);//入读
bool visit[200]={0};//visit赋初始值
for(int k=0;k<y;k++)
{//总共要出队y次
for(int i=0;i<x;i++)
{
if(++s>n) s=1;
if(visit[s]) i--;
}
printf("%d ",s);
visit[s]=true;//输出,记录已出队
}
return 0;
}
链表:
#include<iostream>
#include<cstdio>
#include<cstdlib>//用free()要用这个库
using namespace std;
int m,n,y;
struct Node
{
int data;
Node *next;
}*head,*p,*tail,*temp;
int main()
{
scanf("%d%d%d",&n,&m,&y);
head=new Node;
head->next=NULL;
tail=head;
for(int i=1;i<=n;i++)
{
p=new Node;
p->data=i;
p->next=NULL;
tail->next=p;
tail=p;
}
p=head->next;
tail->next=head->next;//链接尾和头
for(int i=1;i<=y;i++)
{
for(int j=1;j<m-1;j++)
{
p=p->next;
}
printf("%d ",p->next->data);
temp=p->next;
p->next=temp->next;//连接要删除那个结点上下结点
p=p->next;//更新
free(temp);//释放空间
}
return 0;
}
项目2:二叉树遍历
已知一个按先序序列输入的字符序列,如abc,de,g,f,(其中逗号表示空节点)。请建立二叉树并按中序和后序方式遍历二叉树,最后求出叶子节点个数和二叉树深度。
输入
输入一个长度小于50个字符的字符串。
输出
输出共有4行:
第1行输出中序遍历序列;
第2行输出后序遍历序列;
第3行输出叶子节点个数;
第4行输出二叉树深度。
示例输入
abc,de,g,f,
示例输出
cbegdfa cgefdba 3 5
完成程序代码和项目报告。
本实验共需要完成五项任务:
- 根据输入先序建立二叉树;
- 按中序方式遍历二叉树并输出序列;
- 按后序方式遍历二叉树并输出序列;
- 求出二叉树的叶子结点个数;
- 求出二叉树的深度。
思路:
- 定义二叉树结点数据结构
建立一个Node结构体,其中包含一个char类型的data用于存储字母,一个Node类型的Lchild指针指向左子树和一个Node类型的Rchild指针指向右子树。再建立一个指向Node这种结构的*p指针。 - 如何先序建立二叉树?
建立一个build(p &T)函数用于先序建立二叉树,T为返回node的指针。函数中定义一个char类型的c用于读入输入,若c为“,”,则为空结点,T=NULL;若T为换行符“\n”,则输入结束并返回;否则c就是字母结点,则创建一个新的指针T,并让T->data=c,接着递归创建其左子树和右子树。 - 如何按中序、后序方式遍历二叉树并输出序列?
要按中序方式遍历二叉树,先访问左子树,再访问根节点,最后访问右子树;要按后序方式遍历二叉树,先访问左子树,再访问右子树,最后访问根节点;在访问根节点时打印该结点的data即可完成输出。 - 如何求出二叉树的叶子结点个数?
叶子结点就是左右子树都是空子树的结点,据此完成判断。如果二叉树为空,则叶子结点数为0;如果二叉树只有一个结点,则叶子结点数为1;否则,二叉树的叶子结点数为左右子树叶子结点数之和,据此完成递归。 - 如何求出二叉树的深度?
如果二叉树为空,则深度为0;如果二叉树只有一个结点,则深度为1;否则,二叉树的深度为左右子树深度的最大值+1。
算法分析与设计:
每项任务都被设计成一个封装好的函数,这样使整个程序井然有序。
-
先序建立二叉树
-
按中序、后序方式遍历二叉树并输出序列
-
输出二叉树的叶子结点数
-
输出二叉树的深度
-
主函数
数据结构设计:
二叉树结点数据结构采用结构体的形式实现,其中包含一个char类型的data用于存储字母,一个Node类型的Lchild指针指向左子树和一个Node类型的Rchild指针指向右子树。再建立一个指向Node这种结构的*p指针。
具体代码如下:
系统实现:
示例二叉树:
测试结果:
完整代码:
#include<bits/stdc++.h>
using namespace std;
//定义结点结构体
typedef struct Node
{
char data;
Node *Lchild,*Rchild;
}node,*p;
//先序建立二叉树
void build(p &T)
{
char c;
cin>>c;
if(c == ',') T=NULL;
else if(c == '\n') return ;
else
{
T=new node;//让指针实体化 new返回的是node的指针
T->data=c;
build(T->Lchild);
build(T->Rchild);
}
}
//中序遍历
void inorder_traversal(p T)
{
if(T)
{
inorder_traversal(T->Lchild);
cout<<T->data;
inorder_traversal(T->Rchild);
}
}
//后序遍历
void postorder_traversal(p T)
{
if(T)
{
postorder_traversal(T->Lchild);
postorder_traversal(T->Rchild);
cout<<T->data;
}
}
//求出叶子节点个数
int GetLeafCount(p T)
{
int num=0,Lnum,Rnum;
if(!T) return 0;
else if(T->Lchild==NULL && T->Rchild==NULL) return 1;
else
{
Lnum=GetLeafCount(T->Lchild);
Rnum=GetLeafCount(T->Rchild);
}
return num=Lnum+Rnum;
}
//求出二叉树的深度
int GetDepth(p T)
{
int depth=0;
if(!T) return 0;
else
{
int Ldepth=GetDepth(T->Lchild);
int Rdepth=GetDepth(T->Rchild);
return depth=max(Ldepth,Rdepth)+1;
}
}
int main()
{
p T;
build(T);
inorder_traversal(T);
cout<<" ";
postorder_traversal(T);
cout<<" ";
cout<<GetLeafCount(T)<<" "<<GetDepth(T)<<endl;
return 0;
}
项目3:边数最少路径
[问题描述]
给定一个图,设计一个程序,找出一条从某一顶点A到另一顶点B边数最少的一条路径。
[输入]
图的顶点个数N,图中顶点之间的边的关系及要找的路径的起点A和终点B。
[输出]
若A到B无路径,则输出“There is no path”,否则输出A到B路径上各顶点。
[存储结构]
图采用邻接矩阵或邻接表的方式存储。
思路:
- 建立邻接矩阵:这个有向图用一个n*n大小的二维矩阵graph[][]来存储,初始化所有元素为0。当有一条从x指向y的有向边时,graph[x][y]赋值为1。
- 寻路:采用广搜的方法,从a开始依次访问与a邻接的点v1、v2、……、vk,若没有到达b,则继续访问与v1的邻接的点v11、v12、……、v1k,……。如此下去,直到访问到b。用这样的方法,最先到达b的路径一定是边数最少的路径。
- 记录:采用队列来记录被访问过的点。每次访问与队列头部邻接的点,然后删去队列头部的点。如果队列为空队列,则说明a到b无路径。在每次访问点的过程中,把当前点的序号作为与其邻接且未被访问的点的前驱点记录下来以便于输出时进行从b到a的回推。
算法分析与设计:
-
建立邻接矩阵的函数Build()
-
初始化函数Init()
将队列的头和尾初始化为0,将记录路径的path[]的起点设置为a。
3. 寻路函数FindPath()
enq(q *Q,int x)函数的核心代码为Q->q[Q->r]=x,作用是将队列的尾设置为起点a。
front(q *Q)函数的核心代码为return Q->q[Q->f],作用是返回队列头的序号。
deq(q *Q)函数的核心代码为Q->f++,作用是将队列的头后移一个单位。
compare(q Q)通过判断Q.f与Q.r是否相等来判断队列是否为空。
4. 打印路径的函数PrintPath()
利用z[]回溯得到path[]并顺序打印path[]中的序号。
数据结构设计:
- 队列结构体
一维数组q[]存储点,f、r分别模拟一个队列的头和尾。
2. 二维数组graph[][]存储邻接矩阵
用来存储邻接矩阵。
3. 一维数组path[]、z[]
前者用来记录路径上的点的序号,后者用来记录当前点的序号,作为与其邻接且未被访问的点的前驱点。
系统实现:
测试结果:
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define max 100
typedef struct
{
int q[max];
int f,r;
}q;
q Q;
int graph[max][max],z[max],path[max];;
int n,m,x,y,a,b;
bool judge;
void enq(q *Q,int x)
{
Q->q[Q->r]=x;
if(Q->r == max-1) Q->r=0;
else Q->r++;
if(Q->r == Q->f) cout<<"Error!"<<endl;
}
int front(q *Q)
{
if(Q->r == Q->f) cout<<"Error!"<<endl;
else return Q->q[Q->f];
}
void deq(q *Q)
{
if(Q->r == Q->f) cout<<"Error!"<<endl;
else
{
if(Q->f == max-1) Q->f=0;
else Q->f++;
}
}
bool compare(q Q)
{
if(Q.f == Q.r) return true;
else return false;
}
void Build()
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
int x,y;
cin>>x>>y;
graph[x][y]=1;
}
}
void Init()
{
Q.f=0;
Q.r=0;
path[0]=a;
}
void FindPath(int a,int b)
{
if(a == b) graph[a][a]=-1;
else
{
enq(&Q,a);
graph[a][a]=-1;
judge=false;
while(!compare(Q) && !judge)
{
a=front(&Q);
deq(&Q);
int j=1;
while(j<=n && !judge)
{
if(graph[a][j]==1 & graph[j][j]!=-1)
{
enq(&Q,j);
graph[j][j]=1;
z[j]=a;
if(j == b && graph[a][j] == 1) judge=true;
}
if(!judge) j++;
}
}
}
}
void PrintPath(int a,int b)
{
int k=1;
int i=b;
while(i!=a)
{
path[k]=i;
k++;
i=z[i];
}
for(int i=0;i<k;i++) cout<<path[i]<<" ";
}
int main()
{
Build();
cout<<"Input a and b:";
cin>>a>>b;
Init();
FindPath(a,b);
if(judge) PrintPath(a,b);
else cout<<"There is no path"<<endl;
return 0;
}