【红黑树封装map和set】—— 我与C++的不解之缘(二十六)

【红黑树封装map和set】—— 我与C++的不解之缘(二十六)

一、了解源码

SGL-STL30版本中,看一下mapset的部分源码
// set#ifndef__SGI_STL_INTERNAL_TREE_H#include<stl_tree.h>#endif#include<stl_set.h>#include<stl_multiset.h>// map#ifndef__SGI_STL_INTERNAL_TREE_H#include<stl_tree.h>#endif#include<stl_map.h>#include<stl_multimap.h>// stl_set.htemplate<classKey,classCompare= less<Key>,classAlloc= alloc>classset{public:// typedefs:typedef Key key_type;typedef Key value_type;private:typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type; rep_type t;// red-black tree representing set};// stl_map.htemplate<classKey,classT,classCompare= less<Key>,classAlloc= alloc>classmap{public:// typedefs:typedef Key key_type;typedef T mapped_type;typedef pair<const Key, T> value_type;private:typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type; rep_type t;// red-black tree representing map};// stl_tree.hstruct__rb_tree_node_base{typedef __rb_tree_color_type color_type;typedef __rb_tree_node_base* base_ptr; color_type color; base_ptr parent; base_ptr left; base_ptr right;};// stl_tree.htemplate<classKey,classValue,classKeyOfValue,classCompare,classAlloc= alloc>classrb_tree{protected:typedefvoid* void_pointer;typedef __rb_tree_node_base* base_ptr;typedef __rb_tree_node<Value> rb_tree_node;typedef rb_tree_node* link_type;typedef Key key_type;typedef Value value_type;public:// insert⽤的是第⼆个模板参数左形参  pair<iterator,bool>insert_unique(const value_type& x);// erase和find⽤第⼀个模板参数做形参 size_type erase(const key_type& x); iterator find(const key_type& x);protected: size_type node_count;// keeps track of size of tree link_type header;};template<classValue>struct__rb_tree_node:public__rb_tree_node_base{typedef __rb_tree_node<Value>* link_type; Value value_field;};

部分源码如上,我们通过源码可以看到源码中rb_tree使用了泛型思维实现;其中rb_tree是实现key搜索场景还是实现key/value的搜索场景不是写死的,而是通过了第二个模版参数来决定的。

上面意思呢?

set在实例化时第二个模版参数给的是key,而map实例化时第二个模版参数给的是pair<const K, T>,这样我们一颗红黑树就可以实现两种效果,既可以实现key搜索场景的set也可以实现key/value搜索场景map。这里源码当中,模版参数用T来代替了value,而其中value_type并不是我们之前key/value场景中的value;源码当中value_type反而是红黑树节点中存储的数据类型。(这样set存储的数据就是K,而map中存储的数据就是pair<K , V>)。这里第二个模版参数Value已经控制了红黑树节点中存储的数据类型,那为什么还需要第一个模版参数呢?,就比如set模版参数传的是K,K;这里主要原因还是因为mapsetfinderase函数的参数都是Key,所以需要第一个模版参数传给finderase对于set而言两个参数都是key,而对于map而言,mapinsertpair对象,但是finderase的是Key对象。

二、 封装实现mapset

我们复用之前实现的红黑树来实现setmap;我们需要进行一系列的修改,让我们实现的红黑树可以完成复用,让我们实现出来setmap

要复用整个框架,我们首先要进行一些修改;这里对比源码当中实现方法进行一系列修改和完善:

模版参数:红黑树的模版参数要用三个<class K , class T , class KeyOfT>,能够进行insert操作。迭代器:RBTree要实现出迭代器,能够进行遍历。map中的[]: 对于map,我们要实现[]运算符重载;那个实现其完整的[]操作。

1. 复用红黑树框架,实现insert

首先来看如何去封装,实现mapset的框架。

模版参数:

对于三个模版参数K , T , KeyOfT,各有什么用呢?

keyT就不多解释了,在了解源码时已经知道了它们的作用。

KeyOfT的作用是什么?

首先我们知道,红黑树我们修改成K , T结构之后,我们在insert的时候函数参数是T,我们并不知道TK还是pair<K , V>;那我们如何去进行大小比较呢?

这里我们要看一下pair<K , V>的比较方法是什么

在这里插入图片描述

可以看到库里面pair的比较方法是先比较firstfirst大就大,first小就小;如果相等那就比较second

我们不想要这样的比较方法,我们就想要单纯的比较K也就是first(这里我们想要的比较方法,关键我们需要拿到K

现在,KeyOfT的作用就体现出来了:

KeyOfT是一个仿函数,它重载了operator()方法,它的作用就是返回T中的K

如果TK那就直接返回,如果Tpair<K , V>就返回其中的K

那我们在RBTree怎么知道T要返回的是什么呢?(显然是无法知道的,但是上层复用的肯定知道;所以我们就在setmap中写好SetKeyOfTMapKeyOfT,将其放在第三个模版参数的位置;这样我们在下层RBTree中就能够拿到T中的K了。

支持insert实现

现在就对于上述来修改我们之前实现好的RBTree代码。

RBTree.h:

enumColor{ RED, BLACK,};//RBTree节点template<classT>structRBTreeNode{ RBTreeNode* _left; RBTreeNode* _right; RBTreeNode* _parent; T _data; Color _col;RBTreeNode(const T& data):_data(data),_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED){}};template<classK,classT,classKOT>classRBTree{typedef RBTreeNode<T> Node;public:boolinsert(const T& data){if(_root ==nullptr)//空树插入{ _root =newNode(data); _root->_col = BLACK;returntrue;}//非空树插入 Node* tail = _root; Node* parent =nullptr; KOT kot;while(tail){if(kot(data)>kot(tail->_data)){ parent = tail; tail = tail->_right;}elseif(kot(data)<kot(tail->_data)){ parent = tail; tail = tail->_left;}else{returnfalse;}} Node* cur =newNode(data); cur->_col = RED;//新插入节点一定是红色 cur->_parent = parent;if(kot(cur->_data)>kot(parent->_data)){ parent->_right = cur;}elseif(kot(cur->_data)<kot(parent->_data)){ parent->_left = cur;}//这里当父节点存在且为红色时就一直循环//直到父节点不存在或者父节点的颜色为黑色while(parent && parent->_col == RED){ Node* grandfather = parent->_parent;if(parent == grandfather->_left){// Node* uncle = grandfather->_right;if(uncle && uncle->_col == RED)//变色{ uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent;}else{if(cur == parent->_left)//右单旋+变色{RevoleR(grandfather); parent->_col = BLACK; grandfather->_col = RED;break;}else//左右双旋+变色{RevoleL(parent);RevoleR(grandfather); cur->_col = BLACK; grandfather->_col = RED;break;}}}else{ Node* uncle = grandfather->_left;if(uncle && uncle->_col == RED)//变色{ uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent;}else{if(cur == parent->_right)//左单旋+变色{RevoleL(grandfather); parent->_col = BLACK; grandfather->_col = RED;break;}else//右左双旋+变色{RevoleR(parent);RevoleL(grandfather); cur->_col = BLACK; grandfather->_col = RED;break;}}}} _root->_col = BLACK;returntrue;}private:voidRevoleR(Node* parent)//右单旋{ Node* subl = parent->_left; Node* sublr = parent->_left->_right; parent->_left = sublr;if(sublr) sublr->_parent = parent; Node* ppNode = parent->_parent; parent->_parent = subl; subl->_parent = ppNode;if(ppNode ==nullptr){ _root = subl;}else{if(parent == ppNode->_left){ ppNode->_left = subl;}elseif(parent->_right){ ppNode->_right = subl;}}}voidRevoleL(Node* parent)//左单旋{ Node* subr = parent->_right; Node* subrl = parent->_right->_left; parent->_right = subrl;if(subrl) subrl->_parent = parent; Node* ppNode = parent->_parent; parent->_parent = subr; subr->_left = parent; subr->_parent = ppNode;if(ppNode ==nullptr){ _root = subr;}else{if(parent == ppNode->_left){ ppNode->_left = subr;}elseif(parent == ppNode->_right){ ppNode->_right = subr;}}} Node* _root =nullptr;};

myset.h

namespace HL {template<classK>classset{structSetKeyOfT{const K&operator()(const K& key){return key;}};public:set(){}boolinsert(const K& k){return _rb.insert(k);}private: RBTree<K,const K, SetKeyOfT> _rb;};}

mymap.h

namespace HL {template<classK,classV>classmap{structMapKeyOfT{const K&operator()(const pair<const K,V>& kv){return kv.first;}};public:map(){}boolinsert(const pair<const K, V>& kv){return _rb.insert(kv);}private: RBTree<K, pair<const K, V>, MapKeyOfT> _rb;};}

2. 实现iterator,完成setmap的迭代器遍历

对于RBTree的迭代器,它和list的迭代器是非常相似的,都是对于节点的指针进行封装来实现的;通过实现运算符重载来让迭代器能够像指针那样一样的访问行为。

运算符重载

这里先简单来实现operator==operator!=operator*operator->等这些简单一些的运算符重载

template<classT,classRef,classPtr>structRBTreeIterator{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self; Node* _node;RBTreeIterator(Node* node,Node* root):_node(node){} Ref operator*(){return _node->_data;} Ptr operator->(){return&_node->_data;}booloperator==(const Self& it){return _node == it._node;}booloperator!=(const Self& it){return _node != it._node;}};

operator++重载实现

这里operator++operator--是重点也是难点;我们知道mapset的迭代器走的都是中序遍历因为中序遍历是有序的。

而中序遍历(左子树->根节点->右子树),那我们begin()返回的就是中序遍历的第一个节点位置的迭代器。

**迭代器++**的核心逻辑就是,遍历完当前位置,应该遍历哪一个位置?

所以我们就只需要考虑下一个节点位置在哪即可。

中序遍历的顺序:左子树->根节点->右子树

我们优先考虑该节点的右子树,(因为遍历到当前节点时,就说明左子树当前节点已经遍历完了);**如果当前节点的右子树不为空:**那就找到其右子树中的最左节点,该节点就是当前节点中序遍历的下一个节点。如果当前节点的右子树为空: 如果当前节点的右子树为空,就代表当前节点所在子树已经访问完了;要访问下一个节点在当前节点的祖先当中,我们就需要沿着当前节点到根节点的路径中寻找下一个节点。那如何去找呢?其实很简单,我们根据左子树->根节点->右子树这个顺序可以发现:如果当前节点等于其父亲节点的左子树,就说明父节点的左子树访问完了,要接着访问父节点如果当前节点等于其父节点的右子树,就说明父节点的右子树已经访问完了,也说明以父节点为根节点的子树也访问完了,就要继续向上在祖先中寻找下一个节点。

这里当前节点的右子树不为空很好理解,当前节点的右子树为空可能不太好理解,现在就来推理一个树来辅助理解一下。

现在有这样一个红黑树:

在这里插入图片描述

现在就从begin()位置(中序第一个)开始遍历:

在这里插入图片描述

所以迭代器++的过程如上图所示,现在就来实现operator++()代码:

 Self&operator++(){if(_node->_right){ Node* cur = _node->_right;while(cur && cur->_left){ cur = cur->_left;} _node = cur;}else{ Node* parent = _node->_parent; Node* cur = _node;while(parent && cur == parent->_right){ cur = parent; parent = cur->_parent;} _node = parent;}return*this;}

operator--重载实现

对于operator--的实现,大致思路和operator++是一样的;只是我们要对特殊情况进行处理

在上述operator++过程中,我们end()返回的是nullptr的迭代器,所以我们在实现operator--时就要对这一特殊情况进行处理:如果当前位置是nullptr,我们就要让当前迭代器执行最后一个位置。那最后一个位置在哪?首先,begin()是中序遍历(左子树->根节点->右子树)的第一个(也是该树的最左节点),那中序遍历最后一个节点就是该树的最右节点,所以我们按照(右子树->根节点->左子树)的顺序去找该树的最右节点,也就是中序遍历的最后一个节点。那这里问题就又出来了,我们当前迭代器位置是nullptr,我们该如何找到该树的最右节点呢?(这里源码当中是使用了一个哨兵位来充当end()的位置,我们并没有去实现这个哨兵位);那如何找到该树的最右节点呢?其实很简单,我们这里让迭代器多了一个成员_root表示当前树的根节点,这样我们在对end()位置--的时候,就可以通过根节点找到该树的最右节点。其他的思路都和operator++一样,这里就不进行分析了。

直接来看operator--的代码实现:

 Self&operator--(){if(_node ==nullptr){ Node* cur = _root;while(cur && cur->_right){ cur = cur->_right;} _node = cur;}else{if(_node->_left){ Node* cur = _node->_left;while(cur->_left){ cur = cur->_right;} _node = cur;}else{ Node* parent = _node->_parent; Node* cur = _node;while(parent && cur == parent->_left){ cur = parent; parent = cur->_parent;} _node = parent;}}return*this;}
这里迭代器相比于上面写的多了一个成员变量root,所以最终实现的简易迭代器代码如下:
template<classT,classRef,classPtr>structRBTreeIterator{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self; Node* _node; Node* _root;RBTreeIterator(Node* node, Node* root):_node(node),_root(root){} Ref operator*(){return _node->_data;} Ptr operator->(){return&_node->_data;}booloperator==(const Self& it){return _node == it._node;}booloperator!=(const Self& it){return _node != it._node;} Self&operator++(){if(_node->_right){ Node* cur = _node->_right;while(cur && cur->_left){ cur = cur->_left;} _node = cur;}else{ Node* parent = _node->_parent; Node* cur = _node;while(parent && cur == parent->_right){ cur = parent; parent = cur->_parent;} _node = parent;}return*this;} Self&operator--(){if(_node ==nullptr){ Node* cur = _root;while(cur && cur->_right){ cur = cur->_right;} _node = cur;}else{if(_node->_left){ Node* cur = _node->_left;while(cur->_left){ cur = cur->_right;} _node = cur;}else{ Node* parent = _node->_parent; Node* cur = _node;while(parent && cur == parent->_left){ cur = parent; parent = cur->_parent;} _node = parent;}}return*this;}};

RBTreesetmap的迭代器实现

RBTree

这里begin()返回的是中序遍历的第一个位置,也就是红黑树的最左节点;而end()返回的则是nullptr位置

typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T,const T&,const T*> ConstIterator; Iterator begin(){ Node* cur = _root;while(cur && cur->_left){ cur = cur->_left;}returnIterator(cur,_root);} Iterator end(){returnIterator(nullptr,_root);} ConstIterator begin()const{ Node* cur = _root;while(cur && cur->_left){ cur = cur->_left;}returnIterator(cur, _root);} ConstIterator end()const{returnIterator(nullptr, _root);}

setmap

这里setmap迭代器实现起来就简单很多了,直接对RBTree的迭代器进行复用即可,而begin()end()就直接进行调用即可。

set

typedeftypenameRBTree<K,const K, SetKeyOfT>::Iterator iterator;typedeftypenameRBTree<K,const K, SetKeyOfT>::ConstIterator const_iterator; iterator begin(){return _rb.begin();} iterator end(){return _rb.end();} const_iterator begin()const{return _rb.begin();} const_iterator end()const{return _rb.end();}
这里typedef typename RBTree<K, K, SetKeyOfT>::Iterator iterator;typename是告诉编译器这是个类型,让编译器在类模版实例化之后去取其中的类型名(因为编译器是按需实例化的,不加可能会报错)C++语法是运行这样的。

map

typedeftypenameRBTree<K,pair<const K,V>,MapKeyOfT>::Iterator iterator;typedeftypenameRBTree<K,pair<const K,V>,MapKeyOfT>::ConstIterator const_iterator; iterator begin(){return _rb.begin();} iterator end(){return _rb.end();} const_iterator begin()const{return _rb.begin();} const_iterator end()const{return _rb.end();}

测试setmap

这里来测试一下实现的setmap的迭代器

voidtest_map(){ HL::map<int,int> mp; mp.insert({1,1}); mp.insert({2,2}); mp.insert({3,3}); mp.insert({4,4}); mp.insert({5,5});auto it = mp.begin();while(it != mp.end()){ cout << it->first <<": "<< it->second<< endl;++it;} cout << endl; it = mp.end();while(it != mp.begin()){--it; cout << it->first <<": "<< it->second << endl;} cout << endl;}voidtest_set(){ HL::set<int> st; st.insert(1); st.insert(2); st.insert(3); st.insert(4); st.insert(5); st.insert(6); st.insert(7); st.insert(8);auto it = st.begin();while(it != st.end()){ cout <<*it <<" ";++it;} cout << endl; it = st.end();while(it != st.begin()){--it; cout <<*it <<" ";} cout << endl;}intmain(){test_set();test_map();return0;}

运行结果:

在这里插入图片描述

可以看到我们已经完成setmap的迭代器遍历。

三、mapoperator[]的实现

对于mapoperator[],我们知道它有两种功能:

一是key存在,通过key查找value,返回value的引用

二是如果key不存在进插入key,其对应的value是缺省值,然后返回value的引用

所以这里不管key是否存在,我们都要返回对应的value的引用;

我们在实现时就肯定需要调用insert函数,先来看一下库里面mapinsert函数

在这里插入图片描述

可以看到,相比于我们现在实现的insert,库里面返回的是pair<iterator , bool>类型,这样我们就能拿到插入位置的迭代器。

所以我们在实现的时候也需要这样去设计;(再结合一下insert的逻辑,如果插入值存在,我们就可以返回当前位置迭代器和false,这样就达到了我们想要的效果)

修改后的RBTreeinsert

 pair<Iterator,bool>insert(const T& data){if(_root ==nullptr)//空树插入{ _root =newNode(data); _root->_col = BLACK;//return true;returnmake_pair(Iterator(_root,_root),true);}//非空树插入 Node* tail = _root; Node* parent =nullptr; KOT kot;while(tail){if(kot(data)>kot(tail->_data)){ parent = tail; tail = tail->_right;}elseif(kot(data)<kot(tail->_data)){ parent = tail; tail = tail->_left;}else{//return false;returnmake_pair(Iterator(tail, _root),false);}} Node* cur =newNode(data); Node* newnode = cur; cur->_col = RED;//新插入节点一定是红色 cur->_parent = parent;if(kot(cur->_data)>kot(parent->_data)){ parent->_right = cur;}elseif(kot(cur->_data)<kot(parent->_data)){ parent->_left = cur;}//这里当父节点存在且为红色时就一直循环//直到父节点不存在或者父节点的颜色为黑色while(parent && parent->_col == RED){ Node* grandfather = parent->_parent;if(parent == grandfather->_left){// Node* uncle = grandfather->_right;if(uncle && uncle->_col == RED)//变色{ uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent;}else{if(cur == parent->_left)//右单旋+变色{RevoleR(grandfather); parent->_col = BLACK; grandfather->_col = RED;break;}else//左右双旋+变色{RevoleL(parent);RevoleR(grandfather); cur->_col = BLACK; grandfather->_col = RED;break;}}}else{ Node* uncle = grandfather->_left;if(uncle && uncle->_col == RED)//变色{ uncle->_col = parent->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent;}else{if(cur == parent->_right)//左单旋+变色{RevoleL(grandfather); parent->_col = BLACK; grandfather->_col = RED;break;}else//右左双旋+变色{RevoleR(parent);RevoleL(grandfather); cur->_col = BLACK; grandfather->_col = RED;break;}}}} _root->_col = BLACK;//return true;returnmake_pair(Iterator(newnode, _root),true);}

map中实现的operator[]

 pair<iterator,bool>insert(const pair<const K, V>& kv){return _rb.insert(kv);} V&operator[](const K& key){ pair<iterator,bool> ret =insert({ key,V()});return ret.first->second;}
到这里我们实现的map就可以使用[]去访问了。

测试

这里来测试一下我们实现的是否到达了我们预期的效果:

voidtest_map(){ HL::map<string, string> mp; mp.insert({"sort","排序"}); mp.insert({"left","左边"}); mp.insert({"right","右边"}); mp["left"]="左边,剩余"; mp["insert"]="插入"; mp["string"]; HL::map<string,string>::iterator it = mp.begin();while(it != mp.end()){ it->second +='x'; cout << it->first <<":"<< it->second << endl;++it;} cout << endl;}
这里简单使用map[],是可以到达我们的预期效果的。
在这里插入图片描述

四、mapset的构造函数与析构函数

对于构造函数和析构函数,相对来说就非常简单了。
在这里插入图片描述

对于构造函数,我们直接进行复用insert即可。

set(){}template<classInputIterator>set(InputIterator first, InputIterator last){while(first != last){ _rb.insert(*first); first++;}}set(const set& x){for(constauto& e : x){ _rb.insert(e);}}
在这里插入图片描述

mapset一样直接调用RBTree中的insert即可。

map(){}template<classInputIterator>map(InputIterator first, InputIterator last){while(first != last){ _rb.insert(*first); first++;}}map(const set& x){for(constauto& e : x){ _rb.insert(e);}}
对于析构函数,我们就要实现RBTree的析构函数,然后在mapset中进行调用;这里就使用递归来释放每一个节点。

RBTree.h

~RBTree(){Destroy(_root);}voidDestroy(Node* root){if(root ==nullptr)return;Destrot(root->_left);Destrot(root->_right);delete root;}
mapset就直接delete即可,编译器会自动调用RBTree的析构函数。

五、总结

这里并没有去实现erase删除功能,只是简单模拟了mapset的实现。

这里简单总结几个点:

首先就是一个红黑树框架来封装实现mapset其次就是模拟迭代器的实现,整个思路(重点:operator++operator--)然后就是mapoperator[]的特殊功能。最后就是构造函数、析构函数等这些简单的复用。

这里并没有实现像sizefindemptycount等函数,这些就比较简单。

到这里本篇内容就结束了,希望对你有所帮助。

制作不易,感谢大佬的支持。

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

Read more

DeepSeek各版本说明与优缺点分析_deepseek各版本区别

DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处,为广大AI技术爱好者和开发者提供一份参考指南。 1. DeepSeek-V1:起步与编码强劲 DeepSeek-V1是DeepSeek的起步版本,这里不过多赘述,主要分析它的优缺点。 发布时间: 2024年1月 特点: DeepSeek-V1是DeepSeek系列的首个版本,预训练于2TB的标记数据,主打自然语言处理和编码任务。它支持多种编程语言,具有强大的编码能力,适合程序开发人员和技术研究人员使用。 优势: * 强大编码能力:支持多种编程语言,能够理解和生成代码,适合开发者进行自动化代码生成与调试。 * 高上下文窗口:支持高达128K标记的上下文窗口,能够处理较为复杂的文本理解和生成任务。 缺点: * 多模态能力有限:该版本主要集中在文本处理上,缺少对图像、语音等多模态任务的支持。 * 推理能力较弱:尽管在自然语言

By Ne0inhk

用DeepSeek和Cursor从零打造智能代码审查工具:我的AI编程实践

💂 个人网站:【 摸鱼游戏】【神级代码资源网站】【星海网址导航】摸鱼、技术交流群👉 点此查看详情 引言:AI编程革命下的机遇与挑战 GitHub统计显示,使用AI编程工具的开发者平均效率提升55%,但仅有23%的开发者能充分发挥这些工具的潜力。作为一名全栈工程师,我曾对AI编程持怀疑态度,直到一次紧急项目让我彻底改变了看法。客户要求在72小时内交付一个能自动检测代码漏洞、优化性能的智能审查系统,传统开发方式根本不可能完成。正是这次挑战,让我探索出DeepSeek和Cursor这对"黄金组合"的惊人潜力。 一、工具选型:深入比较主流AI编程工具 1.1 为什么最终选择DeepSeek+Cursor? 经过两周的对比测试,我们发现不同工具在代码审查场景的表现差异显著: 工具代码理解深度响应速度定制灵活性多语言支持GitHub Copilot★★★☆★★★★★★☆★★★★Amazon CodeWhisperer★★☆★★★☆★★★★★★☆DeepSeek★★★★☆★★★★★★★☆★★★★☆Cursor★★★☆★★★★☆★★★★★★★★ 关键发现: * Dee

By Ne0inhk
【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱

【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱

【DeepSeek应用】Deepseek R1 本地部署(Ollama+Docker+OpenWebUI) 【DeepSeek应用】DeepSeek 搭建个人知识库(Ollama+CherryStudio) 【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱 【DeepSeek应用】Zotero+Deepseek 阅读与分析文献 【DeepSeek应用】100个 DeepSeek 官方推荐的工具箱 * 1. DeepSeek 工具箱:应用程序 * 2. DeepSeek 工具箱:AI Agent 框架 * 3. DeepSeek 工具箱:RAG 框架 * 4. DeepSeek 工具箱:即时通讯软件 * 5. DeepSeek 工具箱:浏览器插件 * 6. DeepSeek 工具箱:

By Ne0inhk
“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

By Ne0inhk