数据结构与算法实战(八)二分搜索树
一、树结构
- 树结构本身是一种天然的组织结构(例如电脑中的文件夹结构)
- 高效(将数据使用树结构存储后,出奇的高效)
- 二分搜索树
- 平衡二叉树:AVL;红黑树
- 堆 ; 并查集
- 线段树;Trie(字典树,前缀树)
二、二分搜索树
(一)二叉树
-
和链表一样,是动态数据结构
-
class Node{ E e; Node left; //左孩子 Node right; //右孩子 }
-
二叉树具有唯一根节点
-
二叉树每个节点最多有两个孩子
-
二叉树每个节点最多有一个父亲节点(根节点无父亲节点)
-
无孩子的节点被称之为叶子节点
-
二叉树具有天然的递归结构(每个节点的左、右子树也是二叉树)
-
二叉树不一定是“满”的
-
一个节点也是二叉树,NULL 空也可以看作是二叉树
(二)二分搜索树
-
二分搜索树是二叉树
-
二分搜索树的每个节点的值,大于其左子树的所有节点的值,小于其右子树所有节点的值
-
每一颗子树也是二分搜索树
-
存储的元素必须有可比较性
三、二分搜索树的实现
(一)准备工作
public class BST<E extends Comparable<E>> {
private class Node{
E e;
Node left;
Node right;
public Node(E e){
this.e = e;
left = right = null;
}
}
private Node root;
private int size;
public BST(){
root = null;
size = 0;
}
public int size(){
return size;
}
private boolean isEmpty(){
return size == 0;
}
}
(二)添加元素
我们实现的二分搜索树不包含重复元素
如果想包含重复元素,只需要定义:左子树小于等于节点或者右子树大于等于节点
//向二叉搜索树添加新元素e
public void add(E e){
root = add(root,e);
}
//向以node为根的二分搜索树中插入元素e,递归算法
//返回插入新节点后二分搜索树的根
private Node add(Node node, E e){
//递归的终止条件
if(node == null){
size ++;
return new Node(e);
}
//递归
if(e.compareTo(node.e) < 0)
node.left=add(node.left,e);
else if(e.compareTo(node.e) > 0)
node.right=add(node.right,e);
return node;
}
(三)查询操作
//二分搜索树是否包含元素e
public boolean contains(E e){
return contains(root,e);
}
//看以node为根的二分搜索树是否包含元素e ,递归算法
private boolean contains(Node node,E e){
if(node == null)
return false;
if(e.compareTo(node.e) == 0)
return true;
else if(e.compareTo(node.e)<0)
return contains(node.left,e);
else
return contains(node.right,e);
}
(四)前序遍历操作
先遍历节点,再遍历节点的左子树和右子树
//二分搜索树的前序遍历
public void preOrder(){
preOrder(root);
}
//前序遍历以node为根的二分搜索树,递归算法
private void preOrder(Node node){
//递归终止条件
if(node == null)
return;
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
(五)中序遍历和后序遍历
//二分搜索树的中序遍历
public void inOrder(){
inOrder(root);
}
//中序遍历以node为根的二分搜索树,递归算法
private void inOrder(Node node){
//递归终止条件
if(node == null)
return;
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
二分搜索树的中序遍历是顺序的
//二分搜索树的后序遍历
public void postOrder(){
postOrder(root);
}
//后序遍历以node为根的二分搜索树,递归算法
private void postOrder(Node node){
//递归终止条件
if(node == null)
return;
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
(六)非递归实现前序遍历
//非递归前序遍历
public void preOrderNR(){
Stack<Node> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
Node cur = stack.pop();
System.out.println(cur.e);
if(cur.right != null)
stack.push(cur.right);
if(cur.left != null)
stack.push(cur.left);
}
}
(七)层序遍历
//层序遍历
public void levelOrder(){
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
Node cur = queue.remove();
System.out.println(cur.e);
if(cur.left != null)
queue.add(cur.left);
if(cur.right != null)
queue.add(cur.right);
}
}
(八)删除操作
1、删除最大值和最小值
从根节点出发向左走,一直到某一结点的左子树为空,该节点为最小值
从根节点出发向右走,一直到某一结点的右子树为空,该节点为最大值
//寻找最小值
public E minimum(){
if(size == 0)
throw new IllegalArgumentException("BST is empty");
return minimum(root).e;
}
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
//寻找最大值
public E maximum(){
if(size == 0)
throw new IllegalArgumentException("BST is empty");
return maximum(root).e;
}
private Node maximum(Node node){
if(node.right == null)
return node;
return maximum(node.right);
}
如果找到的最小值是叶子节点,直接删除即可;如果为非叶子节点,删除该节点并将它的父亲节点的左子树指向它的右子树。
如果找到的最大值是叶子节点,直接删除即可;如果为非叶子节点,删除该节点并将它的父亲节点的右子树指向它的左子树。
//从二分搜索树中删除最小值的节点,并返回最小值
public E removeMin(){
E ret = minimum();
root = removeMin(root);
return ret;
}
//删除掉以node为根的二分搜索树中的最小节点,递归算法
//返回删除节点后新的二分搜索树的根
private Node removeMin(Node node) {
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
//从二分搜索树中删除最大值的节点,并返回最大值
public E removeMax(){
E ret = maximum();
root = removeMax(root);
return ret;
}
//删除掉以node为根的二分搜索树中的最大节点,递归算法
//返回删除节点后新的二分搜索树的根
private Node removeMax(Node node) {
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
node.right=removeMax(node.right);
return node;
}
2、删除任意元素
删除左右都有孩子的节点d
找到 s = min (d -> right) , s 是 d 的后继
s -> right = delMin(d -> right)
s -> left = d -> left
删除d , s是新的子树根
// 从二分搜索树中删除元素为e的节点
public void remove(E e){
root = remove(root , e);
}
//删除以node为根的二分搜索树中值为e的节点,递归算法
//返回删除节点后新的根
private Node remove(Node node, E e) {
if(node == null)
return null;
if(e.compareTo(node.e) < 0){
node.left = remove(node.left, e);
return node;
}
else if(e.compareTo(node.e) > 0){
node.right=remove(node.right,e);
return node;
}
else { //e == node.e
//待删除结点左子树为空
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
//待删除结点右子树为空
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
//待删除结点左右子树都不为空
//找到比待删除元素大的最小节点,即待删除元素右子树的最小节点
//用这个节点替代删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
四、集合Set
(一)基于二分搜索树实现
集合无重复元素,所以二分搜索树是一个非常好实现集合的底层数据结构
Set<E>
void add(E) // 不能添加重复元素
void remove(E)
boolean contains(E)
int getSize()
boolean isEmpty()
public interface Set<E>{
void add(E e); // 不能添加重复元素
void remove(E e);
boolean contains(E e);
int getSize();
boolean isEmpty();
}
public class BSTSet<E extends Comparable<E>> implements Set<E> {
private BST<E> bst;
public BSTSet(){
bst = new BST<>();
}
@Override
public void add(E e) {
bst.add(e);
}
@Override
public void remove(E e) {
bst.remove(e);
}
@Override
public boolean contains(E e) {
return bst.contains(e);
}
@Override
public int getSize() {
return bst.size();
}
@Override
public boolean isEmpty() {
return bst.isEmpty();
}
}
(二)基于链表实现
public class LinkedListSet<E> implements Set<E>{
private LinkedList<E> list;
public LinkedListSet(){
list = new LinkedList<>();
}
@Override
public void add(E e) {
if(!list.contains(e))
list.addFirst(e);
}
@Override
public void remove(E e) {
list.removeElement(e);
}
@Override
public boolean contains(E e) {
return list.contains(e);
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
}
注意:LinkList.java在之前实现过,这里不再赘述
(三)有序集合和无序集合
(四)LeetCode804
804. 唯一摩尔斯密码词
国际摩尔斯密码定义一种标准编码方式,将每个字母对应于一个由一系列点和短线组成的字符串, 比如: "a"
对应 ".-"
, "b"
对应 "-..."
, "c"
对应 "-.-."
, 等等。
为了方便,所有26个英文字母对应摩尔斯密码表如下:
[".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."]
给定一个单词列表,每个单词可以写成每个字母对应摩尔斯密码的组合。例如,“cab” 可以写成 “-.-…–…”,(即 “-.-.” + “.-” + “-…” 字符串的结合)。我们将这样一个连接过程称作单词翻译。
返回我们可以获得所有词不同单词翻译的数量。
class Solution {
public int uniqueMorseRepresentations(String[] words) {
//26个小写字母对应的摩斯密码
String[] codes = {".-",
"-...",
"-.-.",
"-..","."
,"..-.","--."
,"....","..",".---"
,"-.-",".-..","--",
"-.","---",".--.","--.-"
,".-.","...","-","..-"
,"...-",".--","-..-","-.--"
,"--.."
};
TreeSet<String> set = new TreeSet<>();
for(String word : words){
StringBuilder res = new StringBuilder();
for (int i = 0; i < word.length(); i++) {
res.append(codes[word.charAt(i) - 'a']);
}
set.add(res.toString());
}
return set.size();
}
}
五、映射Map
- 存储(键,值)数据对的数据结构(Key,Value)
- 根据键,寻找值
Map<K,V>
void add(K,V)
V remove(K)
boolean contains(K)
V get(K)
void set(K,V)
int getSize()
boolean isEmpty()
(一)基于链表实现
public class LinkedListMap<K,V> implements Map<K,V> {
private class Node{
public K key;
public V value;
public Node next;
public Node(K key,V value, Node next){
this.key = key;
this.value = value;
this.next = next;
}
public Node(K key){
this(key,null,null);
}
public Node(){
this(null,null,null);
}
@Override
public String toString(){
return key.toString() + ": " + value.toString();
}
}
private Node dummyHead;
private int size;
public LinkedListMap(){
dummyHead = new Node();
size = 0;
}
private Node getNode(K key){
Node cur = dummyHead.next;
while (cur != null){
if(cur.key.equals(key))
return cur;
cur = cur.next;
}
return null;
}
@Override
public void add(K key, V value) {
Node node = getNode(key);
if(node == null){
dummyHead.next = new Node(key,value,dummyHead.next);
size ++;
}
else
node.value = value;
}
@Override
public V remove(K key) {
Node prev = dummyHead;
while(prev.next != null){
if(prev.next.key.equals(key))
break;
prev = prev.next;
}
if(prev.next != null){
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size --;
return delNode.value;
}
return null;
}
@Override
public boolean contains(K key) {
return getNode(key) != null;
}
@Override
public V get(K key) {
Node node = getNode(key);
return node == null ? null : node.value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(key);
if(node == null)
throw new IllegalArgumentException(key+" doesn't exist!!!!");
node.value = newValue;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
(二)基于二分搜索树实现
import java.time.temporal.ValueRange;
public class BSTMap<K extends Comparable<K>,V> implements Map<K,V> {
private class Node{
K key;
V value;
Node left;
Node right;
public Node(K key,V value){
this.key = key;
this.value = value;
left = right = null;
}
}
private Node root;
private int size;
public BSTMap(){
root = null;
size = 0;
}
@Override
public void add(K key, V value) {
root = add(root,key,value);
}
//向以node为根的二分搜索树中插入元素(key,value),递归算法
//返回插入新节点后二分搜索树的根
private Node add(Node node, K key, V value){
//递归的终止条件
if(node == null){
size ++;
return new Node(key,value);
}
//递归
if(key.compareTo(node.key) < 0)
node.left=add(node.left,key,value);
else if(key.compareTo(node.key) > 0)
node.right=add(node.right,key,value);
else
//key == node.key
node.value = value;
return node;
}
//返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node,K key){
if(node == null)
return null;
if(key.compareTo(node.key) == 0)
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left,key);
else
return getNode(node.right,key);
}
@Override
public V remove(K key) {
Node node =getNode(root,key);
if(node != null){
root = remove(root,key);
return node.value;
}
return null;
}
//删除以node为根的二分搜索树中键为key的节点,递归算法
//返回删除节点后新的根
private Node remove(Node node, K key) {
if(node == null)
return null;
if(key.compareTo(node.key) < 0){
node.left = remove(node.left, key);
return node;
}
else if(key.compareTo(node.key) > 0){
node.right=remove(node.right,key);
return node;
}
else { //key == node.key
//待删除结点左子树为空
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
//待删除结点右子树为空
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
//待删除结点左右子树都不为空
//找到比待删除元素大的最小节点,即待删除元素右子树的最小节点
//用这个节点替代删除节点的位置
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
//删除掉以node为根的二分搜索树中的最小节点,递归算法
//返回删除节点后新的二分搜索树的根
private Node removeMin(Node node) {
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
@Override
public boolean contains(K key) {
return getNode(root,key) != null;
}
@Override
public V get(K key) {
return getNode(root,key) == null? null : getNode(root,key).value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(root,key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist");
node.value = newValue;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size ==0;
}
}
left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
@Override
public boolean contains(K key) {
return getNode(root,key) != null;
}
@Override
public V get(K key) {
return getNode(root,key) == null? null : getNode(root,key).value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(root,key);
if(node == null)
throw new IllegalArgumentException(key + " doesn't exist");
node.value = newValue;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size ==0;
}
}