数据结构之二叉搜索树
2016-02-29 19:36
387 查看
一颗二叉搜索树是以一颗二叉树组织的,这样一颗可以使用一个链表数据结构来表示,其中每个节点都是一个对象,除了key之外,每个节点还包含属性left、right和p,它们分别指向节点的左孩子、右孩子和双亲。如果某个孩子结点和父节点不存在,则相应属性的值为NULL,根节点是树中唯一一个父指针为NULL的节点。
6
/ \
5 7
/ \ \
2 5 8
二叉搜索树。对于任何节点x,其左子树中的关键字最大不超过x.key,其有子树中的关键字最小不低于x.key。大部分的搜索树操作的最坏运行时间与树的高度成正比。
二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:
设x二叉搜索树中的一个节点。如果y是x左子树中的一个节点,那么y.key <= x.key。如果y是x右子树中的一个节点,那么y.key >= x.key 。
接下来我们实现二叉搜索树的创建、销毁(递归、非递归)、遍历(先序、中序、后序)、得到最大最小值、插入和删除。
首先我们来看二叉搜索树的对于接口的声明和节点类型的定义:
binaryserach_tree.h:
本次的节点类型的声明和以往的声明有一点不同,就是在Tree_node的结构体中加入了双亲节点,使得我们的后序操作变得方便。
(a)二叉搜索树的创建:首先根结点单独创建,对于剩余节点遍历数组剩下的元素,创建节点,寻找节点的双亲,其创建的代码如下:
其中p_node用于创建新的节点,pa 和 c联动进行寻找p_node插入的位置,最终pa为p_node的双亲,然后比较pa数据域的值和p_node数据域的值,并根据大小将p_node插入到pa的左子女或者右子女。
看完创建过程,接下来我们来看二叉搜索树的销毁。
(b)二叉搜索树的销毁:本文章给大家介绍两种方式:递归销毁和非递归销毁。递归销毁的方式比较简单,非递归的销毁方式需要借助于队列,队列的实现在之前的二叉树已经详细的说明,今天则不重点说明,下面我们具体来看实现:
对于非递归销毁的检测:
对于非递归的检测:
(c)二叉搜索树的遍历:对于遍历比较特殊的是,对于二叉搜索树的中序遍历即可得到升序序列,二叉搜索树的遍历和二叉树遍历方式一样,分为:先序、中序、后序、层序。下面我们来看具体的实现(包括递归、非递归):
对于遍历的检测:
(d)二叉搜索树的查找:对于二叉搜索树的查找可以使用两种方式:递归和非递归,对于二叉搜索树的查找的时间复杂度为o(h),h为树的高度,下面是查找的代码:
(f)二叉搜索树的插入:二叉树的插入操作的代码如下:node和parent用于确定新节点插入的位置,parent为新节点的双亲节点,最后再将新节点挂在parent的左子女上或右子女上。
从一颗二叉搜索树T中删除一个节点z的整个策略可以分为三种基本情况(如下所述),但是只有一种情况比较棘手。
1>如果z没有孩子节点,那么只是简单的删除,并修改它的父节点,用NULL作为孩子来替换z
2>如果在z只有一个孩子,那么将这个孩子提升到树中的z的位置,并修改z的父节点,用z的孩子来替换z;
3>如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来的右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树。这种情况稍显麻烦,因为还与y是否为z的右子树相关。
从一颗二叉搜索树T中删除一个节点z,这个过程取指向T和z的指针作为输入参数,考虑如图所示的4种情况,它与前面概括出的三种情况有所不同。
a>
Z
/ \ R
NULL R -------> / \
/ \
(a)
如图(a)所示 ,z没有左孩子,那么用其右孩子来替换z,这个右孩子可以是NULL,也可以不是。当右孩子为NULL时,此时这种情况归为z没有孩子节点的情形,当右孩子非NULL时,此时这种情况就是z仅有一个孩子节点的情况,该孩子是右孩子;
b>
Z
/ \ ------> L
L NULL / \
/ \
(b)
如图(b)所示,如果z仅有一个孩子为左孩子,那么用其左孩子来替换z;否则z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树并且没有左孩子,则需要将y移出原来的位置进行拼接,并替换树中的z。
c>
Z
/ \
L Y -------> Y
/ \ / \
NULL X L X
/ \ / \ / \
如图(c)所示,用y替换z,并仅留下y的右孩子。
d>
Z
/ \ Z Y Y
L R -------> / / \ ------> / \
/ \ / \ L NULL R L R
Y / \ / \ / \ / \
/ \ X X
NULL X / \ / \
/ \
其代码实现如下:
为了方便大家理解程序,我跟大家大致画出最初创建出的二叉树:
15
/ \
6 18
/ \ / \
3 7 17 20
/ \ \
2 4 13
把节点10和19插入到二叉搜索树中后:
15
/ \
6 18
/ \ / \
3 7 17 20
/ \ \ \
2 4 13 19
/
10
至此二叉搜索树的实现已完成,此后会和大家一起讨论红黑树、B树、AVL树等等,敬请期待!!!!
6
/ \
5 7
/ \ \
2 5 8
二叉搜索树。对于任何节点x,其左子树中的关键字最大不超过x.key,其有子树中的关键字最小不低于x.key。大部分的搜索树操作的最坏运行时间与树的高度成正比。
二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:
设x二叉搜索树中的一个节点。如果y是x左子树中的一个节点,那么y.key <= x.key。如果y是x右子树中的一个节点,那么y.key >= x.key 。
接下来我们实现二叉搜索树的创建、销毁(递归、非递归)、遍历(先序、中序、后序)、得到最大最小值、插入和删除。
首先我们来看二叉搜索树的对于接口的声明和节点类型的定义:
binaryserach_tree.h:
#ifndef _SERACH_TREE_ #define _SERACH_TREE_ #include "tools.h" #define N (10) typedef struct Tree_node{ int data; //数据区域 struct Tree_node *left_child ; //左孩子 struct Tree_node *right_child; //右孩子 struct Tree_node *p; //双亲 }Tree_node; typedef Tree_node *Bin_tree; //二叉搜索树节点指针 //二叉搜索树的接口 //二叉搜索树的创建和销毁 Bin_tree creat_tree(int a[],int n); //创建二叉搜索树 void destroy_tree(Bin_tree *root); //二叉搜索树的销毁 void destroy_tree_nr(Bin_tree *root); //二叉搜索树的非递归销毁 //二叉搜索树的遍历 void pre_order_print(Bin_tree root); //先序遍历 void mid_order_print(Bin_tree root); //中序遍历 void last_order_print(Bin_tree root); //后序遍历 void pre_order_print_nr(Bin_tree root); //先序遍历(非递归) void mid_order_print_nr(Bin_tree root); //中序遍历(非递归) void last_order_print_nr(Bin_tree root); //后序遍历(非递归) void level_order_print(Bin_tree root); //层序遍历 //二叉搜索树的查找 Tree_node * Tree_serach(Bin_tree root,int k); //递归 Tree_node * Tree_serach_nr(Bin_tree root,int k); //非递归 //得到最大关键字元素和得到最小关键字元素 Tree_node *get_tree_minnum(Bin_tree root); Tree_node *get_tree_maxnum(Bin_tree root); //返回节点的后继(右孩子) Tree_node *tree_successor(Bin_tree root); //插入和删除 void tree_insert(Bin_tree root,Tree_node *value); Bin_tree tree_delete(Bin_tree root,Tree_node *value); #endif
本次的节点类型的声明和以往的声明有一点不同,就是在Tree_node的结构体中加入了双亲节点,使得我们的后序操作变得方便。
(a)二叉搜索树的创建:首先根结点单独创建,对于剩余节点遍历数组剩下的元素,创建节点,寻找节点的双亲,其创建的代码如下:
Bin_tree creat_tree(int a[],int n) //创建二叉搜索树 { Tree_node *root = NULL; Tree_node *p_node = NULL; Tree_node *c = NULL; Tree_node *pa = NULL; int i = 0; if(a == NULL || n < 0){ return NULL; } root = (Tree_node *)Malloc(sizeof(Tree_node)); root ->data = a[0]; root->left_child = NULL; root ->right_child = NULL; root ->p = NULL; for(i = 1; i < n; ++i){ p_node = (Tree_node *)Malloc(sizeof(Tree_node)); p_node ->data = a[i]; p_node ->left_child = NULL; p_node ->right_child = NULL; c = root; while(c){ pa = c; if(c ->data > p_node ->data){ c = c ->left_child; }else{ c = c ->right_child; } } if(pa ->data > p_node ->data){ pa ->left_child = p_node; }else{ pa ->right_child = p_node; } p_node ->p = pa; } return root; }
其中p_node用于创建新的节点,pa 和 c联动进行寻找p_node插入的位置,最终pa为p_node的双亲,然后比较pa数据域的值和p_node数据域的值,并根据大小将p_node插入到pa的左子女或者右子女。
看完创建过程,接下来我们来看二叉搜索树的销毁。
(b)二叉搜索树的销毁:本文章给大家介绍两种方式:递归销毁和非递归销毁。递归销毁的方式比较简单,非递归的销毁方式需要借助于队列,队列的实现在之前的二叉树已经详细的说明,今天则不重点说明,下面我们具体来看实现:
static void destroy(Bin_tree root) { if(root){ destroy(root ->left_child); destroy(root ->right_child); free(root); } } void destroy_tree(Bin_tree *root) //二叉搜索树的销毁 { if(root == NULL || *root == NULL){ return; } destroy(*root); *root = NULL; } void destroy_tree_nr(Bin_tree *root) //二叉搜索树的非递归销毁 { Queue *queue = NULL; Tree_node *node = NULL; if(root == NULL || *root == NULL){ return ; } queue = init_queue(); in(queue,*root); *root = NULL; while(!is_queue_empty(queue)){ get_queue_front(queue,(void **)&node); out(queue); if(node ->left_child){ in(queue,node ->left_child); } if(node ->right_child){ in(queue,node->right_child); } free(node); } destory_queue(&queue); }创建和销毁都和大家介绍完了,我们通过查看内存是否泄露来判断程序的对错:
对于非递归销毁的检测:
对于非递归的检测:
(c)二叉搜索树的遍历:对于遍历比较特殊的是,对于二叉搜索树的中序遍历即可得到升序序列,二叉搜索树的遍历和二叉树遍历方式一样,分为:先序、中序、后序、层序。下面我们来看具体的实现(包括递归、非递归):
//二叉搜索树的遍历 void pre_order_print(Bin_tree root) //先序遍历 { if(root){ printf("%5d",root ->data); pre_order_print(root ->left_child); pre_order_print(root ->right_child); } } void mid_order_print(Bin_tree root) //中序遍历 { if(root){ mid_order_print(root ->left_child); printf("%5d",root ->data); mid_order_print(root ->right_child); } } void last_order_print(Bin_tree root) //后序遍历 { if(root){ last_order_print(root ->left_child); last_order_print(root ->right_child); printf("%5d",root ->data); } } void level_order_print(Bin_tree root) //层序遍历 { Queue *queue = NULL; Tree_node *node = NULL; if(root == NULL){ return ; } queue = init_queue(); in(queue,root); while(!is_queue_empty(queue)){ get_queue_front(queue,(void **)&node); out(queue); printf("%5d",node ->data); if(node ->left_child){ in(queue,node ->left_child); } if(node ->right_child){ in(queue,node ->right_child); } } destory_queue(&queue); } void pre_order_print_nr(Bin_tree root) //先序遍历非递归 { Stack *stack = NULL; Tree_node *node = NULL; if(root == NULL){ return; } stack = init_stack(); push(stack,root); while(!is_stack_empty(stack)){ get_top(stack,(void**)&node); pop(stack); printf("%5d",node ->data); if(node ->right_child){ push(stack,node ->right_child); } if(node ->left_child){ push(stack,node ->left_child); } } destroy_stack(&stack); } void mid_order_print_nr(Bin_tree root) //中序遍历非递归 { Stack * stack = NULL; Tree_node *node = NULL; if(root == NULL){ return; } stack = init_stack(); node = root; while(!is_stack_empty(stack) || node != NULL){ while(node != NULL){ push(stack,node); node = node ->left_child; } get_top(stack,(void **)&node); printf("%5d",node ->data); pop(stack); node = node ->right_child; } destroy_stack(&stack); } void last_order_print_nr(Bin_tree root) //后序遍历非递归 { Stack *stack = NULL; Tree_node *node = NULL; Tree_node *prev = NULL; if(root == NULL){ return ; } stack = init_stack(); node = root; push(stack,node); while(!is_stack_empty(stack)){ get_top(stack,(void **)&node); if((node ->left_child == NULL && node ->right_child == NULL) || (prev != NULL && (node ->left_child == prev ||node ->right_child == prev))){ printf("%5d",node ->data); pop(stack); prev = node; }else{ if(node ->right_child){ push(stack,node ->right_child); } if(node ->left_child){ push(stack,node ->left_child); } } } destroy_stack(&stack); }
对于遍历的检测:
(d)二叉搜索树的查找:对于二叉搜索树的查找可以使用两种方式:递归和非递归,对于二叉搜索树的查找的时间复杂度为o(h),h为树的高度,下面是查找的代码:
//二叉搜索树的查找 Tree_node *Tree_serach(Bin_tree root,int k) //递归 { if(root == NULL || root ->data == k){ return root; } if(k < root ->data){ return Tree_serach(root ->left_child,k); }else{ return Tree_serach(root ->right_child,k); } } Tree_node * Tree_serach_nr(Bin_tree root,int k) //非递归 { Tree_node *node = NULL; node = root; while(node != NULL && k!= node ->data){ if(k < root ->data){ node = node ->left_child; }else{ node = node ->right_child; } } return node; }(d)得到最大和最小的值:由于二叉搜索树的性质,我们可以得出,二叉搜索树的最大值位于右子树的最右边的最后一个叶子,最小值位于左子树的最左边的最后一个叶子,其代码如下:
//得到最大关键字元素和得到最小关键字元素 Tree_node *get_tree_minnum(Bin_tree root) { Tree_node *node = NULL; node = root; while(node && node ->left_child){ node = node ->left_child; } return node; } Tree_node *get_tree_maxnum(Bin_tree root) { Tree_node *node = NULL; node = root; while(node && node ->right_child){ node = node ->right_child; } return node; }(e)得到前驱、后继节点:给定一颗二叉搜索树中的一个节点,有时候需要按中序遍历的次序查找它的后继,如果所有的关键字互不相同,则一个节点x的后继是大于x.key的最小关键字的节点,其代码的实现如下:
//返回节点的后继、前驱 Tree_node *tree_successor(Bin_tree r) { // 15 // / \ // 6 18 // / \ / \ // 3 7 17 20 // / \ \ // 2 4 13 // / // 9 // 给定一颗二叉树中的节点,有时则需要按中序遍历的次序查找它的后继 // 如果所有的关键字都不相同,则一个节点x的后继是大于x.key的最小关键字 // 的节点 // 因此可分为两种情况: // 第一种:节点x 的右子树非空,那么x的后继恰是x右子树的最左节点 // 可以通过get_tree_minnum(x.right)的调用找到 例如 关键字为15的节点 // 的后继是关键字为17的节点 // 第二种:如果节点x的右子树为空并有一个后继y,那么y是x的最底层祖先 // 例如:关键字为13的节点的后继是关键字为15的节点 Tree_node *parent = NULL; Tree_node *node = NULL; node = r; if(r == NULL){ return NULL; } if(r ->right_child){ return get_tree_minnum(r ->right_child); } parent = node ->p; while(parent && node == parent ->right_child){ node = parent; parent = parent ->p; } return parent; }
(f)二叉搜索树的插入:二叉树的插入操作的代码如下:node和parent用于确定新节点插入的位置,parent为新节点的双亲节点,最后再将新节点挂在parent的左子女上或右子女上。
void tree_insert(Bin_tree root,Tree_node *value) { Tree_node *node = NULL; Tree_node *parent = NULL; if(value == NULL){ return; } node = root; while(node){ parent = node; if(node ->data < value ->data){ node = node ->right_child; }else { node = node ->left_child; } } value ->p = parent; if(parent == NULL){ root = value; }else{ if(value ->data > parent ->data){ parent ->right_child = value; }else{ parent->left_child = value; } } }(g) 二叉搜索树删除:
从一颗二叉搜索树T中删除一个节点z的整个策略可以分为三种基本情况(如下所述),但是只有一种情况比较棘手。
1>如果z没有孩子节点,那么只是简单的删除,并修改它的父节点,用NULL作为孩子来替换z
2>如果在z只有一个孩子,那么将这个孩子提升到树中的z的位置,并修改z的父节点,用z的孩子来替换z;
3>如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来的右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树。这种情况稍显麻烦,因为还与y是否为z的右子树相关。
从一颗二叉搜索树T中删除一个节点z,这个过程取指向T和z的指针作为输入参数,考虑如图所示的4种情况,它与前面概括出的三种情况有所不同。
a>
Z
/ \ R
NULL R -------> / \
/ \
(a)
如图(a)所示 ,z没有左孩子,那么用其右孩子来替换z,这个右孩子可以是NULL,也可以不是。当右孩子为NULL时,此时这种情况归为z没有孩子节点的情形,当右孩子非NULL时,此时这种情况就是z仅有一个孩子节点的情况,该孩子是右孩子;
b>
Z
/ \ ------> L
L NULL / \
/ \
(b)
如图(b)所示,如果z仅有一个孩子为左孩子,那么用其左孩子来替换z;否则z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树并且没有左孩子,则需要将y移出原来的位置进行拼接,并替换树中的z。
c>
Z
/ \
L Y -------> Y
/ \ / \
NULL X L X
/ \ / \ / \
如图(c)所示,用y替换z,并仅留下y的右孩子。
d>
Z
/ \ Z Y Y
L R -------> / / \ ------> / \
/ \ / \ L NULL R L R
Y / \ / \ / \ / \
/ \ X X
NULL X / \ / \
/ \
其代码实现如下:
static Bin_tree tree_transplant(Bin_tree root,Bin_tree u,Bin_tree v) { if(u ->p == NULL){ root = v; }else if(u == u ->p ->left_child){ u ->p ->left_child = v; }else{ u ->p ->right_child = v; } if(v != NULL){ v ->p = u ->p; } return root; } Bin_tree tree_delete(Bin_tree root,Tree_node *value) { Tree_node *p_node = NULL; if(root == NULL || value == NULL){ return; } if(value ->left_child == NULL){ root = tree_transplant(root,value,value ->right_child); }else if(value ->right_child == NULL){ root = tree_transplant(root,value,value ->left_child); }else { p_node = get_tree_minnum(value ->right_child); if(p_node ->p != value){ root = tree_transplant(root,p_node,p_node ->right_child); p_node ->right_child = value ->right_child; p_node ->right_child ->p = p_node; } root = tree_transplant(root,value,p_node); p_node ->left_child = value ->left_child; p_node ->left_child ->p = p_node; } return root; }接下来我们看测试程序的书写:
#include <stdio.h> #include <stdlib.h> #include "binaryserach_tree.h" int main(int argc,char **argv) { int a = {15,6,18,3,7,17,20,2,4,13}; Bin_tree root = NULL; Tree_node *p_node = NULL; Tree_node *node = NULL; Tree_node *node1 = NULL; root = creat_tree(a,N); #if 0 printf("level_order:\n"); level_order_print(root); printf("\n"); printf("pre_order:\n"); pre_order_print(root); printf("\n"); printf("mid_order:\n"); mid_order_print(root); printf("\n"); printf("last_order:\n"); last_order_print(root); printf("\n"); printf("pre_order_nr:\n"); pre_order_print_nr(root); printf("\n"); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); printf("last_order_nr:\n"); last_order_print_nr(root); printf("\n"); #endif #if 1 p_node = Tree_serach(root,6); if(p_node){ printf("%d is found !\n",p_node ->data); }else{ printf(" is not found !\n"); } p_node = Tree_serach(root,18); if(p_node){ printf("%d is found !\n",p_node ->data); }else{ printf(" is not found !\n"); } p_node = Tree_serach_nr(root,9); if(p_node){ printf("%d is found !\n",p_node ->data); }else{ printf(" is not found !\n"); } #endif #if 0 p_node = get_tree_minnum(root); if(p_node){ printf("minnum :%d\n",p_node ->data); } p_node = get_tree_maxnum(root); if(p_node){ printf("maxnum :%d\n",p_node ->data); } p_node = tree_successor(root); if(p_node){ printf("root:%d\n",p_node ->data); }else{ printf("root:not found!\n"); } p_node = tree_successor(root ->right_child ->right_child); if(p_node){ printf("root ->right_child ->right_child:%d\n",p_node ->data); }else{ printf("root ->right_child ->right_child:not found!\n"); } p_node = tree_successor(root ->left_child ->left_child); if(p_node){ printf("root ->left_child ->left_child:%d\n",p_node ->data); }else{ printf("root ->left_child->left_child:not found !\n"); } p_node = tree_successor(root ->right_child ->left_child); if(p_node){ printf("root ->right_child ->left_child:%d\n",p_node ->data); }else{ printf("root ->right_child->left_child:not found !\n"); } p_node = tree_successor(root ->left_child ->left_child ->right_child); if(p_node){ printf("root ->left_child ->left_child->right_child:%d\n",p_node ->data); }else{ printf("root ->left_child ->left_child->right_child:not found !\n"); } //二叉搜索树的插入: node = (Tree_node *)Malloc(sizeof(Tree_node)); node ->data = 10; node ->left_child = NULL; node ->right_child = NULL; node ->p = NULL; tree_insert(root,node); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); node1 = (Tree_node *)Malloc(sizeof(Tree_node)); node1 ->data = 19; node1 ->left_child = NULL; node1 ->right_child = NULL; node1 ->p = NULL; tree_insert(root,node1); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); p_node = root ->right_child; root = tree_delete(root,p_node); printf("delete root ->right_child:%d\n",p_node ->data); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); root = tree_delete(root,node1); printf("delete node1:%d\n",node1 ->data); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); root = tree_delete(root,node); printf("delete node:%d\n",node->data); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); p_node = root; root = tree_delete(root,p_node); printf("delete root:%d\n",p_node->data); printf("mid_order_nr:\n"); mid_order_print_nr(root); printf("\n"); #endif //destroy_tree(&root); destroy_tree_nr(&root); return 0; }我们先来看其测试结果:
为了方便大家理解程序,我跟大家大致画出最初创建出的二叉树:
15
/ \
6 18
/ \ / \
3 7 17 20
/ \ \
2 4 13
把节点10和19插入到二叉搜索树中后:
15
/ \
6 18
/ \ / \
3 7 17 20
/ \ \ \
2 4 13 19
/
10
至此二叉搜索树的实现已完成,此后会和大家一起讨论红黑树、B树、AVL树等等,敬请期待!!!!
相关文章推荐
- 5-4-十字链表(稀疏矩阵)-数组和广义表-第5章-《数据结构》课本源码-严蔚敏吴伟民版
- 5-3-行逻辑链接的顺序表(稀疏矩阵)-数组和广义表-第5章-《数据结构》课本源码-严蔚敏吴伟民版
- HDU3436[离散化splay]
- 数据结构之四叉树的实现
- 数据结构心得1
- 【数据结构】栈的压入、弹出序列
- 5-2-三元组顺序表(稀疏矩阵)-数组和广义表-第5章-《数据结构》课本源码-严蔚敏吴伟民版
- 数据结构(13)二叉树的动态链表存储和遍历的实现
- 【数据结构与算法】希尔排序
- 数据结构(12)--二叉树的静态链表存储和遍历的实现
- 【数据结构】包含min函数的栈
- C++数据结构之 --二叉树简单实现和4种遍历
- 5-1-数组的顺序存储结构-数组和广义表-第5章-《数据结构》课本源码-严蔚敏吴伟民版
- 跳跃表-随机化数据结构
- 数据结构-Treap
- 树、森林和二叉树的转换
- 数据结构顺序表Java实现
- 串-第4章-《数据结构题集》答案解析-严蔚敏吴伟民版
- 算法与数据结构学习资料及面试
- 4-5-创建索引表-串-第4章-《数据结构》课本源码-严蔚敏吴伟民版