学习笔记:二叉树Binary Tree by mycodeschool

是一组实体的集合(A collection of entities),这些实体被称为节点(Nodes),它们通过链接来模拟层次结构。

根结点/Root:没有父结点的结点。

子结点/Children

父结点/Parent

兄弟结点/Sibling:有相同父结点的结点。

叶结点/Leaf:没有子结点的结点。

若能通过单向链接从结点A走到结点B,则称:

A是B的祖先(A is ancestor of B)。

B是A的子孙(B is descendent of A)。

具有N个结点的树有N-1个边。

深度/depth:从根结点到结点X的路径长度,路径上的每条边都会为长度贡献一个单位。因此,我们也可以说深度是从根结点到结点X路径上的边数

高度/height:结点X到叶结点的最长路径上的边数。

树的高度:根结点的高度。

二叉树

概念

二叉树(Binary tree)是一种特定性质的树,它的每个结点最多只能有两个子结点。

满二叉树(Strict/Proper binary tree):每个结点只能有0或2个子结点。

完全二叉树(Complete binary tree):除最后一层外所有层必须填满。最后一层可以不填满,但结点必须尽量靠左排列。

第 i 层能拥有结点最大数量为2^i。

完美二叉树(Perfect binary tree):所有层级都被填满的二叉树。

完美二叉树拥有高度为 h 的二叉树中结点的最大数量,具体为2 ^ 层数 - 1

反过来,可求出拥有n个结点的完美二叉树的高度为\log_{2}(n+1)-1

平衡二叉树(Balanced binary tree):如果二叉树的每个结点的左右子树高度差都不超过k,则称这样的树是平衡二叉树。通常情况下,k取1。

空树(Empty tree):没有结点的树。空树的高度为-1。

二叉树的两种实现方式:

1.动态创建结点

2.使用数组

二叉搜索树

二叉搜索树(Binary search tree)是一种特殊的二叉树,对于二叉搜索树中每一个结点,左子树的所有结点的值都更小或相等,右子树的所有结点的值都更大。

使用BST存储数据,在搜索到每一个结点时,我们可以通过判断当前值和目标值的大小关系,决定下一步进入哪个子树。和二分查找类似,这样大大提高了搜索效率。

要保持搜索的高效,需要尽可能保持二叉搜索树是平衡的。因为极端条件下,如果每个结点都只有一个子结点,二叉树退化成类似链表的构造,搜索效率就退化成O(n)了。

C++实现二叉搜索树的搜索与插入

#include <iostream>
using namespace std;

struct BstNode{
    int data;
    BstNode* left;
    BstNode* right;
};

BstNode* GetNewNode(int data)
{
    BstNode* newNode = new BstNode;
    newNode -> data=data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

BstNode* Insert(BstNode* root,int data)
{
    if(root == NULL)
    {
        root = GetNewNode(data);
    }else if(data<=root->data)
    {
        root->left = Insert(root->left,data);
    }else{
        root->right = Insert(root->right,data);
    }
    return root;
}

bool Search(BstNode* root,int data)
{
    if(root == NULL){
        return false;
    }
    else if(data==root->data)
    {
        return true;
    }else if(data<root->data)
    {
        return Search(root->left,data);
    }else{
        return Search(root->right,data);
    }
}

int main()
{
    BstNode* root = NULL;//to store address of root node
    root = Insert(root,15);
    root = Insert(root,10);
    root = Insert(root,20);
    root = Insert(root,25);
    root = Insert(root,8);
    root = Insert(root,12);
    int num;
    cout<<"Enter num to be searched"<<endl;
    cin>>num;
    if(Search(root,num)){
        cout<<"found"<<endl;
    }else{
        cout<<"not found"<<endl;
    }
}

 找到二叉搜索树中的最大值和最小值

不断让临时结点向左或向右走就行。

用迭代实现:

int FindMin(BstNode* root){
    if(root==NULL){
        cout<<"tree is empty";
        return -1;
    }
    while(root->left!=NULL){
        root=root->left;
    }
    return root->data;
}

int FindMax(BstNode* root){
    if(root==NULL){
        cout<<"tree is empty";
        return -1;
    }
    while(root->right!=NULL){
        root=root->right;
    }
    return root->data;
}

用递归实现:

int FindMin(BstNode* root){
    if(root==NULL){
        cout<<"tree is empty";
        return -1;
    }else if(root->left==NULL){
        return root->data;
    }
    return FindMin(root->left);
}

int FindMax(BstNode* root){
    if(root==NULL){
        cout<<"tree is empty";
        return -1;
    }else if(root->right==NULL){
        return root->data;
    }
    return FindMax(root->right);
}

得到二叉树的高度 

二叉树的高度即左子树和右子树高度中的较大值,可用递归求解。

int FindHeight(Node* root){
    if(root==NULL){
        return -1;//叶子结点高度为0
    }
    int leftHeight = FindHeight(root->left);
    int rightHeight = FindHeight(root->right);
    return max(leftHeight,rightHeight)+1;
}

二叉树的遍历

和线性表不同,在二叉树中,当我们指向某个结点时,可能会有多个可能的遍历方向。此外,在探索了某个方向后,我们还需要返回以探索不同方向。

树的遍历(Tree traversal):按照某个顺序访问树的某个结点且仅访问一次的过程。

"访问"意味着读取或处理结点中的顺序,在下面的示例中指打印。

遍历通常有两种方法:

广度优先遍历(Breadth-first traversal):先访问同一层级或深度的所有结点,再访问下一层的结点。

深度优先遍历(Depth-first traversal):访问一个结点,意味着要完整遍历路径上的所有子树。

深度优先遍历可分为三种:

前序遍历/DLR(Data Left Right):先访问根结点,再访问左子树,最后访问右子树。

中序遍历/LDR(Left Data Right):先访问左子树,再访问根结点,最后访问右子树。

后序遍历/LRD(Left Right Data):先访问左子树,再访问右子树,最后访问根结点。

层序遍历(Level-order traversal):

层序遍历意味着按照层级顺序访问结点,属于广度优先遍历的一种。

很显然,仅靠一个指针我们无法做到从左到右遍历同层级的结点,因为它们之间没有任何链接。

要实现层序遍历,我们需要使用队列。我们将已知的结点存入队列中,在访问它的时候把它的子节点加入队列,再将其本身移除。

这样做,我们不会丢失子结点的地址;而且由于队列先进先出的特性,可以保证我们先发现的结点会先被访问。

void LevelOrder(Node* root){
   if(root==NULL){
    return;
   }
   queue<Node*>Q;
   Q.push(root);
   while(!Q.empty()){
    Node* current = Q.front();
    cout<<current->data<<endl;
    if(current->left!=NULL){
        Q.push(current->left);
    }
    if(current->right!=NULL){
        Q.push(current->right);
    }
    Q.pop();
   }
}

时间复杂度:每个结点只会访问一遍,所以复杂度为O(n)。

空间复杂度:取决于最多进入队列的结点数目。最坏情况为O(n),此时为完美二叉树;最好情况为O(1),此时每层只有一个结点。

前序遍历(Preorder traversal):

即DLR,先访问根结点,然后左子树,然后右子树。

void Preorder(Node*root){
    if(root==NULL)return;
    cout<<root->data<<endl;
    Preorder(root->left);
    Preorder(root->right);
}

检查二叉树是否是二叉搜索树

思路:给出一个范围,判断每个根结点是否处在这个范围中,并不断更新这个范围。初始时为负无穷到正无穷,因为树的根结点取值没有要求。

bool IsBstUtil(Node* root,int minValue,int maxValue){
    if(root==NULL){
        return true;
    }
    if(root->data<minValue&&root->data>maxValue
        &&IsBstUtil(root->left,minValue,root->data)
        &&IsBstUtil(root->right,root->data,maxValue)){
            return true;
        }else{
            return false;
        }
}
bool IsBinarySearchTree(Node* root){
    return IsBstUtil(root,INT_MAX,INT_MIN);
}

搜索二叉树结点的删除

要删除一个叶结点,首先,我们从其父结点中移除对它的引用,之后回收内存即可。

但对于有子结点的结点,直接断开其和父结点的链接会导致整棵树断掉。

对于只有一个子结点的结点,我们可以将其父结点与其的链接转移到子结点上,直接跳过它,之后回收内存即可。

对于有两个子结点的结点,我们可以用右子树的最小值替代该结点的值。由于树中最小的结点肯定没有左子结点,因为如果有则意味着存在更小的值,我们如此做就将问题简化为第二种情况。之后,我们按照第二种情况的做法,将原本存在的最小值结点删去即可。

Node* Delete(Node *root, int data) {
	if(root == NULL) return root; 
	else if(data < root->data) root->left = Delete(root->left,data);
	else if (data > root->data) root->right = Delete(root->right,data);
	else {
		// Case 1:  No child
		if(root->left == NULL && root->right == NULL) { 
			delete root;
			root = NULL;
		}
		//Case 2: One child 
		else if(root->left == NULL) {
			Node *temp = root;
			root = root->right;
			delete temp;
		}
		else if(root->right == NULL) {
			Node *temp = root;
			root = root->left;
			delete temp;
		}
		// case 3: 2 children
		else { 
			Node *temp = FindMin(root->right);
			root->data = temp->data;
			root->right = Delete(root->right,temp->data);
		}
	}
	return root;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值