Learning "Introduction to Algorithms" (15)----Binary Search Tree (C Language)


foreword

This article mainly explains the binary search tree, and implements all its basic operation functions in C language, and gives the C language code


1. Binary search tree

1. What is a binary search tree?

A binary search tree is a binary tree, which is a linked list storage structure and satisfies specific data properties (see below)

As the name implies, the binary search tree has good search performance and has many applications.
Its operation functions are:

1.中序遍历(输出有序数据序列)
2.返回最大最小值
3.搜索值
4.返回某个结点的左右顺序统计量
5.插入,删除结点

2. What are the advantages of a binary search tree?

(1) application

1.作为字典存储数据,提供插入,删除,搜索功能
2.输出排好序的序列
3.作为优先队列
4.等等

(2) Time performance

In general, the time cost of all its operation functions is:
T ( n ) = O ( h ) = O ( lgn ) where h is the expected tree height, h = O ( lgn ) T(n)=O(h )=O(lgn) \\Where h is the expected tree height, h=O(lgn)T(n)=O(h)=O ( l g n )where h is the expected tree height ,h=O ( l g n )

3. Data characteristics of binary search tree

Let x be a node in a binary search tree. If y is a node in the left subtree of x, then y . key ⩽ x . key y.key\leqslant x.keyy.keyx . key . _ If y is a node in the right subtree of x, theny . key ⩾ x . key y.key\geqslant x.keyy.keyx.key

It essentially maintains a local order. This partial order is somewhat similar to a heap.

(1) The difference between the data nature of the binary search tree and the heap

1.二叉搜索树是将所有小于等于的结点放到左子树,所有大于等于的结点放到右子树
2.堆是在维护一个局部最值,即双亲结点永远要大于(最小堆是小于)孩子结点

For details, please refer to the article:
"Introduction to Algorithms" Learning (7) - Heap Sort and Priority Queue (C Language)

2. Operation function of binary search tree (C language)

1. Data structure

The data structure consists of three parts:

1.存储key值
2.指向左孩子结点的指针
3.指向有孩子结点的指针
4.指向父亲结点的指针
//定义二叉搜索树的结构
typedef struct STree
{
    
    
	int key;//结点的值 
	STree *p;//指向双亲结点 
	STree *left;//指向左孩子结点 
	STree *right;//指向有孩子结点 
}ST; 

2. Inorder traversal of a binary search tree

Rules for inorder traversal:
1. From a horizontal perspective:First traverse the left subtree, then traverse the node, and then traverse the right subtree
2. From a vertical perspective:First the lower right leaf node, then up to the root node, and then to the sitting leaf node

The binary search tree is traversed in order, and the result is an ordered sequence from small to large

(1) Inorder traversal

//中序遍历二叉搜索树
void inorder_tree_walk(ST *x)
{
    
    
	//由于二叉搜索树的特点
	//使用中序遍历的结果是从大到小的有序排列 
	if(x!=NULL)
	{
    
    
		//先遍历左子树 
		inorder_tree_walk(x->left);
		//遍历本结点 
		printf("%5d",x->key);
		//遍历右子树 
		inorder_tree_walk(x->right);
	}
	else
	{
    
    
		return;
	}
}

(2) Print the traversal results

//打印二叉搜索树
void print_tree(ST *x)
{
    
    
	inorder_tree_walk(x);
	printf("\n");	
} 

3. Find data

When the node is found, its address information is returned.
Return NULL if not found

(1) Find data function

//在二叉搜索树中查找数据
ST *tree_search(ST *x,int k)
{
    
    
	//循环结束条件就是
	//1.结点指针为空
	//2.结点指针指向k结点 
	while(x!=NULL&&x->key!=k)
	{
    
    
		//如果k小于本结点,那么向左子树搜索 
		if(k<x->key)
		{
    
    
			x=x->left;
		}
		//如果k大于等于本结点,那么向右子树搜索
		else
		{
    
    
			x=x->right;
		}
	}
	return x;
}

(2) Print search information

//打印查找信息
void print_search(ST *x)
{
    
    
	if(x==NULL)
	{
    
    
		printf("there is no data in tree\n");
	}
	else
	{
    
    
		printf("data is %5d,the address is 0x%x\n",x->key,x);
	}
}

4. Get the maximum value

Return the address of the maximum value
According to the nature of the binary search tree, the maximum value is the rightmost and bottom leaf node

//得到最大值
ST *tree_max(ST *x)
{
    
    
	//最右最下的那个叶子结点就是最大值 
	while(x->right!=NULL)
	{
    
    
		x=x->right;
	}
	return x;
} 

5. Get the minimum value

Returns the address of the minimum value
According to the nature of the binary search tree, the minimum value is the leftmost and bottom leaf node

//得到最小值
ST *tree_min(ST *x)
{
    
    
	//最左最下的那个叶子结点就是最小值 
	while(x->left!=NULL)
	{
    
    
		x=x->left;
	}
	return x;
} 

6. Find the successor node

Definition of successor node:
The successor node is the smallest of all tree nodes larger than this node

//得到指定结点x的后继结点
//后继结点就是所有大于该结点树节点中最小的那个
ST *tree_successor(ST *x)
{
    
    
	//如果该结点有右子树,那么后继就是右子树的最小值 
	if(x->right!=NULL)
	{
    
    
		return tree_min(x->right);
	}
	ST *y=x->p;
	//如果没有右子树,那么后继结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的左子树中的结点
	//那么后继就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->right)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
}

7. Find the predecessor node

Definition of predecessor node:
The predecessor node is the largest of all tree nodes smaller than this node

//得到指定结点x的前驱结点
//前驱结点就是所有小于该结点树节点中最大的那个
ST *tree_precursor(ST *x)
{
    
    
	//如果该结点有左子树,那么前驱就是左子树的最大值 
	if(x->left!=NULL)
	{
    
    
		return tree_max(x->left);
	}
	ST *y=x->p;
	//如果没有左子树,那么前驱结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的右子树中的结点
	//那么前驱就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->left)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
}

8. Insert elements into the tree

//给树中插入函数
ST *tree_insert(ST *x,int k)
{
    
    
	ST *y=NULL;
	ST *head=x;
	//如果是空结点,那么直接连接 
	if(x==NULL)
	{
    
    
		//动态分配空间 
		y=(ST *)malloc(sizeof(ST));
		y->key=k;
		y->left=NULL;
		y->p=NULL;
		y->right=NULL;
		x=y;
		return x;
	}
	//如果树不为空
	//那么按照搜索树的规则,找到要插入对象的位置
	//该位置是一个叶子结点 
	while(x!=NULL)
	{
    
    
		y=x;
		if(x->key>k)
		{
    
    
			x=x->left;
		}
		else
		{
    
    
			x=x->right;
		}
	}
	//分配空间 
	x=(ST *)malloc(sizeof(ST));
	x->key=k;
	x->p=y;
	x->left=NULL;
	x->right=NULL;
	if(y->key>k)
	{
    
    
		y->left=x;
	}
	else
	{
    
    
		y->right=x;
	}
	return head;
}

9. Replace one subtree with another

This function is prepared for the delete function, which requires a large number of calls
In the delete function, its function is to split the u node from the tree, and replace the position of the u node with the subtree rooted at the v node

//将一颗子树替换为另一颗子树
//u是被替换子树根结点
//v是替换子树根结点
void ctree_change(ST *x,ST *u,ST *v)
{
    
    
	if(x==u)
	{
    
    
		x=v;
	}
	else if(u==u->p->left)
	{
    
    
		u->p->left=v;
	}
	else
	{
    
    
		u->p->right=v;
	}
	if(v!=NULL)
	{
    
    
		v->p=u->p;
	}
}

10. Delete node

//删除一个结点
void tree_delet(ST *x,int k)
{
    
    
	ST *k_p;
	k_p=tree_search(x,k);
	if(k_p==NULL)
	{
    
    
		printf("there is no k in tree\n");
	}
	else
	{
    
    
		//如果没有左子树
		//那么直接用右子树替换该子树 
		if(k_p->left==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->right);
			free(k_p);
		}
		//如果没有右子树
		//那么直接用左子树替换该子树
		else if(k_p->right==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->left);
			free(k_p);
		}
		else
		{
    
    
			ST *temp=NULL;
			//找到后继结点 
			temp=tree_min(k_p->right);
			if(temp->p!=k_p)
			{
    
    
				//将后继结点提取出来 
				ctree_change(x,temp,temp->right);
				//将后继结点与被删除结点的右子树相连 
				temp->right=k_p->right;
				temp->right->p=temp;
			}
			//提取被删除结点 
			ctree_change(x,k_p,temp);
			//将后继结点与被删除结点的左子树连接 
			temp->left=k_p->left;
			temp->left->p=temp;
			free(k_p);
		}
	}
} 

3. C language example of binary search tree

1. Actual execution results

insert image description here

2. C language code

#include<stdio.h>
#include<stdlib.h>
#include<time.h>



//输入的规模 
#define SIZE 10
//随机数的范围是0~LIM-1 
#define LIM 100 



//定义二叉搜索树的结构
typedef struct STree
{
    
    
	int key;//结点的值 
	STree *p;//指向双亲结点 
	STree *left;//指向左孩子结点 
	STree *right;//指向有孩子结点 
}ST; 



//中序遍历二叉搜索树
void inorder_tree_walk(ST *x)
{
    
    
	//由于二叉搜索树的特点
	//使用中序遍历的结果是从大到小的有序排列 
	if(x!=NULL)
	{
    
    
		//先遍历左子树 
		inorder_tree_walk(x->left);
		//遍历本结点 
		printf("%5d",x->key);
		//遍历右子树 
		inorder_tree_walk(x->right);
	}
	else
	{
    
    
		return;
	}
}



//打印二叉搜索树
void print_tree(ST *x)
{
    
    
	inorder_tree_walk(x);
	printf("\n");	
} 



//在二叉搜索树中查找数据
ST *tree_search(ST *x,int k)
{
    
    
	//循环结束条件就是
	//1.结点指针为空
	//2.结点指针指向k结点 
	while(x!=NULL&&x->key!=k)
	{
    
    
		//如果k小于本结点,那么向左子树搜索 
		if(k<x->key)
		{
    
    
			x=x->left;
		}
		//如果k大于等于本结点,那么向右子树搜索
		else
		{
    
    
			x=x->right;
		}
	}
	return x;
}



//打印查找信息
void print_search(ST *x)
{
    
    
	if(x==NULL)
	{
    
    
		printf("there is no data in tree\n");
	}
	else
	{
    
    
		printf("data is %5d,the address is 0x%x\n",x->key,x);
	}
}


 
//得到最大值
ST *tree_max(ST *x)
{
    
    
	//最右最下的那个叶子结点就是最大值 
	while(x->right!=NULL)
	{
    
    
		x=x->right;
	}
	return x;
} 



//得到最小值
ST *tree_min(ST *x)
{
    
    
	//最左最下的那个叶子结点就是最小值 
	while(x->left!=NULL)
	{
    
    
		x=x->left;
	}
	return x;
} 



//得到指定结点x的后继结点
//后继结点就是所有大于该结点树节点中最小的那个
ST *tree_successor(ST *x)
{
    
    
	//如果该结点有右子树,那么后继就是右子树的最小值 
	if(x->right!=NULL)
	{
    
    
		return tree_min(x->right);
	}
	ST *y=x->p;
	//如果没有右子树,那么后继结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的左子树中的结点
	//那么后继就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->right)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
}



//得到指定结点x的前驱结点
//前驱结点就是所有小于该结点树节点中最大的那个
ST *tree_precursor(ST *x)
{
    
    
	//如果该结点有左子树,那么前驱就是左子树的最大值 
	if(x->left!=NULL)
	{
    
    
		return tree_max(x->left);
	}
	ST *y=x->p;
	//如果没有左子树,那么前驱结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的右子树中的结点
	//那么前驱就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->left)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
} 



//给树中插入函数
ST *tree_insert(ST *x,int k)
{
    
    
	ST *y=NULL;
	ST *head=x;
	//如果是空结点,那么直接连接 
	if(x==NULL)
	{
    
    
		//动态分配空间 
		y=(ST *)malloc(sizeof(ST));
		y->key=k;
		y->left=NULL;
		y->p=NULL;
		y->right=NULL;
		x=y;
		return x;
	}
	//如果树不为空
	//那么按照搜索树的规则,找到要插入对象的位置
	//该位置是一个叶子结点 
	while(x!=NULL)
	{
    
    
		y=x;
		if(x->key>k)
		{
    
    
			x=x->left;
		}
		else
		{
    
    
			x=x->right;
		}
	}
	//分配空间 
	x=(ST *)malloc(sizeof(ST));
	x->key=k;
	x->p=y;
	x->left=NULL;
	x->right=NULL;
	if(y->key>k)
	{
    
    
		y->left=x;
	}
	else
	{
    
    
		y->right=x;
	}
	return head;
} 



//将一颗子树替换为另一颗子树
void ctree_change(ST *x,ST *u,ST *v)
{
    
    
	if(x==u)
	{
    
    
		x=v;
	}
	else if(u==u->p->left)
	{
    
    
		u->p->left=v;
	}
	else
	{
    
    
		u->p->right=v;
	}
	if(v!=NULL)
	{
    
    
		v->p=u->p;
	}
} 



//删除一个结点
void tree_delet(ST *x,int k)
{
    
    
	ST *k_p;
	k_p=tree_search(x,k);
	if(k_p==NULL)
	{
    
    
		printf("there is no k in tree\n");
	}
	else
	{
    
    
		//如果没有左子树
		//那么直接用右子树替换该子树 
		if(k_p->left==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->right);
			free(k_p);
		}
		//如果没有右子树
		//那么直接用左子树替换该子树
		else if(k_p->right==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->left);
			free(k_p);
		}
		else
		{
    
    
			ST *temp=NULL;
			//找到后继结点 
			temp=tree_min(k_p->right);
			if(temp->p!=k_p)
			{
    
    
				//将后继结点提取出来 
				ctree_change(x,temp,temp->right);
				//将后继结点与被删除结点的右子树相连 
				temp->right=k_p->right;
				temp->right->p=temp;
			}
			//提取被删除结点 
			ctree_change(x,k_p,temp);
			//将后继结点与被删除结点的左子树连接 
			temp->left=k_p->left;
			temp->left->p=temp;
			free(k_p);
		}
	}
} 



//主测试函数 
int main()
{
    
    
	ST *t=NULL;//搜索二叉树 
	ST *temp;//中间存储结点 
	int a[SIZE];
	int i=0;
	//生成原始随机数据 
	srand((unsigned)time(NULL));
	for(i=0;i<SIZE;i++)
	{
    
    
		a[i]=rand()%LIM;
	}
	//打印原始数据
	for(i=0;i<SIZE;i++)
	{
    
    
		printf("%5d",a[i]);
	}
	printf("\n"); 
	//生成搜索二叉树
	for(i=0;i<SIZE;i++)
	{
    
    
		t=tree_insert(t,a[i]);
	}
	//用中序遍历打印生成的搜索二叉树
	//结果是排好序的,从小到大 
	print_tree(t); 
	//查找值
	//存在的值 
	temp=tree_search(t,a[5]);
	print_search(temp);
	//不存在的值 
	temp=tree_search(t,LIM);
	print_search(temp);  
	//返回最大值,最小值
	//最小值 
	temp=tree_min(t);
	printf("min is %5d\n",temp->key);
	//最大值 
	temp=tree_max(t);
	printf("max is %5d\n",temp->key);
	//返回前驱值和后继
	//前驱值
	temp=tree_precursor(t);
	printf("the precursor of root data %5d is %5d\n",t->key,temp->key);
	//后继值 
	temp=tree_successor(t);
	printf("the successor of root data %5d is %5d\n",t->key,temp->key);
	//删除值
	tree_delet(t,a[5]);
	print_tree(t); 
	return 0;
} 

Summarize

Readers are welcome to correct any inappropriateness in this article

Guess you like

Origin blog.csdn.net/weixin_52042488/article/details/126914477