静态树表(次优查找树)(第九章 P222 算法9.3,9.4)

静态树表

 

概述

静态树表是把有序的静态查找表根据数据被查找的概率生成一棵二叉树,使得从二叉树的根节点起,查找左右子树的概率大致相等,使平均查找长度较短。若每个数据的查找概率相等,直接使用折半法即可,不必生成二叉树。

 

引言

 

在静态查找表中对特定关键字进行顺序查找、折半查找或者分块查找,都是在查找表中各关键字被查找概率相同的前提下进行的。而在某些情况下,查找表中各关键字被查找的概率是不同的。

在查找表中各关键字查找概率不相同的情况下,对于使用折半查找算法,按照之前的方式进行,其查找的效率并不一定是最优的。

例如,某查找表中有 5 个关键字,各关键字被查找到的概率分别为:p1 = 0.1,p2 = 0.2,p3= 0.1,p4 = 0.4,p5 = 0.2(全部关键字被查找概率和为 1 ),则根据之前介绍的折半查找算法,查找成功时的平均查找长度为:

但是,如果在查找时,令给定值先和第4个记录(查找表中查找概率高的)进行比较,比较不相等时在继续在左子序列或右子序列中进行折半查找,则查找成功时的平均查找长度为


后者折半查找的效率要比前者高,所以在查找表中各关键字查找概率不同时,要考虑建立一棵查找性能最佳的判定树。若在只考虑查找成功的情况下,描述查找过程的判定树其带权路径长度之和(用 PH 表示)最小时,查找性能最优,称该二叉树为静态最优查找树。

带权路径之和的计算公式为:PH = 所有结点所在的层次数 * 每个结点对应的概率值。

但是由于构造最优查找树花费的时间代价较高,而且有一种构造方式创建的判定树的查找性能同最优查找树仅差 1% - 2%,称这种极度接近于最优查找树的二叉树为次优查找树。

 

 

次优查找树的构建方法

 

 

 

 

完整事例演示

 

例如,一含有 9 个关键字的查找表及其相应权值如下表所示:
 


则构建次优查找树的过程如下:

首先求出查找表中所有的 △P 的值,找出整棵查找表的根结点:

例如,关键字 F 的 △P 的计算方式为:从 G 到 I 的权值和 - 从 A 到 E 的权值和 = 4+3+5-1-1-2-5-3 = 0。 

通过上图左侧表格得知,根结点为 F,以 F 为分界线,左侧子表为 F 结点的左子树,右侧子表为 F 结点的右子树(如上图右侧所示),继续查找左右子树的根结点:

通过重新分别计算左右两查找子表的 △P 的值,得知左子树的根结点为 D,右子树的根结点为 H (如上图右侧所示),以两结点为分界线,继续判断两根结点的左右子树:

通过计算,构建的次优查找树如上图右侧二叉树所示。

后边还有一步,判断关键字 A 和 C 在树中的位置,最后一步两个关键字的权值为 0 ,分别作为结点 B 的左孩子和右孩子,这里不再用图表示。

注意:在建立次优查找树的过程中,由于只根据的各关键字的 P 的值进行构建,没有考虑单个关键字的相应权值的大小,有时会出现根结点的权值比孩子结点的权值还小,此时就需要适当调整两者的位置。

 

总结

由于使用次优查找树和最优查找树的性能差距很小,构造次优查找树的算法的时间复杂度为 O(nlogn),因此可以使用次优查找树表示概率不等的查找表对应的静态查找表(又称为静态树表)。

 

 

 

代码

 

typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */

/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2 

/* 对两个数值型关键字的比较约定为如下的宏定义 */
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))


#define N 9 /* 数据元素个数 */
typedef char KeyType; /* 设关键字域为字符型 */
typedef struct /* 数据元素类型 */
{
	KeyType key;
	int weight;
}ElemType;
ElemType r[N] = { {'A',1},{'B',1},{'C',2},{'D',5},{'E',3},
			   {'F',4},{'G',4},{'H',3},{'I',5} }; /* 数据元素(以教科书例9-1为例),全局变量 */
int sw[N + 1]; /* 累计权值,全局变量 */

typedef ElemType TElemType;


/* ----------------------------     二叉树的二叉链表存储表示    -------------------------------*/

typedef struct BiTNode
{
	TElemType data;
	struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
}BiTNode, *BiTree;

/* ---------------------------------------------------------------------------------------------*/


/* ----------------------------     静态查找表的顺序存储结构    -------------------------------*/

typedef struct
{
	ElemType *elem; /* 数据元素存储空间基址,建表时按实际长度分配,0号单元留空 */
	int length; /* 表长度 */
}SSTable;


/* ---------------------------------------------------------------------------------------------*/




/* --------------------------------    静态查找表(顺序表和有序表)的基本操作(7个)    -------------------------------*/


Status Creat_Seq(SSTable *ST, int n)
{ /* 操作结果: 构造一个含n个数据元素的静态顺序查找表ST(数据来自全局数组r) */
	int i;
	(*ST).elem = (ElemType *)calloc(n + 1, sizeof(ElemType)); /* 动态生成n个数据元素空间(0号单元不用) */
	if (!(*ST).elem)
		return ERROR;
	for (i = 1; i <= n; i++)
		*((*ST).elem + i) = r[i - 1]; /* 将全局数组r的值依次赋给ST */
	(*ST).length = n;
	return OK;
}

void Ascend(SSTable *ST)
{ /* 重建静态查找表为按关键字非降序排序 */
	int i, j, k;
	for (i = 1; i < (*ST).length; i++)
	{
		k = i;
		(*ST).elem[0] = (*ST).elem[i]; /* 待比较值存[0]单元 */
		for (j = i + 1; j <= (*ST).length; j++)
			if LT((*ST).elem[j].key, (*ST).elem[0].key)
			{
				k = j;
				(*ST).elem[0] = (*ST).elem[j];
			}
		if (k != i) /* 有更小的值则交换 */
		{
			(*ST).elem[k] = (*ST).elem[i];
			(*ST).elem[i] = (*ST).elem[0];
		}
	}
}

Status Creat_Ord(SSTable *ST, int n)
{ /* 操作结果: 构造一个含n个数据元素的静态按关键字非降序查找表ST */
  /* 数据来自全局数组r */
	Status f;
	f = Creat_Seq(ST, n);
	if (f)
		Ascend(ST);
	return f;
}

Status Destroy(SSTable *ST)
{ /* 初始条件: 静态查找表ST存在。操作结果: 销毁表ST */
	free((*ST).elem);
	(*ST).elem = NULL;
	(*ST).length = 0;
	return OK;
}

int Search_Seq(SSTable ST, KeyType key)
{ /* 在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为 */
  /* 该元素在表中的位置,否则为0。算法9.1 */
	int i;
	ST.elem[0].key = key; /* 哨兵 */
	for (i = ST.length; !EQ(ST.elem[i].key, key); --i); /* 从后往前找 */
	return i; /* 找不到时,i为0 */
}

int Search_Bin(SSTable ST, KeyType key)
{ /* 在有序表ST中折半查找其关键字等于key的数据元素。若找到,则函数值为 */
  /* 该元素在表中的位置,否则为0。算法9.2 */
	int low, high, mid;
	low = 1; /* 置区间初值 */
	high = ST.length;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if EQ(key, ST.elem[mid].key)  /* 找到待查元素 */
			return mid;
		else if LT(key, ST.elem[mid].key)
			high = mid - 1; /* 继续在前半区间进行查找 */
		else
			low = mid + 1; /* 继续在后半区间进行查找 */
	}
	return 0; /* 顺序表中不存在待查元素 */
}

Status Traverse(SSTable ST, void(*Visit)(ElemType))
{ /* 初始条件: 静态查找表ST存在,Visit()是对元素操作的应用函数 */
  /* 操作结果: 按顺序对ST的每个元素调用函数Visit()一次且仅一次。 */
  /* 一旦Visit()失败,则操作失败 */
	ElemType *p;
	int i;
	p = ++ST.elem; /* p指向第一个元素 */
	for (i = 1; i <= ST.length; i++)
		Visit(*p++);
	return OK;
}


/* -------------------------------------------------------------------------------------------------------------*/

 

/* --------------------------------   静态查找表(静态树表)的操作    -------------------------------*/


Status SecondOptimal(BiTree *T, ElemType R[], int sw[], int low, int high)
{ /* 由有序表R[low..high]及其累计权值表sw(其中sw[0]==0)递归构造 */
  /* 次优查找树T。算法9.3 */
	int i, j;
	double min, dw;
	i = low;
	min = fabs(sw[high] - sw[low]);
	dw = sw[high] + sw[low - 1];
	for (j = low + 1; j <= high; ++j) /* 选择最小的△Pi值 */
		if (fabs(dw - sw[j] - sw[j - 1]) < min)
		{
			i = j;
			min = fabs(dw - sw[j] - sw[j - 1]);
		}
	*T = (BiTree)malloc(sizeof(BiTNode));
	if (!*T)
		return ERROR;
	(*T)->data = R[i]; /* 生成结点 */
	if (i == low)
		(*T)->lchild = NULL; /* 左子树空 */
	else
		SecondOptimal(&(*T)->lchild, R, sw, low, i - 1); /* 构造左子树 */
	if (i == high)
		(*T)->rchild = NULL; /* 右子树空 */
	else
		SecondOptimal(&(*T)->rchild, R, sw, i + 1, high); /* 构造右子树 */
	return OK;
}

void FindSW(int sw[], SSTable ST)
{ /* 按照有序表ST中各数据元素的Weight域求累计权值表sw */
	int i;
	sw[0] = 0;
	for (i = 1; i <= ST.length; i++)
		sw[i] = sw[i - 1] + ST.elem[i].weight;
}

typedef BiTree SOSTree; /* 次优查找树采用二叉链表的存储结构 */
Status CreateSOSTree(SOSTree *T, SSTable ST)
{ /* 由有序表ST构造一棵次优查找树T。ST的数据元素含有权域weight。算法9.4 */
	if (ST.length == 0)
		*T = NULL;
	else
	{
		FindSW(sw, ST); /* 按照有序表ST中各数据元素的Weight域求累计权值表sw */
		SecondOptimal(T, ST.elem, sw, 1, ST.length);
	}
	return OK;
}

Status Search_SOSTree(SOSTree *T, KeyType key)
{ /* 在次优查找树T中查找关键字等于key的元素。找到则返回OK,否则返回FALSE */
	while (*T) /* T非空 */
		if ((*T)->data.key == key)
			return OK;
		else if ((*T)->data.key > key)
			*T = (*T)->lchild;
		else
			*T = (*T)->rchild;
	return FALSE; /* 顺序表中不存在待查元素 */
}


/* --------------------------------------------------------------------------------------------------*/



void print(ElemType c) /* Traverse()调用的函数 */
{
	printf("(%c %d) ", c.key, c.weight);
}

void main()
{
	SSTable st;
	SOSTree t;
	Status i;
	KeyType s;
	Creat_Ord(&st, N); /* 由全局数组产生非降序静态查找表st */
	Traverse(st, print);
	CreateSOSTree(&t, st); /* 由有序表构造一棵次优查找树 */
	printf("\n请输入待查找的字符: ");
	scanf("%c", &s);
	i = Search_SOSTree(&t, s);
	if (i)
		printf("%c的权值是%d\n", s, t->data.weight);
	else
		printf("表中不存在此字符\n");
}

运行结果:

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值