【学习笔记】 数据结构 第五章 查找

一.知识框架

静态查找与动态查找

静态查找是指在查找过程中,数据集合在查找之前是固定不变的,也就是说,查找过程中数据不发生增删改操作。静态查找通常采用静态数据结构,如数组或有序数组,通过某些查找算法(例如二分查找)来进行查找。

动态查找则是指在查找过程中,数据集合可以随时发生增删改操作。动态查找通常采用动态数据结构,比如链表、平衡树、哈希表等,以支持在运行时对数据的操作。

如果数据量固定,并且频繁查找,则选择静态查找更为合适。

如果数据变化频繁,且查找操作不占主导,则动态查找更加灵活。

二.线形结构

2.1 顺序查找

代码来自:【数据结构与算法】查找(Search)【详解】_检索结构查找-CSDN博客

int Sequential_Search(int *a, int n, int key){
	int i;
	a[0] = key;	//设置a[0]为关键字,称之为“哨兵”
	i = n;	//循环从数组尾部开始
	while(a[i] != key){
		i--;
	}
	return i;	//返回0则说明查找失败
}

这种在查找方向的尽头放置“哨兵”免去了在查找过程中每一次比较后都要判断查找位置是否越界,在数据较多时,效率提高很大。

顺序表查找时间复杂度是O(n)。

2.2 折半查找(二分)

折半(二分)适用于有序的顺序表。

int Binary_Search(SeqList L, ElemType key){
	int low = 0, high = L.length - 1, mid;
	while(low <= high){
		mid = (low + hight)/2;	//取中间位置
		if(L.elem[mid] == key){
			return mid;	//查找成功返回所在位置
		}else if(L.elem[mid] > key){
			high = mid - 1;	//从前半部分继续查找
		}else{
			low = mid + 1;	//从后半部分继续查找
		}
	}
	return -1;	//查找失败,返回-1
}

折半(二分)的过程可用二叉树来描述,称判断树,折半查找时间复杂度为O(log^{_{2}}n).仅适用于顺序存储结构,不适用于链式存储结构。

2.3 分块查找

是顺序查找的一种改进方法,在此查找法中,出表本身外,还要建立一个索引表。

查找过程:

1.先查找索引表,确定待查元素所属的分块(顺序或折半)

2.在分块内顺序查找法

把长度n的查找表均分为b块 每块s个元素。

ASL^{_{bs}}=L_{b}+L^{_{s}}=(b+1)/2 + (s+1)/2 =(S^2 +2S +n)/2S

当S=\sqrt{n}时,ASL最小为 \sqrt{n}+1 

如果用折半查找法索引表,则长度为ASL^{_{bs}}=L_{b}+L^{_{s}}\approx[log^{_{2}}(B+1)]+(S+1)/2

三.树形结构

3.1 二叉排序树

二叉排序树(二叉搜索树,二叉查找树)一棵非空的二叉排序树具有以下性质;

1.如果左子树不空,则左子树上所有的节点的值都小于根节点值。

2.如果右子树不空,则右子树上所有的节点的值大于根节点的值。

3.左右子树也分别是二叉排序树。

构造一棵二叉树的结构:

/*二叉树的二叉链表结点结构定义*/
typedef struct BiTNode
{
	int data;	//结点数据
	struct BiTNode *lchild, *rchild;	//左右孩子指针
} BiTNode, *Bitree;

查找操作:

/*
递归查找二叉排序树T中是否存在key
指针f指向T的双亲,其初始调用值为NULL
若查找成功,则指针p指向该数据元素结点,并返回TRUE
否则指针p指向查找路径上访问的最后一个结点并返回FALSE
*/
bool 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
*/
bool 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){
			*T = s;	//插入s为新的根节点
		}else if(key < p->data){
			p->lchild = s;	//插入s为左孩子
		}else{
			p->rchild = s;	//插入s为右孩子
		}
		return TRUE;
		}else{
			return FALSE;	//树种已有关键字相同的结点,不再插入
		}
}

删除操作:

删除节点有三种情况:

1.叶子节点

2.仅有左或右子树的节点

3.左右子树都有的节点

/*
若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点,
并返回TRUE;否则返回FALSE
*/
bool DeleteBST(BiTree *T, int key){
	if(!T){
		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,并重接它的左或右子树。*/
bool 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;
		}
		//此时s指向被删结点的直接前驱,p指向s的父母节点
		p->data = s->data;	//被删除结点的值替换成它的直接前驱的值
		if(q != p){
			q->rchild = s->lchild;	//重接q的右子树
		}else{
			q->lchild = s->lchild;	//重接q的左子树
		}
		pree(s);
	}
	return TRUE;
}

二叉树比较平均时 即深度与完全二叉树相同 查找时间为O(logn) 近似于折半查找。

不平衡的最坏情况 查找时间为O(n)等同于顺序查找 最好构建称一棵平衡二叉树

3.2 平衡二叉树

树上任意节点的左子树和右子树的深度之差不超过1.

节点平衡因子 = 左子树的高度 - 右子树的高度

在二叉排序树中 插入和删除节点后,只需要调整 最小不平衡子树 整棵树将恢复平衡。

调整平衡的方法:旋转树

那边长就往放反向旋转(降低树深度),内侧的先往外侧旋。

3.3 红黑树

参考文章:平衡搜索二叉树之红黑树(拒绝死记硬背,拥抱理解记忆)_红黑二叉树-CSDN博客

3.4 B树 和 B+树

B树

定义:m阶B树时所有节点的平衡因子均等于0的m路平衡查找树

特点:

1.每个节点最多m棵子树

2.关键字数范围 m/2(向上取整)-1 ~ m-1

3.所有叶节点在同一层,且不存在,即查找失败的点。

//m叉排序树的数据结构
struct Node{
  ElemType key[m-1];    //最多有m-1个关键字
  struct Node *child[m];//最多有m棵子树
  int num;              //节点中世界存在的关键字个数
};

一棵含有n个关键字的m阶B树:

叶节点: n-1

最小高度: 最胖的树最矮,让树中的每个节点有m-1个关键字。

最大高度:最瘦的最高,让树中每个节点有[m/2]-1 个关键字根节点只有一个关键字就行。

  h\leq \log _{m/2}\frac{n+1}{2} +1

B+树

一棵m阶的B+树,如果不为空,阶必须满足以下特性:

1.树中每个节点至少有m个关键字,即m棵子树。

2.除根节点外,所有非叶节点 至少含有[m/2]个关键字,即[m/2]棵子树。

3.所有叶节点中包含了全部关键字指向记录的指针,叶节点内的关键字有序排列,叶节点之间也是有序排列,指针相连。

4.所有非终端节点可以看成索引,仅包含了其子树中最大或 最小 关键字的值。

B与B+树的差异 (5阶为例)

1.B+树由分块查找进化而来;B树由二叉排序树进化而来。

2.B+树中,每个非根节点关键字的取值范围3 <=n<= 5,由n棵子树;

在B树中,每个非根节点关键字的取值范围是 2<= n <= 4,有 n+1棵子树。

3.在B+树中,仅叶节点包含信息,非叶节点仅起索引作用;

在B树中,全部节点的关键字都包含信息。

4.在B+树中,叶节点包含了全部的关键字,非叶节点中出现的关键字一定会出现在叶节点中,在B树中,任何节点中的关键字都不重复。

5B+树中支持顺序查找和多线路查找,B树支支持多路查找。

四.散列(哈希)表

散列表又称哈希表,这种数据结构的特点是:数据元素的关键字和它在表中的村粗地址直接相关。关键字与存储地址对应关系的函数称为哈希函数.

哈希函数 Address = Hash(key)

1.哈希是一种以常数平均时间执行插入,删除和查找的技术,但是,不支持元素排序和查找最小(大)值的操作。

2.哈希函数是一种映射关系,很灵活,任何关键字通过它的计算,返回值都落在表允许的范围之内即可。

3.对于不同的关键字,可能得到相同的哈希地址,这种现象称为冲突;哈希地址相同的关键字称为同义词。

4.处理冲突的方法有四种:1.链地址法(数组+链表) 2.开放定址法3.再散列法 4.建立公共溢出区。

5.哈希表的填装因子(表中记录数/表长)越大,关键字冲突可能性越大,同义词越多,查找效率可能更低。

散列表设计原则

1.清楚关键字发布情况。

2.散列表的大小由合理,太大浪费空间,太小则产生太多的同义词。

3.散列表中的数据要均匀分布,不要形成堆积。

4.散列表函数代码要精简。

散列表的设计

除留余数法:Hash(key) = key%p;

如果散列表表长为m,p为小于等于m的最大质数,再一般情况下,对质数取余会让冲突更少,数据元素在散列表的分布更均匀。

数据集{5,10,15,20,25,30,35,40,45,50}

直接定址法

Hash(key)=a*key +b   //a和b为常数

数字分析法:根据数字特点,从数值中截取发布比较均匀的若干位作为散列地址,如手机号码;

随机数法:选择一个随机函数,用关键字作为随机函数种子,返回为散列的值。

即Hash(Key) = radmom(key),可结合除留余数法一起用。

//哈希表中数据元素的结构体
typedef struct Element{
  unsigned int key;  //关键字
  int value;  //数据元素其他数据项,可以是任意数据类型
//char value[1001];  //数据元素其他数据项,可以是任意类型
}Element;

//数据元素单链表
typedef struct Node{
  Element elem;  //数据元素
  struct Node *next; //next指针
}Node;

//哈希表
typedef struct HashTable{
  Struct Node *head;  //数据元素存储地址,动态分配数组
  int tablesize;  //哈希表当前大小,即表长
  int count;  //哈下表中数据元素的个数
}HashTable;

//初始化哈希表,tablesize 为哈希表的表长,返回哈希表的地址
HashTable *InitHashTable(const unsigned int tablesize){
//分配哈希表
HashTable *hh=(HashTable*)malloc(sizeof(HashTable));
hh->tablesize=tablesize;  //哈希表长
hh->head=(Node*)malloc((hh->tablesize)*sizeof(Node));
memset(hh->head,0,(hh->tablesize)*sizeof(Node));
hh->count=0;  //哈希表中数据元素个数置为0
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值