查找算法总结(二分查找/二叉查找树/红黑树/散列表)
1、二分查找
二分查找时,先将被查找的键和子数组的中间键比较。如果被查找的键小于中间键,就在左子数组继续查找,如果大于中间键,就在右子数组中查找,否则中间键就是要找的元素。
/** * 二分查找 */ public class BinarySearch { public static int find(int[] array, int key) { int left = 0; int right = array.length - 1; // while (left <= right)不能改为<,否则的话条件判断不完整 while (left <= right) { int mid = left + (right - left) / 2; if (key == array[mid]) { return mid; } else if (key < array[mid]){ right = mid - 1; } else { left = mid + 1; } } return -1; } }
每次移动left和right指针的时候,需要在mid的基础上+1或者-1, 防止出现死循环, 程序也就能够正确的运行。
但如果条件稍微变化一下,你还会写吗?如,数组之中的数据可能可以重复,要求返回匹配的数据的最小(或最大)的下标;更近一步, 需要找出数组中第一个大于key的元素(也就是最小的大于key的元素的)下标,等等。 这些,虽然只有一点点的变化,实现的时候确实要更加的细心。这些二分检索变种的实现见:
2、二叉查找树
二叉查找树对于树中的每个节点x,它的左子树所有关键字小于X的关键字,而它的右子树中所有关键字大于X的关键字。
二叉查找树主要操作是查找和插入,其流程如下图所示:
二叉查找树节点类如下:
/** * 二叉查找树节点类 */ class Node { // ----------------------------------- Instance Variables public Node left; public Node right; public int data; }
二叉查找树类代码如下:
/** * 二叉查找树类 */ public class BinaryTree { // ----------------------------------- Instance Variables public Node root; // 根节点 public int nodes; // 节点个数 // ----------------------------------- Constructors public BinaryTree() { root = null; nodes = 0; } // ----------------------------------- Public Methods public void insert(int data) { root = insertInternal(root, data); } public void remove(int data) { if (find(data) != null) { root = removeInternal(root, data); nodes--; } } public Node find(int key) { Node node = root; while (node != null) { if (key == node.data) { return node; } else if (key < node.data) { node = node.left; } else { node = node.right; } } return null; } public Node min() { if (root == null) { return null; } Node node = root; while (node.left != null) { node = node.left; } return node; } public Node max() { if (root == null) { return null; } Node node = root; while (node.right != null) { node = node.right; } return node; } @Override public String toString() { if (root != null) { StringBuilder buff = new StringBuilder(); toStringInternal(root, buff); return buff.toString(); } return ""; } // ----------------------------------- Private Methods private Node newNode(int data) { Node node = new Node(); node.data = data; node.left = node.right = null; return node; } private void toStringInternal(Node node, StringBuilder buff) { if (node != null) { toStringInternal(node.left, buff); buff.append(node.data + " "); toStringInternal(node.right, buff); } } private Node insertInternal(Node node, int data) { if (node == null) { node = newNode(data); nodes++; } else if (data < node.data) { node.left = insertInternal(node.left, data); } else if (data > node.data) { node.right = insertInternal(node.right, data); } return node; } private Node removeInternal(Node node, int data) { if (node == null) { return null; } else if (data < node.data) { node.left = removeInternal(node.left, data); } else if (data > node.data) { node.right = removeInternal(node.right, data); } else if (node.left != null && node.right != null) { Node tmp = min(node.right); node.data = tmp.data; node.right = removeInternal(node.right, tmp.data); } else { // 删除节点有一个child节点或者没有child节点 if (node.left != null) { node = node.left; } else if (node.right != null) { node = node.right; } else { node = null; } } return node; } private Node min(Node node) { if (node == null) { return null; } while (node.left != null) { node = node.left; } return node; } }
以上是二叉查找树的代码实现,还有一种平衡查找树是AVL树,和二叉查找树类似。一颗AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树。(假设空树的高度定义为0)。AVL树具体请查看树 - 数据结构 (二叉查找树、AVL树)。
3、红黑树
红黑树是平衡树的一种,保证最坏情况下操作时间复杂度为O(lgo(n))。红黑树的应用比较广泛,比如作为C++中STL的set和map的底层数据结构,Java集合中TreeSet和TreeMap的底层数据结构等。学习红黑树,可以把二叉查找树作为参考,这样有助于加深理解。红黑树的操作主要包括节点旋转、插入、删除等操作。
红黑树讲解和实现请点击:深入理解红黑树
4、散列表
散列是一种用于以常数平均时间执行插入、删除和查找的技术。理想的散列数据结构只不过是一个包含有关键字的具有固定大小的数组。典型情况下,一个关键字就是一个带有相关值(工资信息等)的字符串。
散列函数主要的问题就是解决冲突的消除问题。如果当一个元素被插入时另一个元素已经存在(散列值存在),那么就会产生冲突。解决这种冲突的方法有几种,一般用最简单的两种:分离链接法、开放地址法。
哈希表类代码如下:
/** * 哈希表类 */ public class Hashlist { // ----------------------------------- Instance Variables private List<List<Integer>> array; private int capacity; // 哈希表大小 // ----------------------------------- Constructors public Hashlist() { this(47); } public Hashlist(int capacity) { this.capacity = capacity; if (capacity < 47) { this.capacity = 47; } array = new ArrayList<List<Integer>>(); for (int i = 0; i < capacity; i++) { array.add(new ArrayList<Integer>()); } } // ----------------------------------- Public Methods public boolean find(int data) { int index = hash(data); List<Integer> list = array.get(index); if (list.contains(data)) { return true; } return false; } public void insert(int data) { int index = hash(data); List<Integer> list = array.get(index); if (!list.contains(data)) { list.add(data); } } @Override public String toString() { StringBuilder buff = new StringBuilder(); for (int i = 0; i < array.size(); i++) { List<Integer>list = array.get(i); buff.append(i + ": "); buff.append(list + "\n"); } return buff.toString(); } // ----------------------------------- Private Methods public int hash(int data) { return (data % capacity); } }
参考资料:
1、