Fork me on GitHub

数据结构与算法——二叉搜索树的算法实现

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

 

posted @ 2020-11-13 17:29  索智源  阅读(188)  评论(0编辑  收藏  举报