乱七八糟的东西

姓名: 楼逸杨
赛道: 基础组
类型: 树与图

【树与图的一点点笔记】

关键词: 树、图、树与图的例题、进制、信息编码

结点的度:结点拥有的子树个数或者分支的个数。如A结点有三棵子树、所以A结点的度为3
树的度:树中各结点度的最大值。如图中结点度最大为3(A、D结点),所以树的度为3。
树的深度(或高度):树中结点的最大层次。
树的宽度:整棵树中某一层中最多的结点数称为树的宽度。
树

根结点:一棵树至少有一个结点,这个结点就是根结点
叶子结点:又叫做终端结点,指度为0的结点。
非终端结点:又叫分支结点,指度不为0的结点,都是非终端结点。除了根结点之外的非终端结点,也叫做内部结点。

父结点/双亲结点:称上端结点为下端结点的父结点。
孩子结点:称下端结点为上端结点的子结点。
兄弟结点:称同一个父结点的多个子结点为兄弟结点。
祖先节点:又称从根节点到某个子结点所经过的所有结点称为这个结点的祖先。
子孙结点:称以某个结点为根的子树的任一结点都是该节点的子孙

邻接表:

可以使用动态数组vector来实现,动态分配空间,随着元素的不断插入,它会按照自身的一套机制不断扩充自身的容量。

定义格式:vector<int> q;
常用函数:push_back() 在vector最后添加一个元素
          empty() 判断vector是否为空(返回return时为空)
          size() 返回vector数组元素个数

接下来就是我们威武的蔡老师的智慧结晶

智慧结晶

有用就点个赞吧!!! :star_struck: :star_struck: :star_struck:

树的宽度

void dfs(int x,int deep){
	mm[deep]++;
	max_deep=max(max_deep,mm[deep]);
	for(int i=1;i<=n;i++){
		if(a[x][i]=='1'&&vis[i]==0){
			vis[i]=1;
			dfs(i,deep+1);
		}
	}
}

树的深度

void dfs(int x,int deep){
	max_deep=max(max_deep,deep);
	for(int i=1;i<=n;i++){
		if(a[x][i]=='1'&&vis[i]==0){
			vis[i]=1;
			dfs(i,deep+1);
		}
	}
}

例题1

给出每一个节点的左右孩子,输出每一个节点的父亲节点,-1表示空

这题很简单,因为只有1是没有父亲结点的,其他都有
所以我们可以先初始化

int node[25]={0,-1};

再输入

for(int i=1;i<=n;i++){
		int a,b;
		cin>>a>>b;
		if(a!=-1) node[a]=i;
		if(b!=-1) node[b]=i;
	} 

最后输出就好了

例题2

给定一棵以 1 为根的二叉树,输出他的先序遍历,如果没有儿子为 -1

这题我们要用到函数
不断地遍历

void dfs(int k) {
	if(k==-1) {
		return;
	}
	cout<<k<<" ";
	dfs(l[k]);
	dfs(r[k]);
}

还要将数组内所有元素都变为-1

memset(l,-1,205);
memset(r,-1,205);

最后输入输出就可以了

例题3

现给你一棵树的所有边,请你判断若干点是否存在父子关系。此树有 n 个结点,编号 1 至 n,其中 1 号是根。

这题只要定义一个零接矩阵
用来存储就可以了

for(int i=1;i<n;i++){
		int a1,a2;
		cin>>a1>>a2;
		a[a1][a2]=1;
	}

例题4

已知一棵树,有 N 个结点,编号 1 至 N,其中 1 号是根。求树的所有叶子节点数。

这题只要判断一行中如果只有一个有关联的点就计数

for(int i=1;i<=n;i++){
		int sum=0;
		for(int j=1;j<=n;j++){	
			if(a[i][j]=='1') sum++; 
		}
		if(sum==1&&i!=1) {
			mm[ans]=i;
			ans++;
			cnt++;
		}
	}

二叉树的衍生之FBI树

这题我们要用dfs函数不断地遍历

void dfs(int l,int r){
	if(l==r){//串s不再大于1
		if(s[l]=='0') cout<<'B';//全0串
		if(s[l]=='1') cout<<'I';//全1串
		return ;
	}
	int mid=(l+r)/2;//中间下标
	dfs(l,mid);//递归前半部分
	dfs(mid+1,r);//递归后半部分
	int f1=0,f2=0;//进行判断
	for(int i=l;i<=r;i++){
		if(s[i]=='0') f1=1;//有0的情况
		if(s[i]=='1') f2=1;//有一的情况
	}
	if(f1==1&&f2==0) cout<<'B';//全1串
	else if(f1==0&&f2==1) cout<<'I';//全0串
	else cout<<'F';//有1有0的串
}

图的知识点
图是区别于线性结构(只有一个前驱和一个后驱)和树结构(一个父结点和多个子节点)的数据结构,它是一种多对多的关系。
图是由顶点集和边集组成的,通常表示为:G(V,E),其中G表示图,V表示顶点的集合,E表示边的集合。
由于我无法描述下面的东西
所以用一张图片来描述

总结

顶点的度为为以该顶点为一个端点的边的数目。
对于无向图,顶点的边数为度,度数之和是顶点边数的两倍。如图1所示:顶点V5的度为三。
对于有向图,入度是以顶点为终点,出度相反。有向图的全部顶点入度之和等于出度之和且等于边数。如图2所示:顶点V5的入度为3,出度为1。 \color{red}{(注意:入度与出度是针对有向图来说的。)}

无向完全图

在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有 \color{red}{n(n-1)/2} 条边。

有向完全图

如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有 \color{red}{n(n-1)} 条边。

子图

假设有两个图G=(V,{E})和G=(V‘,{E’}),如果V‘是V的子集,且E’是E的子集,则称G‘为G的子图。如图2、图3、图4、均为图1的子图

图的存储
由于本人水平过于低下只好用我们
我们威武的蔡老师的智慧结晶

智慧结晶

连通图

在无向图G中如果从顶点V到顶点V’有路径,则称V和V‘是联通的。如果对于图中 \color{red}{任意两个顶点vi、vj ∈G,vi和vj都是联通的} ,则称G是联通图

连通分量

无向图中的 \color{red}{极大连通子图} 称为 \color{red}{连通分量} ,指包含了图中尽可能多的顶点以及尽可能多的边,以至于它再加上一个点或者边之后它就不连通了
连通图的极大连通子图就是他本身。
非连通图中有多个连通分量也就是可以有多个极大连通图。

强连通图&强连通分量

在有向图G中,如果对于每一对vi、vj,vi vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。
有向图中的极大强连通子图称作有向图的强连通分量。
强连通图

例题1

给出一个有 n 个结点,m 条边的无向图,从 1 到 n 按顺序输出各个结点的度。

这题我们就要用到邻接矩阵来表示顶点之间的关系
首先输入

cin>>n>>m;
for(int i=1;i<=m;i++){
	int a1,a2;
	cin>>a1>>a2;
	a[a1][a2]=1;
	a[a2][a1]=1;
}

再进行遍历

for(int i=1;i<=n;i++){
		int cnt=0;
		for(int j=1;j<=n;j++){
			if(a[i][j]==1) cnt++;
		}
		cout<<cnt<<'\n';
	}

然后就达成了题目的要求

例题2

给出一个无向图,输出每个点相邻的那些点。

这题我们用动态数组vector就能很好的表示
先定义

vector<vector<int> >g;

再输入与初始化

g.resize(n+1);
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		if(x!=y){
			g[x].push_back(y);
			g[y].push_back(x);
		}
	}

再判断与输出

for(int i=1;i<=n;i++){
		sort(g[i].begin(),g[i].end());
		for(int j=0;j<g[i].size();j++){
			if(j!=g[i].size()-1&&g[i][j]==g[i][j+1]) continue;
			cout<<g[i][j]<<" ";
		}
		cout<<'\n';
	}

例题3

给出一个无向图,和一棵树,求至少要加多少边才能将这棵树变成给定的图,如果无法变成,则输出 impossible。

这题我们只要不停的进行对比就行了,但有可能爆内存,所以可以用short

cin>>n>>m;
	for(int i=1;i<=n-1;i++){
		int x,y;
		cin>>x>>y;
		a[x][y]=1,a[y][x]=1;
	}
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		b[x][y]=1,b[y][x]=1;
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]==0&&b[i][j]==1){
				cnt++;
				a[i][j]=1;
				a[j][i]=1;
			}
			if(b[i][j]==0&&a[i][j]==1){
				cout<<"impossible";
				return 0;
			}
		} 
	}
	cout<<cnt<<'\n';

例题4

图的dfs遍历
这题与树的dfs遍历有一点相似
所以就不讲了
dfs部分

void dfs(int x){
	cout<<x<<' ';//输出顺序
	for(int i=0;i<g[x].size();i++){
		if(vis[g[x][i]]==1) continue;//访问数组为1,就跳过
		vis[g[x][i]]=1;//不然就递归
		dfs(g[x][i]);
	}
}

例题5

题目描述:
给出 N 个点,M 条边的有向图,对于每个点 v,求 ()A(v) 表示从点 v 出发,能到达的编号最大的点。

这题要用bfs搜索加判断

void bfs(){
	queue<int> w;
	w.push(s);
	
	while(w.size()) {
		int h=w.front();
		if(sum<h){//判断大小
			sum=h;
		}
		w.pop();
		for(int i=0; i<g[h].size(); i++) {
			if(vis[g[h][i]]) {
			    continue;
			}
			vis[g[h][i]]=1;
			w.push(g[h][i]);
		}
	}
}

搜索

for(int i=1;i<=n;i++){
		sum=-1;
		s=i;
		memset(vis,0,sizeof(vis));
		vis[s]=1;
		bfs();
		cout<<sum<<' ';
	}

例题6

给出一个无向图,求图的连通分量的个数。保证无重边和自环。

这题与图的dfs遍历与连通块问题都有相似,就是他们的子问题

先进行遍历

void dfs(int x){
	for(int i=0;i<g[x].size();i++){
		if(vis[g[x][i]]==1) continue;
		vis[g[x][i]]=1;
		dfs(g[x][i]);
	}
}

再进行计数

for(int i=1;i<=n;i++){
		if(vis[i]==0){
			cnt++;//计数器++
			vis[i]=1;//访问数组为1
			dfs(i);//遍历
		}
	}
	cout<<cnt;//输出

计算机常用进制:十进制、二进制、八进制、十六进制

一、进制

进制的表达:
1.
二进制 B(binary)
八进制 O(octal)
十进制 D(decimal)
十六进制 H(hexadecimal)

(数值)_{(进制)}

(123)_{(8)}
2.编程语言表达方式
二进制 0b10或0B10
八进制 025
十进制 36
十六进制 0x1A或0X1A
进制转换
先讲位权
例如:十进制整数部分第一位的位权为1,第2位为10,第3位为100,第4位为1000……
所以R进制数的第X位的位权是X的R-1次方
按权展开法:
12345D=
110^(5-1)+210^(4-1)+310^(3-1)+410^(2-1)+510^(1-1)=
1
10000+21000+3100+410+51=
12345
以此类推
134.56O=
18^2+38^1+48^0+58^(-1)+6*8^(-2)=
64+24+4+0.625+0.09375=
92.71875
短除法
比如 123D转二进制
123%2=1
61%2=1
30%2=0
15%2=1
7%2=1
3%2=1
1%2=1
所以123D=1111011B
比大小
(35.565)O ?(1A.2465)H
0b11110.11 ? 01386.43
第一题

  • 大于
  • 小于
  • 等于
  • 布吉岛
0 投票人

第二题

  • 大于
  • 小于
  • 等于
  • 布吉岛
0 投票人

答案 大于 、小于
下面的代码完成了 将一个M进制的数N转换成十进制表示的式子

#include <bits/stdc++.h>
using namespace std;
int main(){
	char m[1005];
	int n,a[1005];
	cin>>n>>m;
	int len1=strlen(m);
	for(int i=0;i<len1;i++){
		if(m[i]>='A'&&m[i]<='F'){
			a[i]=m[i]-'A'+10; 
		}
		else a[i]=m[i]-'0';
	}
	int cnt=len1-1;
	for(int i=0;i<len1;i++){
		if(a[i]!=0) {
			if(i!=0) cout<<'+'<<a[i]<<'*'<<n<<'^'<<cnt;
			else cout<<a[i]<<'*'<<n<<'^'<<cnt;
		}
		cnt--;
	}
    return 0;
}

展示所用代码禁止复制,如有发现,必将追究

编码

位(bit) 最小单位
字节 (Byte,B)基本单位,每8位组成一个字节
字 2字节

单位转换

1B=8bit
1KB=2^10B=1024B
1MB=2^10KB=1024km
1GB=2^10MB=1024MB
1TB=2^10GB=1024GB

32位编译器

char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节

64位编译器

char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节

1、机器数

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用机器数的最高位存放符号,正数为0,负数为1。

比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是0000 0011。如果是 -3 ,就是 100 00011 。

那么,这里的 0000 0011 和 1000 0011 就是机器数。

2、机器数的真值

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。

例如上面的有符号数 1000 0011,其最高位1代表负,其真正数值是 -3,而不是形式值131(1000 0011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1

二. 原码, 反码, 补码的基础概念和计算方法
在探求为何机器要使用补码之前,让我们先了解原码、反码和补码的概念。对于一个数,计算机要使用一定的编码方式进行存储,原码、反码、补码是机器存储一个具体数字的编码方式。
三、
原码:除符号以外,前面十几就是几;符号位是0就是正数,1就是负数。
反码:正数部分和原码保持一致;负数部分,符号位置1,其他数位相反。
补码:正数部分保持相同;负数部分,在反码基础上加1;
位运算


常见运用

  1. 按位与(&)

逻辑:对两个数的每一位进行比较,只有当两个对应的位都为1时,结果位才为1,否则为0。

用法示例:检查一个数是否同时被另外两个数整除(基于位向量的共同属性)。

int a = 12; // 二进制为 1100  
int b = 15; // 二进制为 1111  
int c = 3;  // 二进制为 0011  
  
// 检查a是否同时被b和c整除(即a的二进制表示中,1的位必须在b和c的对应位上也是1)  
if ((a & b) == a && (a & c) == a) {  
    printf("a is divisible by both b and c\n");  
}
  1. 按位或(|)

逻辑:对两个数的每一位进行比较,只要有一个对应的位为1,结果位就为1。

用法示例:合并两个数的特定位,常用于设置标志位。

int flags = 0; // 初始化为0,无标志位设置  
#define FLAG1 1 // 定义标志位1  
#define FLAG2 2 // 定义标志位2  
  
// 设置标志位1  
flags |= FLAG1;  
  
// 同时设置标志位1和标志位2  
flags |= FLAG2;  
  
// 此时flags的二进制表示为 0011,表示两个标志位都被设置
  1. 按位异或(^)

逻辑:对两个数的每一位进行比较,如果两位相同,则结果位为0;如果不同,则结果位为1。

用法示例:无需临时变量交换两个数的值,或用于简单的加密解密操作。

int x = 5; // 二进制为 0101  
int y = 9; // 二进制为 1001  
  
// 交换x和y的值  
x = x ^ y; // x变为1100 (即12)  
y = x ^ y; // y变回0101 (即5),因为x现在是12,y是原来的9  
x = x ^ y; // x变回1001 (即9),恢复y的值
  1. 按位取反(~)

逻辑:对数的每一位进行取反操作,即0变1,1变0。

注意:对于有符号整数,取反后的结果可能不是直观的“反码”,因为还涉及到符号位的扩展。

用法示例:获取一个数的反码(通常用于无符号整数或特定场景下的有符号整数操作)。

unsigned int a = 5; // 无符号整数,二进制为 0000 0101  
unsigned int b = ~a; // 结果为 1111 1010,即无符号整数中的反码
  1. 左移(<<)

逻辑:将数的二进制表示向左移动指定的位数,右边超出的部分被丢弃,左边新增的位用0填充。

用法示例:快速乘以2的幂次方。

int a = 5; // 二进制为 0101  
int b = a << 2; // 结果为 10100,即20,相当于a乘以2的2次方
  1. 右移(>>)

逻辑:将数的二进制表示向右移动指定的位数,右边超出的部分被丢弃。对于有符号整数,通常使用算术右移(左边用符号位填充);对于无符号整数,左边用0填充。

用法示例:快速除以2的幂次方,并保留整数部分。

int a = -8; // 二进制(补码表示)为 1111 1000(假设为32位)  
int b = a >> 2; // 对于有符号整数,使用算术右移,结果为 1111 1110(即-2),相当于a除以2的2次方取整  
  
// 对于无符号整数  
unsigned int c = 16; // 二进制为 0001 0000  
unsigned int d = c >> 2; // 使用逻辑右移,结果为 0000 0100,即4

1.判断整型变量奇偶

使用位运算符:&
原理

2的0次是1,2的1次是2。根据按权展开可知,只需判断最后一位即可。1的二进制是000…001。根据&运算符的特点,奇数末位是1,和1与为1,偶数则为0。
代码

//判断int类型变量奇偶
bool Judge(int n)
{
	return(n&1);//奇数返回1,偶数返回0
}

2.交换变量

使用位运算符:^
原理

两个数相同,则返回0,否则,返回1。a=a^b;b=(a^b)^b=a;a=(a^b)^a=b;
代码

//交换变量 利用异或
void Change()
{
	int a, b;
	printf("请输入两个整数:\n");
	scanf("%d %d",&a,&b);
	printf("%d %d交换后为:", a, b);
	a ^= b;
	b ^= a;
	a ^= b;
	printf("%d %d\n", a, b);
}

3.乘/除2的n次

使用位运算符:<<和>> 乘用的比较多
原理

移位。不溢出的情况下,左移补0,就是乘2了。除是右移,类似,但是可能损失精度。
代码

//num乘2的n次
int Multi(int num, int n)
{
	return num << n;
}
//num除2的n次
int Devide(int num, int n)
{
	return num >> n;
}

4.对2的次幂取余

使用位运算符:&
原理

例如,15对8取余,15的二进制是00…01111 最多就是余7喽。8-1就是7,二进制为00…111,和00…0111相与就是余数7。
代码

//得到余数
int Yu(int num,int n)
{
	int i = 1 << n;
	return num&(i-1);
}

5.得到整型变量二进制形式某位置的值
原理

向右移位与1进行“&”操作,可以用来得到二进制。

//取十进制整型变量a的二进制形式的第k位
int GetWei(int n, int k)
{
	int m;
	m = n >> k & 1;
	return m;
}

ASCII码


格雷码
在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环码或反射码。在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用 8421 8421 8421码,则数 0111 0111 0111变到 1000 1000 1000时四位均要变化,而在实际电路中, 4 4 4位的变化不可能绝对同时发生,则计数中可能出现短暂的其它代码( 1100 1100 1100、 1111 1111 1111等)。在特定情况下可能导致电路状态错误或输入错误。使用格雷码可以避免这种错误。格雷码有多种编码形式。
图片

头文件C语言

#include <assert.h>    //设定插入点
#include <ctype.h>     //字符处理
#include <errno.h>     //定义错误码
#include <float.h>     //浮点数处理
#include <iso646.h>        //对应各种运算符的宏
#include <limits.h>    //定义各种数据类型最值的常量
#include <locale.h>    //定义本地化C函数
#include <math.h>     //定义数学函数
#include <setjmp.h>        //异常处理支持
#include <signal.h>        //信号机制支持
#include <stdarg.h>        //不定参数列表支持
#include <stddef.h>        //常用常量
#include <stdio.h>     //定义输入/输出函数
#include <stdlib.h>    //定义杂项函数及内存分配函数
#include <string.h>    //字符串处理
#include <time.h>     //定义关于时间的函数
#include <wchar.h>     //宽字符处理及输入/输出
#include <wctype.h>    //宽字符分类

头文件c++

#include <algorithm>    //STL 通用算法
#include <bitset>     //STL 位集容器
#include <cctype>         //字符处理
#include <cerrno>      //定义错误码
#include <cfloat>     //浮点数处理
#include <ciso646>         //对应各种运算符的宏
#include <climits>     //定义各种数据类型最值的常量
#include <clocale>     //定义本地化函数
#include <cmath>      //定义数学函数
#include <complex>     //复数类
#include <csignal>         //信号机制支持
#include <csetjmp>         //异常处理支持
#include <cstdarg>         //不定参数列表支持
#include <cstddef>         //常用常量
#include <cstdio>      //定义输入/输出函数
#include <cstdlib>     //定义杂项函数及内存分配函数
#include <cstring>     //字符串处理
#include <ctime>      //定义关于时间的函数
#include <cwchar>      //宽字符处理及输入/输出
#include <cwctype>     //宽字符分类
#include <deque>      //STL 双端队列容器
#include <exception>    //异常处理类
#include <fstream>     //文件输入/输出
#include <functional>   //STL 定义运算函数(代替运算符)
#include <limits>      //定义各种数据类型最值常量
#include <list>      //STL 线性列表容器
#include <locale>         //本地化特定信息
#include <map>       //STL 映射容器
#include <memory>         //STL通过分配器进行的内存分配
#include<new>            //动态内存分配
#include <numeric>         //STL常用的数字操作
#include <iomanip>     //参数化输入/输出
#include <ios>       //基本输入/输出支持
#include <iosfwd>     //输入/输出系统使用的前置声明
#include <iostream>    //数据流输入/输出
#include <istream>     //基本输入流
#include <iterator>        //STL迭代器
#include <ostream>     //基本输出流
#include <queue>      //STL 队列容器
#include <set>       //STL 集合容器
#include <sstream>     //基于字符串的流
#include <stack>      //STL 堆栈容器
#include <stdexcept>    //标准异常类
#include <streambuf>    //底层输入/输出支持
#include <string>     //字符串类
#include <typeinfo>        //运行期间类型信息
#include <utility>     //STL 通用模板类
#include <valarray>        //对包含值的数组的操作
#include <vector>     //STL 动态数组容器

C++函数

6 个赞

根结点:一棵树至少有一个结点,这个结点就是根结点

只要是无向无环的连通图都属于树吧,所以无根树里面根是不固定的(李姐错误紫衫

3 个赞