亲自动手绘图——红黑树,我不信还手撕不清楚

红黑树及其绘制 红黑树是重要的数据结构,而其操作又很复杂,如果能够可视化地展示插入与删除过程,则学习起来会容易得多。 为了学习它们,我翻译以下文章(论文)并实现了相应算法,并放到网络上,与说中文的程序爱好者共同进步。 1、红黑树-[英文版, Wikipedia] 2、Tidy Drawings of Trees-[英文版, Charles Wetherell & Alfred Shannon] 3、Tidier Drawings of Trees-[英文版, Edward M. Reingold & John S. Tilford] 压缩包里有英文原文、文章翻译和C#实现的源代码。翻译水平不高,还望多多谅解,欢迎交流。 欢迎复制传播文章翻译,但要保证完整性,并注出处。 C#实现的源代码也可以复制传播,详见源文件注释,使用时也请注明出处。 熊怀东 shoulea@163.com 12:37 2012/8/25 立即下载

前言

红黑树是自平衡的二叉查找树,在许多地方都有实际应用比如JAVA的HashMap,在链表长度大于8就会转化为红黑树;在linux经典的epoll中也使用了红黑树来保存文件描述符的插入删除操作。如果频繁地对数据进行插入删除,还要保证效率,使用红黑树是比较好的选择。

红黑树在实际表现上,还是一颗二叉树。在许多性质上和二叉树一样,所以要从二叉树查找开始讲起。

二叉查找树

二叉查找树是最简单的实现,规定左子节点一定小于右子节点。

插入情况只需要对当前节点比大小,不断的递归直到遇到空节点插入。

查询和插入相同,不断比大小即可,在这一点上红黑树和二叉查找树是一致的。

通过这个性质可以轻松写出找到最大值、最小值,删除最大值、最小值的代码,就是一直遍历左或者右节点就能找到最值

//最小值
func min(x *node) *node {
	if x.left == nil {
		return x
	}
	return Min(x.left)
}
//最大值
func max(x *node) *node {
	if x.right == nil {
		return x
	}
	return Max(x.right)
}
//删除最小值
func deleteMin(x *node) *node {
	if x.left == nil {
		return nil
	}
	x.left = deleteMin(x.left)
	x.n = size(x.left) + size(x.right) + 1
}
//删除最大值只需要改变代码中left为right

删除最大值或最小值就是找到那个节点然后断开,再把那个节点的子节点连接上它的父节点,因为最小值那个节点的子节点只可能有一个

稍微复杂一点的就是删除操作,对于任意一个节点,删除意味着断开这个点的所有连接,如果这个点有子节点,那么需要另外一个点来替代它,这个点添加进去后要使得二叉查找树性质不变。根据性质可以找到这个点就是被删除节点的右子节点的最小值

如图,删除4这个点,需要用它的右子节点6后的最小节点,也就是5来填充4这个位置,代码如下:

 public Node delete(Node x, Key key) {
        if (x == null) {
            return null;
        }
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            x.right = delete(x.right, key);
        } else if (cmp < 0) {
            x.left = delete(x.left, key);
        } else {
            Node t = x;
            x = min(t.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        }
        x.n = size(x.left) + size(x.right) + 1;
        return x;
    }

不断递归找到需要删除的点,然后断开该点的前后连接,删除它右子节点的最小节点来替代原来的位置

删除节点有一个核心思路就是要找到被删除节点的右子节点后的最小节点,在二叉查找树可以很容易地把点移动,但是对于红黑树来说,就不能简单的移动某个点,但是都需要经过这个过程。

二叉查找树的查询速度看起来和二叉查找相似,但是插入和删除不需要像数组那样移动元素,听起来效率挺高,但是存在最坏情况是:插入一些已经排序过的节点,比如0-100,在每次插入过程都插入右节点,这样就查找树就退化成普通链表。

2-3树

普通二叉查找树无法适应最坏情况,如果有一种树能够适应各种不同的数据情况,让运行情况都在对数级别,就能够解决二叉树查找树不稳定的缺点。

为了解决这种情况,保证树的平衡性,适当地改造一下二叉树,普通的二叉树只保存一个值和两个左右节点,现在将树改造成一个节点能够保存2个值,而有三条指针指向其他节点,形成左-中-右节点,这样的节点称为3-节点

如图,一棵树存在以上两种节点,3-节点中间的节点表示:左值<中节点<右值

对于这样的节点,在插入节点的时候需要一些变换才能保证树的平衡性

情况1:插入的节点是2-节点

直接将2-节点变成3-节点

情况2:插入的节点是个3-节点

如图,重新构造3-节点,浮动到父节点

情况3:父节点是3-节点,对父节点插入

父节点是3-节点,对其进行插入,会使得父节点分裂成3个2-节点

上面只是展示了几种情况,实际上如果在树中间插入一个节点,在节点变换的时候还要考虑子节点的情况,添加一个新的3-节点,需要将原有的2-节点进行复制,维护两种不同的节点,增加了额外的开销,实在是有些得不偿失

红黑树

对于2-3树需要新增3-节点的数据结构,虽然有缺点,但是理解实现起来并不复杂,完全可以用标准二叉树的结构,只需要增加额外的信息就可以来表示3-节点。

在内部使用一条红色的节点来表示3-节点,a这个节点的为红节点

//节点表示代码 go语言实现,后面方一个java版本
const (
	BLACK = false
	RED   = true
)
//节点
type node struct {
	key         int
	val         interface{}
	left, right *node
	n           int
	color       bool
}

func newRedNode(key int, val interface{}) *node {
	return &node{
		key:   key,
		val:   val,
		n:     1,
		color: RED,
	}
}
//红黑树结构
type RedBlackTree struct {
	root *node
}

func (b *RedBlackTree) size(x *node) int {
	if x == nil {
		return 0
	}
	return x.n
}

func (b *RedBlackTree) Size() int {
	return b.size(b.root)
}

n表示该节点下面还有多少节点,插入新节点总是红色,用布尔类型来表示红黑

把红黑树比作2-3树的表示方式,那么红黑树是平衡的,红黑树满足以下条件:

  1. 红链接均为左边(ps:方便3-节点的表示)
  2. 任何一个节点不能同时和红链接连接(ps:不然一个节点变成了4-树)
  3. 跟节点不能是红链接

以上这三条规则共同组成了红黑树的规则:++任意一条空链接到root节点的距离都是相等的。(只算黑链接的距离,不计算红链接)++

红黑树的插入情况和上面图片中2-3树的三种情况一样,但是只需要维护红链接的规则即可,规则3很容易解决,只需要将root节点的连接设置为BLACK就行了,主要是考虑另外两种情况

违反规则1

对于(1)情况,只需要将节点插入就行了,不需要做其他操作,因为插入总是在树底插入,而且红链接也是在左边,符合规则。

对于(2)情况,插入是在右边,就违背了规则1,需要将(2)变为(1)相同的结构,需要一个操作叫旋转。

左旋代码很简单,只需改变一下连接关系

func (b *RedBlackTree) rotateLeft(h *node) *node {
	x := h.right
	h.right = x.left
	x.left = h
	x.color = h.color
	h.color = RED
	x.n = h.n
	h.n = b.size(h.left) + b.size(h.right) + 1
	return x
}

注意旋转要改变节点的数量,目标节点会增加

违反规则2

违反规则2在插入时候可能会出现(1)(2)两种情况,最终是要变为(3)这种情况(如果3情况是跟节点,只需要将头部的红色链接变为黑色),如果是(1)情况,需要转变为(2),再转换为(3),这个时候需要与刚才相反的操作——右旋

与左旋完全相反的操作

func (b *RedBlackTree) rotateRight(h *node) *node {
	x := h.left
	h.left = x.right
	x.right = h
	x.color = h.color
	h.color = RED
	x.n = h.n
	h.n = b.size(h.left) + b.size(h.right) + 1
	return x
}

注意:左旋和右旋都是对子节点的判断

总结一下插入操作只需要在插入节点后递归向上处理是否违反规则情况,在规则1处理后有可能会违反规则2,所以顺序处理:

  1. 如果左边不是为红链接,右节点为红————>左旋
  2. 如果左节点为红,左节点的左节点为红————>右旋转
  3. 如果左右都为红节点————> 变成黑色

代码:

//判断节点颜色
func isRed(x *node) bool {
	if x == nil {
		return false
	}
	return x.color == RED
}

//变换反色
func reverseColor(x *node) {
	x.color = !x.color
}

//如果跟节点为红,子节点为黑或者跟节点为黑子节点为红就取反色
func flipColor(x *node) {
	rootRed := isRed(x) && !isRed(x.left) && !isRed(x.right)
	rootBlack := !isRed(x) && isRed(x.left) && isRed(x.right)
	if rootRed || rootBlack {
		reverseColor(x)
		reverseColor(x.left)
		reverseColor(x.right)
	}
}

//插入方法的平衡树方法,基于上面叙述翻译
func (b *RedBlackTree) balance(x *node) *node {
	if isRed(x.right) && !isRed(x.left) {
		x = b.rotateLeft(x)
	}
	if isRed(x.left) && isRed(x.left.left) {
		x = b.rotateRight(x)
	}
	if isRed(x.left) && isRed(x.right) {
		flipColor(x)
	}
	return x
}

插入方法

//跟节点永远都是黑色,key用int表示方便一点
func (b *RedBlackTree) Put(key int, val interface{}) {
	b.root = b.put(b.root, key, val)
	b.root.color = BLACK
}
func (b *BlackReadTree) put(x *node, key int, val interface{}) *node {
	if x == nil {
		return newRedNode(key, val)
	}
	compare := x.key - key
	if compare > 0 {
		x.left = b.put(x.left, key, val)
	} else if compare < 0 {
		x.right = b.put(x.right, key, val)
	} else {
		x.val = val
	}

	return b.balance(x)
}

插入方法和普通二叉树查找树一致的,只是最后需要平衡树操作,所以需要递归自下而上平衡每个节点,这一个平衡操作不管是插入删除都需要用到。

删除

红黑树的删除是最复杂的操作,相比于红黑树插入只需要找到节点然后自下而上平衡即可。删除操作需要找到目标点,然后像二叉查找树一样删除右子节点的最小节点来填充被删除的部分,但却并不能随意的删除一个节点,因为这样做会导致一个空链接出现,破坏了红黑树的完美平衡性。

首先要介绍删除最小节点和最大节点的方法,因为删除操作中需要用到该方法,通过以下几种情况来了解删除操作可能发生的情况。

情况1:删除的节点是一个2-节点

删除一个2-节,不能直接把这个节点删除,因为删除一个节点后会导致红黑树完美平衡被破坏,所以需要一个操作:++借++,为了保证平衡所以需要像右边借一个节点来左边替代被删除节点的位置。

但是像如图这种情况明显借不到一个节点能够代替被删除节点,那么只能把它变为4-节点,然后删除一个节点使得4-节点变为3-节点,相当于降低树的层数。

如果可以借到节点呢?

如果右边有一个红色的节点,可以经过两次旋转操作就借到了节点,也不会破坏树的平衡性。

情况2:删除的节点是3-节点

如果是3-节点直接删除即可

以上是删除2-节点最简单的情况(3-节点很简单),如果稍微复杂一点呢?

对于这样一棵树删除a节点就比较复杂了,如果只看abc这三个节点,和情况1是一模一样的,可以变换为b-c形式,但是却不能直接和剩下的节点连接。

这种情况说明了一个问题,在删除最小节点时候不能在找到了该节点的时候再做变换,而是从一开始就要准备删除节点时候的变换。换句话来说:借一个节点要趁早,不要等到找到了最小节点才去右边借,而是一开始能借就借,借不到就变为4-节点,节点怎么变化只要符合二叉树原理即可。这时候你会问,如果后面能借到节点,前面借的节点怎么办?

没关系,用balance还回去就行了,一个借4-节点如果没有被使用,只需要把链接都变为黑色就变回了4-节点,这一点和插入使用的balance方法是一样的。

流程为:

  1. 判断左子是不是2-节点(当前节点的left和left.left是不是黑色,空节点为黑色)
  2. 如果是一个2-节点,就判断能不能从右边借一个节点(right.left是不是红链接)
  3. 如果能借到节点就进过两次旋转,如果借不到就变成一个4-节点,等待下面删除
  4. 不断递归调用balance方法平衡树

其中需要注意的一点就是,为了保证平衡操作能够正常运行,不管有没有++借++到节点,都将他们变为4-节点(左右子节点都是红链接),这样可以保证balance操作的时候能够正确平衡节点

那么整个操作流程图为:

  1. 从跟节点c开始,判断是需要借一个节点的,先把节点变为4-节点,但是没有借到,继续向下走
  2. 到了e节点,判断是需要借一个节点,先把节点变为4-节点(需要将自己的链接变为黑色,不然就是5-节点),发现借不到,继续向下
  3. 找到了a节点,发现a节点的链接已经是红色了,直接删除即可
  4. 递归到b节点开始平衡,按照平衡规则右边不能出现红链接,左旋
  5. 递归到e节点开始平衡,按照平衡规则右边不能出现红链接,左旋

除此之外,删除一个最小节点并没有太多情况,因为如果一颗完美平衡的红黑树,在每一层往下走的时候,就只有能借到节点、借不到节点、不需要借的情况,仔细思考一下。从右子节点拿一个红链接的节点对树的平衡没有什么影响,能拿就拿,不能拿就降低树的层数(变为3-节点),反正如果借用的节点没有用到,balance会还回去,这就是删除最小节点的思路

代码

func (b *RedBlackTree) borrowLeft(x *node) *node {
	//节点变换为4-节点 
	flipColor(x)
	//能借到节点就经过两次旋转
	if !isRed(x.left) && isRed(x.right.left) {
		x.right = b.rotateRight(x.right)
		x = b.rotateLeft(x)
	}
	return b.balance(x)
}

func (b *BlackReadTree) DeleteMin() {
	if !isRed(b.root.left) && !isRed(b.root.right) {
		b.root.color = BLACK
	}
	b.root = b.deleteMin(b.root)
	if !b.Empty() {
		b.root.color = RED
	}
}

//主要逻辑
func (b *RedBlackTree) deleteMin(x *node) *node {
	if x.left == nil {
		return nil
	}
	//判断是否需要借一个节点
	if !isRed(x.left) && !isRed(x.left.left) {
		x = b.borrowLeft(x)
	}
	x.left = b.deleteMin(x.left)

	return b.balance(x)
}

func (b *RedBlackTree) Empty() bool {
	return b.root == nil
}

删除最大值和删除最小值类似,删除最小值需要向右边借用一个节点,而删除最大值向左边借节点,向左边借节点只需要一次循转,但是左边有两种情况

  1. 在自己的左边,右旋自己
  2. 在父节点的左边有红链接,右旋父节点

整个流程和删除最小值区别主要是在于对于“借”节点的判断,代码如下:

func (b *RedBlackTree) borrowRight(x *node) *node {
	flipColor(x)
	//如果左边能借向左边借
	if isRed(x.left.left) {
		x = b.rotateRight(x)
	}
	return b.balance(x)
}
func (b *RedBlackTree) DeleteMax() {
	if !isRed(b.root.left) && !isRed(b.root.right) {
		b.root.color = BLACK
	}
	b.root = b.deleteMax(b.root)
	if !b.Empty() {
		b.root.color = RED
	}

}

func (b *RedBlackTree) deleteMax(x *node) *node {
	//自己左边是红链接直接旋转
	if isRed(x.left) {
		x = b.rotateRight(x)
	}
	if x.right == nil {
		return nil
	}
	if !isRed(x.right) && !isRed(x.right.left) {
		x = b.borrowRight(x)
	}
	x.right = b.deleteMax(x.right)
	return b.balance(x)
}

删除最大和最小键可以得出一个思路,不断的借用一个节点来保证当前节点不是2-节点,而是一个3-节点或者4-节点,这样可以安全的删除一个节点而不用担心删除一个2-节点导致树平衡问题,然后自底向上平衡树。

最复杂的红黑树删除来了,删除操作结合删除最大值、最小值和普通二叉查找树的思想,由于每一轮向下遍历树的时候是不确定删除的节点最终是在树的哪个位置(删除最大最小值可以确定),所以需要结合删除最值的特性。

  • 二叉查找树特性:找到删除节点后,需要拿到右子节点的最小值填充,如果右子节点不存在可以直接删除
  • 每一轮遍历的时候,将删除最大值最小值判断结合起来,能向左边借就向左边借节点,能向右边借就向右边借,最后再平衡

融合代码如下:

func (b *RedBlackTree) delete(x *node, key int) *node {
	//借右节点
	if key <x.key {
		if !isRed(x.left) && !isRed(x.left.left) {
			x = b.borrowLeft(x)
		}
		x.left = b.delete(x.left, key)
	} else {
		//借右边第一种情况
		if isRed(x.left) {
			x = b.rotateRight(x)
		}
		//二叉查找树无右节点删除情况
		if key == x.key && x.right == nil {
			return nil
		}
		//借右边第二种情况
		if !isRed(x.right) && !isRed(x.right.left) {
			x = b.borrowRight(x)
		}
		//二叉查找树删除方法
		if key == x.key {
			x.key = min(x.right).key
			x.val = min(x.right).val
			x.right = b.deleteMin(x.right)
		//继续递归
		} else {
			x.right = b.delete(x.right, key)
		}
	}
	return b.balance(x)
}

完整删除方法

func (b *RedBlackTree) Delete(key int) {
	if !isRed(b.root.left) && !isRed(b.root.right) {
		b.root.color = BLACK
	}
	b.root = b.delete(b.root, key)
	if !b.Empty() {
		b.root.color = RED
	}
}
func min(x *node) *node {
	if x.left != nil {
		return min(x.left)
	}
	return x
}

最后贴一个JAVA代码

public class RedBlackTreeMap<Key extends Comparable<Key>, Value> {
    private final boolean RED = true;
    private final boolean BLACK = false;

    private Node root;

    private class Node {
        Key key;
        Value val;
        Node left, right;
        boolean color;
        int n;

        public Node(Key key, Value val, boolean color, int n) {
            this.key = key;
            this.val = val;
            this.color = color;
            this.n = n;
        }
    }

    public void put(Key key, Value val) {
        root = put(root, key, val);
        root.color = BLACK;
    }

    private Node put(Node x, Key key, Value val) {
        if (x == null) {
            return new Node(key, val, RED, 1);
        }
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            x.left = put(x.left, key, val);
        } else if (cmp > 0) {
            x.right = put(x.right, key, val);
        } else {
            x.val = val;
        }
        if (isRed(x.right) && !isRed(x.left)) {
            x = rotateLeft(x);
        }
        if (isRed(x.left) && isRed(x.left.left)) {
            x = rotateRight(x);
        }
        if (isRed(x.left) && isRed(x.right)) {
            flipColor(x);
        }
        return x;
    }

    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        while (x != null) {
            int cmp = key.compareTo(x.key);
            if (cmp < 0) {
                x = x.left;
            } else if (cmp > 0) {
                x = x.right;
            } else {
                return x.val;
            }
        }
        return null;
    }

    public void deleteMin() {
        if (isEmpty()) {
            throw new RuntimeException();
        }
        if (!isRed(root.left) && !isRed(root.right)) {
            root.color = RED;
        }
        root = deleteMin(root);
        if (!isEmpty()) {
            root.color = BLACK;
        }
    }

    public void deleteMax() {
        if (isEmpty()) {
            throw new RuntimeException();
        }
        if (!isRed(root.left) && !isRed(root.right)) {
            root.color = RED;
        }
        root = deleteMax(root);
        if (!isEmpty()) {
            root.color = BLACK;
        }
    }

    public void delete(Key key) {
        if (isEmpty()) {
            throw new RuntimeException();
        }
        if (!isRed(root.left) && !isRed(root.right)) {
            root.color = RED;
        }
        root = delete(root, key);
        if (!isEmpty()) {
            root.color = BLACK;
        }
    }

    private Node delete(Node x, Key key) {
        if (key.compareTo(x.key) < 0) {
            if (!isRed(x.left) && !isRed(x.left.left)) {
                x = moveLeft(x);
            }
            x.left = delete(x.left, key);
        } else {
            if (isRed(x.left)) {
                x = rotateLeft(x);
            }
            if (key.compareTo(x.key) == 0 && x.right == null) {
                return null;
            }
            if (!isRed(x.right) && !isRed(x.right.left)) {
                x = moveRight(x);
            }
            if (key.compareTo(x.key) == 0) {
                x.val = get(x.right, min(x.right).key);
                x.key = min(x).key;
                x.right = deleteMin(x.right);
            } else {
                x.right = delete(x.right, key);
            }
        }
        return balance(x);

    }

    private Node deleteMin(Node x) {
        if (x == null) {
            return null;
        }

        if (!isRed(x.left) && !isRed(x.left.left)) {
            x = moveLeft(x);
        }
        x.left = deleteMin(x.left);
        return balance(x);
    }

    private Node deleteMax(Node x) {
        if (x == null) {
            return null;
        }
        if (!isRed(x.right) && !isRed(x.right.left)) {
            x = moveRight(x);
        }
        x.right = deleteMax(x.right);
        return balance(x);
    }

    public int size() {
        return size(root);
    }

    private int size(Node x) {
        if (x == null) {
            return 0;
        }
        return x.n;
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    private void flipColor(Node x) {
        boolean rootRed = x.color == RED && x.left.color == BLACK && x.right.color == BLACK;
        boolean rootBlack = x.color == BLACK && x.left.color == RED && x.right.color == RED;
        if (rootBlack || rootRed) {
            x.color = !x.color;
            x.left.color = !x.left.color;
            x.right.color = !x.right.color;
        }

    }

    private Node rotateLeft(Node x) {
        Node h = x.right;
        x.right = h.left;
        h.left = x;
        h.color = x.color;
        x.color = RED;
        h.n = x.n;
        x.n = size(h.left) + size(h.right) + 1;
        return h;
    }

    private Node rotateRight(Node x) {
        Node h = x.left;
        x.left = h.right;
        h.right = x;
        h.color = x.color;
        x.color = RED;
        h.n = x.n;
        x.n = size(h.left) + size(h.right) + 1;
        return h;
    }

    private boolean isRed(Node x) {
        if (x == null) {
            return false;
        }
        return x.color == RED;
    }

    private Node moveLeft(Node x) {
        flipColor(x);
        if (isRed(x.right.left)) {
            x.right = rotateRight(x.right);
            x = rotateLeft(x);
        }
        return balance(x);
    }

    private Node moveRight(Node x) {
        flipColor(x);
        if (isRed(x.left.left)) {
            x = rotateRight(x);
        }
        return balance(x);
    }

    private Node balance(Node h) {

        if (isRed(h.right)) {
            h = rotateLeft(h);
        }
        if (isRed(h.left) && isRed(h.left.left)) {
            h = rotateRight(h);
        }
        if (isRed(h.left) && isRed(h.right)) {
            flipColor(h);
        }

        h.n = size(h.left) + size(h.right) + 1;
        return h;
    }

    private Node min(Node x) {
        if (x.left == null) {
            return x;
        }
        return min(x.left);
    }

    private Node max(Node x) {
        if (x.right == null) {
            return x;
        }
        return max(x.right);
    }
红黑树画图C# C#编程,包含源码,可执行程序,用到的文件,画图可实现动态,实现学生成绩存储,仅供参考。 立即下载

相关推荐

Dirsearch的下载、安装和使用(全)

Dirsearch工具的下载、安装和使用,超详细超全面

weixin_68416970的博客 1万+

【两步】教你学会画红黑树

目录红黑树的特性:第一步,画2-3-4树。第二步,用这棵2-3-4树,画红黑树。彩蛋 红黑树的特性: 1.每个节点不是红色就是黑色 2.根节点是黑色 3.每个叶子节点(NIL节点,空节点)是黑色的 4.如果一个节点是红色的,则他的子节点必须是黑色的 5.从一个节点到该节点的所有子孙节点的所有路径上包含相同数目的黑色节点 根据这5个特性,我们以{1,2,3,4,5,6,7,8,9,10,11}为例,画一个红黑树。 第一步,画2-3-4树。 2-3-4树可参考文章: https://wenku.bai

qq_45754768的博客 1086

Diffix3D+复现

上下载模型,将其放在一个目录中,并将目录名填在demo代码中from_pretrained的cache_dir参数里。目录结构应该是"<PATH>/nvidia/difix/…给的requirements文件中pytorch和xformers的版本没有指定,这里测试过以下版本,可以成功运行。将readme中的demo代码新建一个python脚本复制进去,然后将脚本放在src目录中运行。如果遇到网络问题无法加载huggingface上的预训练模型,可以手动从。如果出现,以下错误重新。

NeverMakeIt的博客 1088

亲自动手画红黑树

在前面我们学习了平衡二叉树,伸展树,今天我们来看看另外一种平衡二叉树—红黑树,本来这篇博客早在一年前就该写的,后来发生了太多故事,博客停止了更新了一年,如今又有了最初的斗志,决定好好把博客写下去,对知识的梳理的同时,也不断巩固。 对于各种平衡树,我只能进行简单的理解和实现,无法涉及太多的应用,主要的还是本身自己知识广度和深度有限,不过我相信:不积跬步,无以至千里 本文主要参考了算法导论一书 ...

小张的专栏 4134

芯片封装、功率密度提升及热管理系统优化三个维度分析高功率芯片散热技术挑战

本文探讨高功率芯片散热技术挑战与创新。随着新能源汽车MOSFET用量增至400颗/车,文章聚焦封装技术创新(双面散热、集成模块)、功率密度提升(SGT-MOSFET优化)和热管理系统协同优化三大方向。通过热仿真案例(如CPO散热优化)展示温升改善方案,强调需协同器件级设计、系统级仿真与先进封装技术(混合键合/玻璃基板)来解决高密度互连与散热矛盾,为功率密度跃升提供技术支撑。

莱歌数字的博客 1475

IntelliJ IDEA 2025.1 + Gradle 镜像配置避坑指南:解决依赖下载慢的终极方案

本文针对IntelliJ IDEA 2025.1开发环境中Gradle依赖下载缓慢的问题,提供了详细的镜像配置避坑指南。通过解析Gradle依赖解析机制,重点指导如何正确配置阿里云等国内镜像仓库,并分享验证配置生效、排查常见错误及高级优化技巧,旨在彻底解决依赖下载难题,提升开发效率。

weixin_28835551的博客 532

红黑树的插入过程(手动画图)

手动画的红黑树的插入过程

zh_ch_yu的博客 436

使用C# HslCommunication库实现三菱PLC通信(含完整代码示例)

在工业自动化开发中,上位机与PLC的通信是关键环节。HslCommunication库是一个开源的.NET工业通信库,支持三菱、西门子、欧姆龙等多个品牌PLC的通信协议。本文将演示如何通过库实现C#程序与三菱PLC的通信,并提供完整的代码示例。

StevenChen的博客 2294

手撕红黑树Java_亲自动手绘图——红黑树,我不信还手撕不清楚

前言红黑树是自平衡的二叉查找树,在许多地方都有实际应用比如JAVA的HashMap,在链表长度大于8就会转化为红黑树;在linux经典的epoll中也使用了红黑树来保存文件描述符的插入删除操作。++如果频繁地对数据进行插入删除,还要保证效率,使用红黑树是比较好的选择。++红黑树在实际表现上,还是一颗二叉树。在许多性质上和二叉树一样,所以要从二叉树查找开始讲起。二叉查找树二叉查找树是最简单的实现,规...

weixin_39567943的博客 230

性能优化-高通cDSP(QDSP6)架构详解

高通cDSP开发的商业价值30秒快速认识高通cDSP开发的商业价值【高通cDSP旨在处理一些简单但需要大量计算的场景,如图像处理、科学计算、AI训练、计算机视觉等。这些场景使用CPU来运算会极大的降低CPU的吞吐量,因此利用现代寄存器较宽的特性设计出了适配数据并行的DSP芯片,cDSP就是典型的代表。利用cDSP+CPU既可以满足一般的使用场景,也可以完成现代图像和视频数据的处理。

发狂的小花的博客 5947

Trae的付费之路还挺艰难,茫茫多的人想付付不了(附解决办法)

不得不说,Trae国际版这个付费其实整的挺复杂的,但没办法,毕竟有政策限制,付费成功后,你就是尊贵的Pro用户啦,使用Claude 4不排队还是挺爽的,而且600次其实不算少了,有一个小技巧,简单的问题就把模型切到Gemini-2.5-flag,因为使用这个模型是没有次数限制的。其次付费的时候有个ZIP Code,这东西也是个迷之存在,有的地区节点需要填,有的不需要,如果你选到了不需要填的节点,那很好,付费就行了,如果需要填,可以填一个51800试试。

cpp加油站的专栏 5883

红黑树

什么是红黑树红黑树是特殊的二叉搜索树,它比二叉搜索树多了一点颜色. 红黑树的五个性质: 1. 每一个结点要么是红色,要么是黑色 2. 根结点必须是黑色 3. 每个叶子结点(NIL)为黑色 4. 如果一个结点是红色,那么它的两个孩子结点都为黑色 5. 对于每个结点,从该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点红黑树的结构体如下: 至少包含:color, key,

jun2016425的博客 438

Flink实战案例(十三):Flink DataStream 八大分区策略与自定义分区器

声明:本系列博客是根据SGG的视频整理而成,非常适合大家入门学习。 《2021年最新版大数据面试题全面开启更新》 分区策略决定了一条数据如何发送给下游。Flink中默认提供了八大分区策略(也叫分区器)。 本文基于Flink 1.9.0总结Flink DataStream中的八大分区策略以及手动实现一个自定义分区器。 八大分区策略继承关系图 ChannelSelector: 接口,决定将记录写入哪个Channel。有3个方法: void setup(int numberOfChannels):

微信搜:import_bigdata,大数据领域硬核原创作者 1125

UML入门

举一个简单的例子,来看这样一副图,其中就包括了UML类图中的基本图示法。   首先,看动物矩形框,它代表一个类(Class)。类图分三层,第一层显示类的名称,如果是抽象类,则就用斜体显示。第二层是类的特性,通常就是字段和属性。第三层是类的操作,通常是方法或行为。前面的符号,+ 表示public,- 表示private,# 表示protected。 右下角的飞翔,它表示一个接口图,

罗先生的专栏 2417

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

推荐一个好用的线上画算法图的实现(B树、栈、红黑树……)

地址:点击打开链接

强哥叨逼叨 5900

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

红黑树2——怎么画红黑树

在了解红黑树之前我们需要先了解2-3-4树,因为我们手动构建红黑树的话就需要先构建2-3-4树。

perturb的博客 362

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

红黑树之插入后调整流程图

依据TreeMap中fixAfterInsertion方法流程得到下面的流程图 X:表示插入节点 P:表示插入节点的父节点 parentOf(x) U:表示插入节点的叔节点 rightOf(parentOf(parentOf(x)) or leftOf(parentOf(parentOf(x)) G:表示插入节点的父节点的父节点 parentOf(parentOf(x)) private...

NO CODE NO LIFE 396

红黑树的插入全过程图化

本篇是在之前的学习插入时的补充篇幅。目标时从头图化出一个完整的红黑树的构造过程。 理论参考 构建过程中随机生成一个数字作为红黑树的域。 Case场景分为两种,一种是CaseA1,2,3系列,一种是CaseB1,2,3系列,具体参见理论参考中的修正操作伪代码。 插入节点275。 没啥好说的,根节点了,黑色。 插入节点711。 插入节点260。 插入节点515(满足Case...

lwgzj的博客 5085

InnoDB 内存结构总览:Change Buffer、AHI 与 Log Buffer

本文系统梳理InnoDB内存结构中的ChangeBuffer、Adaptive Hash Index(AHI)与LogBuffer。ChangeBuffer缓存二级索引页不在内存时的变更,减少随机IO,仅在页加载时合并;聚簇索引修改立即执行,因数据本身不可延后。AHI基于高频等值查询自动构建哈希索引,加速定位,仅支持等值操作。LogBuffer为redo log内存缓冲,配合磁盘日志实现崩溃恢复。三者均服务于性能优化,其中ChangeBuffer与AHI共享BufferPool空间,而LogBuffer独立

bksczm的博客 307

信创环境下PageOffice文档控件适配与实践

信创适配做到文档控件这一层,拼的已经不是"有没有国产版本",而是同一个功能在两类终端上是否真的等价。等价的标准很具体:同一次点击能得到同样的结果,同一份公文能得到同样的版式,同一个项目不需要维护两套代码。这背后是三件并不轻松的工作:服务端用一套接口同时支撑两类终端,客户端把两种系统与四种芯片的安装包分发清楚,文档内核层由产品侧跟着 WPS 的版本走。浏览器这一层的压力,则被收进了产品自己的窗口里——升级节奏由产品掌握,不再交给运气。

zi_wu_xian的专栏 334

第三篇 第 6 课

前置:第三篇第 1 课(stdx 引入),第二篇第 1 课(std.regex)。环境:仓颉 SDK 1.2.0。本文所有代码与输出均逐行实测。

weixin_41618057的博客 557

【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端

上一篇讲了 MCP 的概念:标准协议、三大角色、M+N 替代 M×N。

nice_lcj520的博客 605

Java 集合容器高频面试题全解:HashMap 树化阈值、1.7 死循环与 ConcurrentHashMap 演进(2026 招聘 JD 真题复盘)

HashMap 树化阈值 8 的泊松分布依据、退化阈值 6 的防抖设计、JDK 1.7 头插成环死循环的排查与修复、1.8 尾插后为何仍线程不安全、ConcurrentHashMap 放弃分段锁的原因与 ForwardingNode 协作扩容、computeIfAbsent 死锁陷阱、大集合找重复的场景题选型,附三层追问链自测。题目来源为 38 期公开招聘 JD 样本与字节、拼多多、建行、美团、快手公开面经。

蒙眼过河 425

spdlog 同步日志库使用详解

spdlog是一个轻量级 C++ 日志库,支持同步日志、异步日志、多线程日志、日志文件轮转和自定义日志格式。对于嵌入式 Linux 应用开发,我建议先使用。它可以帮我们完成:日志级别过滤。时间戳和线程 ID 输出。日志格式化。终端和文件输出。多线程安全写入。日志文件按大小或时间轮转。我们不需要自己实现互斥锁、文件写入、日志格式化和轮转等底层功能。

zengjiajun2006的博客 1121
上一篇: 一文带你玩转 Redis 的 RESP 协议 !
下一篇: 凡人修仙传!大专毕业3年,我从月薪3k的码农变成大厂的Java架构师!
JavaCaiy
博客等级 码龄6年 1万+粉丝 95原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值