C language build and check

1. The storage method of the tree

Before knowing and checking, we have to know three storage methods of the tree:

1. Parent representation

Parent representation : Parent representation is the simplest storage method, which uses a one-dimensional array of size n to represent n nodes in the tree. In the array, each element stores the subscript of the parent node of the node, and the subscript of the parent node of the root node is -1. Since each node has only one parent node, the parent node of a node can be quickly found, but the child node of a node needs to traverse the entire array, which is inefficient.

Define its storage structure in C language:

#define MAX_TREE_SIZE 100

typedef struct {
    
    
    int data;        // 节点数据
    int parent;      // 父节点在数组中的下标
} PTNode;

typedef struct {
    
    
    PTNode nodes[MAX_TREE_SIZE];    // 存储节点的数组
    int root;                       // 根节点在数组中的下标
    int size;                       // 树中节点的个数
} PTree;

In the above code, the PTNode structure represents a node in the tree, including the data of the node and the subscript of its parent node in the array. The PTree structure represents the entire tree, including an array of storage nodes, the subscript of the root node in the array, and the number of nodes in the tree. The value of MAX_TREE_SIZE can be adjusted as needed to suit the specific situation.

This experiment and check set is expressed in this way

2. Child Representation

The child representation adopts a chain storage structure, and each node contains a pointer to its first child node and a pointer to its next sibling node. If a node has no children, its child pointer is null. The advantage of the child notation is that it is convenient to find the children of a node, but finding the parent of a node requires traversing the entire tree.

We are most familiar with this method. We all use this method for the previous binary tree and Huffman tree related operations.

Implement its storage structure in C language:

#define MAX_TREE_SIZE 100

typedef struct CTNode {
    
    
    int child;                  // 子节点在数组中的下标
    struct CTNode* next;        // 指向下一个兄弟节点的指针
} *ChildPtr;

3. Child brother representation (binary tree representation)

The child-sibling notation also uses a chained storage structure, where each node contains a pointer to its first child node and a pointer to its next sibling node. If a node has no child nodes, its child node pointer is empty; if a node has no sibling nodes, its sibling node pointer is empty. The child-sibling notation is convenient for traversing all nodes of the tree, but finding a node's parent requires traversing the entire tree.

The following is an example code for defining a child-sibling notation storage structure in C language:

#define MAX_TREE_SIZE 100

typedef struct CSNode {
    
    
    int data;                   // 节点数据
    struct CSNode* firstchild;  // 指向第一个子节点的指针
    struct CSNode* nextsibling; // 指向下一个兄弟节点的指针
} CSNode, *CSTree;

In the above code, the CSNode structure represents each node in the tree, including the node's data, a pointer to the first child node, and a pointer to the next sibling node. The CSTree type represents the entire tree, which is actually a pointer to the root node. The value of MAX_TREE_SIZE can be adjusted as needed to suit the specific situation. Different from the previous two storage methods, the child-brother representation does not require an additional array or linked list to store nodes, but directly uses the pointer relationship between nodes to represent the tree structure.

2. Check and collect

1. Basic knowledge

Union search set is a tree-type data structure, which is used to deal with the merging and query problems of some disjoint sets (Disjoint Sets). It supports two operations: find and merge.

In the union search set, each element has a parent node. If the parent node of an element is itself, it means that the element is a representative element of a set, which is called the "ancestor" of the set. By looking up the ancestors of the element, you can get the collection that the element belongs to. At the same time, if the ancestors of two elements are different, the two collections they belong to can be merged.

Common application scenarios of union search include image processing, network connectivity issues, path compression and other fields.

There are many ways to implement union search, among which the most common ones are the "merge by rank" and "path compression" algorithms based on arrays. Merging by rank means that when merging two sets, the set with fewer elements is merged into the set with more elements, and the rank of the new set is updated; path compression means that when searching for the ancestors of elements, all nodes passed on the path are merged are directly connected to the ancestor nodes to reduce the complexity of subsequent lookups.

2. Description

This is a new test site for the 408 postgraduate entrance examination after 2022. It is not difficult, but remember to master it.

This is the picture of the requirements of the test center for the Wangdao postgraduate entrance examination:

9rtI.jpg

3. Implementation of the core part

1. Define the structure:

To define a union search structure, you first need to define a union search structure, including the number of elements, rank array and parent node array:

#include <stdio.h>
#include <stdlib.h>
#define size 10
int parents[size];       //集合元素数组(双亲指针数组)
int high[size];          //记录每个集合的树高

/*这里解释一下,因为采用的是双亲表示法,所以我们用的是数组存储,数组的下标就是结点的编号
 这里为了举例,我们假设结点最大为10*/

2. Initialization

When initializing and searching, point the parent node of each element to itself:

//并查集的初始化(S即是并查集)
void Initial(int S[]){
    
    
	for(int i=0;i<size;i++){
    
            //这里初始每个元素自己就是一个集合
		S[i]=-1;
		high[i]=1;                 //初始树高设为1
	}
}

3. "Check" function

Find the ancestor node of the element When looking for the ancestor node of the element, it is necessary to search upwards along the parent node until the root node is found.

//定义“查”函数
int find1(int S[],int x){
    
                 //因为后面要写优化的find算法,这里叫find1
	while(S[x]>0)                    //循环寻找x所在集合的根
		x=S[x];
	return x;                        //根的S[]小于0
}

4. "And" function

Merging two collections When merging two collections, you can judge which collection's root node should be the root node of the new collection according to the size of the rank array. At the same time, when the root node of the new collection is hung under the root node of the old collection, the rank array needs to be updated:

//定义“并”函数
void union1(int S[],int root1,int root2){
    
    
	if(root1==root2)               //要求root1和root2是不同的集合
		return;
	S[root2]=root1;                //将根root2连接到另一根root1下面
}
/*这里root1和root2的编号,你可以举个例子,比如这里root1=2,root2=3,分别代表下标为2和3的集合,
  把root2的双亲定为root1,就相当于把root2连接到另一根root1下面*/

4.Optimize operation

1. Optimization principle

The optimization of union search is mainly realized by two methods of path compression and rank merging.

Path compression refers to directly connecting all nodes encountered on the search path to the root node while searching for the root node, so as to reduce the time required for the next search. It can be done recursively or iteratively. Specifically, when searching for the root node, it can search recursively or circularly upward along the path, and directly connect all nodes on the search path to the root node. Doing so can make subsequent lookup operations more efficient, because each node will be directly connected to the root node without revisiting other nodes on the path.

Merging by rank refers to merging two sets according to their ranks (that is, the number of nodes), so as to ensure that the smaller set is connected to the larger set, further reducing the depth of path compression. Specifically, when two sets need to be merged, their ranks are first compared, and then the set with the smaller rank is joined to the set with the larger rank. Doing so can ensure that smaller collections are merged into larger collections, thereby reducing the depth of path compression and improving the query performance of union lookups.

The above two optimization methods correspond to the optimized "check" and optimized "union" operations respectively.

2. Optimize code

//定义“查”的优化算法
int find(int x) {
    
    
	if (x != parents[x]) {
    
    
		parents[x] = find(parents[x]);                    //把x元素的双亲直接修改为根结点
	}
	return parents[x];
}
/*如果不理解可以举个例子,比如2合并到7下面,然后把3合并到2下面,这样就相当于把3合并到7的集合里,但是查早3的根结点是先找到2再找到7
  如果我们在找到3的时候把3从2下面直接合并到根结点7下面,这样以后查找的时候,我们可以节省时间复杂度*/

//定义“并”的优化算法
bool union2(int x,int y){
    
    
	x = find1(parents,x);						//寻找 x的集合根
	y = find1(parents,y);						//寻找 y的集合根
	if(x == y)                                  //如果两个在同一个集合里,我们就不合并
		return false;
	else								//否则
	{
    
    
		if(high[x]==high[y]) high[y]++;	//如果 x的高度和 y的高度相同,则令 y的高度加1
		parents[x]=y;						//让 x的双亲,也就是把x合并到y集合里
	}
	return true;
}

Five. C language complete test code

#include <stdio.h>
#include <stdlib.h>
#define size 10
int parents[size];       //集合元素数组(双亲指针数组)
int high[size];          //记录每个集合的树高

/*这里解释一下,因为采用的是双亲表示法,所以我们用的是数组存储,数组的下标就是结点的编号
 这里为了举例,我们假设结点最大为10*/

//并查集的初始化(S即是并查集)
void Initial(int S[]){
    
    
	for(int i=0;i<size;i++){
    
            //这里初始每个元素自己就是一个集合
		S[i]=-1;
		high[i]=1;                 //初始树高设为1
	}
}

//定义“查”函数
int find1(int S[],int x){
    
                 //因为后面要写优化的find算法,这里叫find1
	while(S[x]>0)                    //循环寻找x所在集合的根
		x=S[x];
	return x;                        //根的S[]小于0
}

//定义“并”函数
void union1(int S[],int root1,int root2){
    
    
	if(root1==root2)               //要求root1和root2是不同的集合
		return;
	S[root2]=root1;                //将根root2连接到另一根root1下面
}
/*这里root1和root2的编号,你可以举个例子,比如这里root1=2,root2=3,分别代表下标为2和3的集合,
  把root2的双亲定为root1,就相当于把root2连接到另一根root1下面*/

//定义“查”的优化算法
int find(int x) {
    
    
	if (x != parents[x]) {
    
    
		parents[x] = find(parents[x]);                    //把x元素的双亲直接修改为根结点
	}
	return parents[x];
}
/*如果不理解可以举个例子,比如2合并到7下面,然后把3合并到2下面,这样就相当于把3合并到7的集合里,但是查早3的根结点是先找到2再找到7
  如果我们在找到3的时候把3从2下面直接合并到根结点7下面,这样以后查找的时候,我们可以节省时间复杂度*/

//定义“并”的优化算法
bool union2(int x,int y){
    
    
	x = find1(parents,x);						//寻找 x的集合根
	y = find1(parents,y);						//寻找 y的集合根
	if(x == y)                                  //如果两个在同一个集合里,我们就不合并
		return false;
	else								//否则
	{
    
    
		if(high[x]==high[y]) high[y]++;	//如果 x的高度和 y的高度相同,则令 y的高度加1
		parents[x]=y;						//让 x的双亲,也就是把x合并到y集合里
	}
	return true;
}

int main(){
    
    
	Initial(parents);                //初始化集合
	int x1=find1(parents,2);         //这里检验一下find1操作,初始化后找下标为2的集合,返回的是自己
	printf("合并操作前,含下标为2的集合此时根为:%d\n",x1);
	union1(parents,7,2);             //这里是把下标为2的集合合并到根为7的集合中
	int x2=find1(parents,2);         //这里再查找2那么输出应该和上面不同
	printf("合并操作后,含下标为2的集合此时根为:%d\n",x2);
	printf("-------------------------------------------\n");
	//到这里,我们的并查集基础操作已经完成了
	Initial(parents);              //为了测试优化的并操作,这里重新初始化一下
	int x3=find1(parents,2);         //这里检验一下find1操作,初始化后找下标为2的集合,返回的是自己
	printf("优化合并操作前,含下标为2的集合此时根为:%d\n",x3);
	union2(2,7);             //这里是把下标为2的集合合并到根为7的集合中
	int x4=find1(parents,2);         //这里再查找2那么输出应该和上面不同
	printf("优化合并操作后,含下标为2的集合此时根为:%d\n",x4);
	printf("-------------------------------------------\n");
}


6. Running results

9MRB.jpg

Guess you like

Origin blog.csdn.net/weixin_51496226/article/details/131314468