算法学习系5——树、二叉树、二叉搜索树
重点:二叉搜索树的运用,递归的使用
前言
本文主要针对于树这种数据结构进行分析与应用,其中重点分析了二叉搜索树的相关结点操作
树
是一个二维的数据结构,是有限结点组成一个具有层次关系的集合。
特点:二叉树除叶子外每一个结点最多有两个分支,每一个树都有一个根结点,除根结点外每一个结点都有父结点,除叶子外每一个结点都有父结点,二叉树树分为左子树和右子树;
如果各个结点形成了一个闭环,那么就形成了图,链表可以认为是一个特殊的树,虽然他有两个指针;那么树也可以当作特殊的图;
基本代码
public class TreeNode {
public int val;
public TreeNode left, right;
public TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
二叉树的遍历
遍历就是分别去访问你的根,左子树,右子树,根据不同的顺序分为三种遍历:前序、中序、后序;
前序:根左右
中序:左根右
后序:左右根
树的搜索,因为它本身的特殊结构,与线性数据结构不同,遍历常用递归,而且实现相对简单。
二叉搜索树
二叉搜索树,左子树上的所有结点小于根结点;右子树上的结点大于根结点;并且左右子树也同样如此;
复杂度
查询、插入、删除都是O(log n);
因为二叉搜索树类似于二分查找,每次指比较一半的结点,所以复杂度为O(log n);
查询
public Node find(int key) {
Node currentNode = root;
while (currentNode != null && currentNode.key != key) {
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
}else{
currentNode = currentNode.rightChild;
}
}
return currentNode;
}
插入
待插入的结点从根结点开始进行比较,小于根结点则与根结点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父结点的信息 及 待插入的位置是父结点的左子树还是右子树,才能插入到正确的位置
public void insert(int key, int value) {
if (root == null) {
root = new Node(key, value);
return;
}
Node currentNode = root;
Node parentNode = root;
boolean isLeftChild = true;
while (currentNode != null) {
parentNode = currentNode;
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
isLeftChild = true;
} else {
currentNode = currentNode.rightChild;
isLeftChild = false;
}
}
Node newNode = new Node(key, value);
if (isLeftChild) {
parentNode.leftChild = newNode;
} else {
parentNode.rightChild = newNode;
}
}
遍历
public void preOrder(Node rootNode) {
if (rootNode != null) {
System.out.println(rootNode.key + " " + rootNode.value);
preOrder(rootNode.leftChild);
preOrder(rootNode.rightChild);
}
}
public void inOrder(Node rootNode) {
if (rootNode != null) {
inOrder(rootNode.leftChild);
System.out.println(rootNode.key + " " + rootNode.value);
inOrder(rootNode.rightChild);
}
}
public void postOrder(Node rootNode) {
if (rootNode != null) {
postOrder(rootNode.leftChild);
postOrder(rootNode.rightChild);
System.out.println(rootNode.key + " " + rootNode.value);
}
}
删除
- 如果结点为叶子(没有左、右子树),此时删除该结点直接删除即可,并修改其父结点指向它的引用为null;
- 结点只包含左子树,或者右子树的话,此时直接删除该结点,并将其左子树或者右子树设置为其父结点的左子树或者右子树即可,此操作不会破坏树结构;
- 当结点的左右子树都不空的时候,如果把树结构中的所有节点按顺序排好的话,它的前驱和它的后继两个节点刚好在它左右紧挨着它。当一个节点被删除时,为了保证二叉树的结构不被破坏,要让它的前驱或者后继节点来代替它的位置,然后将它的前驱或者后继节点同样做删除操作,
小于它的最大值,就是在树中在它左边最靠右的那个节点。同样,大于它的最小值,就是在树中在它右边最靠左的那个节点。当一个节点既有左子节点又有右子节点时,前驱就是它的左子节点的右子节点的右子节点…直到最右子节点;后继就是它的右子节点的左子节点的左子节点…直到最左子节点
/**
* 删除节点
*
* @param node 待删除节点
*/
private void deleteNode(Node node) {
// 如果按顺序排列好节点,它的前驱和后继就是这个序列上紧挨着它左右两侧的节点.
// 如果节点只有左节点或者只有右节点
if (node.haveLeftChild() && !node.haveRightChild()) {// 只有左节点
if (node.isLeftChild) {
node.fatherNode.leftChildNode = node.leftChildNode;
} else if (node.isRightChild) {
node.fatherNode.rightChildNode = node.leftChildNode;
} else// 待删除节点是根节点
treeRoot = node.leftChildNode;
node.leftChildNode.fatherNode = node.fatherNode;
} else if (node.haveRightChild() && !node.haveLeftChild()) {// 只有右节点
if (node.isLeftChild) {
node.fatherNode.leftChildNode = node.rightChildNode;
} else if (node.isRightChild) {
node.fatherNode.rightChildNode = node.rightChildNode;
} else// 待删除节点是根节点
treeRoot = node.rightChildNode;
node.rightChildNode.fatherNode = node.fatherNode;
} else if (node.haveLeftChild() && node.haveRightChild()) {// 有左右子节点
Node successorNode = getSuccessorNode(node);
if (successorNode == node.rightChildNode) {// 后继节点是右子节点
successorNode.fatherNode = node.fatherNode;
if (node.isLeftChild)
node.fatherNode.leftChildNode = successorNode;
else if (node.isRightChild)
node.fatherNode.rightChildNode = successorNode;
else {// 是根节点
successorNode = treeRoot;
}
successorNode.fatherNode = node.fatherNode;
successorNode.leftChildNode = node.leftChildNode;
node.leftChildNode.fatherNode = successorNode;
} else {// 后继节点是右子节点的最左子节点
if (successorNode.haveRightChild()) {// 左子节点有右子树
successorNode.fatherNode.leftChildNode = successorNode.rightChildNode;
successorNode.rightChildNode.fatherNode = successorNode.fatherNode;
replaceNode(node, successorNode);
} else {// 左子节点没有右子树
// 叶节点,直接删除
successorNode.fatherNode.leftChildNode = null;
replaceNode(node, successorNode);
}
}
} else {// 没有子节点
if (node.isLeftChild) {
node.fatherNode.leftChildNode = null;
} else if (node.isRightChild) {
node.fatherNode.rightChildNode = null;
}
}
node = null;
}
/**
* 非相邻节点的替换逻辑(非相邻加粗!)
* @param node 被替换节点
* @param replaceNode 替换的节点
*/
private void replaceNode(Node node, Node replaceNode) {
if (node.isLeftChild)
node.fatherNode.leftChildNode = replaceNode;
else if (node.isRightChild)
node.fatherNode.rightChildNode = replaceNode;
else {// node是根节点
treeRoot = replaceNode;
}
node.leftChildNode.fatherNode = node.rightChildNode.fatherNode = replaceNode;
replaceNode.leftChildNode = node.leftChildNode;
replaceNode.rightChildNode = node.rightChildNode;
}
/**
* 获取一个节点的后继节点
* @param node
* @return
*/
private Node getSuccessorNode(Node node) {
if (!node.haveRightChild()) {// 没有右子树
return null;
}
Node targetNode = node.rightChildNode;
while (targetNode.haveLeftChild()) {// 找右子树的最左孩子,保证返回的节点一定没有左子树
targetNode = targetNode.leftChildNode;
}
return targetNode;
}
/**
* 删除数中的数据
* @param baseData
*/
public void deleteData(T baseData) {
Node node = searchNode(baseData);
deleteNode(node);
}