二叉树的使用方法

转载:
http://blog.jobbole.com/108184/
https://blog.csdn.net/zhanggonglalala/article/details/79738213
https://blog.csdn.net/heyanxi0101/article/details/79593542

一、概述

平衡树:所有结点左右子树深度差≤1
排序树:所有结点“左小右大
字典树:由字符串构成的二叉排序树
判定树:分支查找树(例如12个球如何只称3次便分出轻重)
带权树:路径带权值(例如长度)
最优树:是带权路径长度最短的树,又称 Huffman树,用途之一是通信中的压缩编码。

二、二叉树的定义

二叉树通常以结构体的形式定义,如下,结构体内容包括三部分:本节点所存储的值、左孩子节点的指针、右孩子节点的指针。这里需要注意,子节点必须使用指针,就像我们定义结构体链表一样,下一个节点必须使用地址的方式存在在结构体当中。

struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
};

当然,我们也可以为我们的的树节点结构体重新定义一下名字,使用C语言中的typedef方法就可以了。

typedef struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
} BiNode, *BiTree;

三、二叉树的创建

二叉树的操作通常使用递归方法。二叉树的操作可以分为两类,一类是需要改变二叉树的结构的,比如二叉树的创建、节点删除等等,这类操作,传入的二叉树的节点参数为二叉树指针的地址,这种参入传入,便于更改二叉树结构体的指针(即地址)。这里稍微有一点点绕,可能需要多思考一下。

如下是二叉数创建的函数,这里我们规定,节点值必须为大于0的数值,如果不是大于0的数,则表示结束继续往下创建子节点的操作。然后我们使用递归的方法以此创建左子树和右子树。

int CreateTree(struct TreeNode** root) {
 
	int val;
	scanf_s("%d", &val);
	if (val <= 0) {
		*root = NULL;
		return 0;
	}
 
	*root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
	if (!root) {
		printf("创建失败\n");
	}
 
	if (val > 0) {
		(*root)->val = val;
		CreateTree(&((*root)->left));
		CreateTree(&((*root)->right));
	}
	return 0;
}

四、二叉树的遍历

二叉树的遍历有四种方式:前序遍历、中序遍历、后序遍历、层序遍历。

1.1、前序遍历
先访问根节点,再分别前序遍历左、右两棵子树。

//前序遍历
void PreShow(Tree T)
{
	if (!T)
	{
		return;
	}
	
	printf("%c ", T->id);
	PreShow(T->left);
	PreShow(T->right);
}

1.2、中序遍历
先中序遍历左子树,然后访问根节点,最后中序遍历右子树。

//中序遍历
void MidShow(Tree T)
{
	if (!T)
	{
		return;
	}
	
	MidShow(T->left);
	printf("%c ", T->id);
	MidShow(T->right);
}

1.3、后序遍历
先后序遍历左子树,再后序遍历右子树,最后访问根节点。

//后序遍历
void BackShow(Tree T)
{
	if (!T)
	{
		return;
	}
	
	BackShow(T->left);
	BackShow(T->right);
	printf("%c ", T->id);
}

1.4、层序遍历
从根节点开始,逐层访问各子节点。

前、中、后序遍历都采用递归的方式,实际上也就是用到了栈。
而层序遍历则用队列的方式实现,具体的步骤如下:

(1)初始化一个队列,二叉树的根节点进队,即插入队尾。

(2)队首的树节点出队,访问这个树节点。先看它有没有左孩子,如果有,就让左孩子进队。
再看它有没有右孩子,如果有,就让右孩子也进队。

(3)重复过程(2),直到队列清空为止。

//层序遍历
void LevelShow(Tree T)
{
	if (!T)
	{
		return;
	}
 
	Queue m_queue;
	Queue *Q;
	QueueNode *p, *q;
 
	Q = &m_queue;
	InitQueue(Q); //队列初始化
 
	p = (QueueNode *)malloc(sizeof(QueueNode));
	p->next = NULL;
	p->treenode = T;
	InQueue(Q, p); //根节点进队
 
	while (!IsQueueEmpty(Q))
	{
		OutQueue(Q, &q); //当前队首节点出队
		printf("%c ", q->treenode->id);
 
		if (q->treenode->left) //左孩子非空,则左孩子进队
		{
			p = (QueueNode *)malloc(sizeof(QueueNode));
			p->next = NULL;
			p->treenode = q->treenode->left;
			InQueue(Q, p);
		}
		if (q->treenode->right) //右孩子非空,则右孩子进队
		{
			p = (QueueNode *)malloc(sizeof(QueueNode));
			p->next = NULL;
			p->treenode = q->treenode->right;
			InQueue(Q, p);
		}
	}
}

五、二叉树的删除

先删除左子树,再删除右子树,最后删除根节点,采用递归实现。

//清除二叉树
void ClearTree(Tree *T)
{
	if (!*T)
	{
		return;
	}
 
	ClearTree(&(*T)->left);
	ClearTree(&(*T)->right);
	free(*T);
	*T = NULL;
}

六、获得二叉树的高度

如果二叉树只有根节点,那么它的高度就为1。否则二叉树的高度等于1+左、右子树高度的较大者。按这种方法递归实现。

 //获得二叉树的高度
    int GetHeight(Tree T)
    {
    	if (T)
    	{
    		return MaxOfTwo(GetHeight(T->left), GetHeight(T->right)) + 1;
    	} 
    	else
    	{
    		return 0;
    	}
    }

 
//两数较大值
int MaxOfTwo(int a, int b)
{
	if (a >= b)
	{
		return a;
	} 
	else
	{
		return b;
	}
}

七、获得二叉树的节点数

二叉树的节点数 = 左子树节点数 + 右子树的节点数 + 1。递归实现即可。

//获得二叉树的节点数
int GetNodeNumber(Tree T)
{
	if (T)
	{
		return GetNodeNumber(T->left) + GetNodeNumber(T->right) + 1;
	} 
	else
	{
		return 0;
	}
}

八、二叉树叶子节点的数量

int LeafNodeNum(struct TreeNode* root) {
	if (root == NULL) {
		return 0;
	}
 
	if (root->left == NULL&&root->right == NULL) {
		return 1;
	}
	else {
		return LeafNodeNum(root->left) + LeafNodeNum(root->right);
	}
}

九、插入二叉树节点

public void insert(int id, double dd) {
    Node newNode = new Node();    // 创建要添加的节点
    newNode.iData = id;
    newData.dData = dd;
    if (root == null)      // 如果该树在插入之前没有其他的子节点
        root = newNode;    // 直接使用根节点指向新的节点
    else {
        Node current = root;    // 维护一个当前节点的变量
        Node parent;            // 维护一个父节点的变量
        while (true) {
            parent = current;   // 首先让父节点保存当前节点的状态
            if (id < current.iData) {  // 如果要插的数据 比当前节点的值小
                current = current.leftChild;  // 向左走
                if (current == null) {  // 走到当前的节点为空时 着说明其父节点应该为一个子叶节点 可直接插入
                    parent.leftChild = newNode;  // 使用当前节点的父节点 让其左子叶的引用指向 要插入的节点
                    return;    // 插入后返回
                }
            } else {    // 和上面类似
                current = current.rightChild;
                if (current == null) {
                    parent.rightChile = newNode;
                    return;
                }
            }
        }
    }
}

十、删除二叉树节点

在删除之前需要找到要删的那个节点,这个节点找到之后,可能会有三种情况出现在你面前:

  1. 该节点是叶节点、没有子节点
  2. 该节点有一个子节点
  3. 该节点有两个子节点

对于情况1:
找到要删除的节点,此节点没有子叶节点 ,直接将该节点的父节点对其的引用置为null。

public boolean delete(int key) {
    // 查找要删除的节点
    Node current = root;    // 记录当前的节点
    Node parent = root;    // 记录当前节点的父节点
    boolean isLeftChild = true;    // 是否为左子节点的标志位
    while (current.iData != key) {
        parent = current;
        if (key < current.iData) {
            isLeftChid = true;
            current = current.leftChild;
        } else {
            isLeftChid = false;
            current = current.rightChild;
        }
        if (current = null)
            return false;
    }
    // 如果没有子节点
    if (current.leftChild == null && current.rightChild == null) {
        if (current == root)    // 如果是根节点 
            root = null;
        else if (isLeftChild)    // 如果这个节点在左边
            parent.leftChild = null;    // 将其父节点的左节点的索引置为null
        else
            parent.rightChild = null;    // 否则将其父节点的右节点的索引置为null
    }
}

对于情况2:
由于要删除的节点还存在有一个子叶节点,所以直接将父节对其的引用,变到其的子叶节点。
(简单讲就是让其的一个子节点代替他)

// 连接上面的 delete 方法
else if(current.rightChild == null) {    // 如果这个节点的右边为空,左边还有相应的连接
    if (current == root)                 // 如果这个节点为 根节点
        root = current.lefeChild;        // 那么直接将根节点替换为 原根节点的左子节点
    else if (isLeftChild)                // 如果这个节点为一个左子节点
        parent.leftChild = current.leftChild;    // 让其父节点对于左子节点的连接直接连向要删除节点的左子节点
    else                                 // 这是一个右子节点
        parent.rightChild = current.leftChild;   // 让其父节点对于右子节点的连接直接连向要伤处节点的左子节点
} else if (current.leftChild == null) {  // 如果这个节点的左边为空,右边还有相应的连接
    if (current == root)                 // ----->   同上
        root = current.rightChild;
    else if  (isLeftChild)
        parent.leftChild = current.rightChild;
    else
        parent.rightChild = current.rightChild;
}

对于情况3:
对于要删除的节点有两个子叶节点的,这个删除的过程中,比较复杂的过程就是寻找代替要删除节点的后继节点,这个节点具有的特征就是,这个节点要比被删除的节点大,但是在比被删除节点大的节点中,这个节点应该是最小的。在寻找后继节点分过程中,涉及到一种算法,也可以说是一种思维方式,首先,找到被删除节点的右子节点(绝对可以找到,因为这个节点是由两个子节点的),这个顺理成章,因为右子节点和其下可能存在的右子树的所有值,绝对比要删除的节点的值大,但是要在这些所有比较大的值中找到最小的那个,所以继续向被删除节点的右子节点的左子节点查找,一直找左子节点,知道没有左子节点的时候,那么那个节点就是我们要找的后继节点,但是这时候还是有两种情况,第一种是:被删除的节点的右子节点直接是后继节点,因为它没有字节的左子节点;还有一种就是,我们在要删除的节点的右子节点的子树上找到那棵树上最小的值;针对这两种不同的情况,要采取不同的方式去应对。

// 连接上面的delete代码 
else {
     Node successor = getSuccessor(current);    // 在这获取到后继节点
     if (current == root)    // 如果要删除的节点为 根,那么直接让后继节点代替根
         root = successor;
     else if (isLeftChild)    // 如果要删除的节点在 当前节点的左边 那么让其父节点对左子节点的引用 直接指向后继节点
         parent.leftChild = successor;
     else                    // 否则 父节点应该将对右子节点的引用 直接指向后继节点
         parent.rightChild = successor;
     successor.leftChild = current.leftChild; // 统一处理后继节点的左边子树:让后继节点的左引用 直接与删除节点的左子树相连
    }
    return true;
}

// 获取后继节点的方法
private Node getSuccessor (Node delNode) {
    // 后继节点的父节点
    Node successorParent = delNode;
    // 后继节点
    Node successor = delNode;
    // 当前节点
    Node current = delNode.rightChild;
    while (current != null) {
        // 后继节点父节点首先保存后继节点的状态
        successorParent = successor;
        // 后继节点 不断的向左更新
        successor = current;
        current = current.leftChild;
    }
    // 假如我们找到的后继节点不直接是 要删除节点的右节点  而是在其右节点那条子树上面最小的一个节点
    if (successor != delNode.rightChild) {
        // 后继节点的父节点断开其与后继节点左边的引用,重新连接上后继节点的右子节点(因为后继节点是没有左子节点的,锁以要保存之前树的状态,还要把后继节点的右子节点处理一下,不管 其存在不存在)
        successorParent.leftChild = successor.rightChild;
        // 这时候后继节点的右边已经空了 上一条语句已经将其给了自己父节点的左子节点    然后让后继节点的右边 连接要删除节点的右子树
        successor.rightChild = delNode.rightChild;
    }
    return successor;    
}
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
遍历二叉树方法有三种:先序遍历、中序遍历和后序遍历。其中,先序遍历是指先遍历根节点,再遍历左子树和右子树;中序遍历是指先遍历左子树,再遍历根节点和右子树;后序遍历是指先遍历左子树和右子树,再遍历根节点。 线索二叉树是在二叉树的基础上,通过添加线索(即前驱和后继指针)来实现遍历的优化。线索二叉树的遍历方法有两种:中序遍历和后序遍历。其中,中序遍历是指按照节点的中序遍历顺序遍历线索二叉树;后序遍历是指按照节点的后序遍历顺序遍历线索二叉树。 举例说明: ```java //先序遍历二叉树 public void preOrder(TreeNode root) { if (root != null) { System.out.print(root.val + " "); preOrder(root.left); preOrder(root.right); } } //中序遍历二叉树 public void inOrder(TreeNode root) { if (root != null) { inOrder(root.left); System.out.print(root.val + " "); inOrder(root.right); } } //后序遍历二叉树 public void postOrder(TreeNode root) { if (root != null) { postOrder(root.left); postOrder(root.right); System.out.print(root.val + " "); } } //中序遍历线索二叉树 public void inOrderThreaded(TreeNode root) { TreeNode cur = root; while (cur != null) { while (cur.left != null && !cur.left.isThreaded) { cur = cur.left; } System.out.print(cur.val + " "); if (cur.right != null && cur.isThreaded) { cur = cur.right; } else { cur = cur.right; while (cur != null && !cur.isThreaded) { cur = cur.left; } } } } //后序遍历线索二叉树 public void postOrderThreaded(TreeNode root) { TreeNode cur = root; while (cur != null) { while (cur.left != null && !cur.left.isThreaded) { cur = cur.left; } while (cur.right != null && cur.isThreaded) { System.out.print(cur.val + " "); cur = cur.right; } System.out.print(cur.val + " "); cur = cur.right; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值