C语言数据结构基础笔记——树、二叉树简介

1.树

树是一种 非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
(图片来源于网络)

任何一棵树,都应该被观察成根和子树。

     什么是根和子树呢?

我们利用人与人之间的关系来形象化了解 树 这种数据结构:

节点的度 :一个节点含有的子树的个数称为该节点的度;
叶节点或终端节点 :度为 0 的节点称为叶节点; 
非终端节点或分支节点 :度不为 0 的节点;
双亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点;
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点; 
兄弟节点 :具有相同父节点的节点互称为兄弟节点; 
树的度 :一棵树中, 最大的节点的度称为树的度
节点的层次 :从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
树的高度或深度 :树中节点的最大层次; 
堂兄弟节点 :双亲在同一层的节点互为堂兄弟;
节点的祖先 :从根到该节点所经分支上的所有节点;
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙。
森林 :由 m m>0 )棵 互不相交 的树的集合称为森林

         “子树都是递归定义的”,所以所有的子树都可以以相同的方法拆解、观察:

                                                     

          子树之间应当绝对独立,否则有环的话就是另一种数据结构——图


2.尝试与选型

                                           没有规定一棵树有多少个孩子

     因此,一棵树中的任意节点到底需要存多少个指针对于我们来说是未知的。                              

如果使用定长数组,势必会造成浪费(有的节点的度很大,有的很小) 

                                           

也可以采用每个树节点中都放顺序表的方法,顺序表作为指针数组存放每一个子树地址(因为是指针数组的指针,所以subA应该为结构体二级指针)。


下面来了解最常用的树的表示法。 

2.1左孩子右兄弟表示法
struct TreeNode{int val;struct TreeNode* leftchild;struct TreeNode* nextbrother;
}

但是请注意,“亲兄弟”才算“兄弟”。

对于树

                          

其中的指针指向就是

         

     如果没有左儿子与右兄弟了,我们便让这个结构的相对应的指针指向空。

若一个父亲想找所有的儿子,就可以:

struct TreeNode* cur;
cur=A->leftchild;
while(cur){//...................cur=cur->next;
}

2.2双亲表示法:

也就是用下标和深度来实现。

                         

(之后的并查集也是这样使用 )

每一个节点存放数据和父亲所在的下标,类似于KMP匹配算法的感觉。A没有父亲,所以存放不存在的下标-1,B、C存放父亲(A)的下标0,以此类推

(尽管物理结构是数组,其实逻辑结构是森林)

3.二叉树

简单来说,每个节点最多两个孩子的树,就是二叉树(但不是一定有两个孩子,也可以作为叶子或者只有一个儿子),也叫BinaryTree、B树等。

3.1搜索二叉树

B树单纯的用来存储数据意义不大,并不如链表和顺序表。其结构存在意义之一如下图的“搜索二叉树”

    搜索二叉树同时具有存储数据和插入数据的功能

  并且最多查找当前树高度次就能找到需要的数据:

因为搜索二叉树满足左子树<根<右子树(左子>树根>右子树同理),比方要搜索数据34,先与48比较,34小于48所以找左儿子25(后文中叫做leftchild或者左子树),34大于25所以找右儿子,成功匹配。每一次搜索都是向下走一层,所以最多搜索树的高度次就能到叶子的位置,效率极高。若要查找的是35,我们按照规则向下一直走到叶子的位置都未能找到,说明该树中不包含35,匹配失败

3.2完全二叉树 

先来了解完全二叉树的特殊形式:满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为 K ,且结点总数是2^{k}-1 ,则它就是满二叉树。

也就是说,满二叉树必须所有非叶节点都有两个后代。

我们再了解一下二叉树的编号命名规则:根为0号,每一辈都从左到右(第一个leftchild 第一个rightchild 第二个leftchild 第二个rightchild)依次编号,编为一辈,再编下一辈。

                       


完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。对于深度为 K 的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的节点一一对应时,称之为完全二叉树。 要注意的是 满二叉树是一种特殊的完全二叉树
换句话说,只有最后一排可以是双叶子不满(单叶子),并且存在的叶子必须是在编号上连续的。

   完全二叉树的部分规律

规律1:有两个儿子的节点个数+1=叶子结点

也就是对于首项为1,公比为2的等比数列中,前n-1项之和加一等于第n项。根据满二叉树可得,an就是叶子结点数,s(n-1)就是有两个儿子的节点数。

我们再以此推广到完全二叉树:每一个正常结点变成一个单儿子节点,就会损失相同个数的叶子节点和有两个儿子的节点(包括他自己),所以在“损失”的累积后,等式依然成立。

a_{n}=s_{n-1}+1              

规律2:

由于右孩子都是偶数,所以-1之后除以二,可以消除小数点的影响。如对于E和D,

(4-1)/2=1(1.5),(3-1)/2=1,D和E就都能找到父亲了。

想利用数组找回二叉树也很容易,按照数量1 2 4 8....的顺序拿数据拼接即可

                                                     


那能否利用数组存储其他类型的树呢?

先说结论,数组只适合满二叉树和完全二叉树 ,否则就如下图,会造成大量空间的浪费

            

其余普通的更适合二叉链

(根为第一层,不从第零层开始计数,但是编号是根一般编为0号,便于和数组下标对接,这一点很容易混淆,望注意)


4.堆(Heap)

4.1堆概念

(请勿将数据结构中的堆与操作系统中的堆概念混淆,仅仅是名字相同而已)

堆必须是一个完全二叉树,分为大堆和小堆(也叫大根堆和小根堆)。

小堆(任何一个父亲<=孩子)

大堆(任何一个父亲>=孩子)

小根堆可用于找最小值,大根堆可用于找最大值

但注意:没有规定兄弟之间的大小关系(也就是叔侄之间没有确定的大小关系),当然,爷孙之间肯定也有大小关系

“只有搜索二叉树规定了左右兄弟的大小

4.2实现堆的基本结构和接口

基于上文,我们用顺序表为物理结构,构建堆(以小堆为例,根为最小)

typedef int HeapDataType;
typedef struct Heap {HeapDataType* arr;int size;int capacity;
}HP;
4.2.1 HPPush

不管是对于一个空堆,还是已经形成的堆,新插入的数据第一步只需要很简单的先加入顺序表。尾差在数组尾巴,空间不够(size==capacity)就扩容

但是假若插入的数据较小,已经大于他此时的父辈了呢?

利用向上调整算法:插入数据后和父亲比,比父亲小就和父亲交换。

     

先进入数组,从child找到parent的下标(由于child是大于等于0的整数,所以parent一定大于等于0),若child更下,就封装一个交换函数,交换child和parent的值,再找到新的child和parent.

如果child已经交换到下标为0的位置了,child已经和parent一起作为根了(并不影响,已达成我们的目的),可以跳出循环了。

实现如下

void Swap(HeapDataType* p1, HeapDataType* p2) {assert(p1 && p2);HeapDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdJustUp(HeapDataType* arr, int child) {int parent = (child - 1) / 2;while (child>0) {if (arr[child] < arr[parent]) {Swap(&arr[child], &arr[parent]);child = parent;parent = (parent - 1) / 2;}else {break;}}}void HPPush(HP* php, HeapDataType x) {assert(php);//不够就扩容if (php->capacity == php->size) {int newcapacity = php->size == 0 ? 4 : 2 * php->capacity;HeapDataType* tmp = (HeapDataType*)realloc(php->arr, newcapacity * sizeof(HeapDataType));if (tmp == NULL) {perror("realloc fail");exit(1);}php->arr = tmp;php->capacity = newcapacity;}php->arr[php->size] = x;php->size++;//向上调整算法AdJustUp(php->arr, php->size - 1);
}

(整个树的形状与结构应该一直在你的脑海中)

我们对push操作进行测试:

     

现在,只要经过Push操作进堆,所有数据都满足小堆的排列规则。

一些其他用例:

                   

将一个数组使用堆插入后,他都会直接变成期望的堆————原因就在于插入时有向上调整算法,每插入一个就会根据大小而改变位置


4.2.2HeapTop与HeapPop

HeapTop ——取堆顶,也就是根,小堆就是取最小值,大堆就是取最大值

直接返回arr[0]即可。

HeapPop——规定,删除是删除堆顶的数据

如果直接将下标为1的数据往前挪动,能行吗?

显然不能通过。

于是,计算机的先辈们发明了这样一种方法:

1.首先交换首位数据arr[size-1]和arr[0]

2.再删除尾数据

3.最后进行向下调整算法(让不合规的根走到叶子的位置去)

                              

   向下调整算法:

任意两个亲兄弟,右孩子的下标都是左孩子+1(没有其他特殊情况,+1不可能到下一辈,因为没有右孩子的位置一定是末位置)

不论是往左还是往右,我们一直在寻找更小的那一个数。所以我们利用假设法,找到左右孩子中更小的那个,进行交换操作,并且迭代。

     

每一轮结束后都将child走到新的左孩子的位置,并且由于完全二叉树的定义,不存在child+1就走到其他亲戚处的可能性(唯一的可能性是退化成下图中的树形,但是此时child+1必定是大于child的),更何况这不是一个完全二叉树。

                                         

代码实现:

void AdjustDown(HP* php) {//建议还是将参数设置为上图的参数,便于复用int parent = 0;int child = parent * 2 + 1;//int smaller = leftchild;//开始假设法找小while (child<php->size) {//smaller = leftchild;if (php->arr[child] > php->arr[child+1]) {child ++;}//已经找到小,向下调整if (php->arr[parent] > php->arr[child]) {Swap(&php->arr[parent], &php->arr[child]);parent = child;child = parent * 2 + 1;}else {break;}}
}void HPPop(HP* php) {assert(php);Swap(&php->arr[0], &php->arr[php->size - 1]);php->size--;//向下调整算法AdjustDown(php);
}

我们先打印出堆顶数据(此时的堆结构如前文中push后的结构,再pop掉最小的14) 

新的、由6个数据构成的新的小堆就完成了。

 4.2.3HeapEmpty

直接返回size是否等于0即可

bool HPEmpty(HP* php) {return php->size == 0;
}

(进行测试功能是可以直接复制粘贴,只要完成目标就行,此时不太需要考虑精简代码 )

到此为止,希望认真学明白的同学建议先独立实现以上接口,再接着阅读

4.3 效率分析与优化(重点)
1.堆的层数和数量关系

   回头看我们的堆,完全二叉树都有如下规律:

        

(满二叉树是完全二叉树的特殊形式)

                                              

对于任意h层的完全二叉树,其节点个数就在2^(h-1)~2^h - 1之间。现在我们知道,在之后的时间复杂度计算中,h大概的数值在logN左右(默认省略了底数2)。

2.前文操作的时间复杂度

push和pop的时间复杂度都是O(logn),远远优于O(n)(刚刚的移动数组就是O(n))

                                 

而push、pop的建堆的时间复杂度就是AdJustUp 和 AdJustDown的时间复杂度 。

我们虽然实现了堆的各个接口,但是我们前文中想要push操作就只能一次一次写,而真实的运用情况应该是给了你很多数据(比方给了你一个数组),你希望将这些数据用堆的方式来管理,这个时候如果依然按照前文中 ,先初始化一个堆,再利用循环一次一次Push(共循环n次),其时间复杂度是O(n*logn)(n次logn的操作),因此我们尝试写一个新的初始化函数,能用堆处理交给我们的数组。

void HPInitArry(HP* php, HeapDataType* a, int n) {assert(php);//a数组就是用户给我们的数据php->arr = (HeapDataType*)malloc(sizeof(HeapDataType) * n);if (php->arr = NULL) {perror("malloc fail!");exit(1);}memcpy(php->arr, a, n * sizeof(HeapDataType));php->capacity = php->size = n;//至此,我们开辟了一个新的(希望实现成堆的)空间,并且把a数组的内容拷贝了进来。//接下来想办法将这些拷贝进来的数据实现成堆}
3.向上调整算法和向下调整算法用于建堆

先分析逻辑:

       回忆向上调整的逻辑,“我们在本来建立好的堆的末尾(对应的物理结构——数组的末尾)加上新的数据作为新的叶子(child),再依靠公式parent=(child-1)/2  , 来找新叶子的父亲,比较是否需要调整,以此类推,最坏情况可能需要调整到根部  ”

      那么,对于我们已经建立好的但是没有符合数据规律的堆,我们先跳过根部(第1层),

从第二层的第一个元素(数组下标为1)开始,每个元素都进行一次向上调整。这样,每次向上调整之前,要调整的数据之上的数据都是已经调整好的、满足条件的堆的排布。

晃眼一看,每次AdJustUp的复杂度为log n,一共调整(n-1)次,那时间复杂度不就是O(n* logn)吗?

                          

                                           “华生,你没有发现忙点 ”

错误的,只有在最后一层才调整logn, 其余时候,在第几层就调整几次,,,,,

所以整体的调整次数应该是:

                 

(在第h层,其调整次数就是h-1,但是每一层需要调整的数量都是上一层的两倍)

     这是一个高中经典的等差+等比数列求和问题,解法大致为让F(h)左端乘以公比为二式,再让原式与二式作差即可,此处不多赘述。结果如下:

                                         

    我们将h按照N最大的情况进行换算可得,

                                           需要的总次数=

       所以时间复杂度也就是O(nlogn).

我把这种方法称之为“向下遍历的向上调整算法

void HPInitArry(HP* php, HeapDataType* a, int n) {assert(php);//a数组就是用户给我们的数据php->arr = (HeapDataType*)malloc(sizeof(HeapDataType) * n);if (php->arr = NULL) {perror("malloc fail!");exit(1);}memcpy(php->arr, a, n * sizeof(HeapDataType));php->capacity = php->size = n;//至此,我们开辟了一个新的(希望实现成堆的)空间,并且把a数组的内容拷贝了进来。//接下来想办法将这些拷贝进来的数据实现成堆//1.先使用向下遍历的向上调整算法for (int i = 1; i < php->size; i++) {AdJustUp(php->arr, i);}
}

向上遍历的向下调整

同理可得:从倒数第二层开始,每一层都向下调一次,调整完一个数据就--,继续调整上一个数据。

 

//2.使用向上遍历的向下调整算法
for (int i = (php->size - 1 - 1) / 2; i > 0; i--) {AdjustDown(php->arr, php->size,i);
}

ps:php->size-1是最后一个元素的下标,也就是child,(child-1)/2才是我们希望找到的倒数第二排的父亲。 

同样的计算方法,最后计算出来答案是O(n). 

为什么在建堆时,“向上遍历的向下调整”优于“向下增加的向上调整”呢?

因为向下调整直接跳过了个数几乎占一半的叶子辈分,需要排序的数据量几乎少一半

而向上调整只跳过了个数为1的根辈分,基本上等于没有跳过。


4.4堆排序简介 

我们现在已经将客户给的数组数据进行堆化了,接下来我们依靠堆化的数据实现经典的堆排序。

    假设我们此时希望升序排列,那就应当使用小根堆,然后依次top再pop.

但是,当我们花了那么多心思写完了整个堆,再top和pop,此时的复杂度为前面的造轮子O(n)+O(n),取为O(n),这样的整体代码成本是不是太高了?     

     综上所述,与其使用向上调整建堆,还不如使用快速排序(也是nlogn)直接对数组进行排序。

          此时我们构建整个堆没有任何时间优势。

那么,能否不造轮子呢???

     直接对客户给我们的数组a使用向上遍历的向下调整算法,获得数组{0,3,1,4,6,9,2,7,5,8}

假设我们希望获得升序的排列,那么应该将调整算法改为大根堆还是小根堆呢?

按照上面的思路,为了从小到大一个一个拿出我们希望的数据,应该是使用小根堆,来康康效果:

向上向下调整的基础都是:“前进”的方向本身已经形成了符合规定的堆

如上图,建小根堆之后,我们拿出最小的数据,剩下的数据关系一乱,就不能使用向下调整,只能重新建堆,时间成本就高了(应该在O(n^{3}))。而重新建堆之后,每建一次堆,都只能选出一个最小的数,比冒泡排序还慢。

接下来就是变魔术的时刻了,我们聪明的前辈们想出了这样一个方法:

    升序用大堆,降序用小堆。(不用实现轮子,但是得实现向下调整算法)

以升序为例:

我们先造出大根堆{9 8 3 7 8 1 2 5 4 0},根(也就是9)一定是最大的数字,让其与较小的0互换位置,并且size--,让9留在数组中但是不再对9进行任何调整的处理,让它置身于事外。再让0做向下调整算法,让除9之外次大的数字坐到根的位置上。

其本质与HPPop是一个道理,只是让希望删除的数据留在数组末尾。

由于前文的实现都是小根堆,所以我们此处实现降序:

     

创建堆的时间复杂度是O(N),类似于pop的堆排序算法的时间复杂度是:

               

也就是 

 

约等于nlogn .

至此,我们的堆排序算法简介就到此。

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

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

相关文章

【OJ】string类题目

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 415字符串相加1.1 分析1.2 代码 2. 344反转字符串2.1 分析2.2 代码 3. HJ1字符串最后一个单词的长度3.1 分析3.2 代码 4. 387.字符串中的第一个唯一字符4.1 分析4.2 代码 5. 125验证回文串5.1 分析5.2 代码 1. 415字符…

wordpress被恶意搜索攻击(网址/?s=****)解决方法。

源地址&#xff1a;https://www.ctvol.com/seoomethods/1413686.html 什么叫恶意搜索攻击&#xff1f; wordpress恶意搜索攻击并不是像病毒一样的攻击&#xff0c;而是一种seo分支黑帽手段&#xff0c;通过被攻击网站搜索功能中长尾关键词来实现攻击&#xff0c;通过网址不断…

【LeetCode热题100】146. LRU 缓存(链表)

一.题目要求 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c…

Jenkins插件Parameterized Scheduler用法

Jenkins定时触发构建的同时设定参数。可以根据不同的定时构建器设置不同参数或环境变量的值。可以设置多个参数。并结合when控制stage流程的执行。结合when和triggeredBy区分定时构建的stage和手动执行的stage。 目录 什么是Parameterized Scheduler&#xff1f;如何配置实现呢…

代码随想录训练营Day24:● 理论基础 ● 77. 组合

理论基础 回溯算法解决的问题 回溯法&#xff0c;一般可以解决如下几种问题&#xff1a; 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合 切割问题&#xff1a;一个字符串按一定规则有几种切割方式 子集问题&#xff1a;一个N个数的集合里有多少符合条件的子集 排列…

yolo项目中如何训练自己的数据集

1.收集自己需要标注的图片 2.打开网站在线标注网站 2.1 点击右下角Get Start 2.2点击这里上传自己的图片 上传成功后有英文的显示 点击左边的Object Detection&#xff0c;表示用于目标检测 2.3选择新建标签还是从本地加载标签 如果是本地加载标签&#xff08;左边&#…

基本常用函数help()

Python内置函数 help()函数&#xff1a;查看对象的帮助信息 print()函数&#xff1a;用于打印输出 input()函数&#xff1a;根据输入内容返回所输入的字符串类型 format()函数&#xff1a;格式化显示 len()函数&#xff1a;返回对象的长度或项目个数 slice()函数&#xf…

26-Java访问者模式 ( Visitor Pattern )

Java访问者模式 摘要实现范例 访问者模式&#xff08;Visitor Pattern&#xff09;使用了一个访问者类&#xff0c;它改变了元素类的执行算法&#xff0c;通过这种方式&#xff0c;元素的执行算法可以随着访问者改变而改变访问者模式中&#xff0c;元素对象已接受访问者对象&a…

TouchGFX之MVP

TouchGFX用户接口遵循Model-View-Presenter&#xff08;MVP&#xff09;架构模式&#xff0c;它是Model-View-Controller&#xff08;MVC&#xff09;模式的派生模式。 两者都广泛用于构建用户接口应用。 MVP模式的主要优势是&#xff1a; 关注点分离&#xff1a;将代码分成不…

mysql 排序底层原理解析

前言 本章详细讲下排序&#xff0c;排序在我们业务开发非常常见&#xff0c;有对时间进行排序&#xff0c;又对城市进行排序的。不合适的排序&#xff0c;将对系统是灾难性的&#xff0c;这个不是危言耸听。可能有些人会想&#xff0c;对于排序mysql 是怎么实现的&#xff0c;…

Android 地图SDK 绘制点 删除 指定

问题 Android 地图SDK 删除指定绘制点 详细问题 笔者进行Android 项目开发&#xff0c;对于已标记的绘制点&#xff0c;提供撤回按钮&#xff0c;即删除绘制点&#xff0c;如何实现。 解决方案 新增绘制点 private List<Marker> markerList new ArrayList<>…

Oracle数据库:使用 bash脚本 + 定时任务 自动备份数据

Oracle数据库&#xff1a;使用 bash脚本 定时任务 自动备份数据 1、前言2、为什么需要自动化备份&#xff1f;3、编写备份脚本4、备份脚本授权5、添加定时任务6、重启 crond / 检查 crond 服务状态7、备份文件检查 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收…

AI赋能写作:AI大模型高效写作一本通

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

Java学习笔记(15)

JDK7前时间相关类 Date时间类 Simpledateformat Format 格式化 Parse 解析 默认格式 指定格式 EE&#xff1a;表示周几 Parse&#xff1a;把字符串时间转成date对象 注意&#xff1a;创建对象的格式要和字符串的格式一样 Calendar日历类 不能创建对象 Getinstance 获取当…

【C#】【SAP2000】读取SAP2000中所有Frame对象在指定工况的温度荷载值到Grasshopper中

if (build true) {// 连接到正在运行的 SAP2000// 使用 COM 接口获取 SAP2000 的 API 对象cOAPI mySapObject (cOAPI)System.Runtime.InteropServices.Marshal.GetActiveObject("CSI.SAP2000.API.SapObject");// 获取 SAP2000 模型对象cSapModel mySapModel mySap…

315曝光黑灰产业链:主板机

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 315晚会曝光主板机黑灰产业链&#xff0c;主板机是什么呢?可能很多人还不知道。在这里松松给大家普及一下&#xff0c;也欢迎大家关注卢松松哟! 主板机是什么呢? 通过报废手机的主板&#xff0c;拆出来后组装成主…

专升本 C语言笔记-06 常用的3种输入输出函数

1.scanf() 与 printf() 的使用 scanf() 格式化输入数据 格式:scanf("格式控制字符串",参数地址列表) scanf("%d,%d,%d",&a,&b,&c); printf("a %d\n",a); printf("b %d\n",b); printf("c %d\n",c); 注意 注…

【数据结构】堆

目录 一、树的介绍以及堆 1.树 2.二叉树以及堆 二、堆的实现 1.heap.h 2.heap.c 1)堆的初始化和销毁 2&#xff09; 堆的插入 3&#xff09;堆的删除 4&#xff09;取堆顶数据 5&#xff09;堆的数据个数 6&#xff09;堆的判空 3.test.c 一、树的介绍以及堆 …

跨境电商选品实战——Ownips公开数据信息安全采集+Python爬虫轻松搞定Lazada电商选品

文章目录 一、引言二、Lazada电商平台选品实战2.1、分析Lazada电商平台的商品列表接口2.2、定位商品列表计算逻辑2.3、封装高质量住宅IP2.4、运行爬虫 三、数据处理及选品分析四、Ownips——企业级全球静态住宅IP&#xff0c;高效采集公开数据 一、引言 互联网与外贸的结合&am…

Maya自定义工具架

有时候我们需要自己定义工具架上的内容&#xff0c;比如将一个工具放到工具架上&#xff0c;或者删除一个工具 添加一个工具 例如我们想在多边形建模栏位上添加一个分离按钮&#xff0c;默认 1 先切换到想要添加的工具架栏位 2 打开菜单&#xff0c;找到我们想添加的工具 …