树
树是n(n大于等于0)个节点的有限集,且这些节点满足以下关系:
- 有且仅有一个结点没有父节点,该结点称为树的根;
- 除根外,其余每个节点有且仅有一个父结点;
- 树的每个节点都构成以它为根的树。
树的表示,由于每个节点的儿子数可以变化很大而且事先不知道,所以一般树的表示方法是儿子-兄弟表示法。具体的结构体如下:
struct TreeNode{
Type element;
struct TreeNode* firstChild; //指向最左边的孩子
struct TreeNode* nexrSibling; //指向右边的兄弟结点
};
二叉树
二叉树在满足树的条件的同时,满足以下条件:每个节点最多有两个子树,这两个子树有左右之分,次序不可颠倒。
二叉树的表示:二叉树可以用以下的结构体保存,用链表方式存储:
struct TreeNode{
Type element;
struct TreeNode* left;
struct TreeNode* right;
};
也可以用数组存放,按从上至下、从左到右顺序存储n个结点的二叉树的结点父子关系。若起始结点的编号为1,则编号为i的结点的左孩子在2i,右孩子在2i+1。
二叉树的深度遍历
二叉树的深度遍历分为前序、中序和后序。这里的前中后指的是访问根节点的时机,如果一开始就访问根节点,称为前序;在中间访问称为中序;最后访问称为后序。具体的代码如下。
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x),left(NULL),right(NULL){}
};
void depthTraversal(TreeNode *node){
if(node == NULL){
return;
}
printf("%d ",node->val); //前序
depthTraversal(node->left);
// 此时访问node为中序
depthTraversal(node->right);
// 此时访问node为后序
}
二叉树的层次遍历
二叉树的层次遍历,就和图的广度优先搜索基本上是一个框架,所以这里直接给出代码。
void breadthTraversal(TreeNode *root){
if(root == NULL){
return;
}
queue<TreeNode *> q;
q.push(root);
while(!q.empty()){
TreeNode *node = q.front();
q.pop();
printf("%d ",node->val);
if(node->left != NULL){
q.push(node->left);
}
if(node->right != NULL){
q.push(node->right);
}
}
}
二叉树的序列化和反序列化
个人认为二叉树的序列化还是很有用的,主要的用途是在刷题的时候,遇到二叉树的题目时,能够迅速生成一颗二叉树进行测试。虽然像Letcode这种平台可以查看其测试的代码,但自己写的总还是要熟悉一点。
这里提供的方式是层次遍历的方式,因为这种方式比较直观,代码这里参考的是左程云《程序员代码面试指南》里面的代码,只不过使用的语言改为了C++。下面看具体的代码。
string serialize(TreeNode *root){
if(root == NULL){
return "# ";
}
queue<TreeNode *> q;
q.push(root);
string res = to_string(root->val) + " ";
while(!q.empty()){
TreeNode *node = q.front();
q.pop();
if(node->left != NULL){
res += to_string(node->left->val) + " ";
q.push(node->left);
}else{
res += "# ";
}
if(node->right != NULL){
res += to_string(node->right->val) + " ";
q.push(node->right);
}else{
res += "# ";
}
}
return res;
}
TreeNode *getTreeNode(string s){
if(s == "#"){
return NULL;
}
return new TreeNode(stod(s));
}
TreeNode *deserialize(string data){
istringstream iss(data);
string s;
vector<string> v;
while(iss>>s){
v.push_back(s);
}
int index = 0;
TreeNode *root = getTreeNode(v[index++]);
if(root == NULL){
return root;
}
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
TreeNode *node = q.front();
q.pop();
node->left = getTreeNode(v[index++]);
if(node->left != NULL){
q.push(node->left);
}
node->right = getTreeNode(v[index++]);
if(node->right != NULL){
q.push(node->right);
}
}
return root;
}
二叉查找树
二叉查找树(Binary Search Tree), 它是一颗具有下列性质的二叉树:
- 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
- 左、右子树也分别为二叉排序树。
- 等于的情况只能出现在左子树或右子树中的某一侧。
根据《算法导论》里面的伪代码,自己实现了一下。首先,是定义结构。
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include<algorithm>
using namespace std;
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
~TreeNode(){
if(left != NULL){
delete left;
}
if(right != NULL){
delete right;
}
}
};
void inorder(TreeNode *root){
if(root == NULL){
return;
}
inorder(root->left);
cout<<root->val<<" ";
inorder(root->right);
}
这里有一个辅助函数,inorder,主要是中序遍历该二叉树,判断是否已经是排好序的。
查询
搜索二叉树的查询和二分查找差不多,基本思想是,如果当前结点不为空且不等于查找值,如果查找值小于当前结点,就去左边查找(根据二叉查找树的性质,右边不可能存在要查找的值),否则,就去右边查找。下面是具体的代码。
TreeNode *treeFind(TreeNode *root, int val){
while(root != NULL && root->val != val){
if(val < root->val){
root = root->left;
}else{
root = root->right;
}
}
return root;
}
插入
插入的情况其实和查找差不多,就是根据插入值的大小,在左子树和右子树之间跳转,直到跳转到空结点。这里需要注意的是,在跳转的过程中,需要记录一个父节点。
TreeNode* treeInsert(TreeNode *root, int val){
TreeNode *insertNode = new TreeNode(val);
TreeNode *parent = NULL; // 当前节点的父节点
TreeNode *current = root; //当前节点
while(current != NULL){
parent = current;
if(val < current->val){
current = current->left;
}else{
current = current->right;
}
}
if(parent == NULL){ // 树为空的情况
root = insertNode;
}else if(val < parent->val){
parent->left = insertNode;
}else{
parent->right = insertNode;
}
return root;
}
删除
删除分为三种情况:
- 删除结点左子树为空,使用右子树替代。
- 删除结点右子树为空,使用左子树替代。
- 左右结点的不为空,使用右子树的最小值结点替代该结点。注意,替代的结点可能有右孩子,需要使用右孩子替代替代结点的位置。
TreeNode* treeDelete(TreeNode *root, int val){
if(root == NULL){
return root;
}
TreeNode *current = root;
TreeNode *parent = NULL;
while(current != NULL && current->val != val){
parent = current;
if(val < current->val){
current = current->left;
}else{
current = current->right;
}
}
if(current == NULL){
return root; // 没有对应的结点
}
if(current->left == NULL){ //只有右子树
treeRepalce(current,parent, current->right,root);
}else if(current->right == NULL){ //只有左子树
treeRepalce(current,parent, current->left,root);
}else{
/* 先找到右子树的最小值,也就是中序的下一个节点 */
TreeNode *node = current->right;
TreeNode *nodeParent = current;
while(node->left != NULL){
nodeParent = node;
node = node->left;
}
if(nodeParent != current){ /* 如果找到的不是查找结点的右子树 */
treeRepalce(node,nodeParent,node->right,root); //该子树的父亲可能有右子树,需要调整
node->right = current->right;
}
treeRepalce(current,parent,node,root);
node->left = current->left;
}
return root;
}
测试
写了一个代码,大致测了一下,感觉没有大的问题。
int main(int argc, char const *argv[])
{
srand(time(0));
vector<int> v;
for(int i=0;i<10;i++){
v.push_back(rand() % 100);
cout<<v[i]<<" ";
}
cout<<endl;
TreeNode *root = NULL;
for(int i=0;i<10;i++){
root = treeInsert(root,v[i]);
}
inorder(root);
cout<<endl<<endl;
TreeNode *node;
for(int i=0;i<10;i++){
int val = rand() % 100;
node = treeFind(root,val);
if(node != NULL){
cout<<"find "<<val<<" success"<<endl;
}else{
cout<<"find "<<val<<" failed"<<endl;
}
}
cout<<endl;
random_shuffle(v.begin(),v.end());
for(int i=0;i<10;i++){
cout<<v[i]<<" ";
}
cout<<endl;
for(int i=0;i<10;i++){
root = treeDelete(root,v[i]);
inorder(root);
cout<<endl;
}
cout<<endl;
delete root;
return 0;
}
以前是用C语言写的,并且使用的是递归的方法,感觉不是很好,于是又重新写了一次。
网友评论