二叉排序树
又称 二叉查找树、二叉搜索树,是一种对排序和查找都很有效的特殊二叉树。
性质
(1)其左右子树也分别为二叉排序树
(2)中序遍历其可得到一个递增的有序序列
(3)左子树所有结点均小于根结点,右子树所有结点均大于根结点
二叉排序树的存储
//二叉排序树的存储
typedef struct
{
int key;
//还可设置其他数据项
}KeyType;
typedef struct BSTNode
{
KeyType data;
struct BSTNode *L,*R;
}BSTNode,*BSTree;
二叉排序树的查找:时间复杂度O(log2n)
//二叉排序树的查找
BSTree SearchBST(BSTree T,int key)
{
if((!T)||key==T->data.key)
{
return T;
}
else if(key<T->data.key)
{
return SearchBST(T->L,key);
}
else
{
return SearchBST(T->R,key);
}
}
结点的值相同但创建树的序列不同,树的形态也会不同,但中序遍历这两棵树的序列相同,因此平均查找长度(ASL)和树的形态有关。
二叉排序树上的查找和折半查找相差不大,最坏情况下ASL和顺序查找相同,最好情况下ASL和折半查找相似,其ASL和log2n是同数量级的,不过二叉排序树更适用于对表进行插入删除的操作。
二叉排序树的插入:时间复杂度O(log2n)
//二叉排序树的插入
void InsertBST(BSTree &T,int e)//这里需用到C++的引用类型
{
BSTree S;
if(!T)
{
S=malloc(sizeof(BSTNode));
S->data.key=e;
S->L=S->R=NULL;
T=S;
}
else if(e<T->data.key)
{
InsertBST(T->L,e);
}
else
{
InsertBST(T->R,e);
}
}
二叉排序树的插入操作是以查找为基础的,所以时间复杂度相同,当树中不存在关键字等于key的结点时才可以插入。且因为二叉排序树的特性,插入操作只能在叶子端进行。
二叉排序树的创建:时间复杂度O(nlog2n)
//二叉排序树的创建
void CreateBST(BSTree &T,int ENDFLAG)
{
int e;
T=NULL;
scanf("%d",&e);
while(e!=ENDFLAG)//ENDFLAG为自定义常量,作输入结束标志
{
InsertBST(T,e);
scanf("%d",&e);
}
return ;
}
二叉排序树的创建基本上就是插入n个结点,也就是调用n次插入函数,所以时间复杂度为插入函数的n倍。
二叉排序树的删除:时间复杂度O(log2n)
(1)若该结点为叶子结点,则直接修改双亲结点的指针
(2)若该结点缺右子树,则用左孩子填补被删除的指针
(3)若该结点缺左子树,则用右孩子填补被删除的指针
(4)若该结点具有左右子树,在左子树上找中序最后一个结点(即左子树中最大的结点)填补,或在右子树上找中序第一个结点填补(即右子树最小结点)后者可能会增加树的深度,所以一般采用前者。
void DeleteBST(BSTree &T,int key)
{
BSTree p,q,s,f;
p=T;
f=NULL;
while(p)
{
if(p->data.key==key)
{
break;
}
f=p;
if(p->data.key>key)
{
p=p->L;
}
else
{
p=p->R;
}
}
if(!p)
{
return ;
}
q=p;
if(p->L&&p->R)
{
s=p->L;
while(s->R)
{
q=s;
s=s->R;
}
p->data=s->data;
if(q!=p)
{
q->R=s->L;
}
else
{
q->L=s->L;
}
free(s);
return ;
}
else if(!p->R)
{
p=p->L;
}
else if(!p->L)
{
p=p->R;
}
if(!f)
{
T=p;
}
else if(q==f->L)
{
f->L=p;
}
else
{
f->R=p;
}
free(p);
return ;
}
同插入一样,删除的基本过程也是查找,所以时间复杂度仍是O(log2n)
平衡二叉树
平衡二叉树又称AVL树,结点的平衡因子为左右子树的深度之差,可以是空树,或者是具有以下特征的二叉排序树:
(1)左子树和右子树的深度之差的绝对值不超过1
(2)左子树和右子树也是平衡二叉树
平衡二叉树的调整:先按照二叉排序树进行插入,若插入结点后破坏了其平衡特性,则对平衡二叉树进行调整,主要分为四个情况
(1)LL型:顺时针右旋
(2)RR型:逆时针左旋
(3)LR型:先逆时针左旋,再顺时针右旋
(4)RL型:先顺时针右旋,再逆时针左旋
平衡二叉树的建立
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct AVLTree
{
int K;
int H;
struct AVLTree *L, *R;
} * AVLTree, BTNode;
int max(int a, int b)
{
return a > b ? a : b;
}
int Depth(AVLTree T)
{
if (T == NULL)
{
return -1;
}
else
{
return T->H;
}
}
AVLTree LL(AVLTree root)
{
AVLTree p;
p = root->L;
root->L = p->R;
p->R = root;
root->H = max(Depth(root->L), Depth(root->R)) + 1;
return p;
}
AVLTree RR(AVLTree root)
{
AVLTree p;
p = root->R;
root->R = p->L;
p->L = root;
root->H = max(Depth(root->L), Depth(root->R)) + 1;
return p;
}
AVLTree RL(AVLTree root)
{
root->R = LL(root->R);
root = RR(root);
return root;
}
AVLTree LR(AVLTree root)
{
root->L = RR(root->L);
root = LL(root);
return root;
}
AVLTree Insert(AVLTree root, int num)
{
if (!root)
{
root = (AVLTree)malloc(sizeof(BTNode));
root->K = num;
root->H = 0;
root->L = NULL;
root->R = NULL;
}
else
{
if (num < root->K)
{
root->L = Insert(root->L, num);
if (abs(Depth(root->L) - Depth(root->R)) >= 2)
{
if (num > root->L->K)
{
root = LR(root);
}
else
{
root = LL(root);
}
}
}
else if (num == root->K)
{
//无操作
}
else
{
root->R = Insert(root->R, num);
if (abs(Depth(root->L) - Depth(root->R)) >= 2)
{
if (num > root->R->K)
{
root = RR(root);
}
else
{
root = RL(root);
}
}
}
}
if (Depth(root->L) == Depth(root->R))
{
root->H = 0;
}
else
{
root->H = max(Depth(root->L), Depth(root->R)) + 1;
}
return root;
}
void InOrder(AVLTree T)
{
if (T == NULL)
{
return ;
}
InOrder(T->L);
printf("%d ", T->K);
InOrder(T->R);
return;
}
int main()
{
AVLTree root = NULL;
int n, num;
printf("Please input the number of the BiTree node:");
scanf("%d", &n);
while (n--)
{
printf("Please input the Node:");
scanf("%d", &num);
root = Insert(root, num);
}
printf("The root node of the AVLTree is:");
printf("%d\n", root->K);
printf("The InOrder of the AVLTree is:");
InOrder(root);
return 0;
}
平衡二叉树的建立是以二叉排序树的插入为基础的,当树中不存在有相同值的结点时,即插入结点,之后树的深度加一,再判断是否平衡,若不平衡则进行相应的旋转操作,一步一步绘制出AVL树。
B树
前面的查找方法均适用于存储在计算机内存中较小的文件,统称为内查找法。若文件很大且存放于外存进行查找时,就应该使用适用于外查找的平衡多叉树——B树
B树(m阶)的性质:
(1)每个结点最多有m颗子树
(2)若根结点不是叶子结点,则至少有两颗子树
(3)除根之外的所有非终端结点至少有m/2颗子树
(4)所有叶子结点都在同一层次上且不带信息,通常称为失败节点(失败结点并不存在,引入是为了更好的分析B树的查找性能)
(5)所有的非终端结点最多有m-1个关键字
B树的存储
//B树的存储
typedef struct BTNode
{
int keynum;
struct BTNode *parent;
int K[m + 1];
struct BTNode *ptr[m + 1];
} BTNode, *BTree;
typedef struct Result
{
BTNode *pt;
int i;
int tag;
} Result;
B树的查找
//B树的查找
int Search(BTree T, int key)
{
BTree p = T;
int end;
if (p)
{
end = p->keynum;
}
else
{
return 0;
}
int i = 0;
if (end == 0)
{
return i;
}
else if (key >= p->K[end])
{
i = end;
return i;
}
else if (key <= p->K[1])
{
return i;
}
else
{
for (i = 1; i < end; i++)
{
if (p->K[i] <= key && key < p->K[i + 1])
{
return i;
}
}
}
}
Result SearchBTree(BTree &T, int key)
{
BTree p = T;
BTree q = NULL;
int found = FALSE;
int i = 0;
while (p && !found)
{
i = Search(p, key);
if (i > 0 && p->K[i] == key)
{
found = TRUE;
}
else
{
q = p;
p = p->ptr[i];
}
}
Result result;
if (found)
{
result.pt = p;
result.i = i;
result.tag = 1;
return result;
}
else
{
result.pt = q;
result.i = i;
result.tag = 0;
return result;
}
}
B树的插入
//B树的插入
void Insert(BTree &q, int i, int x, BTree &ap)
{
int j;
for (j = m - 1; j > i; j--)
{
q->K[j + 1] = q->K[j];
}
for (j = m; j > i; j--)
{
q->ptr[j] = q->ptr[j - 1];
}
q->K[i + 1] = x;
q->ptr[i + 1] = ap;
q->keynum++;
}
void Split(BTree &q, int s, BTree ap &ap)
{
int i;
ap = malloc(sizeof(BTNode));
for (i = s + 1; i <= m; i++)
{
ap->K[i - s - 1] = q->K[i];
ap->ptr[i - s - 1] = q->ptr[i];
}
if (ap->ptr[0])
{
for (i = 0; i <= 1; i++)
{
ap->ptr[i]->parent = ap;
}
}
ap->keynum = (m - s) - 1;
ap->parent = q->parent;
q->keynum = q->keynum - (m - s);
}
void NewRoot(BTree T &T, BTree q, int x, BTree &ap)
{
BTree NT = malloc(sizeof(BTNode));
NT->K[1] = x;
NT->ptr[0] = T;
NT->ptr[1] = ap;
NT->keynum = 1;
NT->parent = NULL;
ap->parent = NT;
T->parent = NT;
T = NT;
}
int InsertBTree(BTree &T, int K, BTree q, int i)
{
int x = K;
BTree ap = NULL;
int finished = FALSE;
while (q && !finished)
{
Insert(q, i, x, ap);
if (q->keynum < m)
{
finished = TRUE;
}
else
{
int s = m / 2;
Split(q, s, ap);
x = ap->K[0];
q = q->parent;
if (q)
{
i = Search(q, x);
}
}
}
if (!finished)
{
NewRoot(T, q, x, ap);
}
return OK;
}
B+树
B+树是一种B-树的变形树,更适合文件索引系统。
B+树与B-树的差异在于:
(1)有n颗子树的结点中含有n个关键字
(2)所有叶子结点中包含了全部关键字的信息以及指向这些关键字的指针,且叶子结点本身依关键字的大小自小而大顺序来链接
(3)所有非终端结点可以看成是索引部分,结点中仅含其子树(根结点)中的最大或最小关键字
B+树的优点程序员小灰在《漫画算法》里总结的很到位
通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。因此我们可以对B+树进行两种查找运算
(1)从根结点开始进行随机查找
(2)从最小关键字起顺序查找