实验三 高级数据结构(1) 并查集的应用+ Treap树的应用

目的:

(1)熟悉并掌握并查集的应用

(2)熟悉并掌握BST

(3)熟悉并掌握Treap树的建立与应用

实验内容:

1.严重急性呼吸系统综合症 (SARS) 是一种病因不明的非典型肺炎,于 2003 年 3 月中旬被公认为全球威胁。为了尽量减少传染给他人,最好的策略是将嫌疑人与其他人分开。

在不传播疾病大学 (NSYSU) 中,有许多学生团体。同组的学生经常互相交流,一个学生可以加入多个组。为防止SARS的可能传播,南洋大学收集了所有学生团体的成员名单,并在其标准操作程序(SOP)中制定了以下规则。

一旦组中的成员成为嫌疑人,则该组中的所有成员都是嫌疑人。

然而,他们发现,当一名学生被认定为嫌疑人时,要识别所有嫌疑人并不容易。你的工作是编写一个找到所有嫌疑人的程序。

输入

输入文件包含几种情况。每个测试用例以一行中的两个整数 n 和 m 开头,其中 n 是学生数,m 是组数。您可以假设 0 < n <= 30000 和 0 <= m <= 500。每个学生都由一个介于 0 和 n-1 之间的唯一整数编号,最初学生 0 在所有情况下都被识别为嫌疑人。这一行后面是 m 个组的成员列表,每组一行。每行都以一个整数 k 开始,它本身表示组中的成员数。在成员数量之后,有 k 个整数代表该组中的学生。一行中的所有整数至少用一个空格分隔。

n = 0 和 m = 0 的情况表示输入结束,不需要处理。

输出

对于每个案例,在一行中输出嫌疑人的数量。

样本输入

100 4

2 1 2

5 10 13 11 12 14

2 0 1

2 99 2

200 2

1 5

5 1 2 3 4 5

1 0

0 0

样本输出

4

1

1

编程思路

这题用到并查集,下面是并查集的基础知识

图论------并查集(详细版)_哔哩哔哩_bilibili

简单说,并查集就是,把元素合并到一个组。首先让每个元素的祖先是它自己,如果要合并多个元素,就是让多个元素指向共同祖先,那么有共同祖先的元素就是一个组的。所以想判断两个元素是不是一个组的,就看他们是不是共同祖先。

cpp 复制代码
#include <stdio.h>
#define MAXN 20001
int father_of_[MAXN];

void init(int n){
	int i;
	for(i = 0;i < n; i++){
		father_of_[i] = i;
	}
}

int find(int x){
	if(x == father_of_[x]){
		return x;
	}else{
		father_of_[x] = find(father_of_[x]);
		return father_of_[x];
	}
}

void unionn(int i,int j){
	int i_father = find(i);
	int j_father = find(j);
	father_of_[i_father] = j_father;
}

void print_father_arr(int num){
	int i;
	for(i=0;i<num;i++){
		printf("%d ",father_of_[i]);
	}
	printf("\n");
}


int main(){
	int n,m;
	int i,j;
	int k;
	int cnt;
	int tem[1000]; //用于存储输入的临时数据 
	int ans[1000] = {0}; //用于存储答案 
	scanf("%d %d",&n,&m);
	ans[0] = 1; //ans0用于记录答案个数 
	while(!(n == 0 && m == 0)){
		init(n); //初始化并查集 
		for(i=0;i<m;i++){
			scanf("%d",&k); //输入k 
			for(j=0;j<k;j++){
				scanf("%d",&tem[j]); // 输入学生编号 
			}
	
			for(j=1;j<k;j++){
				unionn(tem[0],tem[j]); //合并刚才输入的学生为一组 
			}
		}
		cnt = 1;
		for(i = 1;i<n;i++){
			if(find(0) == find(i)){ //如果学生i和学生0有共同祖先,说明学生i是嫌疑人 
				cnt++; //记录嫌疑人个数 
			}
		}
		ans[ans[0]] = cnt; //将cnt保存 
		ans[0]++;
		
//		printf("ans = %d\n",cnt);
		scanf("%d %d",&n,&m);
	}
	printf("结果为:\n");
	for(i = 1;i<ans[0];i++){ //输出所以答案 
		printf("%d\n",ans[i]);
	}
	
	return 0;
}

运行结果

  1. Treap树的应用

少林寺的第一个和尚是方丈,作为功夫大师,他规定每个加入少林的年轻和尚,要选一个老和尚来一场功夫战斗。每个和尚有一个独立的id和独立的战斗等级grade,新和尚可以选择跟他的战斗等级最接近的老和尚战斗。

方丈的id是1,战斗等级是109。他丢失了战斗记录,不过他记得和尚们加入少林的早晚顺序。请帮他恢复战斗记录。

输入:第一行是一个整数n,0 <n <=100,000,和尚的人数,但不包括大师本人。下面有n行,每行有两个整数k,g,表示一个和尚的id和战斗等级,0<= k ,g<=5,000,000。和尚以升序排序,即按加入少林的时间排序。最后一行用0表示结束。

输出:按时间顺序给出战斗,打印出每场战斗中新和尚和老和尚的id。

样例输入:

3

2 1

3 3

4 2

0

样例输出:

2 1

3 2

4 2

编程思路

这题用到treap,但是c语言是没有提供treap这个结构体的。而要自己写一个treap太麻烦了,所以我用python写。

先下载一下treap的库,然后就能在python里用treap了

pip install treap

暴力做法

如果是暴力做法,根本不用什么treap。直接把和尚的id和等级放进一个list。每次有新和尚进来,就是遍历一次list,找到list里和新和尚等级最接近的老和尚比武就是行了,然后把新和尚加入list。

python 复制代码
import math
lis = [[1,109]]
ans = []
def insert(hs): #加入新和尚
    lis.append(hs)
    p = 0
    for i in range(len(lis) - 1):
        if  math.fabs(lis[i][1] - lis[-1][1]) < math.fabs(lis[p][1] - lis[-1][1]):
            p = i
    ans.append([lis[-1][0],lis[p][0]])
    # print(ans)

insert([2,1])
insert([3,3])
insert([4,2])
insert([5,15])
insert([6,107])

for i in ans:
    print(i)

运行结果

treap树做法

下面是treap树的基础知识

树堆Treap_哔哩哔哩_bilibili

简单说,treap树就是二叉搜索树和堆的结合体,它同时拥有二次搜索树和堆的性质,而且可以防止树退化成一个单链。

那么我们用treap是为了减少我们的搜索次数,提高我们程序的运行效率。我们不想每次来一个新和尚都要把整个寺庙的和尚都搜索一遍,这样效率就很低,我们要减少搜索次数。

对应到这题,我将和尚的id作为treap的key,将和尚的等级作为treap的优先级。这样我们生成的树的结构就是,等级低的和尚在上面,等级高的和尚在下面。

然后我们搜索的时候,就从等级低的向等级高的搜索,直到找到和新和尚等级最相近的老和尚。

举例说明

这里我先插入了5个和尚,构成了一颗treap。现在我要插入第六个和尚,id为6,等级为109。

在插入之前我们就要搜索到和和尚6等级接近的和尚,我们可以看到这个和尚就是和尚1。

那搜索的逻辑是什么呢。

就是先从根节点搜索。记录当前节点,当前节点左儿子和右儿子与新和尚的等级差距。如果当前节点的差距最小就退出搜索。否则左儿子的差距小就搜索左儿子,右儿子的差距小就搜索右儿子。

对应到上面这个例子的搜索顺序就是,2/1 -> 1/109 -> 退出。

搜索完再把新和尚插入到treap

代码

python 复制代码
import treap as tp
import math
# | Data
# descriptors
# defined
# here:
# |
# | key
# |
# | left
# |
# | priority
# |
# | right
# |
# | value

def new_treap_node(key,priority):
    node = tp.treap_node()
    node.key = key
    node.value = key
    node.priority = priority
    return node


def search(tmp_node:tp.treap_node,inser_node:tp.treap_node): #搜索最接近的和尚
    c1 = math.fabs(tmp_node.priority  - inser_node.priority)
    if tmp_node.left == None:
        c2 = 100000
    else:
        c2 = math.fabs(tmp_node.left.priority  - inser_node.priority)
    if tmp_node.right == None:
        c3 = 100000
    else:
        c3 = math.fabs(tmp_node.right.priority  - inser_node.priority)

    if c2 < c1 and c2 < c3:
        return search(tmp_node.left,inser_node)
    elif c3 < c1 and c3 < c2:
        return search(tmp_node.right,inser_node)
    else:
        return tmp_node

ans = []
t = tp.treap()
t.insert(1,1,109)
num = input()
num = int(num)
for i in range(num):
    s = input()
    s = s.split(' ')
    s0 = int(s[0])
    s1 = int(s[1])
    node = new_treap_node(s0,s1) #创建新节点
    result = search(t.root,node) #搜索最接近的和尚
    ans.append([node.key, result.key]) #将答案保存
    t.insert(node.key, node.value, node.priority) #将新和尚插入到treap

for i in ans:
    print(i)


# t.insert(1,1,109)
# t.insert(2,2,1)
# t.insert(3,3,3)
# t.insert(4,4,2)
# t.insert(5,5,15)
# node = new_treap_node(6,109)
#
# # node = tp.treap_node()
# # node.key = 6
# # node.value = 6
# # node.priority = 104
# print(t)
# print(node)
# result = search(t.root,node)
# ans.append([node.key,result.key])
# t.insert(node.key,node.value,node.priority)
# print(t)
# print(ans)

运行结果

相关推荐
xiaoshiguang32 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡2 小时前
【C语言】判断回文
c语言·学习·算法
别NULL2 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇3 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
ZSYP-S4 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos4 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习4 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA4 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo4 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc4 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法