【数据结构】实验十二:图 & 查找

实验十二  图+查找

一、实验目的与要求

1)掌握拓扑排序的应用;

2)掌握查找的概念和算法;

3)掌握查找的基本原理以及各种算法的实现;

4)掌握查找的应用。

二、实验内容

1.  用邻接表建立一个有向图的存储结构。利用拓扑排序算法输出该图的拓扑排序序列。

2. 0n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0n-1之内。在范围0n-1内的n个数字中有且只有一个数字不在该数组中,请使用折半查找算法找出这个数字。

示例 1:

输入: [0,1,3]

输出: 2

示例 2:

输入: [0,1,2,3,4,5,6,7,9]

输出: 8

三、实验结果

1)请将调试通过的主要源代码、输出结果粘贴在下面(必要的注释、Times New Roman 5号,行间距1.5倍)

2)简述算法步骤(选画技术路线图),格式如下:

S1:

S2:

3)请分析算法的时间复杂度。

4)请将源代码(必要的注释)cpp文件压缩上传


题目1

1.1)实验源代码

#include <iostream>
using namespace std;
//拓扑排序 
#define MVNum 100
typedef char VerTexType;

//图的邻接表存储表示
typedef struct ArcNode{
    int adjvex;  
    struct ArcNode *nextarc;	
}ArcNode; 

typedef struct VNode{ 
    VerTexType data;
    ArcNode *firstarc;//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];

typedef struct{ 
    AdjList vertices;//邻接表 
	AdjList converse_vertices;//逆邻接表
    int vexnum, arcnum;//图的当前顶点数和边数 
}ALGraph;

//定义顺序栈
typedef struct{
	int *base;
	int *top;
	int stacksize;
}spStack;

int indegree[MVNum];//存放个顶点的入度
spStack S;

//初始化栈
void InitStack(spStack &S){
	S.base = new int[MVNum];
	if(!S.base)
		exit(1);
	S.top = S.base;
	S.stacksize = MVNum;
}

//进栈 
void Push(spStack &S , int i){
	if(S.top - S.base == S.stacksize){
		return;
	}
	*S.top++ = i;
}

//出栈 
void Pop(spStack &S , int &i){
	if(S.top == S.base){
		return;
	}
	i = *--S.top;
}

//判断空栈 
bool StackEmpty(spStack S){
	if(S.top == S.base){
		return true;
	}
	return false;
}

//确定点v在G中的位置
int LocateVex(ALGraph G , VerTexType v){
	for(int i = 0; i < G.vexnum; ++i){
		if(G.vertices[i].data == v){
			return i;
		}
	}
	return -1;
}

//创建有向图G的邻接表、逆邻接表
int CreateUDG(ALGraph &G){
	int i , k;
	cout <<"请输入总顶点数,总边数:";
	cin >> G.vexnum >> G.arcnum;
    cout << endl;
	cout << "输入点的名称:" << endl;
	for(i = 0; i < G.vexnum; ++i){
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vertices[i].data;
		G.converse_vertices[i].data = G.vertices[i].data;
		G.vertices[i].firstarc=NULL;//初始化表头结点的指针域为NULL 
		G.converse_vertices[i].firstarc=NULL;
    }
	cout << endl;
	cout << "输入边依附的顶点:" << endl;
	for(k = 0; k < G.arcnum;++k){
		VerTexType v1 , v2;
		int i , j;
		cout << "请输入第" << (k + 1) << "条边依附的顶点:";
		cin >> v1 >> v2;
		i = LocateVex(G, v1);  
		j = LocateVex(G, v2);//确定v1和v2在G中位置,即顶点在G.vertices中的序号 
		ArcNode *p1=new ArcNode;//生成一个新的边结点*p1 
		p1->adjvex=j;//邻接点序号为j
		p1->nextarc = G.vertices[i].firstarc;
		G.vertices[i].firstarc=p1;//将新结点*p1插入顶点vi的边表头部
		ArcNode *p2=new ArcNode;
		p2->adjvex=i;
		p2->nextarc = G.converse_vertices[j].firstarc;
		G.converse_vertices[j].firstarc=p2;
    }
    return 1; 
}

//求入度
void FindInDegree(ALGraph G){
	int i , count;
	for(i = 0 ; i < G.vexnum ; i++){
		count = 0;
		ArcNode *p = G.converse_vertices[i].firstarc;
		if(p){
			while(p){
				p = p->nextarc;
				count++;
			}
		}
		indegree[i] = count;
	}
}

//有向图G采用邻接表存储结构,若G无回路,则生成G的一个拓扑序列topo[]并,否则ERROR 
int TopologicalSort(ALGraph G , int topo[]){
	int i , m;
    FindInDegree(G);
    InitStack(S);
    for(i = 0; i < G.vexnum; ++i){
    	if(!indegree[i]){
    		Push(S, i); //入度为0者进栈
		}
	}
	m = 0;//对输出顶点计数,初始为0 
	while(!StackEmpty(S)){
		Pop(S, i);//将栈顶顶点vi出栈
		topo[m]=i;//将vi保存在拓扑序列数组topo中 
		++m;//对输出顶点计数 
		ArcNode *p = G.vertices[i].firstarc;//p指向vi的第一个邻接点 
		while(p){
			int k = p->adjvex;//vk为vi的邻接点   
			--indegree[k];//vi的每个邻接点的入度减1 
			if(indegree[k] ==0){
				Push(S, k); //若入度减为0,则入栈
			}  
			p = p->nextarc;//p指向顶点vi下一个邻接结点 
		}
	}
	if(m < G.vexnum){
		return 0;
	}
	return 1;
}

int main(){
	ALGraph G;
	CreateUDG(G);
	int *topo = new int [G.vexnum];
	cout << endl;
	cout << "有向图的邻接表、逆邻接表创建完成" << endl<< endl;
	if(TopologicalSort(G , topo)){
		cout << "该有向图的拓扑有序序列为:";
		for(int j = 0 ; j < G.vexnum; j++){
			if(j != G.vexnum - 1){
				cout << G.vertices[topo[j]].data << " , ";
			}
			else{
				cout << G.vertices[topo[j]].data << endl << endl;
			}
		}
	}
	else{
		cout << "网中存在环,无法进行拓扑排序!" <<endl;
	}
	return 0;
}

1.2)测试用例及其输出结果

测试用例1

 

程序运行结果1

 

测试用例2

程序运行结果2

 

(2)简述算法步骤

S1:构建邻接表结构。

 

S2:构建栈,并定义初始化栈、进栈、出栈、判断空栈这四个函数体。初始化栈时,创建一个新的数组存放数据,并令顶指针为初始位置,栈空间大小为最大数。进栈时,先判断是否栈满,如果栈已满则直接return,如果栈未满则顶指针存入数据后后置++。出栈时,先判断是否栈空,如果栈空则直接return,如果栈非空则顶指针下移一位后弹出存放的数据。判断空栈时,如果顶指针和底指针相等,则当前栈为空栈,否则当前栈非空。

S3:构建“确定节点v在邻接表中的位置”函数。主要通过for循环遍历G,如果当前节点数据与目标节点v匹配,则返回当前节点的位置。如果循环结束后依然未匹配,则返回-1数值,表示节点v不存在于有向图G中。

S4:构建“创建有向图G的邻接表和逆邻接表”函数。首先需要输入总顶点数和总边数、各节点的名称、各边依附的节点信息。通过输入各节点的名称时生成邻接表和逆邻接表,并将指向第一条依附该节点的边的指针置空。接着通过输入各边依附的节点信息时,确定两个节点在有向图G中的位置,并生成两个个临时新边界点p1和p2,令p1、p2的邻接点序号分别为该边第二个节点的位置和第一个节点的位置,然后令p1、p2的下一条边为另外一个节点的第一条边指针。

S4:构建“求入度”函数。通过遍历节点,设置临时边指针p为逆邻接表中当前遍历节点的第一条边,当p非空的时候,p指向其下一条边,计数变量加一,然后输出当前节点的入度数。

S5:构建“邻接表存储结构”函数。先求出有向图G中各节点的入度,然后初始化一个栈,通过for循环遍历有向图G,并将入度为0的节点进行进栈操作。当存入的栈非空时,将顶指针指向的节点进行出栈操作,并保存在拓扑序列数组中,计数输出节点个数。接着,再临时新建一个p指针表示当前节点的第一条边指针。当p非空时,vk是vi的邻接点,vi的每个邻接点入度减去1。如果vk的入度为0,则将vk入栈,并令p指向vi的下一个邻接节点。最后通过if条件语句判断是否有回路,如果有回路则返回0,否则返回1。

S6:构造主函数。主要通过调用上述函数,得出拓扑序列。

(3)分析算法的时间复杂度

拓扑排序算法的主要思想为:

第一步,栈S初始化,累加器count初始化。

第二步,扫描顶点表,将没有前驱(即入度为0)的顶点压栈。

第三步,当栈S非空时循环,(1)vj=退出栈顶元素;输出vj;累加器加1;(2)将顶点vj的各个邻接点的入度减1;(3)将新的入度为0的顶点入栈。

第四步,if (count<vertexNum) 输出有回路信息。

如果AOV网络有n个顶点,e条边,在拓扑排序的过程中,搜索入度为零的顶点所需的时间是O(n)。在正常情况下,每个顶点进一次栈,出一次栈,所需时间O(n)。每个顶点入度减1的运算共执行了e次。所以总的时间复杂为O(n+e)。

 


题目2

1.1)实验源代码

#include <cstdio>
#include <iostream>
using namespace std;

void half(int *a,int n){
	int start=0,end=n-1;
    while(start<=end){
        int mid = start+((end-start)>>1);   //折半查找 
        //a[mid]!=mid时,向前判断,因为肯定是之前的不等引发当前a[mid]!=mid
        if(a[mid] != mid){
            if(mid==0 || a[mid-1]==mid-1){
                cout<<endl<<"缺失的数字是:"<<mid<<endl;//确定mid并输出 
            }
            end = mid-1;
        }
		else{
            start = mid+1;
        }
    }
}

int main(){
	int n,i,a[10000];
	cout<<"请输入你需要输入的数字数目:";
	cin>>n;
	cout<<endl<<"请输入一个递增序列:";
	for(i=0;i<n;i++){
		cin>>a[i];
	}
	half(a,n);
	return 0;
}

(1.2)测试用例及其输出结果

测试用例1:

n=3

array=【0,1,3】

程序运行结果1:

测试用例2:

n=9

array=【0,1,2,3,4,5,6,7,9】

程序运行结果2:

(2)简述算法步骤

S1:构造“折半查找”函数。首先设置下标值start、end和mid,mid即为缺失的数字的下标,之后利用while循环进行查找循环。每次折半查找都要更新min的值,由于该序列是隔1递增的,因此查找的终止条件为a[mid]!=mid。

S2:构造主函数。输入需要查找的数字和已知的递增序列,再通过调用half函数输出运行结果。

 

(3)分析算法的时间复杂度

折半查找判定树的构造:如果当前low和high之前有奇数个元素,则mid分隔后,左右两部分元素个数相等。如果当前low和high之间有偶数个元素,则mid分隔后,左半部分比右半部分少一个元素。

在折半查找的判定树中,若mid=(low+high)/2,向下取整,则对于任何一个结点,因此必有:右子树结点-左子树结点数=0或1。当有奇数个元素时等于0,有偶数个元素时等于1。所以折半查找判定数一定是平衡二叉树,树的深度为[log2n]+1。查找成功时所进行的关键码比较次数至多为[log2n]+1,查找不成功时和给定值进行比较的次数最多不超过树的深度。

综上所述,折半查找的时间复杂度为O(log2n)。

 


补充:请使用顺序查找算法进行时间复杂度对比分析。

【选作:】使用索引顺序表查找算法进行时间复杂度、空间复杂度的对比分析。

三、实验结果

顺序查找算法:

(1)源代码

//顺序查找算法

#include <cstdio>

#include <iostream>

using namespace std;



int main(){

       int n,a[1000],i;

       cout<<"请输入n:";

       cin>>n;

       cout<<"请输入排序好的数组:";

       for(i=0;i<n;i++){

              scanf("%d",&a[i]);

       }

       int j=0,loss=0,flag=0;

       while(j<n){

              if(a[j]!=j){

                     loss=j;

                     flag++;

                     break;

              }

              j++;

       }

       if(!flag){

              loss=n;

       }

       cout<<"缺失值为:"<<loss;

       return 0;

}

(2)测试用例及其运行结果

(3)时间复杂度分析

最好情况:一次比较就找到所要查找的元素,时间复杂度为O(1)。最差情况:比较n次(共有n个元素),时间复杂度为O(n)。平均情况:综合两种情况,顺序查找的时间复杂度为O(n)。

索引顺序表查找算法:

(1)源代码

#include <stdio.h>
#define BLOCK_NUMBER 3
#define BLOCK_LENGTH 6

typedef struct IndexNode
{
	int data;	//数据
	int link;	//指针
}IndexNode;

//索引表
IndexNode indextable[BLOCK_NUMBER];

//块间查找
int IndexSequlSearch(int s[], int l, IndexNode it[], int key){
	int i = 0;
	while (key > it[i].data && i < BLOCK_NUMBER){
		i++;
	}
	if (i > BLOCK_NUMBER){
		return -1;
	}
	else{//在块间顺序查找
		int j = it[i].link - 1;
		while (key != s[j] && j < l) {
			j++;
		}
		if (key == s[j]){
			return j + 1;
		}
		else{
			return -1;
		}
	}
}

//建立索引表
void IndexTable(int s[], int l, IndexNode indextable[]){
	int j = 0;
	for (int i = 0; i < BLOCK_NUMBER; i++){
		indextable[i].data = s[j];
		indextable[i].link = j;
		for (j; j < indextable[i].link + BLOCK_LENGTH && j < l; j++){
			if (s[j] > indextable[i].data){
				indextable[i].data = s[j];
			}
		}
	}
	for (int i = 0; i < BLOCK_NUMBER; i++){
		indextable[i].link++;
	}
}

//打印查找的数据元素以及其在线索表中的位置
void print(int searchNumber, int index){
	if (index == -1){
		printf("查找元素:%d\n", searchNumber);
		printf("查找失败!\n");
		return;
	}
	else{
		printf("查找元素:%d\n", searchNumber);
		printf("元素位置:%d\n", index);
		return;
	}
}

int main(){
	int s[18] = {22,48,60,58,74,49,86,53,13,8,9,20,33,42,44,38,12,24};
	IndexTable(s, 18, indextable);
	printf("线索表:");
	for (int i = 0; i < 18; i++){
		printf("%d ",s[i]);
	}
	printf("\n");
	int indexNumber1 = 38;
	int index1 = IndexSequlSearch(s, 18, indextable, indexNumber1);
	print(indexNumber1, index1);
	int indexNumber2 = 28;
	int index2 = IndexSequlSearch(s, 18, indextable, indexNumber2);
	print(indexNumber2, index2);
	return 0;
}

(2)测试用例及其运行结果

(3)时间、空间复杂度分析

算法思想:

首先,将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即后一块中任一元素都必须大于前一块中最大的元素;然后,块之间进行二分或者顺序查找,块内进行顺序查找。

时间复杂度:

整体算法是对顺序查找的改进,介于顺序查找和二分查找之间,时间复杂度为O(log2m+n/m)。

空间复杂度:

一般情况下,为进行分块查找,可以将长度为n的表均匀分成b块,每块还有s个元素,即b = [n/s]。索引表采用顺序查找的方式,则查找成功时的平均查找长度为:(s+b+2)/2。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MorleyOlsen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值