【大话数据结构】表的查找和二叉排序树

查找(Searching)是指在查找表中确定一个关键字等于给定值的数据元素或记录。

静态查找表:查找某元素是否存在,或检索某元素的属性。

动态查找表:查找时能够插入或删除元素。

有序表查找

前提是线性表记录的关键字有序,比如从小到大,比如对无序表先做个排序。下面列了三种方法:

二分查找(Binary Search)

条件:线性表记录是关键码有序比如从小到大;线性表采用顺序存储。

思想:取中间记录为比较对象,给定值比其小则在左半区间继续找,比其大则在右半区间继续找,重复上述过程。

int Binary_Search(int *a, int key)
{
	int low, high, mid;
	low = 0;
	high = int(sizeof(a)/sizeof(int)) - 1;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if (key < a[mid])
			high = mid - 1;
		else if (key > a[mid])
			low = mid + 1;
		else
			return mid;
	}
	return 0;
}

插值查找(Interpolation Search)

思想:根据key与表中最大最小记录比较后的查找方法,关键是一个插值的计算公式。

int Interpolation_Search(int *a, int key)
{
	int low, high, mid;
	low = 0;
	high = int(sizeof(a) / sizeof(int)) - 1;
	while (low <= high)
	{
		mid = low + (high - low)*(key - a[low]) / (a[high] - a[low]); /*插值*/
		if (key < a[mid])
			high = mid - 1;
		else if (key > a[mid])
			low = mid + 1;
		else
			return mid;
	}
	return 0;
}

斐波那契查找(Fibonacci Search)

/*Fibonacci查找, F={0,1,1,2,3,5,8,13,21,34,...}*/
int Fibonacci_Search(int *a, int key)
{
	int low, high, mid, i, k, n;
	low = 0;
	high = int(sizeof(a) / sizeof(int))-1;
	n = high;
	k = 0;
	while (n > F[k]-1) /*找最后一位在Fibonacci数列中的位置*/
		k++;
	for (i = n; i < F[k]-1; i++)  /*把表中不到F[k]的用最后一位补全*/
		a[i] = a[n];
	while (low <= high)
	{
		mid = low + F[k - 1] - 1;  /*计算当前分隔的下标*/
		if (key < a[mid])
		{
			high = mid - 1;
			k = k - 1;
		}
		else if (key > a[mid])
		{
			low = mid + 1;
			k = k - 2;
		}
		else
		{
			if (mid <= n)
				return mid;
			else
				return n;
		}
	}
	return 0;
}

二叉排序树(查找,插入,删除)

二叉排序树(Binary Sort Tree)是动态查找最重要的数据结构。它不为空的时候具有以下性质:

  • • 若左子树不为空,左子树上所有结点的值都小于根结点的值
  • • 若右子树不为空,右子树上所有结点的值都小于根结点的值
  • • 左、右子树也都是二叉排序树

也就是说,这个二叉树的结点间满足一定的次序关系,显然查找就更容易了。构造二叉排序树不是为了排序,而是为了提高查找、插入、删除的速度。

/*二叉树的结构*/
typedef struct BiTreeNode
{
	int data;
	struct BiTreeNode *lchild, *rchild;
}BiTreeNode, *BiTree;

/* 递归查找二叉排序树中是否存在key */
bool BinarySortTreeSearch(BiTree T, int key, BiTree f, BiTree *p)
{
	if (!T)  /*若查找不成功,把当前结点(是NULL)的父结点(肯定是个叶子)给p*/
	{
		*p = f;
		return false;
	}
	else if (key == T->data)
	{
		*p = T;
		return true;
	}
	else if (key < T->data)
		return BinarySortTreeSearch(T->lchild, key, T, p);
	else if (key > T->data)
		return BinarySortTreeSearch(T->rchild, key, T, p);
}

/* 二叉排序树插入操作 */
bool BinarySortTreeInsert(BiTree T, int key)
{
	BiTree p, s;
	if (!BinarySortTreeSearch(T, key, NULL, &p)) //查找不到,则插入
	{
		s = (BiTree)malloc(sizeof(BiTreeNode));
		s->data = key;
		s->lchild = NULL;
		s->rchild = NULL;
		if (!p)
			T = s;      //p为空,说明原树为空,s成为新的根结点
		else if (p->data > key)
			p->lchild = s;
		else if (p->data < key)
			p->rchild = s;
		return true;
	}
	else				//查找到了说明树中有相同的结点,则不插入
		return false;
}

/* 二叉排序树的构建,利用插入操作  */
void BinarySortTreeBuild()
{
	int i;
	int a[10] = { 65, 34, 78, 23, 45, 76, 89, 12, 32, 86 };
	BiTree T = NULL;
	for (i = 0; i < 10; i++)
		BinarySortTreeInsert(T, a[i]);
}

/* 删除二叉排序树中等于key的数据元素结点 */
bool BinarySortTreeDelete(BiTree T, int key)
{
	if (!T)
		return false;
	else
	{
		if (key == T->data)
			return BiSTNodeDelete(&T);  //删除结点p,重接它的左树或右树
		else if (key < T->data)
			return BinarySortTreeDelete(T->lchild, key);
		else if (key > T->data)
			return BinarySortTreeDelete(T->rchild, key);
	}
}
bool BiSTNodeDelete(BiTree *p)
{
	BiTree q, s;
	if ((*p)->lchild == NULL)
	{
		q = *p; (*p) = (*p)->rchild; free(q);
	}
	else if ((*p)->rchild == NULL)
	{
		q = *p;  (*p) = (*p)->lchild; free(q);
	}
	else //左右子树都不为空
	{
		s = (*p)->lchild;
		while (s->rchild) //比待删结点(*p)小且最接近它的结点
		{
			q = s; s = s->lchild;
		}
		(*p)->data = s->data;
		if (q != (*p))
			q->rchild = s->lchild;
		else
			q->lchild = s->lchild;
		free(s);
	}
}

平衡二叉树(AVL树)

如果二叉树不平衡,在查找深度较深的结点时效率反而高。

平衡的意思是,每个结点的平衡因子(BF)是-1,0,1。平衡因子是指结点的左子树深度减右子树深度。

举例说明:对同一个数组,a[10] = {3,2,1,4,5,6,7,10,9,8},用前面的方法构造二叉树会如图(左),而理想的平衡二叉树如图(右)


如果构建的二叉树不平衡,那么频繁的查找、插入、删除的效率会非常低。所以就在构建时让它是平衡二叉树,这样才是一种比较理想的动态查找表算法。

散列表查找(哈希表)

散列表是一种非常高效的数据查找结构。

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct  
{
	int *elem;  //数组存储地址,动态分配
	int count;
}HashTable;
int m = 0;
/* 初始化散列表 */
void InitHashTable(HashTable *H)
{
	int i;
	m = HASHSIZE;
	H->count = m;
	H->elem = (int *)malloc(m*sizeof(int));
	for (i = 0; i < m; i++)
		H->elem[i] = NULLKEY;
}
/* 定义散列函数 */
int Hash(int key)
{
	return key%m; //除留余数法
}
/* 插入关键字进散列表 */
void InsertHash(HashTable *H, int key)
{
	int addr = Hash(key);  //计算散列地址
	while (H->elem[addr] != NULLKEY)
	{
		addr = (addr + 1) % m;  //定址法的线性探测
	}
	H->elem[addr] = key;  //有空位再插入关键字
}
int searchHash(HashTable H, int key, int *addr)
{
	*addr = Hash(key);
	while (H.elem[*addr] != key)
	{
		*addr = (*addr + 1) % m;
		if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
			return UNSUCCESS; //地址元素为空或是循环回到原点,都说明key不存在
	}
	return SUCCESS;
}
如果不发生冲突,查找的复杂度是 O(1),它回避了反复的比较操作,基本是一步到位。
但散列表查找关系到 HashTable 的创建,装填因子 α (=填入表的记录数/散列表长度) 越大,冲突的可能性越大,为了尽可能减少冲突,一般把散列表的长度设置的比查找集合大。虽然浪费了一点空间,但得到了效率的提升。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值