数据结构与算法——二叉搜索树的算法实现
1. 二叉树的概念
当要在一组数中要找到一个数,比如 26?该怎么找?
61 | 25 | 7 | 11 | 15 | 99 | 19 | 21 | 55 | 26 |
最暴力的答案: 从左至右 或 从右至左遍历一次,找到这个数字
但把数据进行排序(按照从小到大的顺序排列)后,再查找相应的这条记录?
5 | 7 | 11 | 15 | 19 | 21 | 25 | 26 | 61 | 99 |
聪明的答案:最快的方式,是采用折半法(俗称二分查找)
当我们有新的数据加进来,或者删除其中的一条记录,为了保障查找的效率,我们仍然要保障数组有序,但是,会涉及到大量数据的移动!在插入和删除操作上,就需要耗费大量的时间(需进行元素的移位),首先需要解决一个问题,插入时不移动元素,这时可以用到链表,但是要保证其有序的话,首先得遍历链表寻找合适的位置,又如何高效的查找合适的位置,这时就可以用到二叉树的形式,以数据集第一个元素为根节点,之后将比根节点小的元素放在左子树中,将比根节点大的元素放在右子树中,在左右子树中同样采取此规则。那么在查找 x 时,若 x 比根节点小可以排除右子树所有元素, 去左子树中查找(类似二分查找),这样查找的效率非常好,而且插入的时间复杂度为 O(h),h 为树的高度,较 O(n) 来说效率提高不少。故二叉搜索树用作一些查找和插入使用频率比较高的场景。
二叉树一般采用链式存储方式:每个结点包含两个指针域,指向两个孩子结点,还包含一个数据域,存储结点信息。
2. 树的节点结构体的定义
1 #define MAX_NODE 1024 2 3 #define isLess(a, b) (a<b) 4 #define isEqual(a, b) (a==b) 5 6 typedef int ElemType; 7 8 typedef struct _Bnode 9 { 10 ElemType data; //元素类型 11 struct _Bnode* lchild, * rchild; //双指针,指向左右孩子节点 12 }Bnode, * Btree;
3. 二叉搜索树插入节点
将要插入的结点 e,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上 操作直到找到一个空位置用于放置该新节点。
1 bool InsertBtree(Btree** root, Bnode* node) 2 { 3 Bnode* tmp = NULL; 4 Bnode* parent = NULL; 5 if (!node) 6 { 7 return false; 8 } 9 else 10 { //清空新节点的左右子树 11 node->lchild = NULL; 12 node->rchild = NULL; 13 } 14 if (*root) 15 { //存在根节点 16 tmp = *root; 17 } 18 else { //不存在根节点 19 *root = node; 20 return true; 21 } 22 while (tmp != NULL) 23 { 24 parent = tmp; //保存父节点 25 printf("父节点: %d\n", parent->data); 26 if (isLess(node->data, tmp->data)) 27 { 28 tmp = tmp->lchild; 29 } 30 else 31 { 32 tmp = tmp->rchild; 33 } 34 } 35 //若该树为空树,则直接将 node 放置在根节点上 36 if (isLess(node->data, parent->data)) 37 { //找到空位置后,进行插入 38 parent->lchild = node; 39 } 40 else 41 { 42 parent->rchild = node; 43 } 44 return true; 45 }
4. 二叉搜索树删除节点
将要删除的节点的值,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以 上操作直到找到一个节点的值等于删除的值,则将此节点删除。删除时有 4 中情况须分别处理:
√ 删除节点不存在左右子节点,即为叶子节点,直接删除
√ 删除节点存在左子节点,不存在右子节点,直接把左子节点替代删除节8点
√ 删除节点存在右子节点,不存在左子节点,直接把右子节点替代删除节点
√ 删除节点存在左右子节点,则取左子树上的最大节点或右子树上的最小节点替换删除节点。
5. 删除节点_以及查找最大节点:
1 /****************************** 2 * 采用二叉搜索树上最大的结点 * 3 ******************************/ 4 int findMax(Btree* root) 5 { 6 assert(root != NULL); 7 //方式一 采用递归 8 /*if(root->rchild==NULL) 9 { 10 return root->data; 11 } 12 return findMax(root->rchild); 13 */ 14 //方式二 采用循环 15 while (root->rchild) 16 { 17 root = root->rchild; 18 } 19 return root->data; 20 } 21 22 /************************** 23 * 采用递归方式查找结点 * 24 ***************************/ 25 Btree* DeleteNode(Btree* root, int key) 26 { 27 if (root == NULL) return NULL; //没有找到删除节点 28 if (root->data > key) 29 { 30 root->lchild = DeleteNode(root->lchild, key); 31 return root; 32 } 33 if (root->data < key) 34 { 35 root->rchild = DeleteNode(root->rchild, key); 36 return root; 37 } 38 //删除节点不存在左右子节点,即为叶子节点,直接删除 39 if (root->lchild == NULL && root->rchild == NULL)return NULL; 40 //删除节点只存在右子节点,直接用右子节点取代删除节点 41 if (root->lchild == NULL && root->rchild != NULL)return root->rchild; 42 //删除节点只存在左子节点,直接用左子节点取代删除节点 43 if (root->lchild != NULL && root->rchild == NULL)return root->lchild; 44 ////删除节点存在左右子节点,直接用左子节点最大值取代删除节点 45 int val = findMax(root->lchild); 46 root->data = val; 47 root->lchild = DeleteNode(root->lchild, val); 48 return root; 49 }
6. 二叉搜索树搜索
1 /************************ 2 * 采用递归方式查找结点 3 *************************/ 4 Bnode* queryByRec(Btree *root, ElemType e) 5 { 6 if (root == NULL || isEqual(root->data, e)) 7 { 8 return root; 9 } 10 else if(isLess(e, root->data)) 11 { 12 return queryByRec(root->lchild, e); 13 } 14 else 15 { 16 return queryByRec(root->rchild, e); 17 } 18 } 19 20 /*************************** 21 * 使用非递归方式查找结点 * 22 ****************************/ 23 Bnode* queryByLoop(Bnode *root, int e) 24 { 25 while(root != NULL && !isEqual(root->data, e)) 26 { 27 if(isLess(e, root->data)) 28 { 29 root = root->lchild; 30 } 31 else 32 { 33 root = root->rchild; 34 } 35 } 36 return root; 37 }
7. 二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种次序依次访问所有结点,使得每个结点被当且访问一次。共分为四种方式:
7.1 前序遍历
先访问根节点,然后前序遍历左子树,再前序遍历右子树
上图前序遍历结果: 19 7 5 11 15 25 21 61
前序遍历实现:
递归实现
1 /*************************** 2 * 采用递归方式实现前序遍历 * 3 ****************************/ 4 void PreOrderRec(Btree *root) 5 { 6 if (root == NULL) 7 { 8 return; 9 } 10 printf("- %d -", root->data); 11 preOrderRec(root->lchild); 12 preOrderRec(root->rchild); 13 }
非递归实现
具体过程:
首先申请一个新的栈,记为 stack;
将头结点 head 压入 stack 中;
每次从 stack 中弹出栈顶节点,记为 cur,然后打印 cur 值,如果 cur 右孩子不为空,则将右孩子压入栈中;
如果 cur 的左 孩子不为空,将其压入 stack 中;
重复步骤 3,直到 stack 为空.
1 /************************ 2 * 借助栈实现前序遍历 3 *************************/ 4 void PreOrder(Btree *root) 5 { 6 Bnode cur ; 7 if (root == NULL) 8 { 9 return; 10 } 11 SqStack stack; 12 InitStack(stack); 13 PushStack(stack, *root); //头节点先入栈 14 15 while (!(IsEmpty(stack))) //栈为空,所有节点均已处理 16 { 17 PopStack(stack, cur); //要遍历的节点 18 printf("- %d -", cur.data); 19 if (cur.rchild != NULL) 20 { 21 PushStack(stack, *(cur.rchild)); //右子节点先入栈,后处理 22 } 23 if (cur.lchild != NULL) 24 { 25 PushStack(stack, *(cur.lchild)); //左子节点后入栈,接下来先处理 26 } 27 } 28 DestroyStack(stack); 29 }
7.2 中序遍历
先访问根节点的左子树,然后访问根节点,最后遍历右子树
中序遍历结果: 5 7 11 15 19 21 25 61
7.3 后序遍历
从左到右,先叶子后节点的方式遍历访问左右子树,最后访问根节点
后序遍历结果: 5 15 11 7 21 61 25 19
7.4 层序遍历
从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问
上图层序遍历结果: 19 7 25 5 11 21 61 15
完整源码实现
stack.h
1 #pragma once 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include "tree.h" 5 #define MaxSize 128 //预先分配空间,这个数值根据实际需要预估确定 6 7 typedef struct _SqStack 8 { 9 Bnode *base; //栈底指针 10 Bnode *top; //栈顶指针 11 }SqStack; 12 13 bool InitStack(SqStack &S) //构造一个空栈 S 14 { 15 S.base = new Bnode[MaxSize];//为顺序栈分配一个最大容量为 Maxsize 的空间 16 if (!S.base) //空间分配失败 17 return false; 18 S.top=S.base; //top 初始为 base,空栈 19 return true; 20 } 21 22 bool PushStack(SqStack &S, Bnode e) // 插入元素 e 为新的栈顶元素 23 { 24 if (S.top-S.base == MaxSize) //栈满 25 return false; 26 *(S.top++) = e; //元素 e 压入栈顶,然后栈顶指针加 1,等价于*S.top=e; 27 S.top++; 28 return true; 29 } 30 31 bool PopStack(SqStack &S, Bnode &e) //删除 S 的栈顶元素,暂存在变量 e 中 32 { 33 if (S.base == S.top) //栈空 34 { 35 return false; 36 } 37 e = *(--S.top); //栈顶指针减 1,将栈顶元素赋给 e 38 return true; 39 } 40 41 Bnode* GetTop(SqStack &S) //返回 S 的栈顶元素,栈顶指针不变 42 { 43 if (S.top != S.base) //栈非空 44 { 45 return S.top - 1; //返回栈顶元素的值,栈顶指针不变 46 } 47 else 48 { 49 return NULL; 50 } 51 } 52 53 int GetSize(SqStack &S) //返回栈中元素个数 54 { 55 return (S.top-S.base); 56 } 57 58 bool IsEmpty(SqStack &S) //判断栈是否为空 59 { 60 if (S.top == S.base) 61 { 62 return true; 63 } 64 else 65 { 66 return false; 67 } 68 } 69 70 void DestroyStack(SqStack &S) //销毁栈 71 { 72 if(S.base) 73 { 74 free(S.base); 75 S.base = NULL; 76 S.top = NULL; 77 } 78 }
tree.h
1 #ifndef __TREE_H__ 2 #define __TREE_H__ 3 4 #define MAX_NODE 1024 5 6 #define isLess(a, b) (a<b) 7 #define isEqual(a, b) (a==b) 8 9 typedef int ElemType; 10 11 typedef struct _Bnode 12 { 13 ElemType data; //元素类型 14 struct _Bnode *lchild, *rchild; //指向左右孩子节点 15 }Bnode, Btree; 16 17 #endif
tree.cpp
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "tree.h" 4 #include "stack.h" 5 6 bool InsertBtree(Btree **root, Bnode *node) 7 { 8 Bnode *tmp = NULL; 9 Bnode *parent = NULL; 10 if(!node) 11 { 12 return false; 13 } 14 else 15 { 16 //清空新节点的左右子树 17 node->lchild = NULL; 18 node->rchild = NULL; 19 } 20 if(*root) 21 { 22 //存在根节点 23 tmp= *root; 24 } 25 else 26 { 27 //不存在根节点 28 *root = node; 29 return true; 30 } 31 32 while(tmp != NULL) 33 { 34 parent = tmp; //保存父节点 35 //printf("父节点: %d\n", parent->data); 36 if(isLess(node->data,tmp->data)) 37 { 38 tmp = tmp->lchild; 39 } 40 else 41 { 42 tmp = tmp->rchild; 43 } 44 } 45 //若该树为空树,则直接将 node 放置在根节点上 46 if(isLess(node->data, parent->data)) 47 { 48 //找到空位置后,进行插入 49 parent->lchild = node; 50 } 51 else 52 { 53 parent->rchild = node; 54 } 55 return true; 56 } 57 58 59 /************************ 60 * 采用二叉搜索树上最大的结点 61 *************************/ 62 int findMax(Btree* root) 63 { 64 if(root->rchild==NULL) 65 { 66 return root->data; 67 } 68 return findMax(root->rchild); 69 } 70 71 /************************ 72 * 采用递归方式查找结点 * 73 *************************/ 74 Btree* DeleteNode(Btree* root, int key, Bnode* &deletedNode) 75 { 76 if(root==NULL)return NULL; 77 if(root->data > key) 78 { 79 root->lchild = DeleteNode(root->lchild, key, deletedNode); 80 return root; 81 } 82 if(root->data < key) 83 { 84 root->rchild = DeleteNode(root->rchild, key, deletedNode); 85 return root; 86 } 87 deletedNode = root; 88 //删除节点不存在左右子节点,即为叶子节点,直接删除 89 if(root->lchild==NULL && root->rchild==NULL)return NULL; 90 //删除节点存在右子节点,直接用右子节点取代删除节点 91 if(root->lchild==NULL && root->rchild!=NULL)return root->rchild; 92 //删除节点存在左子节点,直接用左子节点取代删除节点 93 if(root->lchild!=NULL && root->rchild==NULL)return root->lchild; 94 ////删除节点存在左右子节点,直接用左子节点最大值取代删除节点 95 int val = findMax(root->lchild); 96 root->data=val; 97 root->lchild = DeleteNode(root->lchild,val, deletedNode); 98 return root; 99 } 100 101 /************************ 102 * 采用递归方式查找结点 103 *************************/ 104 Bnode* QueryByRec(Btree *root, ElemType e) 105 { 106 if (isEqual(root->data, e) || NULL == root) 107 { 108 return root; 109 } 110 else if(isLess(e, root->data)) 111 { 112 return QueryByRec(root->lchild, e); 113 } 114 else 115 { 116 return QueryByRec(root->rchild, e); 117 } 118 } 119 120 /************************** 121 * 使用非递归方式查找结点 * 122 ***************************/ 123 Bnode* QueryByLoop(Bnode *root, int e) 124 { 125 while(NULL != root && !isEqual(root->data, e)) 126 { 127 if(isLess(e, root->data)) 128 { 129 root = root->lchild; 130 } 131 else 132 { 133 root = root->rchild; 134 } 135 } 136 return root; 137 } 138 139 /************************ 140 * 采用递归方式实现前序遍历 141 *************************/ 142 void PreOrderRec(Btree *root) 143 { 144 if (root == NULL) 145 { 146 return; 147 } 148 printf("- %d -", root->data); 149 PreOrderRec(root->lchild); 150 PreOrderRec(root->rchild); 151 } 152 153 /************************* 154 * 借助栈实现前序遍历 * 155 **************************/ 156 void PreOrder(Btree *root) 157 { 158 Bnode cur ; 159 if (root == NULL) 160 { 161 return; 162 } 163 SqStack stack; 164 InitStack(stack); 165 PushStack(stack, *root); //头节点先入栈 166 while (!(IsEmpty(stack))) //栈为空,所有节点均已处理 167 { 168 PopStack(stack, cur); //要遍历的节点 169 printf("- %d -", cur.data); 170 if (cur.rchild != NULL) 171 { 172 PushStack(stack, *(cur.rchild)); //右子节点先入栈,后处理 173 } 174 if (cur.lchild != NULL) 175 { 176 PushStack(stack, *(cur.lchild)); //左子节点后入栈,接下来先处理 177 } 178 } 179 DestroyStack(stack); 180 } 181 182 int main(void) 183 { 184 int test[]={19, 7, 25, 5, 11, 15, 21, 61}; 185 Bnode * root=NULL, *node =NULL; 186 node = new Bnode; 187 node->data = test[0]; 188 InsertBtree(&root, node);//插入根节点 189 190 for(int i=1;i<sizeof(test)/sizeof(test[0]);i++) 191 { 192 node = new Bnode; 193 node->data = test[i]; 194 if(InsertBtree(&root, node)) 195 { 196 printf("节点 %d 插入成功\n", node->data); 197 } 198 } 199 printf("前序遍历结果: \n"); 200 PreOrderRec(root); 201 printf("\n"); 202 system("pause"); 203 204 //二叉搜索树删除 205 printf("删除节点 15\n"); 206 Bnode *deletedNode = NULL; 207 root = DeleteNode(root, 15, deletedNode); 208 printf("二叉搜索树删除节点 15, %s\n", deletedNode?"删除成功":"删除不成功,节点不存在"); 209 210 if(deletedNode) delete deletedNode; 211 printf("删除后,再次前序遍历结果: \n"); 212 PreOrderRec(root); 213 printf("\n"); 214 215 //二叉搜索树查找节点 216 Bnode * node1 = QueryByLoop(root, 20); 217 printf("搜索二叉搜索树,节点 20 %s\n", node1?"存在":"不存在"); 218 Bnode * node2 = QueryByLoop(root, 21); 219 printf("搜索二叉搜索树,节点 21 %s\n", node2?"存在":"不存在"); 220 system("pause"); 221 222 return 0; 223 }
520