八、查找(未完)

查找概论

  • 【静态查找】:数据集合稳定,不需要添加,删除元素的查找操作
  • 【动态查找】:数据集合在查找的过程中需要同时添加或删除元素的查找操作

顺序表查找

顺序查找

  • O(n)
  • 又称线性查找
  • 从第一个(或者最后一个)记录开始,逐个将记录的关键字和给定值进行比较
// a-数组,n-数组长度,key-要查找的关键字
int Sequential_Search(int *a, int n, int key)
{
    int i;
    for( i=1; i <= n; i++ )              // 防止越界
    {
        if( a[i] == key )
        {
            return i;
        }
    }
    return 0;
}

优化:【哨兵】无需每次比较都检查是否越界

int Sequential_Search(int *a, int n, int key)
{
    int i = n;            // 循环从尾部开始
    a[0] = key;           // a[0]设为哨兵
    while( a[i] != key )
    {
        i--;
    }
    return i;             // 返回 0 失败
}

有序表查找

折半查找

  • O(logn)
  • 又称二分查找
  • 取中间记录作比较,再选择作左半区或右半区继续比较
int Binary_Search(int *a, int n, int key){
	int low,high,mid;
	low = 1;
	high = n;
	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;
}

插值查找

  • O(logn)
  • 根据key与查找表中最大最小记录的关键字比较后的查找方法
  • 表长较长,关键字分布较均匀,性能比折半查找好
  • 极端不均匀的数据用插值查找不合适
  • 插值计算公式:
    • 将 mid = (low+high)/2 = low + (high - low)/2 的 1/2 优化
    • 改为 (key-a[low]) / (a[high]-a[low])
int Binary_Search(int *a, int n, int key){
	int low,high,mid;
	low = 1;
	high = n;
	while(low<=high){
		mid = low + (key-a[low])/(a[high]-a[low])*(high-low); // 插值与折半唯一不同点
		if(key<a[mid];
			high = mid-1;
		else if(key>a[mid])
			low = mid+1;
		else
			return mid;
	}
	return 0;
}

斐波那契查找

  • O(logn)
  • 利用斐波那契数组
void fibonacci(int *f)
{
	int i;
	f[0] = 1;
	f[1] = 1;
	for(i=2; i < MAXSIZE; ++i)
	{
		f[i] = f[i-2] + f[i-1];
	}
}

int Fibonacci_search(int *a,int key,int n)
{
	int low,high,mid,i,k;
	low = 1;
	high = n;
	k = 0;
	int F[MAXSIZE];               // 声明斐波那契数组
	fibonacci(F);                 // 生成斐波那契数组
	while( n > F[k]-1 ) {         // 算出n位于斐波那契数组位置
		++k;
	}
	for( i=n; i < F[k]-1; ++i){   // 将不满的数值补全
		a[i] = a[n];
	}
	while( low <= high ){
		mid = low + F[k-1] - 1;   // 计算当前分隔的下标
		if( key < a[mid] ){
			high = mid - 1;       // high调整
			k = k - 1;            // 斐波那契数组下标减一位
		}
		else if( key > a[mid] ){
			low = mid + 1;        // low调整
			k = k - 2;            // 斐波那契数组下标减二位
		}
		else{
			if( mid <= n ) {
				return mid;
			}
			else{
				return n;         // mid > n 说明是补全数值,返回n
			}
		}
	}
	return 0;
}

线性索引查找

稠密索引

  • 在线性索引中将数据集中的每个记录对应一个索引项
  • 索引项按照关键码有序排列
    在这里插入图片描述

分块索引

  • 减少索引项个数
  • 将数据集进行分块,使分块有序,对每个分块建立一个索引项
  • 分块有序
    • 块内无序
    • 块间有序:如第二个块中关键字均大于第一个块
      在这里插入图片描述

倒排索引

  • 记录号表存储具有相同次关键字的所有记录的记录号

二叉排序树

  • 动态查找表
  • 二叉排序数(Binary Sort Tree)又称为二叉查找树,它或者是一棵空树,或者是具有下列性质的二叉树:
    • 若它的左子树不为空,则左子树上所有结点的值均小于它的根结构的值;
    • 若它的右子树不为空,则右子树上所有结点的值均大于它的根结构的值;
    • 它的左、右子树也分别为二叉排序树

二叉排序树存储结构

typedef struct BiTNode
{
    int data;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

二叉排序树查找

// 递归查找二叉排序树 T 中是否存在 key
// 指针 f 指向 T 的双亲,其初始值调用值为 NULL
// 若查找成功,则指针 p 指向该数据元素结点,并返回 TRUE
// 否则指针 p 指向查找路径上访问的最后一个结点,并返回 FALSE
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
    if( !T )   				    // 查找不成功
    {
        *p = f;
        return FALSE;
    }
    else if( key == T->data )   // 查找成功
    {
        *p = T;
        return TRUE;
    }
    else if( key < T->data )
    {
        return SearchBST( T->lchild, key, T, p );   // 在左子树继续查找
    }
    else
    {
        return SearchBST( T->rchild, key, T, p );   // 在右子树继续查找
    }
}

二叉排序树插入

// 当二叉排序树 T 中不存在关键字等于 key 的数据元素时,
// 插入 key 并返回 TRUE,否则返回 FALSE
Status InsertBST(BiTree *T, int key)
{
    BiTree p, s;
    if( !SearchBST(*T, key, NULL, &p) )  // 查找不成功
    {
        s = (BiTree)malloc(sizeof(BiTNode));
        s->data = key;
        s->lchild = s->rchild = NULL;
        
        if( !p )        //  p 指向查找路径上访问的最后一个结点
        {
            *T = s;     // 插入 s 为新的根结点
        }
        else if( key < p->data )
        {
            p->lchild = s;  // 插入 s 为左孩子
        }
        else
        {
            p->rchild = s;  // 插入 s 为右孩子
        }
        return TRUE;
    }
    else
    {
        return FALSE;   // 树中已有关键字相同的结点,不再插入
    }
}

二叉排序树删除

// 二叉排序树T中存在关键字等于key的数据元素,则删除该元素结点
Status DeleteBST(BiTree *T, int key)
{
    if( !*T )                   // 不存在关键字等于key的元素
    {
        return FALSE;
    }
    else
    {
        if( key == (*T)->data )          // 找到关键字等于key的元素
        {
            return Delete(T);
        }
        else if( key < (*T)->data )
        {
            return DeleteBST(&(*T)->lchild, key);
        }
        else
        {
            return DeleteBST(&(*T)->rchild, key);
        }
    }
}

// 从二叉排序树中删除结点p,并重接它的左右子树
Status Delete(BiTree *p)
{
    BiTree q, s;
    if( (*p)->rchild == NULL )       // 右子树空则只需重接左子树
    {
        q = *p;
        *p = (*p)->lchild;
        free(q);
    }
    else if( (*p)->lchild == NULL )  //  左子树空则只需重接右子树
    {
        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;
}

平衡二叉树(AVL树)

  • 每个结点左右子树高度差至多为1的二叉排序树

AVL存储结构

typedef struct BiTNode
{
	int data;
	int bf;                    // 结点的平衡因子
	struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

右旋

void R_Rotate(BiTree *p)
{
	BiTree L;
	L = (*p)->lchild;
	(*p)->lchild = L->rchild;
	L->rchild = (*p);
	*p = L;
}

左旋

void L_Rotate(BiTree *p)
{
	BiTree R;
	R = (*p)->rchild;
	(*p)->rchild = R->lchild;
	R->lchild = (*p);
	*p = R;
}

左平衡旋转

#define LH 1    // 左高
#define EH 0    // 等高
#define RH -1   // 右高

void LeftBalance(BiTree *T)
{
	BiTree L, Lr;
	L = (*T)->lchild;
	switch(L->bf)
	{
		case LH:
			(*T)->bf = L->bf = EH;
			R_Rotate(T);
			break;
		case RH:
			Lr = L->rchild;
			switch(Lr->bf)
			{
				case LH:
					(*T)->bf = RH;
					L->bf = EH;
					break
				case EH:
					(*T)->bf = L->bf = EH;
					break;
				case RH:
					(*T)->bf = EH;
					L->bf = LH;
					break;
			}
			Lr->bf = EH;
			L_Rotate(&(*T)->lchild);
			R_Rotate(T);
	}
}

AVL主函数

Status InsertAVL(BiTree *T, int e, Status *taller)
{
	if( !*T )
	{
		*T = (BiTree)malloc(sizeof(BiTNode));
		(*T)->data = e;
		(*T)->lchild = (*T)->rchild = NULL;
		(*T)->bf = EH;
		*taller = TRUE;
	}
	else
	{
		if(e == (*T)->data)
		{
			*taller = FALSE;
			return FALSE;
		}
		if(e < (*T)->data)
		{
			if(!InsertAVL(&(*T)->lchild, e, taller))
			{
				return FALSE;
			}
			if(*taller)
			{
				switch((*T)->bf)
				{
					case LH:
						LeftBalance(T);
						*taller = FALSE;
						break;
					case EH:
						(*T)->bf = LH;
						*taller = TRUE;
						break;
					case RH:
						(*T)->bf = EH;
						*taller = FALSE;
						break;
				}
			}
		}
		else
		{
			if(!InsertAVL(&(*T)->rchild, e, taller))
			{
				return FALSE;
			}
			if(*taller)
			{
				switch((*T)->bf)
				{
					case LH:
						(*T)->bf = EH;
						*taller = FALSE;
						break;
					case EH:
						(*T)->bf = RH;
						*taller = TRUE;
						break;
					case RH:
						RightBalance(T);
						*taller = FALSE;
						break;
				}
			}
		}
	}
	return TRUE;
}

多路查找树(B树)

  • 每个结点孩子数可多于两个,且每个结点可存储多个元素

2-3树

2-3-4树

B树

B+树

散列表查找(哈希表)

散列函数构造

直接定址法

数字分析法

平方取中法

折叠法

除留余数法

随机数法

处理散列冲突

开放定址法

再散列函数法

链地址法

公共溢出区法

算法实现

#define HASHSIZE 12
#define NULLKEY -32768

typedef struct
{
	int *elem;	// 数据元素的基址,动态分配数组
	int count;	// 当前数据元素的个数
}HashTable;

int InitHashTable(HashTable *H)
{
	H->count = HASHSIZE;
	H->elem = (int *)malloc(HASHSIZE * sizeof(int));
	if( !H->elem )
	{
		return -1;
	}
	for( i=0; i < HASHSIZE; i++ )
	{
		H->elem[i] = NULLKEY;
	}
	return 0;
}

// 使用除留余数法
int Hash(int key)
{
	return key % HASHSIZE;
}

// 插入关键字到散列表
void InsertHash(HashTable *H, int key)
{
	int addr;
	
	addr = Hash(key);
	
	while( H->elem[addr] != NULLKEY )	// 如果不为空,则冲突出现
	{
		addr = (addr + 1) % HASHSIZE;	// 开放定址法的线性探测
	}
	
	H->elem[addr] = key;
}

// 散列表查找关键字
int SearchHash(HashTable H, int key, int *addr)
{
	*addr = Hash(key);
	
	while( H.elem[*addr] != key )
	{
		*addr = (*addr + 1) % HASHSIZE;
		if( H.elem[*addr] == NULLKEY || *addr == Hash(key) )
		{
			return -1;
		}
	}
	
	return 0;
}

性能分析

  • 散列函数是否均匀
  • 处理冲突的方法
  • 散列表装填因子
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值