赞
踩
1、排序树——特点:所有结点“左小右大
2、平衡树——特点:所有结点左右子树深度差≤1
3、红黑树——特点:除了具备二叉查找树的特性外还有5个特性以致保持自平衡。
4、字典树——由字符串构成的二叉排序树
5、判定树——特点:分支查找树(例如12个球如何只称3次便分出轻重)
6、带权树——特点:路径带权值(例如长度)
7、最优树——是带权路径长度最短的树,又称 Huffman树,用途之一是通信中的压缩编码。
或是一棵空树;或者是具有如下性质的非空二叉树:
(1)若左子树不为空,左子树的所有结点的值均小于根的值;
(2)若右子树不为空,右子树的所有结点均大于根的值;
(3)它的左右子树也分别为二叉排序树。
例:二叉排序树 如图9.7:
二叉排序树的查找过程和次优二叉树类似,通常采取二叉链表作为二叉排序树的存储结构。中序遍历二叉排序树可得到一个关键字的有序序列,一个无序序列可以通过构造一棵二叉排序树变成一个有序序列,构造树的过程即为对无序序列进行排序的过程。每次插入的新的结点都是二叉排序树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索,插入,删除的复杂度等于树高,期望O(logn),最坏O(n)(数列有序,树退化成线性表).
虽然二叉排序树的最坏效率是O(n),但它支持动态查询,且有很多改进版的二叉排序树可以使树高为O(logn),如SBT,AVL,红黑树等.故不失为一种好的动态排序方法.
在二叉排序树b中查找x的过程为:
- Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree &p){
- //在根指针T所指二叉排序樹中递归地查找其关键字等于key的数据元素,若查找成功,
- //则指针p指向该数据元素节点,并返回TRUE,否则指针P指向查找路径上访问的
- //最好一个节点并返回FALSE,指针f指向T的双亲,其初始调用值为NULL
- if(!T){ p=f; return FALSE;} //查找不成功
- else if EQ(key, T->data.key) {P=T; return TRUE;} //查找成功
- else if LT(key,T->data.key)
- return SearchBST(T->lchild, key, T, p); //在左子树继续查找
- else return SearchBST(T->rchild, key, T, p); //在右子树继续查找
- }
向一个二叉排序树b中插入一个结点s的算法,过程为:
- /*当二叉排序树T中不存在关键字等于e.key的数据元素时,插入e并返回TRUE,否则返回FALSE*/
- Status InsertBST(BiTree &T, ElemType e){
- if(!SearchBST(T, e.key, NULL,p){
- s=(BiTree)malloc(sizeof(BiTNode));
- s->data = e; s->lchild = s->rchild = NULL;
- if(!p) T-s; //被插结点*s为新的根结点
- else if LT(e.key, p->data.key) p->lchld = s; //被子插结点*s为左孩子
- else p->rchild = s; //被插结点*s为右孩子
- return TRUE;
- }
- else return FALSE; //树中已有关键字相同的结点,不再插入
- }
在二叉排序树删去一个结点,分三种情况讨论:
- Status DeleteBST(BiTree &T, KeyType key){
- //若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素,并返回
- //TRUE;否则返回FALSE
- if(!T) return FALSE; //不存在关键字等于key的数据元素
- else{
- if(EQ(key, T->data.key)) {return Delete(T)}; 找到关键字等于key的数据元素
- else if(LT(key, T->data.key)) return DeleteBST(T->lchild, key);
- else return DeleteBST(T->rchild, key);
- }
- }
- Status Delete(BiTree &p){
- //从二叉排序树中删除结点p,并重接它的左或右子树
- if(!p->rchild){ //右子树空则只需重接它的左子树
- q=p; p=p->lchild; free(q);
- }
- else if(!p->lchild){ //左子树空只需重接它的右子树
- q=p; p=p->rchild; free(q);
- }
- else{ //左右子树均不空
- q=p;
- s=p->lchild;
- while(s->rchild){
- q=s;
- s=s->rchild
- } //转左,然后向右到尽头
- p->data = s->data; //s指向被删结点的“前驱”
- if(q!=p)
- q->rchild = s->lchild; //重接*q的右子树
- else
- q->lchild = s->lchild; //重接*q的左子树
- free(s);
- }
- return TRUE;
- }
-
每个结点的Ci为该结点的层次数。最坏情况下,当先后插入的关键字有序时,构成的二叉排序树蜕变为单支树,树的深度为n,其平均查找长度为 (和顺序查找相同),最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log2(n)成正比 (O(log2(n)))。
Size Balanced Tree(SBT)
AVL树
红黑树
Treap(Tree+Heap)
这些均可以使查找树的高度为O(logn)
性质: 左右子树都是平衡二叉树且所有结点左、右子树深度之差的绝对值 ≤ 1。
若定义结点的“平衡因子” BF(Balance Factor) = 左子树深度 –右子树深度 则:平衡二叉树中所有结点的BF ∈[ -1, 0, 1 ]
例:判断下列二叉树是否AVL树?
平衡二叉树是二叉排序树的另一种形式。
我们希望由任何初始序列构成的二叉排序树都是平衡二叉树。因为平衡二叉树上任何结点的左右子树的深度之差都不超过1,则可以证明它的深度和logN是同数量级的(其中N是结点的个数)。由此,它的平均查找长度也和logN同数量级。
C语言描述:
- typedef struct BSTNode {
- ElemType data;
- int bf; //结点的平衡因子
- struct BSTNode *lchild, *rchild;
- //左、右孩子指针
- } BSTNode, * BSTree;
typedef struct BSTNode { ElemType data; int bf; //结点的平衡因子 struct BSTNode *lchild, *rchild; //左、右孩子指针 } BSTNode, * BSTree;
构造二叉平衡(查找)树的方法是:在插入过程中,采用平衡旋转技术。
插入算法 :
算法思想:
在平衡二叉排序树BBST上插入一个新的数据元素e的递归算法可描述如下:
1.若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结 点,树的深度增1;
2.若e的关键字和BBST的根结点的关键字相等,则不进行插入;
3.若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:
i.BBST的根结点的平衡因子为-1(右子树的深度大于左子树的深度):则将根结点的平衡因子更改为0,BBST的深度不变;
ii.BBST的根结点的平衡因子为0(左、右子树的深度相等):则将根结点的平衡因子更改为1,BBST的深度增1;
iii.BBST的根结点的平衡因子为1(左子树的深度大于右子树的深度):
a. 若BBST的左子树根结点的平衡因子为1,则需进行单向右旋平衡处理,并且在右旋处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变;
b. 若BBST的左子树根结点的平衡因子为-1,则需进行先向左、后向右的双向旋转平衡处理,并且在旋转处理之后,修改根结点和其左、右子树根结点的平衡因子,树的 深度不变。
4.若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。其处理操作和上述3.中描述相对称。
- typedef struct BSTNode {
- ElemType data;
- int bf; //结点的平衡因子
- struct BSTNode *lchild, *rchild;
- //左、右孩子指针
- } BSTNode, * BSTree;
- void R_Rotate (BSTree &p) {
- //对以*p为根的二叉排序树作右旋处理,处理之后p指向新的树根结点,
- //即旋转处理之前的左子树的根结点
- lc = p->lchild; //lc指向的*p的左子树根结点
- p->lchild = lc->rchild; //lc的右子树挂接为*p的左子树
- lc->rchild = p;
- p = lc; //p指向新的根结点
- } // R_Rotate
-
- void L_Rotate (BSTree &p) {
- //对以*p为根的二叉排序树作左旋处理,处理之后p指向新的树根结点,
- //即旋转处理之前的右子树的根结点
- rc = p->rchild; //rc指向的*p的右子树根结点
- p->rchild = rc->lchild; //rc的左子树挂接为*p的右子树
- rc->lchild = p;
- p = rc; //p指向新的根结点
- } // L_Rotate
-
-
-
- #define LH +1 //左高
- #define EH 0 //等高
- #define RH -1 //右高
-
-
-
- Status InsertAVL (BSTree &T, ElemType e, Boolean &taller) {
- //若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入
- //一个数据元素为e的新结点,并返回1,否则返回0。若因插入而使二
- //叉排序树失去平衡,则作平衡旋转处理,布尔变量taller反映T长高
- //与否。
- if (!T) { //插入新结点,树“长高”,置taller为TRUE
- T = (BSTree) malloc (sizeof (BSTNode));
- T->data = e;
- T->lchild = T->rchild = NULL;
- taller = TRUE;
- }
- else {
- if (EQ(e.key, T->data.key)) //树中已存在和e有相同关键字的
- {taller = FALSE; return 0;} //结点则不再插入
- if (LT(e.key, T->data.key)) { //应继续在*T的左子树中进行搜索
- if (!InsertAVL(T->lchild, e, taller)) //未插入
- return 0;
- if (taller) //已插入到*T的左子树中且左子树“长高”
- switch (T->bf) { //检查*T的平衡度
- case LH: //原本左子树比右子树高,需要作左平衡处理
- LeftBalance (T);
- taller = FALSE;
- break;
- case EH: //原本左右子树等高,现因左子树增高而使树增高
- T->bf = LH;
- taller = TRUE;
- break;
-
- case RH: //原本右子树比左子树高,现左、右子树等高
- T->bf = EH;
- taller = FALSE;
- break;
- } // switch (T->bf)
- } // if
- else { //应继续在*T的右子树中进行搜索
- if (!InsertAVL(T->rchild, e, taller)) //未插入
- return 0;
- if (taller) //已插入到*T的右子树中且右子树“长高”
- switch (T->bf) { //检查*T的平衡度
- case LH: //原本右子树比左子树高,现左、右子树等高
- T->bf = EH;
- taller = FALSE;
- break;
- case EH: //原本左右子树等高,现因右子树增高而使树增高
- T->bf = RH;
- taller = TRUE;
- break;
- case RH: //原本右子树比左子树高,需要作右平衡处理
- RightBalance (T);
- taller = FALSE;
- break;
- } // switch (T->bf)
- } // else
- } // else
- return 1;
- } // InsertAVL
-
-
- void LeftBalance (BSTree &T) {
- //对以指针T所指结点为根的二叉树作左平衡旋转处理,本算法
- //结束时,指针T指向新的根结点
- lc = T->lchild; //lc指向*T的左子树根结点
- switch (lc->bf) { //检查*T的左子树的平衡度,并作相应平衡处理
- case LH: //新结点插入在*T的左孩子的左子树上,要作单右旋处理
- T->bf = lc->bf = EH;
- R_Rotate (T);
- break;
- case RH: //新结点插入在*T的左孩子的右子树上,要作双旋处理
- rd = lc->rchild; //rd指向*T的左孩子的右子树根
- switch (rd->bf) { //修改*T及其左孩子的平衡因子
- case LH:
- T->bf = RH;
- lc->bf = EH;
- break;
- case EH:
- T->bf = lc->bf = EH;
- break;
- case RH:
- T->bf = EH;
- lc->bf = LH;
- break;
- } // switch (rd->bf)
- rd->bf = EH;
- L_Rotate (T->lchild); //对*T的左子树作左旋平衡处理
- R_Rotate (T); //对*T作右旋平衡处理
- } // switch (lc->bf)
- } // LeftBalance
红黑树一种含有红黑结点并能自平衡的二叉查找树。除了符合二叉查找树的基本特性外,它必须满足下面性质:
从性质5又可以推出:
下图中这棵树,就是一颗典型的红黑树:
红黑树也是二叉查找树,二叉查找树这一数据结构并不难,而红黑树之所以难是难在它是自平衡的二叉查找树。
正是因为这些规则限制,才能保证红黑树的自平衡。红黑树从根到叶子的最长路径不会超过最短路径的2倍。在进行插入和删除等可能会破坏树的平衡的操作时,需要重新自处理达到平衡状态。
二分查找过程可用二叉树来描述:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树。由此得到的二叉树,称为描述二分查找的判定树(Decision Tree 决策树)或比较树(Comparison Tree)。
举例:12个球如何用天平只称3次便分出轻重?
分析:12个球中必有一个非轻即重,即共有24种“次品”的可能性。每次天平称重的结果有3种,连称3次应该得到的结果有33=27种。说明仅用3次就能找出次品的可能性是存在的。
思路:首先,将12个球分三组,每组4个,任意取两组称。会有两种情况:平衡,或不平衡。其次,一定要利用已经称过的那些结论;即充分利用“旧球”的标准性作为参考。
即路径带有权值。例如:
- // stdafx.h : include file for standard system include files,
- // or project specific include files that are used frequently, but
- // are changed infrequently
- //
-
- #pragma once
-
- #include <stdio.h>
- #include "stdlib.h"
- #include <iostream>
- using namespace std;
-
-
- //宏定义
- #define TRUE 1
- #define FALSE 0
- #define OK 1
- #define ERROR 0
- #define INFEASIBLE -1
- #define OVERFLOW -2
- #define STACKEMPTY -3
- #define QUEUEEMPTY -3
-
- #define MAX 10 // MAXIMUM STACK CONTENT
- #define MAX_QUEUE 10 // MAXIMUM QUEUE CONTENT
-
- typedef int Status;
- typedef int ElemType;
-
- typedef struct{
- unsigned int weight;
- unsigned int parent, lchild,rchild;
- }HTNode, *HuffmanTree; //动态分配数组存储赫夫曼树
-
- typedef char * * HuffmanCode;//动态分配数组存储赫夫曼编码表
test.cpp文件:
- // Test.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
-
-
-
- /************************************************************************/
- /* 算法:
- */
- /************************************************************************/
-
- void select(HuffmanTree &HT,int n,int &h1,int &h2)
- {
- int i ,j;
-
- for(i=1;i<=n;i++)
- if(!HT[i].parent) //一旦找到父结点不为0的结点就停止
- {
- h1=i;
- break;
- }
- for(j=i+1;j<=n;j++)
- if(!HT[j].parent)
- {
- h2=j;
- break;
- }
- for(i=1;i<=n;i++)
- if(HT[h1].weight>HT[i].weight&&!HT[i].parent&&(h2!=i))
- h1=i; //进行比较,找权值最小,和h2不同的结点
- for(j=1;j<=n;j++)
- if(HT[h2].weight>HT[j].weight&&!HT[j].parent&&(h1!=j))
- h2=j; //进行比较,找权值最小,和h1不同的结点
- if(h1>h2)
- {
- int temp; //将权值最小的结点赋给h1
- temp=h1;
- h1=h2;
- h2=temp;
- }
- }
- /************************************************************************/
- /*
- w存放n 个字符的权值(均>0),构造赫夫曼树HT,并求出n 个字符的赫夫曼编码HC。
- */
- /************************************************************************/
- void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w,int n)
- {
- if(n<=1) return;
- int m,i;
- char *cd;
- int s1, s2;
- // HuffmanTree p;
- m = 2*n-1;
- HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); //0号单元未用
- for (i=1; i<=n; i++) { //初始化,相当: p = HT; p = {*w, 0, 0,0 }, ++p;
- HT[i].weight=w[i-1];
- HT[i].parent=0;
- HT[i].lchild=0;
- HT[i].rchild=0;
- }
- for (i=n+1; i<=m; i++) { //初始化 p = {*w, 0, 0,0 }, ++p;
- HT[i].weight=0;
- HT[i].parent=0;
- HT[i].lchild=0;
- HT[i].rchild=0;
- }
-
- //添加查看便于调试
- printf("\n-------------------------------------------");
- printf("\n哈夫曼树的构造过程如下所示:\n");
- printf("HT初态:\n");
- printf(" 结点 weight parent lchild rchild");
- for (i=1; i<=m; i++)
- printf("\n%4d%8d%8d%8d%8d",i,HT[i].weight,HT[i].parent,HT[i].lchild, HT[i].rchild);
-
- for (i=n+1; i<=m; i++) { // 建哈夫曼树
- // 在HT[1..i-1]中选择parent为0且weight最小的两个结点,
- // 其序号分别为s1和s2。
- select(HT, i-1,s1,s2);
- HT[s1].parent = i; HT[s2].parent = i;
- HT[i].lchild = s1; HT[i].rchild = s2;
- HT[i].weight = HT[s1].weight + HT[s2].weight;
- //添加查看,便于调试
- printf("\nselect: s1=%d s2=%d\n", s1, s2);
- printf(" 结点 weight parent lchild rchild");
- for (int j=1; j<=i; j++)
- printf("\n%4d%8d%8d%8d%8d",j,HT[j].weight,
- HT[j].parent,HT[j].lchild, HT[j].rchild);
-
- }
-
- //---从叶子到根逆向求每个字符的赫夫曼编码---
- int start,f;
- unsigned int c;
- HC=(HuffmanCode)malloc((n+1)*sizeof(char *)); //分配n个字符编码的头指针向量
- cd=(char *)malloc(n*sizeof(char)); //分配求编码的工作空间
- cd[n-1]='\0'; //编码结束符
- for(i=1;i<=n;++i)
- {
- //逐个字符求赫夫曼编码
- start=n-1;
- for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)//从叶子到根逆向求编码
- if(HT[f].lchild==c)
- cd[--start]='0';
- else
- cd[--start]='1';
- HC[i]=(char *)malloc((n-start)*sizeof(char)); //为第i个字符编码分配空间
- strcpy(HC[i],&cd[start]); //从cd复制编码到HC
- }
- free(cd); //释放工作区间
- }
- void main()
- {
- HuffmanTree HT; HuffmanCode HC; int *w,n,i;
- printf("输入结点数: ");
- scanf("%d",&n);
- HC=(HuffmanCode)malloc(n*sizeof(HuffmanCode));
- w=(int *)malloc(n*sizeof(int));
- printf("输入%d个结点的权值: ",n);
- for(i=0;i<n;i++)
- scanf("%d",&w[i]);
- HuffmanCoding(HT,HC,w,n);
- printf("\n-------------------------------------------\n");
- printf("\n各结点的赫夫曼编码:\n");
- printf("编号 权值 编码\n");
- for(i=1;i<=n;i++)
- printf("%2d,%6d:%6s\n",i,w[i-1],HC[i]);
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。