写的是真的丑,就冲着能跑而已,大概意会一下,一切为了做题服务,这周末考完试会去把图的代码敲好,为实验考试做准备
问题 C: 算法7-10,7-11:关节点和重连通分量
题目描述
假若在删去顶点v以及和v相关联的各边之后,将图的一个连通分量分割成两个或两个以上的连接分量,则称顶点v为该图的一个关节点。一个没有关节点的连通图称为重连通图。在重连通图上,任意一对顶点之间至少存在两条路径,则在删去某个顶点以及依附于该顶点的各边时也不会破坏图的连通性。
利用深度优先搜索可以求出图的关节点,并由此可以判断图是否是重连通的。
通过修改深度优先搜索遍历的算法便可以得到求关节点的算法,其算法描述如下:
在本题中,读入一个无向图的邻接矩阵(即数组表示),建立无向图并按照以上描述中的算法求出所有的关节点,并输出这些关节点。
输入
输入的第一行包含一个正整数n,表示图中共有n个顶点。其中n不超过50。
以后的n行中每行有n个用空格隔开的整数0或1,对于第i行的第j个整数,如果为1,则表示第i个顶点和第j个顶点有直接连接,0表示没有直接连接。当i和j相等的时候,保证对应的整数为0。
输入保证邻接矩阵为对称矩阵,即输入的图一定是无向图,且保证图中只有一个连通分量
输出
第一行有一个整数x,即图中关节点的个数。
第二行输出x个整数,表示所有关节点的顶点编号,请按照编号从小到大的顺序输出。每个整数后输出一个空格,并请注意行尾输出换行。
样例输入
4 0 1 1 1 1 0 0 0 1 0 0 0 1 0 0 0
样例输出
1 0
提示
在本题中,需要掌握图的深度优先遍历的方法,并需要掌握通过深度优先搜索求得图中关节点的算法。通过生成深度优先生成树可以得出两类关节点的特性:
1. 若生成树的根有两棵或两棵以上的子树,则此根顶点必为关节点。
2. 若生成树中某个非叶子顶点v,其某棵子树的根和子树中的其他结点均没有指向v的祖先的回边,则说明v是关节点。
注意以上两点特性,就可以成功的通过深度优先搜索遍历的算法得出图中的关节点了。
思路
本来没怎么看懂题,导致走了很多弯路,后来大佬提醒说可以删除点来找有无两个连通分量,就套用之前写的输出连通分量的代码
这里最需要注意的是在记录哪一个是关节点的时候,是记i,不是ptr,因为碰巧几个样例结果一样导致提交错误,后来改了很多次才发现
代码
#include<iostream>
using namespace std;
struct dfs{
bool **edge;
};
void createdfs(int n,dfs &d){//注意这里是引用
d.edge=new bool*[n];
for(int i=0;i<n;i++){
d.edge[i]=new bool[n];
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
{
//cout<<i<<" "<<j;
cin>>d.edge[i][j];
}//cout<<d.edge[i][j];
}
//cout<<d.edge[0][0];
}
void outputdfs(int n,dfs d){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
{
//cout<<"Sf"<<endl;
cout<<d.edge[i][j]<<" ";
}
cout<<endl;
}
}
int getfirstver(int n,dfs& d,int ptr,int*visited){
for(int i=0;i<n;i++){
if(d.edge[ptr][i]==1&&visited[i]==0){
d.edge[ptr][i]=0;
d.edge[i][ptr]=0;return i;
}
}
return -1;
}
int getnextver(int n,dfs &d,int w,int*visited){
for(int i=0;i<n;i++){
if(d.edge[w][i]==1&&visited[i]==0)
{
//cout<<w<<" "<<i<<" "<<d.edge[w][i]<<endl;
d.edge[w][i]=0;
d.edge[i][w]=0;
return i;}//循环的时候这个节点就过去了
}
return -1;
}
void sdfs(int n,dfs &d,int *&visited,int &ptr){
int w=0;
visited[ptr]=1;
//cout<<ptr<<" ";//<<"ptr"
for(w=getfirstver(n,d,ptr,visited);w!=-1;w=getnextver(n,d,ptr,visited)){
//cout<<"eaf"<<w<<endl;
if(!visited[w]){
//outputdfs(n,d);
//cout<<endl;
sdfs(n,d,visited,w);}
}
}
void copydfs(dfs &x,dfs d,int n){
x.edge=new bool*[n];
for(int i=0;i<n;i++){
x.edge[i]=new bool[n];
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
{
//cout<<i<<" "<<j;
x.edge[i][j]=d.edge[i][j];
}//cout<<d.edge[i][j];
}
}
void dfstra(int n,dfs &d,int *&visited ,int ptr,int i,int*&c,int &temp){
int count=0;
dfs x;
copydfs(x,d,n);
for(int t=0;t<n;t++){
visited[t]=0;
}
for(int j=0;j<n;j++){
x.edge[i][j]=0;
x.edge[j][i]=0;
}
for(ptr=0;ptr<n;ptr++){
if(!visited[ptr]&&ptr!=i){
sdfs(n,x,visited,ptr);
if(count==0) count++;
else break;
//cout<<"ptr"<<ptr;
//cout<<endl;
}
}
if(ptr<n) {
//cout<<ptr<<" ";
c[temp]=i;
temp++;}
}
int main(){
int n=0;
cin>>n;
dfs d;
createdfs(n,d);
int *c=new int[n];
int temp=0;
//cout<<d.edge[1][0];
//outputdfs(n,d);
int *visited=new int[n];
/*ptr=0;
dfstra(n,d,visited,ptr,0,c,temp);*/
for(int i=0;i<n;i++){
int ptr=0;
dfstra(n,d,visited,ptr,i,c,temp);
//cout<<endl;
}
cout<<temp<<endl;
for(int j=0;j<temp;j++){
cout<<c[j]<<" ";
}
cout<<endl;
/*int c=getfirstver(n,d,1);
cout<<c<<endl;*///测试找第一个邻接结点
/*int a=getnextver(n,d,1);
for(;a!=-1;a=getnextver(n,d,1))
cout<<"a"<<a<<endl;*///测试找下一个邻接结点
return 0;
}
算法7-12:有向无环图的拓扑排序
题目描述
由某个集合上的一个偏序得到该集合上的一个全序,这个操作被称为拓扑排序。偏序和全序的定义分别如下:
若集合X上的关系R是自反的、反对称的和传递的,则称R是集合X上的偏序关系。
设R是集合X上的偏序,如果对每个x,y∈X必有xRy或yRx,则称R是集合X上的全序关系。
由偏序定义得到拓扑有序的操作便是拓扑排序。
拓扑排序的流程如下:
1. 在有向图中选一个没有前驱的顶点并且输出之;
2. 从图中删除该顶点和所有以它为尾的弧。
重复上述两步,直至全部顶点均已输出,或者当前图中不存在无前驱的顶点为止。后一种情况则说明有向图中存在环。
采用邻接表存储有向图,并通过栈来暂存所有入度为零的顶点,可以描述拓扑排序的算法如下:
在本题中,读入一个有向图的邻接矩阵(即数组表示),建立有向图并按照以上描述中的算法判断此图是否有回路,如果没有回路则输出拓扑有序的顶点序列。
输入
输入的第一行包含一个正整数n,表示图中共有n个顶点。其中n不超过50。
以后的n行中每行有n个用空格隔开的整数0或1,对于第i行的第j个整数,如果为1,则表示第i个顶点有指向第j个顶点的有向边,0表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出
如果读入的有向图含有回路,请输出“ERROR”,不包括引号。
如果读入的有向图不含有回路,请按照题目描述中的算法依次输出图的拓扑有序序列,每个整数后输出一个空格。
请注意行尾输出换行
样例输入
4 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0
输出
3 0 1 2
提示
在本题中,需要严格的按照题目描述中的算法进行拓扑排序,并在排序的过程中将顶点依次储存下来,直到最终能够判定有向图中不包含回路之后,才能够进行输出。
另外,为了避免重复检测入度为零的顶点,可以通过一个栈结构维护当前处理过程中入度为零的顶点
思路
很普通的拓扑排序输出,就是记录入度,为0入栈,然后出栈检查出度
容易出问题的点是,当时为了方便,出边表是最远的离点最近,比如1的出边为4,3,2,导致不能在出栈的时候一边减去邻接结点入度,一边检验入度是否为0入栈,需要在循环外搞一下
代码
#include<iostream>
#include <cassert>
using namespace std;
struct StackNode{
int data;
StackNode *link;
StackNode(int d = 0, StackNode *next = NULL):link(next),data(d){}
};
class LinkedStack{
private:
StackNode *top;
public:
LinkedStack():top(NULL){}//无头结点
~LinkedStack(){
makeEmpty();
}
void Push(const int &x);
bool Pop(int &x);
bool getTop(int &x)const;
int getSize()const;
bool IsEmpty()const{
return top == NULL;
}
bool IsFull()const{
return false;
}
void makeEmpty();
};
void LinkedStack::makeEmpty(){
StackNode *p;
while (top){//最后top为NULL
p = top;
top = top->link;
delete p;
}
}
void LinkedStack::Push(const int &x){
top = new StackNode(x, top);
assert(top);
}
bool LinkedStack::Pop(int &x){
if (IsEmpty()){
return false;
}
StackNode *p = top;
top = top->link;
x = p->data;
delete p;
return true;
}
bool LinkedStack::getTop(int &x)const{
if (IsEmpty()) return false;
x = top->data;
return true;
}
int LinkedStack::getSize()const{
StackNode *p = top;
int k = 0;
while (p){
p = p->link;
k++;
}
return k;
}
struct node{
int count;
int data;
node*next;
node(int d=-1,node*link=NULL,int c=0):data(d),next(link),count(c){}
};
struct dfs{
bool **edge;
};
void createdfs(int n,dfs &d){//注意这里是引用
d.edge=new bool*[n];
for(int i=0;i<n;i++){
d.edge[i]=new bool[n];
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
{
cin>>d.edge[i][j];
}
}
}
void ctreatelist(dfs d,int n,node*&p){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(d.edge[i][j]!=0){
node*newnode=new node(j);
newnode->next=p[i].next;
p[i].next=newnode;
p[j].count++;
}
}
}
}
void listprint(node*p,int n){
node*ptr=p[0].next;
for(int i=0;i<n;i++){
ptr=p[i].next;
cout<<i<<" :"<<ptr->count<<" ";
while(ptr!=NULL){
cout<<ptr->data<<" ";
ptr=ptr->next;
}
cout<<endl;
}
}
void fun(node*p,int n){
LinkedStack s;
int c=0,i;
node*ptr;
for(i=0;i<n;i++){
if(p[i].count==0)
{s.Push(i);
p[i].count=-1;}
}
int *k=new int[n];
while(!s.IsEmpty()){
s.Pop(i);
k[c]=i;
c++;
ptr=p[i].next;
p[i].count=-1;
while(ptr!=NULL){
p[ptr->data].count--;
ptr=ptr->next;
}
for(i=0;i<n;i++){
if(p[i].count==0)
{s.Push(i);
p[i].count=-1;}
}
//listprint(p,n);
}
if(c<n) cout<<"ERROR"<<endl;
else{
for(int j=0;j<n;j++){
cout<<k[j]<<" ";
}
cout<<endl;
}
}
int main(){
int n=0;
cin>>n;
dfs d;
createdfs(n,d);
node*p=new node[n];
ctreatelist(d,n,p);
fun(p,n);
return 0;
}
算法7-15:迪杰斯特拉最短路径算法
题目描述
在带权有向图G中,给定一个源点v,求从v到G中的其余各顶点的最短路径问题,叫做单源点的最短路径问题。
在常用的单源点最短路径算法中,迪杰斯特拉算法是最为常用的一种,是一种按照路径长度递增的次序产生最短路径的算法。
可将迪杰斯特拉算法描述如下:
在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并按照以上描述中的算法求出源点至每一个其它顶点的最短路径长度。
输入
输入的第一行包含2个正整数n和s,表示图中共有n个顶点,且源点为s。其中n不超过50,s小于n。
以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出
只有一行,共有n-1个整数,表示源点至其它每一个顶点的最短路径长度。如果不存在从源点至相应顶点的路径,输出-1。
请注意行尾输出换行。
样例输入
4 1 0 3 0 1 0 0 4 0 2 0 0 0 0 0 1 0
样例输出
6 4 7
提示
在本题中,需要按照题目描述中的算法完成迪杰斯特拉算法,并在计算最短路径的过程中将每个顶点是否可达记录下来,直到求出每个可达顶点的最短路径之后,算法才能够结束。
迪杰斯特拉算法的特点是按照路径长度递增的顺序,依次添加下一条长度最短的边,从而不断构造出相应顶点的最短路径。
另外需要注意的是,在本题中为了更方便的表示顶点间的不可达状态,可以使用一个十分大的值作为标记。
思路
书上的属于比较详细的,做题的话简化一下就好,只需要记录源点到各点最短路径长度就行
循环中要注意的是已经找到最短路径的点不再重复比较
代码
#include<iostream>
using namespace std;
struct dfs{
int **edge;
};
void createdfs(int n,dfs &d){//注意这里是引用
d.edge=new int*[n];
for(int i=0;i<n;i++){
d.edge[i]=new int[n];
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
{
//cout<<i<<" "<<j;
cin>>d.edge[i][j];
}//cout<<d.edge[i][j];
}
//cout<<d.edge[0][0];
}
void fun(dfs d,int n,int s,int*dist,int*visited){
for(int i=0;i<n;i++){
if(d.edge[s][i]>0){
dist[i]=d.edge[s][i];
}
else dist[i]=-1;
}
int k=1;
int min=10000;
int count=0;
int temp=0;
while(k!=n){
k++;
for(int j=0;j<n;j++){
if(j!=s&&visited[j]!=1&&dist[j]!=-1){
if(dist[j]<min&&dist[j]!=-1){
min=dist[j];
count=j;
//cout<<count<<" ";
}
}
}min=10000;
visited[count]=1;
//if(k!=n){
for(int i=0;i<n;i++){//cout<<visited[i]<<" ";
if(d.edge[count][i]>0&&visited[i]!=1){
if(dist[i]==-1) dist[i]=d.edge[count][i]+dist[count];
else{
temp=dist[count]+d.edge[count][i];
if(temp<dist[i]) dist[i]=temp;}
}
//cout<<"dist[i]"<<dist[i]<<" ";
}
//cout<<endl;
//}
temp=0;
}
}
int main(){
int n=0,s=0;
cin>>n;
dfs d;
cin>>s;
createdfs(n,d);
int *dist=new int[n];
int *visited=new int[n];
for(int i=0;i<n;i++){
dist[i]=visited[i]=0;
}
visited[s]=1;
fun(d,n,s,dist,visited);
for(int i=0;i<s;i++){
cout<<dist[i]<<" ";
}
for(int i=s+1;i<n;i++){
cout<<dist[i]<<" ";
}
cout<<endl;
}
算法10-2:折半插入排序
题目描述
折半插入排序同样是一种非常简单的排序方法,它的基本操作是在一个已经排好序的有序表中进行查找和插入。不难发现这个查找的过程可以十分自然的修改成折半查找的方式进行实现。
折半插入排序的算法可以描述如下:
输入
输出
样例输入
10 2 8 4 6 1 10 7 3 5 9
样例输出
1 2 3 4 5 6 7 8 9 10
提示
代码
#include<iostream>
using namespace std;
void fun(int *&a,int n){
int i,j,low,high,m;
for(i=2;i<=n;i++){
a[0]=a[i];
low=1;high=i-1;
while(low<=high){
m=(low+high)/2;
if(a[0]<a[m]) high=m-1;
else low=m+1;
}
if(a[2]<a[1]){
a[2]=a[1];
a[1]=a[0];
}
else{
for(j=i-1;j>=high+1;j--){
a[j+1]=a[j];
}
a[high+1]=a[0];}
/* for(int j=1;j<n+1;j++){
cout<<a[j]<<" ";
}
cout<<endl;*/
}
}
int main(){
int n=0;
cin>>n;
int*a=new int[n+1];
for(int i=1;i<n+1;i++){
cin>>a[i];
}
fun(a,n);
for(int j=1;j<n+1;j++){
cout<<a[j]<<" ";
}
cout<<endl;
return 0;
}
算法10-6~10-8:快速排序
题目描述
快速排序是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排序的记录分割成两个独立的部分,其中一部分记录的关键字均比另一部分的关键字小,在分成两个部分之后则可以分别对这两个部分继续进行排序,从而使整个序列有序。
快速排序的算法可以描述如下:
输入
输出
样例输入
10 2 8 4 6 1 10 7 3 5 9
样例输出
1 2 3 4 5 6 7 8 9 10
提示
代码
#include<iostream>
using namespace std;
int partition(int*&a,int low,int high){
int keyp=0;
a[0]=a[low];
keyp=a[low];
while(low<high){
while(low<high&&a[high]>=keyp)
high--;
a[low]=a[high];//将比关键值小的放在低端,这个时候高位空出,
//但值保留,关键值在表中的原位置被覆盖 a[high]<keyp
while(low<high&&a[low]<=keyp)
low++;
a[high]=a[low];
}
a[low]=a[0];
return low;//==断开的位置
}
void qsort(int*&a,int low,int high){
int ploc;
if(low<high){
ploc=partition(a,low,high);
qsort(a,low,ploc-1);
qsort(a,ploc+1,high);
}
}
int main(){
int n=0;
cin>>n;
int *a=new int[n+1];
for(int i=1;i<=n;i++){
cin>>a[i];
}
qsort(a,1,n);
for(int j=1;j<=n;j++){
cout<<a[j]<<" ";
}
cout<<endl;
return 0;
}
算法10-12~10-14:归并排序
题目描述
归并排序是基于归并操作完成的,而一次归并操作是通过两个或两个以上的有序表合并成一个新的有序表完成的。常见的归并排序是2-路归并排序,其核心操作是将一维数组中前后相邻的两个有序序列归并成一个有序序列。其算法可以描述如下:
输入
输出
样例输入
10 2 8 4 6 1 10 7 3 5 9
样例输入
1 2 3 4 5 6 7 8 9 10
提示
在本题中,需要按照题目描述中的算法完成2-路归并排序的算法。
不难发现,2-路归并算法的时间复杂度为O(nlog2n),且需要和原始数据等数量的辅助空间。递归形式的2-路归并排序算法在形式上比较简洁,但是实用性较差。通常在需要使用归并排序的场合,往往使用非递归形式的归并排序。
与快速排序和堆排序相比,归并排序的特点在于其是一种稳定的排序方法。
代码
#include<iostream>
using namespace std;
int a[100000];
void merge(int a[],int b[],int i,int m,int n){
int j,k;
for(j=m+1,k=i;i<=m&&j<=n;k++){
if(a[i]<a[j]) b[k]=a[i++];
else b[k]=a[j++];
}
if(i<=m)
while(k<=n&&i<=m) b[k++]=a[i++];
else if(j<=n)
while(k<=n&&j<=n) b[k++]=a[j++];
}
void msort(int a[],int b[],int s,int t){
int m;
int c[t];
if(s==t) b[t]=a[s];
else {
m=(s+t)/2;
msort(a,c,s,m);
msort(a,c,m+1,t);
merge(c,b,s,m,t);
}
}
int main(){
int n=0;
cin>>n;
for(int i=1;i<n+1;i++){
cin>>a[i];
}
msort(a,a,1,n);
for(int j=1;j<n+1;j++){
cout<<a[j]<<" ";
}
cout<<endl;
return 0;
}