9.1.树的定义

 

9.2.二叉树

人们把每个节点最多拥有不超过两个子节点的树定义为二叉树。由于限制子节点的数量为 2,人们可以为插入数据、删除数据、以及在二叉树中查找数据编写有效的程序了。

在考虑一种更加特殊的二叉树——二叉查找树的时候,鉴别子节点是很重要的。二叉查找树是一种较小数据值存储在左节点内而较大数据值存储在右节点内的二叉树。正如即将看到的那样,这种属性可以使查找非常有效。

 

9.2.1.构造二叉查找树

二叉查找树由节点组成,所以需要一个 Node 类,这个类类似于链表实现中用到的 Node 类。

    public class Node
{
public int Data;
public Node Left;
public Node Right;
public void DisplayNode()
{
Console.WriteLine(Data + " ");
}
}

下面是插入方法

public class BinarySearchTree
{
//根
public Node root; public BinarySearchTree()
{
root = null;
}
} public void Insert(int i)
{
Node newNode = new Node();
newNode.Data = i;
//如果根节点没数据
if (root == null)
{
root = newNode;
}
else
{
Node current = root;
Node parent;
while (true)
{
parent = current;
if (i < current.Data)
{
current = current.Left;
if (current == null)
{
parent.Left = newNode;
break;
}
}
else
{
current = current.Right;
if (current == null)
{
parent.Right = newNode;
break;
}
}
}
}
}

 

9.2.2.遍历二叉查找树

 

中序遍历

 

既然这个方法是按照升序方式访问每一个节点,所以此方法必须访问到每棵子树的左节点和右节点,

跟着是访问根节点的左子节点下的子树,再接着是访问根节点的右子节点下的子树

//中序遍历
public void InOrder(Node theRoot)
{
if (theRoot != null)
{
InOrder(theRoot.Left);
theRoot.DisplayNode();//显示
InOrder(theRoot.Right);
}
}

 

先序遍历

 

//先序遍历
public void PreOrder(Node theRoot)
{
if (!(theRoot == null))
{
theRoot.DisplayNode();
PreOrder(theRoot.Left);
PreOrder(theRoot.Right);
}
}

 

 

 

后序遍历

 

//后续遍历
public void PostOrder(Node theRoot)
{
if (!(theRoot == null))
{
PostOrder(theRoot.Left);
PostOrder(theRoot.Right);
theRoot.DisplayNode();
}
}

 

9.2.3 在二叉查找树中查找节点和最大/最小值

人们总可以在根节点左子树的最左侧子节点上找到 BST 内的最小值。

public int FindMin()
{
Node current = root;
while (current.Left != null)
current = current.Left;
return current.Data;
}

最大值同理

public int FindMax()
{
Node current = root;
while (current.Right != null)
current = current.Right;
return current.Data;
}

 

查找指定值

public Node Find(int key)
{
Node current = root;
while (current.Data != key)
{
if (key < current.Data)
current = current.Left;
else
current = current.Right;
if (current == null)
return null;
}
return current;
}

 

9.2.4.删除叶子节点

step1”叶子节点:直接删除该节点,再修改其父节点的指针(注意分是根节点和不是根节点)

(例:删除72)

step2”单支节点(即只有左子树或右子树):让p的子树与p的父亲节点相连,再删除p即可;(注意分是根节点和不是根节点两种情况)

(例:删除79)

step3”节点p的左子树和右子树均不为空:首先找到p的后继y,因为y一定没有左子树,所以可以删除y,并让y的父亲节点成为y的右子树的父亲节点,并用y的值代替p的值;或者可以先找到p的前驱x,x一定没有右子树,所以可以删除x,并让x的父亲节点成为y的左子树的父亲节点。

 

9.2.4.1.删除带有一个子节点的节点

当要删除的节点有一个子节点的时候,需要检查四个条件:

1.这个节点的子节点可能是左子节点;

2.这个节点的子节点可能是右子节点;

3.要删除的这个节点可能是左子节点;

4.要删除的这个节点可能是右子节点。

//如果右子节点为空null
else if (current.Right == null)
{
//查看是否在根节点上
if (current == root)
root = current.Left;
else if (isLeftChild)
parent.Left = current.Left;
else
parent.Right = current.Right;
}
//如果左子节点为空
else if (current.Left == null)
{
if (current == root)
root = current.Right;
else if (isLeftChild)
parent.Left = parent.Right;
else
parent.Right = current.Right;
}

说明:

 

9.2.4.2.删除带有两个子节点的节点

为了找到后继节点,要到原始节点的右子节点上。根据定义这个节点必须比原始节点大。然后,开始沿着左子节点路径走直到用完节点为止。既然子树(像一棵树)内的最小值必须是在左子节点路径的末端,沿着这条路径到达末端就会找到大于原始节点的最小节点。

 

后继节点的代码

public Node GetSuccessor(Node delNode)
{
//把删除的节点作为继任者的父节点
Node successorParent = delNode;
//把删除的节点作为继任者的节点
Node successor = delNode;
//当前节点的右子节点
Node current = delNode.Right; while (current != null)
{
successorParent = current;
successor = current;
current = current.Left;
} //current.Left = null; if (successor != delNode.Right)
{
//1.把后继节点的右子节点赋值为后继节点的父节点的左子节点
successorParent.Left = successor.Right;
//2.要删除节点的右子节点赋值为后继节点的右子节点
successor.Right = delNode.Right; //后继节点的右子节点的左子节点必须设置为空,不然会死循环
successor.Right.Left = null;
}
return successor;
}

 

两个节点的代码

         else
{ Node successor = GetSuccessor(current); if (current == root)
{
root = successor;
}
else if (isLeftChild)
{
parent.Left = successor;
}
else
{
//3.从父节点的右子节点中移除当前节点,并且把它指向后继节点
parent.Right = successor;
}
//4.从当前节点中移除当前节点的左子节点,并且把它指向后继节点的左子节点
successor.Left = current.Left;
}

 

 

完整的删除代码

public bool Delete(int key)
{
Node current = root;
Node parent = root;//让父节点也等于根
//判断是否为左子节点
bool isLeftChild = true;
//循环删除节点
while (current.Data != key)
{ parent = current;
//值小于当前节点的值,在左字节点查找
if (key < current.Data)
{
//取左子节点的数据
isLeftChild = true;
current = current.Left;
}
else
{
isLeftChild = false;
current = current.Right;
}
}
//如果查找不到,就退出
if (current == null)
{
return false;
}
//查看这个节点的左子节点和右子节点是否为空
if ((current.Left == null) & (current.Right == null))
{
//如果是根节点,就把它设置为空
if (current == root)
root = null;
//如果为左子节点,就把父节点的左子节点设置为空
else if (isLeftChild)
parent.Left = null; else//如果为右子节点,就把父节点的右子节点设置为空
parent.Right = null;
}
//如果右子节点为空null
else if (current.Right == null)
{
//查看是否在根节点上
if (current == root)
root = current.Left;
else if (isLeftChild)
parent.Left = current.Left;
else
parent.Right = current.Right;
}
//如果左子节点为空
else if (current.Left == null)
{
if (current == root)
root = current.Right;
else if (isLeftChild)
parent.Left = parent.Right;
else
parent.Right = current.Right;
}
//左子节点和右子节点都有的情况
else
{ Node successor = GetSuccessor(current); if (current == root)
{
root = successor;
}
else if (isLeftChild)
{
parent.Left = successor;
}
else
{
//3.从父节点的右子节点中移除当前节点,并且把它指向后继节点
parent.Right = successor;
}
//4.从当前节点中移除当前节点的左子节点,并且把它指向后继节点的左子节点
successor.Left = current.Left;
}
return true;
}

数据结构和算法 – 9.二叉树和二叉查找树的更多相关文章

  1. 【数据结构与算法】二叉树的 Morris 遍历(前序、中序、后序)

    前置说明 不了解二叉树非递归遍历的可以看我之前的文章[数据结构与算法]二叉树模板及例题 Morris 遍历 概述 Morris 遍历是一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1 ...

  2. Java数据结构和算法(六)--二叉树

    什么是树? 上面图例就是一个树,用圆代表节点,连接圆的直线代表边.树的顶端总有一个节点,通过它连接第二层的节点,然后第二层连向更下一层的节点,以此递推 ,所以树的顶端小,底部大.和现实中的树是相反的, ...

  3. JS数据结构第五篇 --- 二叉树和二叉查找树

    一.二叉树的基本概念 从逻辑结构角度来看,前面说的链表.栈.队列都是线性结构:而今天要了解的“二叉树”属于树形结构. 1.1 多叉树的基本概念,以上图中“多叉树”为例说明 节点:多叉树中的每一个点都叫 ...

  4. 数据结构与算法之二叉树 ——in dart

    用dart语言实现的二叉树,实现了插入.查找.删除,中序遍历.前序.后序遍历等功能. class BinaryTree<E extends Comparable> { Node<E& ...

  5. Java数据结构和算法(七)--AVL树

    在上篇博客中,学习了二分搜索树:Java数据结构和算法(六)--二叉树,但是二分搜索树本身存在一个问题: 如果现在插入的数据为1,2,3,4,5,6,这样有序的数据,或者是逆序 这种情况下的二分搜索树 ...

  6. 二叉树和二叉查找树--数据结构与算法JavaScript描述(10)

    二叉树和二叉查找树 概念 树是一种非线性的数据结构,以分层的方式存储数据. 树被用来存储具有层级关系的数据,比如文件系统的文件: 树还被用来存储有序列表. 一棵树最上面的节点称为根节点. 如果一个节点 ...

  7. javascript数据结构与算法-- 二叉树

    javascript数据结构与算法-- 二叉树 树是计算机科学中经常用到的一种数据结构.树是一种非线性的数据结构,以分成的方式存储数据,树被用来存储具有层级关系的数据,比如文件系统的文件,树还被用来存 ...

  8. 常见基本数据结构——树,二叉树,二叉查找树,AVL树

    常见数据结构——树 处理大量的数据时,链表的线性时间太慢了,不宜使用.在树的数据结构中,其大部分的运行时间平均为O(logN).并且通过对树结构的修改,我们能够保证它的最坏情形下上述的时间界. 树的定 ...

  9. 数据结构和算法(Golang实现)(27)查找算法-二叉查找树

    二叉查找树 二叉查找树,又叫二叉排序树,二叉搜索树,是一种有特定规则的二叉树,定义如下: 它是一颗二叉树,或者是空树. 左子树所有节点的值都小于它的根节点,右子树所有节点的值都大于它的根节点. 左右子 ...

随机推荐

  1. BZOJ1500——维修序列

    动态的最大子段和 就是splay啊,说一下GSS1吧,维护四个值,一个是这个区间和(下面说sum), 一个是从左边开始的最大和(下面说ls)和右边开始的最大和(下面说rs), 还有一个就是最大区间和( ...

  2. img标签src=""和background-image:url();引发两次请求页面bug

      img标签src=""和background-image:url();引发两次请求页面bug 具体原因是,在img 对象的src 属性是空字符串("")的时 ...

  3. navigationcontroller手势翻页和navigationbar

    一. 系统导航默认手势 #import "CBNavigationController.h" //手势返回 @interface CBNavigationController () ...

  4. C++ 零碎知识点

    C++的一些知识点比较零碎,下面清单的形式做一些记录与归纳,以供参考. 1.赋值操作符重载(深复制): (1)由于目标对象可能引用了以前的一些数据,所以应该先delete这些数据: (2)注意到对象可 ...

  5. dp重拾-完全背包--HDU 4508

    减肥记 湫湫给了你每日食物清单,上面描述了当天她想吃的每种食物能带给她的幸福程度,以及会增加的卡路里量. Input 输入包含多组测试用例. 每组数据以一个整数n开始,表示每天的食物清单有n种食物. ...

  6. BZOJ 4665: 小w的喜糖

    Sol DP+容斥. 这就是一个错排的扩展...可是想到容斥却仅限于种数的容斥,如果种数在一定范围内我就会做了QAQ. 但是容斥的是一定在原来位置的个数. 发现他与原来的位置无关,可以先把每个同种的糖 ...

  7. 用消息机制解耦Activity跳转

    我见过的Activity方式有三种: 1, 默认的,在一个Activity里创建一个Intent,然后startActivity/startActivityForResult: 2, 给被跳转到的Ac ...

  8. 13 HashTable抽象哈希表类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  9. struts2配置文件的加载顺序以及 struts.xml package 的配置说明

    查看StrutsPrepareAndExecuteFilter:(核心过滤器)两个功能 :预处理 和 执行 在预处理功能中 init 方法中会有加载配置文件的代码: dispatcher.init() ...

  10. robotFramework——截屏

    测试执行过程中进行截屏并且保存,是任何一款自动化测试工具或者框架必备的功能.那么Robotframework如何进行截屏呢?Robotframework提供了一个“Screenshot”库.    使 ...