【二叉平衡搜索树】Treap

news2024/12/25 13:37:50

前置

本篇是平衡树-treap的补充学习笔记。

Treap - 树堆
学习基础:适合一定基础的:比如,实现了经典二叉搜索树(常用的几个函数写过), 和二叉堆(数组的上浮下沉会写吗?),至少了解旋转的概念且写过旋转的接口(有一定理解,比如实现AVL树/红黑树)(涉及到相关思想)。
实现语言:Java。 —其它语言的小伙伴可拷贝我的代码转译成自己的语言(笔者虽然多语言学习,但使用侧重点不同, 对语言的理解也浅薄, 频繁切换语言不妥。)
纯根据理解手写。
来源:算法导论---红黑树章节后的treap思考题
参考书籍:算法导论

  • 有关问题:
  • 算法导论第6章堆排序详细地说明了:二叉堆概念, 上浮和下沉操作的相关概念伪代码。实现一下二叉堆(优先级队列)
  • 算法导论第12章介绍了transplant接口, 和二叉搜索树的其它好用的函数。
  • 算法导论第13章介绍了红黑树, 红黑树的代码很难写且不易理解,只需了解一下旋转这个操作和伪代码即可。
  • 可以去油管或者b站, 或者搜索引擎查找相关问题。

笔者水平有限, 本篇基于笔记改写, 缺点很多, 不太适合新手, 适合部分熟悉的朋友回顾复习, 在此见谅。

引入

  • 对于一般的二叉搜索树, 已知n个数据序列插入到一个二叉搜索树有可能得到性能极差(高度极不平衡的二叉搜索树)。这种单支树的例子, 相信你已经不会陌生了。对于随机化构建的二叉搜索树, 经过概率论分析的数学期望, 得到的二叉搜索树是趋向平衡的。
    之前写过一篇动态随机化的平衡树结构-----跳表, 非形式地讨论了这个问题。
    接下来, 介绍另一种动态随机化的平衡树结构-----Treap

为什么有了随机化构建, 而依旧有研究动态化构建的必要呢? 尽管在跳表篇给了解释, 但此篇为不甚了解的朋友作出非严格地说明。已知n个数据构建随机二叉搜索树, 当然可行。 缺点? 如果我们未知数据量n呢?或者随机化二叉搜索树,我们还要动态地插入删除呢?, 在只允许我们一次取一个数据的场景, 经典的随机化构建二叉搜索树不现实了。 我都拿不到所有数据,如何随机化地插入二叉搜索树呢? 相比Treap跳表动态树结构, 经典随机二叉搜索树可以说是静态结构。

介绍

Treap 是一种弱平衡的树结构, 什么叫做弱平衡呢? 不同平衡树对平衡的定义不同, 确切的,它们对平衡的宏观定义是相同的(维持一棵好性能的树), 但对树的平衡严格程度要求是不一样的。

一般地, 弱平衡树更好实现, 相比于强平衡的AVL(左右子树高度差不为1), 红黑树(左右高度差小于2倍), 跳表明显好实现多了。其次,强平衡树在高度上有最坏的保证(最坏也是性能比较好的),弱平衡树最坏保证是经典二叉搜索树的最坏情况(单支链表的情况)。
归咎原因, 弱平衡没有在高度上严格保证, 这里的Treap还把基本操作交给概率这一事物, 平均来看很好, 但失败的后果会很糟糕(虽然失败导致最坏的情况从概率数字来看几乎不可能发生)。

现在,我们可以正式地介绍一下Treap了,
在此,简单回顾一下:

对于二叉搜索树: 基本性质: 左子树的key <= 根 <= 右子树的key。
对于二叉堆:基本性质:孩子的key>=父亲的key(最小堆), 兄弟之间的大小关系不在意。

综合二者性质,依旧让key的关系满足: 左 < 根 < 右, 可以取等自己调整一下。但我个人风格是key最好互异。
堆的性质从何体现, 我们引入了一个priority字段,表示优先级。
类比二叉堆的最小堆, 那么最小Treap,满足parent.priority < node.priority

![[Pasted image 20240927090041.png]]

综合:
Treap 具有以下几个重要性质:

  1. 二叉搜索树性质:对于每个节点,其左子树的所有节点的键值小于该节点的键值,而右子树的所有节点的键值大于该节点的键值。假设键值是不一样的。

  2. 堆性质:每个节点的优先级大于或等于其子节点的优先级。这条性质导致这个结构和堆一致。

  3. 随机性:节点的优先级通常是随机生成器(如Java中的Random对象)生成的,这使得 Treap 的结构在插入和删除操作中保持平均 O ( l o g n ) O(log n) O(logn) 的时间复杂度。

  4. 动态平衡性:支持随机化的动态插入,删除, 比随机化二叉搜索树自由。

实现

  1. 以下代码比较简单, 若你是学Java的应该不难读懂,其它语言的小伙伴可以借助chatgpt翻译成自己的语言,这是一种我比较经典的写法了。
  
import java.util.Random;  
import java.util.Comparator;  
public class Treap<K,V> {  
    //比较器, 可以手动传递比较器对象, 否则K必须实现Comparable接口  
    private Comparator<? super K> comparator;  
    //随机数生成器, 为每一个节点生成一个随机数  
    private final Random random = new Random();  
    private Node<K,V> root;  //根节点  
    public class Node<K,V>{  
        K key;  //键  
        V value;  //值  
        Node<K,V> left; //左子树  
        Node<K,V> right;  //右子树  
        Node<K,V> parent; //父指针  
        int priority;  //优先级  
        public Node(K key, V value) {  
            this.key = key;  
            this.value = value;  
            left = right = parent = null;  
            priority = random.nextInt();//为新生成的节点分配一个随机数。  
        }  
    }  
    public Treap(){  
        this(null);  
    }  
    public Treap(Comparator<? super K> comparator) {  
        this.comparator = comparator;  
    }
}
查询操作

![[Pasted image 20240927090041.png]]
查询操作, 方法思路同经典二叉搜索树, 在此不多赘述。

  1. 下面实现了三个方法, 核心关注search方法, contains返回一个布尔值,判断关键字key的节点是否存在。get方法根据键获取值。
public Node<K,V> search(K key){  
    if(root == null){  
        return null;  //这里单独检查一下。
    }  
    Node<K,V> current = root;//遍历Treap  
    int cmp = 0;  //记录比较结果
    if(comparator != null){  
        //比较器不为空,那么优先使用比较器。  
        while(current!=null){  
	        cmp = comparator.compare(current.key, key);  
            if(cmp==0){  
                return current;//找到了!直接返回节点的引用。  
            }  
            else if(cmp<0){  
		        //当前值太小了, 前往右子树
                current = current.right;  
            }  
            else{  
                //cmp>0  
                current = current.left;  
            }  
        }  
    }  
    else{  
        //comparator == null  
        @SuppressWarnings("unchecked!")  
        Comparable<? super K> comparable = (Comparable<? super K>)current.key;  
        // 使用者必须确保K类型是可比较的, 否则报错。  
        cmp = comparable.compareTo(key);  
        while(current != null){  
            comparable = (Comparable<? super K>)current.key;  
            cmp = comparable.compareTo(key);  
            //逻辑与比较器相同
            if(cmp==0){  
                return current;  
            }  
            else if(cmp<0){  
                current = current.right;  
            }  
            else{  
                current = current.left;  
            }  
        }  
    }  
    return null;//出循环了即为空。  
}  
//根据serach结果造contains函数和get函数
public boolean contains(K key){  
    return search(key) != null;  
}  
  
public V get(K key){  
    return search(key) != null ? search(key).value : null;  
}

插入操作

写法很简单, 套路更简单。
经典BST插入+二叉堆的上浮调整。

TREAP-INSERT(T, x):
    y <- T.root
    p <- NIL
    while y ≠ NIL:
        p <- y
        if y.key == x.key:
            // Update the value
            y.val <- x.val
            return
        elseif y.key > x.key:
            y <- y.left
        else: // y.key < x.key
            y <- y.right
    if p == NIL:
        T.root <- x // Insert as root if tree was empty
    elseif p.left == y:
        p.left <- x
    else:
        p.right <- x
    siftUp(T, x) // Perform the sift up operation


这张图举例:
BST操作不多说, 下面来说明指针版本的上浮操作处理, 旋转又来了。
可能数组的堆上浮写熟悉了, 头一次处理指针版本的。
![[Pasted image 20240927122757.png]]

  
public void put(K key, V value){  
    if(key != null) {  
        insert(new Node(key, value));  
    }  
}  
public void insert(K key, V value){  
    if(key != null) {  
        insert(new Node(key, value));  
    }  
}  
public void insert(Node<K,V> node) {  
    if (root == null) {  
        root = node;  //这里单独处理, 保证后续代码在非空情形下
        return;  
    }  
  
    Node<K,V> current = root; // 当前节点  
    Node<K,V> parent = null; // 父节点  
    int cmp = 0;  
  
    // 遍历查找插入位置  
    while (current != null) {  
        parent = current;  
        if (comparator != null) {  
            cmp = comparator.compare(node.key, current.key);  
        } else {  
            @SuppressWarnings("unchecked")  
            Comparable<? super K> comparable = (Comparable<? super K>) current.key;  
            cmp = comparable.compareTo(node.key);  
        }  
  
        if (cmp == 0) {  
            current.value = node.value; // 更新值  
            return;  
        } else if (cmp < 0) {  
            current = current.right; // 前往右子树  
        } else {  
            current = current.left; // 前往左子树  
        }  
    }  
  
    // 插入节点  
    node.parent = parent;  
    if (cmp < 0) {  
        parent.right = node; // 插入到右子树  
    } else {  
        parent.left = node; // 插入到左子树  
    }  
  
    siftUp(node); // 上浮操作  
}
旋转接口

这里眼熟一下, 实现avl或者红黑树,简单回顾一下左旋和右旋两个接口。

private void leftRotate(Node<K,V> x) {
    Node<K,V> y = x.right;
    x.right = y.left;
    if (y.left != null) {
        y.left.parent = x;
    }
    
    y.parent = x.parent;
    if (x.parent == null) {
        root = y; // y 变为新的根节点
    } else if (x == x.parent.left) {
        x.parent.left = y; // x 是左子节点
    } else {
        x.parent.right = y; // x 是右子节点
    }
    
    y.left = x; // x 变为 y 的左子节点
    x.parent = y; // 更新 x 的父指针
}

private void rightRotate(Node<K,V> y) {
    Node<K,V> x = y.left;
    y.left = x.right;
    if (x.right != null) {
        x.right.parent = y;
    }
    
    x.parent = y.parent;
    if (y.parent == null) {
        root = x; // x 变为新的根节点
    } else if (y == y.parent.left) {
        y.parent.left = x; // y 是左子节点
    } else {
        y.parent.right = x; // y 是右子节点
    }
    
    x.right = y; // y 变为 x 的右子节点
    y.parent = x; // 更新 y 的父指针
}

siftUp
  
private void siftUp(Node<K, V> node){  
    //上浮的临界是根节点,到根节点就必须终止了。  
    //这里直接node.parent, 不需要申请新变量。---对可读性没多大英影响  
    while(node != root && node.priority < node.parent.priority){  
        //开始上浮  
        if(node.parent.left == node){  
            //执行右旋  
            rightRotate(node.parent);  
        }  
        else{  
            //parent.right == node  
            //执行左旋  
            leftRotate(node.parent);  
        }  
        //经过旋转后,原先的父亲节点指针parent成为node的孩子节点  
        node.parent = node.parent;  
    }  
}

删除

以下两个接口函数, 你必须在BST中学明白了。
这两个方法在Treap的删除操作中非常重要。

transplant && minimum
  • transplant 方法:用于替换树中一个节点 u 为另一个节点 v,并更新它们的父节点关系。
  • minimum 方法:用于查找给定节点的最小值节点,通过遍历左子树实现。
private void transplant(Node<K,V> u, Node<K,V> v) {
    if (u.parent == null) {
        root = v; // u 是根节点
    } else if (u == u.parent.left) {
        u.parent.left = v; // u 是左子节点
    } else {
        u.parent.right = v; // u 是右子节点
    }
    
    if (v != null) {
        v.parent = u.parent; // 更新 v 的父指针
    }
}

public Node<K,V> minimum(Node<K,V> node) {
    if (node == null) {
        return null; // 如果节点为空,返回 null
    }
    while (node.left != null) {
        node = node.left; // 一直遍历左子树,直到找到最小值
    }
    return node;
}
删除
public void delete(Node<K,V> node) {
    if (node.left == null) {
        transplant(node, node.right); // 只有右子树
    } else if (node.right == null) {
        transplant(node, node.left); // 只有左子树
    } else {
        // 找到右子树中的最小节点
        Node<K,V> leftMin = minimum(node.right);
        if (leftMin.parent != node) {
            transplant(leftMin, leftMin.right);
            leftMin.right = node.right;
            if (leftMin.right != null) {
                leftMin.right.parent = leftMin;
            }
        }
        transplant(node, leftMin);
        leftMin.left = node.left;
        leftMin.left.parent = leftMin;
    }
    siftDown(node); // 维护 Treap 的性质
}

siftDown
private void siftDown(Node<K,V> node) {
    Node<K,V> child = node.left;
    boolean isLeft = true;//左孩子还是右孩子,

    while (child != null) {
        // 选择优先级较高的孩子节点
        if (node.right != null && node.right.priority > child.priority) {
            child = node.right;
            isLeft = false;
        }

        // 执行旋转
        if (child.priority > node.priority) {
            if (isLeft) {
                rightRotate(node);
            } else {
                leftRotate(node);
            }
            node = child; // 更新当前节点为孩子节点
            child = (isLeft) ? node.left : node.right; // 继续下沉
            isLeft = true; // 重置为左子树
        } else {
            break; // 如果当前节点的优先级已大于所有孩子,结束
        }
    }
}

其它问题思考

Treap的唯一性

给定一组关键字和优先级均互异的节点, 可以组成唯一的Treap树与这些节点关联。
第一步, 根的唯一性:根节点是具有最高优先级的节点, 由于每个键值的优先级是唯一的,所以具有最高优先级的节点也是唯一的。比如最小`Treap中,根节点是最小的priority。
第二步, 递归构建。 由于根节点唯一了, 那么可以分组,一组的节点的key均小于root.key, 另一组均大于root.key。那么左右两边的序列元素确定了。
第三步, 递归中子树遵循第一条, 因此结构必定唯一。 因为树是由递归定义的。

以上性质涉及,二叉搜索树的有序性, 堆的性质。总之, 可以证明满足上述条件的Treap具有唯一性。 证毕!

待补充。。。

由于其它问题,比如树的期望高度证明需要概率知识, 数学这一块依旧是硬伤。
在此就不献丑了, 留待日后补充。

总源码

关于Treap实现有多种方案。 比如,我的方案是旋转treap,可以参考treap。
有兴趣的小伙伴可以根据我的代码拓展很多功能, 在此篇就不写多了, 只能说本篇的可拓展性强吧
学艺不精, 难免错误, 希望并感谢大佬您提出宝贵的意见, 谢邀。
感谢我的朋友们的鼓励,和数学编程问题的指导, 这几天精神真是颓废, 今天好多了。

import java.util.Random;  
import java.util.Comparator;  
public class Treap<K,V> {  
    //比较器, 可以手动传递比较器对象, 否则K必须实现Comparable接口  
    private Comparator<? super K> comparator;  
    //随机数生成器, 为每一个节点生成一个随机数  
    private final Random random = new Random();  
    private Node<K,V> root;  //根节点  
    public class Node<K,V>{  
        K key;  //键  
        V value;  //值  
        Node<K,V> left; //左子树  
        Node<K,V> right;  //右子树  
        Node<K,V> parent; //父指针  
        int priority;  //优先级  
        public Node(K key, V value) {  
            this.key = key;  
            this.value = value;  
            left = right = parent = null;  
            priority = random.nextInt();//为新生成的节点分配一个随机数。  
        }  
    }  
    public Treap(){  
        this(null);  
    }  
    public Treap(Comparator<? super K> comparator) {  
        this.comparator = comparator;  
    }
public Node<K,V> search(K key){  
    if(root == null){  
        return null;  //这里单独检查一下。
    }  
    Node<K,V> current = root;//遍历Treap  
    int cmp = 0;  //记录比较结果
    if(comparator != null){  
        //比较器不为空,那么优先使用比较器。  
        while(current!=null){  
	        cmp = comparator.compare(current.key, key);  
            if(cmp==0){  
                return current;//找到了!直接返回节点的引用。  
            }  
            else if(cmp<0){  
		        //当前值太小了, 前往右子树
                current = current.right;  
            }  
            else{  
                //cmp>0  
                current = current.left;  
            }  
        }  
    }  
    else{  
        //comparator == null  
        @SuppressWarnings("unchecked!")  
        Comparable<? super K> comparable = (Comparable<? super K>)current.key;  
        // 使用者必须确保K类型是可比较的, 否则报错。  
        cmp = comparable.compareTo(key);  
        while(current != null){  
            comparable = (Comparable<? super K>)current.key;  
            cmp = comparable.compareTo(key);  
            //逻辑与比较器相同
            if(cmp==0){  
                return current;  
            }  
            else if(cmp<0){  
                current = current.right;  
            }  
            else{  
                current = current.left;  
            }  
        }  
    }  
    return null;//出循环了即为空。  
}  
//根据serach结果造contains函数和get函数
public boolean contains(K key){  
    return search(key) != null;  
}  
  
public V get(K key){  
    return search(key) != null ? search(key).value : null;  
}
public void put(K key, V value){  
    if(key != null) {  
        insert(new Node(key, value));  
    }  
}  
public void insert(K key, V value){  
    if(key != null) {  
        insert(new Node(key, value));  
    }  
}  
public void insert(Node<K,V> node) {  
    if (root == null) {  
        root = node;  //这里单独处理, 保证后续代码在非空情形下
        return;  
    }  
  
    Node<K,V> current = root; // 当前节点  
    Node<K,V> parent = null; // 父节点  
    int cmp = 0;  
  
    // 遍历查找插入位置  
    while (current != null) {  
        parent = current;  
        if (comparator != null) {  
            cmp = comparator.compare(node.key, current.key);  
        } else {  
            @SuppressWarnings("unchecked")  
            Comparable<? super K> comparable = (Comparable<? super K>) current.key;  
            cmp = comparable.compareTo(node.key);  
        }  
  
        if (cmp == 0) {  
            current.value = node.value; // 更新值  
            return;  
        } else if (cmp < 0) {  
            current = current.right; // 前往右子树  
        } else {  
            current = current.left; // 前往左子树  
        }  
    }  
  
    // 插入节点  
    node.parent = parent;  
    if (cmp < 0) {  
        parent.right = node; // 插入到右子树  
    } else {  
        parent.left = node; // 插入到左子树  
    }  
  
    siftUp(node); // 上浮操作  
}
private void leftRotate(Node<K,V> x) {
    Node<K,V> y = x.right;
    x.right = y.left;
    if (y.left != null) {
        y.left.parent = x;
    }
    
    y.parent = x.parent;
    if (x.parent == null) {
        root = y; // y 变为新的根节点
    } else if (x == x.parent.left) {
        x.parent.left = y; // x 是左子节点
    } else {
        x.parent.right = y; // x 是右子节点
    }
    
    y.left = x; // x 变为 y 的左子节点
    x.parent = y; // 更新 x 的父指针
}

private void rightRotate(Node<K,V> y) {
    Node<K,V> x = y.left;
    y.left = x.right;
    if (x.right != null) {
        x.right.parent = y;
    }
    
    x.parent = y.parent;
    if (y.parent == null) {
        root = x; // x 变为新的根节点
    } else if (y == y.parent.left) {
        y.parent.left = x; // y 是左子节点
    } else {
        y.parent.right = x; // y 是右子节点
    }
    
    x.right = y; // y 变为 x 的右子节点
    y.parent = x; // 更新 y 的父指针
}
private void siftUp(Node<K, V> node){  
    //上浮的临界是根节点,到根节点就必须终止了。  
    //这里直接node.parent, 不需要申请新变量。---对可读性没多大英影响  
    while(node != root && node.priority < node.parent.priority){  
        //开始上浮  
        if(node.parent.left == node){  
            //执行右旋  
            rightRotate(node.parent);  
        }  
        else{  
            //parent.right == node  
            //执行左旋  
            leftRotate(node.parent);  
        }  
        //经过旋转后,原先的父亲节点指针parent成为node的孩子节点  
        node.parent = node.parent;  
    }  
}
private void transplant(Node<K,V> u, Node<K,V> v) {
    if (u.parent == null) {
        root = v; // u 是根节点
    } else if (u == u.parent.left) {
        u.parent.left = v; // u 是左子节点
    } else {
        u.parent.right = v; // u 是右子节点
    }
    
    if (v != null) {
        v.parent = u.parent; // 更新 v 的父指针
    }
}

public Node<K,V> minimum(Node<K,V> node) {
    if (node == null) {
        return null; // 如果节点为空,返回 null
    }
    while (node.left != null) {
        node = node.left; // 一直遍历左子树,直到找到最小值
    }
    return node;
}
public void delete(Node<K,V> node) {
    if (node.left == null) {
        transplant(node, node.right); // 只有右子树
    } else if (node.right == null) {
        transplant(node, node.left); // 只有左子树
    } else {
        // 找到右子树中的最小节点
        Node<K,V> leftMin = minimum(node.right);
        if (leftMin.parent != node) {
            transplant(leftMin, leftMin.right);
            leftMin.right = node.right;
            if (leftMin.right != null) {
                leftMin.right.parent = leftMin;
            }
        }
        transplant(node, leftMin);
        leftMin.left = node.left;
        leftMin.left.parent = leftMin;
    }
    siftDown(node); // 维护 Treap 的性质
}
private void siftDown(Node<K,V> node) {
    Node<K,V> child = node.left;
    boolean isLeft = true;//左孩子还是右孩子,

    while (child != null) {
        // 选择优先级较高的孩子节点
        if (node.right != null && node.right.priority > child.priority) {
            child = node.right;
            isLeft = false;
        }

        // 执行旋转
        if (child.priority > node.priority) {
            if (isLeft) {
                rightRotate(node);
            } else {
                leftRotate(node);
            }
            node = child; // 更新当前节点为孩子节点
            child = (isLeft) ? node.left : node.right; // 继续下沉
            isLeft = true; // 重置为左子树
        } else {
            break; // 如果当前节点的优先级已大于所有孩子,结束
        }
    }
}
}

红尘漩涡不由己, 何朝散发弄扁舟。
乘风破浪三万里, 方是我辈魔道人。
—20240927
— author:Autumn Whisper。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2170257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LDRA Testbed(TBrun)软件集成测试(部件测试)_操作指南

系列文章目录 LDRA Testbed软件静态分析_操作指南 LDRA Testbed软件静态分析_自动提取静态分析数据生成文档 LDRA Testbed软件静态分析_Jenkins持续集成&#xff08;自动静态分析并用邮件自动发送分析结果&#xff09; LDRA Testbed软件静态分析_软件质量度量 LDRA Testbed软件…

工厂模式和抽象工厂模式的实验报告

1. 实验结果&#xff1a; 记录并附上不同模型对象&#xff08;例如&#xff1a;士兵、机器人、骑士&#xff09;的展示效果截图。 2. 性能分析&#xff1a; 记录并比较抽象工厂模式与直接实例化的性能测试结果&#xff0c;分析它们在不同数量级对象创建时的开销与效益。 2.1…

系统架构设计师 - 案例特训专题 - 数据库设计篇

案例特训专题 - 数据库设计篇 数据库设计篇规范化与反规范化 ★★★规范化 - 范式反规范化 数据库索引数据库视图数据库分区分表分库分区分区的常见方式 分表分库 分布式数据库 ★★★NoSQL ★★★其他数据库扩展知识 ★★★数据库性能优化集中式数据库优化分布式数据库优化 大…

Thingsboard规则链:Related Device Attributes节点详解

引言 在物联网&#xff08;IoT&#xff09;领域&#xff0c;Thingsboard作为一款强大的物联网平台&#xff0c;其规则链功能为企业提供了高度定制化的数据处理和自动化控制方案。其中&#xff0c;Related Device Attributes节点是一个特别实用的组件&#xff0c;它能够访问和操…

sql server每天定时执行sql语句

sql server每天定时执行sql语句 1、打开SQL Server Management Studio 2、鼠标右击【SQL Server 代理】&#xff0c;选择【启动(S)】&#xff0c;如已启动&#xff0c;可以省略此步骤&#xff1b; 3、右键&#xff0c;新建-》作业&#xff0c;在作业上-》新建作业&#xff…

超全攻略|手把手带你0经验转行产品经理

▌想入行&#xff0c;你需要先知道怎么才能走得更顺利 现在&#xff0c;如果有人问我&#xff0c;说重新回答大学毕业&#xff0c;刚要入行产品经理的时候&#xff0c;知道哪些事情&#xff0c;能让自己在后面的产品经理生涯中走的更顺利。 ❓是知道面试的套路&#xff0c;能…

PatrOwl:一款开源可扩展的安全协调运营平台

关于PatrOwl PatrOwl是一款开源可扩展的安全协调运营平台&#xff0c;广大研究人员可以使用该工具完成组织内部的安全协调运营。 该工具是一种可扩展、免费且开源的解决方案&#xff0c;用于协调安全操作。其中的PatrowlManager是前端应用程序&#xff0c;用于管理资产、实时审…

一文读懂常见的几种 LangChain 替代品,看到就是赚到!!

前言 在 LLM &#xff08;大规模语言模型&#xff09;应用开发领域&#xff0c;开源框架扮演着至关重要的角色&#xff0c;为广大开发者提供了强大的工具支持。作为这一领域的领军者&#xff0c;LangChain 凭借其创新设计和全面功能赢得了广泛赞誉。但与此同时&#xff0c;一些…

小程序开发平台源码系统 各行各业适用的小程序开的平台 带完整的安装代码包以及搭建部署教程

系统概述 本系统采用模块化设计&#xff0c;包含前端展示层、后端逻辑处理层、数据库存储层以及管理后台等多个核心组件。前端展示层负责小程序的界面设计与交互体验&#xff1b;后端逻辑处理层则负责数据处理、业务逻辑实现及与第三方服务的对接&#xff1b;数据库存储层用于…

如火似茶的AI Bots到底有什么现实意义呢?

你好&#xff0c;我是三桥君 自AIGC潮流兴起以来&#xff0c;基于自注意力机制的大模型成为资本市场疯狂炒作的对象。然而&#xff0c;经过一年多的狂热之后&#xff0c;市场逐渐回归理性。这时候会有人担心&#xff0c;大模型是否会像元宇宙、Web 3.0&#xff0c;甚至比特币那…

低空经济腾飞,无人机飞手人才培养先行

低空经济的腾飞为无人机飞手人才的培养提出了迫切需求&#xff0c;并且这一领域的发展已经引起了广泛的关注。以下是对“低空经济腾飞&#xff0c;无人机飞手人才培养先行”的详细分析&#xff1a; 一、低空经济的腾飞背景 低空经济作为新兴的经济形态&#xff0c;正以前所未…

如何搭建适合自己的数据中台?六步法

数据中台是企业数据价值实现的能力框架&#xff0c;包括数据集成、数据开发、数据管理、数据服务、数据资产运营等能力&#xff0c;是企业业务数据化的承载体&#xff0c;是企业业务通过数据视角的一种呈现&#xff0c;担负了企业数字化所需的核心综合数据能力。但由于数据中台…

fastzdp_redis第一次开发, 2024年9月26日, Python操作Redis零基础快速入门

提供完整录播课 安装 pip install fastzdp_redisPython连接Redis import redis# 建立链接 r redis.Redis(hostlocalhost, port6379, db0)# 设置key r.set(foo, bar)# 获取key的值 print(r.get(foo))RESP3 支持 简单的理解: 支持更丰富的数据类型 参考文档: https://blog.c…

Sublime Text4的下载安装以及汉化

sublime官网&#xff1a;https://www.sublimetext.com/ 按照指示一步步操作即可 汉化操作&#xff1a; 等一会就会弹出搜索框&#xff0c; 帮助菜单这里可以切换语言&#xff0c;

OpenAi以及Dify结合生成Ai模型

文章目录 1、Dify介绍2、使用 Dify3、部署Docker1.系统要求2.系统虚拟化3.下载docker 4、安装WSL1.检查是否已经安装 五、访问系统六、添加模型 1、Dify介绍 Dify官方地址。 Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、…

HOJ网站开启https访问 申请免费SSL证书 部署证书详细操作指南

https://console.cloud.tencent.com/ 腾讯云用户 登录控制台 右上角搜SSL 点击 SSL证书 进入链接 点申请 免费证书 有效期3个月 &#xff08;以后每三个月申请一次证书 上传&#xff09; 如果是腾讯云申请的域名 选 自动DNS验证 自动添加验证记录 如果是其他平台申请域…

利士策分享,快钱诱惑与稳健之道:探索财富积累的两种路径

利士策分享&#xff0c;快钱诱惑与稳健之道&#xff1a;探索财富积累的两种路径 在这个瞬息万变的时代&#xff0c;面对“赚快钱”的即时诱惑与“稳健的长远赚钱方式”的持久魅力&#xff0c;我们不禁要深思&#xff1a;在追求财富的道路上&#xff0c;哪一种方式更为明智&…

图神经学习笔记

图神经网络基础 图神经网络用于挖掘事物的“普遍联系”&#xff0c;理解原理、应用技术。本文汇总图神经网络相关介绍和基础模型。 图及特性 图是由顶点和边组成的&#xff0c;如下图左所示。一般图中的顶点表示实体对象&#xff08;比如一个用户、一件商品、一辆车、一张银行…

Spring RestTemplate 升级 WebClient 导致 OutOfMemoryError

Spring Boot是 Java 企业应用程序的一个非常流行的框架。与内部或外部应用程序集成的一种常见方法是通过 HTTP REST 连接。我们正在从RestTemplate升级到基于 Java NIO 的WebClient&#xff0c;它可以通过允许在调用 REST 服务端点时进行并发来显著提高应用程序性能。WebClient…

Windows环境部署Oracle 11g

Windows环境部署Oracle 11g 1.安装包下载2. 解压安装包3. 数据库安装3.1 执行安装脚本3.2 电子邮件设置3.3 配置安装选项3.4 配置系统类3.5 选择数据库安装类型3.6 选择安装类型3.7 数据库配置3.8 确认安装信息3.9 设置口令 Oracle常用命令 2023年10月中旬就弄出大致的文章&…