红黑树:

基于BST(平衡二叉树)产生
旋转操作: 保持高度在logN

  1. 每个节点或是红色,或是黑色
  2. 根节点是黑色
  3. 每个叶子节点是黑色
  4. 如果一个节点是红色,那么他的两个子节点都是黑的
  5. 对每个节点,从该节点到其个叶子节点的所有路径上包含相同数目的黑节点


    就是父子节点之间不能出现两个连续的红节点
    查找复杂度:logN

插入数据:

  1. 新节点赋为红色,如果未黑色,会导致根到叶子路径上有一条路上,多一个额外黑色节点
  2. 设要插入的节点未N 父为 P ,

    处理方式一

    新结点N的叔叔结点U是红色的

    处理: 只是变色
    image.png
    新节点N 叔叔节点是黑色,N 是左子
    处理方式,对祖父节点右旋
    这时会修改父指针的指向

image.png

N s是右子

先选择父, 在右旋根
image.png

数据机构

数组+ 单项链表 + 红黑树
image.png
节点较少是:数组+ 单向链表
节点较多是 大于8 : 数组 +红黑树

基本属性

  1. /**
  2. * 默认容量
  3. */
  4. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  5. /**
  6. * 最大容量
  7. */
  8. static final int MAXIMUM_CAPACITY = 1 << 30;
  9. /**
  10. * 负载因子
  11. */
  12. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  13. /**
  14. 转向红黑树的阈值
  15. */
  16. static final int TREEIFY_THRESHOLD = 8;
  17. /**
  18. * 红黑树转链表的阈值
  19. */
  20. static final int UNTREEIFY_THRESHOLD = 6;
  21. /**
  22. * 转红黑树时,table最小长度
  23. */
  24. static final int MIN_TREEIFY_CAPACITY = 64;

1.7死循环的问题:

1.7会造成循环链表
1.8 采用尾插法,避免死循环

HashMap红黑树插入操作

  1. static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
  2. TreeNode<K,V> x) {
  3. // 新节点默认为红色
  4. x.red = true;
  5. // xp表示x的父结点,xpp表示x的祖父结点,xppl表示xpp的左孩子结点,xppr表示xpp的右孩子结点
  6. for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
  7. // 如果x没有父结点,则表示x是第一个结点,自动为根节点,根节点为黑色
  8. if ((xp = x.parent) == null) {
  9. x.red = false;
  10. return x;
  11. }
  12. // 如果父结点不是红色(就是黑色),或者x没有祖父节点,那么就证明x是第二层节点,父节点为根节点
  13. // 这种情况无需就行操作
  14. else if (!xp.red || (xpp = xp.parent) == null)
  15. return root;
  16. // 进入到这里,表示x的父节点为红色
  17. // 如果x的父节点是祖父结点的左孩子
  18. if (xp == (xppl = xpp.left)) {
  19. // 祖父结点的右孩子,也就是x的叔叔节点不为空,且为红色
  20. if ((xppr = xpp.right) != null && xppr.red) {
  21. // 父节点和叔叔节点都为红色,只需要变色,且将x替换为祖父节点然后进行递归
  22. xppr.red = false;
  23. xp.red = false;
  24. xpp.red = true;
  25. x = xpp;
  26. }
  27. // 如果叔叔节点为空,或者为黑色
  28. else {
  29. // 如果x节点为xp的右孩子
  30. if (x == xp.right) {
  31. // 先进行左旋,并且把x替换为xp进行递归,在左旋的过程中产生了新的root节点
  32. root = rotateLeft(root, x = xp);
  33. // x替换后,修改xp和xpp
  34. xpp = (xp = x.parent) == null ? null : xp.parent;
  35. }
  36. // 如果x本来是左孩子,或者已经经过了上面的左旋之后,进行变色加右旋
  37. if (xp != null) {
  38. xp.red = false;
  39. if (xpp != null) {
  40. xpp.red = true;
  41. root = rotateRight(root, xpp);
  42. }
  43. }
  44. }
  45. }
  46. // 如果x的父节点是祖父结点的右孩子
  47. else {
  48. if (xppl != null && xppl.red) {
  49. xppl.red = false;
  50. xp.red = false;
  51. xpp.red = true;
  52. x = xpp;
  53. }
  54. else {
  55. if (x == xp.left) {
  56. root = rotateRight(root, x = xp);
  57. xpp = (xp = x.parent) == null ? null : xp.parent;
  58. }
  59. if (xp != null) {
  60. xp.red = false;
  61. if (xpp != null) {
  62. xpp.red = true;
  63. root = rotateLeft(root, xpp);
  64. }
  65. }
  66. }
  67. }
  68. }
  69. }

红黑树的左右选操作

  1. static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
  2. TreeNode<K,V> p) {
  3. // pp是祖父结点
  4. // p是待旋转结点
  5. // r是p的右孩子结点
  6. // rl是r的左孩子结点
  7. TreeNode<K,V> r, pp, rl;
  8. if (p != null && (r = p.right) != null) {
  9. // 如果rl不为空,则设置p.right=rl
  10. if ((rl = p.right = r.left) != null)
  11. rl.parent = p;
  12. // 如果祖父结点为null,那么r设置为黑色,r左旋之后即为root节点
  13. if ((pp = r.parent = p.parent) == null)
  14. (root = r).red = false;
  15. // 如果待旋转结点是左孩子节点
  16. else if (pp.left == p)
  17. pp.left = r;
  18. // 如果待旋转结点为右孩子
  19. else
  20. pp.right = r;
  21. r.left = p;
  22. p.parent = r;
  23. }
  24. return root;
  25. }
  26. static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
  27. TreeNode<K,V> p) {
  28. TreeNode<K,V> l, pp, lr;
  29. if (p != null && (l = p.left) != null) {
  30. if ((lr = p.left = l.right) != null)
  31. lr.parent = p;
  32. if ((pp = l.parent = p.parent) == null)
  33. (root = l).red = false;
  34. else if (pp.right == p)
  35. pp.right = l;
  36. else
  37. pp.left = l;
  38. l.right = p;
  39. p.parent = l;
  40. }
  41. return root;
  42. }

HashMap的树话

  1. final void treeify(Node<K,V>[] tab) {
  2. TreeNode<K,V> root = null;
  3. // 遍历当前链表
  4. for (TreeNode<K,V> x = this, next; x != null; x = next) {
  5. next = (TreeNode<K,V>)x.next;
  6. x.left = x.right = null;
  7. if (root == null) {
  8. x.parent = null;
  9. x.red = false;
  10. root = x;
  11. }
  12. else {
  13. K k = x.key;
  14. int h = x.hash;
  15. Class<?> kc = null;
  16. // 每遍历一个链表上的元素就插入到红黑树中
  17. for (TreeNode<K,V> p = root;;) {
  18. int dir, ph;
  19. K pk = p.key;
  20. // 判断待插入结点应该插入在左子树还是右子树
  21. // 先比较hash值
  22. if ((ph = p.hash) > h)
  23. dir = -1;
  24. else if (ph < h)
  25. dir = 1;
  26. // 如果hash值相等,然后比较k.compareTo(pk)
  27. else if ((kc == null &&
  28. (kc = comparableClassFor(k)) == null) ||
  29. (dir = compareComparables(kc, k, pk)) == 0)
  30. // 如果还相等则再比较identityHashCode
  31. dir = tieBreakOrder(k, pk);
  32. // 根据dir的值就知道了待插入结点该插在左子树还是右子树了
  33. TreeNode<K,V> xp = p;
  34. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  35. x.parent = xp;
  36. if (dir <= 0)
  37. xp.left = x;
  38. else
  39. xp.right = x;
  40. root = balanceInsertion(root, x);
  41. break;
  42. }
  43. }
  44. }
  45. }
  46. moveRootToFront(tab, root);
  47. }

PUT的过程

  1. 生成hashCode
  2. 判断数组是否为空,初始化数组
  3. 根据hashCode算出数组下表
  4. 判断当前数组元素是否为空

如果为空:封装node 对象
如果不为空:
如果当前类型是是treeNode, 就是红黑树,把key value插入红黑树中
如果不是treeNode类型,就是链表
链表方式查找,封装node插入尾部,
判断个数是不是大于8,如果是转成红黑树
5:. modeCount ++
6。 HashMap元素size +1
7:判断是否扩容

get的过程

  1. 根据key生成hashCode
  2. 如果数组为空,直接返回
  3. 计算数组下表
  4. 当前数组是否为空,空直接返回
  5. 判断该数组第一个位置上的key是否相同
  6. 如果不等,还没有下个节点就返回
  7. 有下个节点判断是链表,还是红黑树
    1. 遍历链表
    2. 遍历红黑树
  8. 找到返回