查找
查找表按照操作方式来分有两大种:静态查找表和动态查找表。静态查找表只作查找操作的查找表。动态查找表在查找过程中同时插入查找表中不存在的数据元素, 或者从查找表中删除已经存在的某个数据元素。
顺序表查找(查找O(N)、增删快)
int sequence(int* a,int n,int key){
int i;
a[0]=key;//设置哨兵
i=n;
while(a[i]!=key)//从尾部开始查找
i--;
}
return i;//返回0表明查找失败
}
有序表查找(查找O(logN)、增删慢)
二分查找:前提是关键码有序,顺序存储。
思想:取中间记录作为比较对象。若key小于中间值,则在左半区继续查找;否则,在右半区查找。
循环
int Binary_Search(int* a,int n,int key){
int low=0,high=n-1,mid;//左右都闭
while(low<=high){
mid=(low+high)/2;//二分查找
if(key<a[mid])
high=mid-1;//key小于中间,左部分查找
else if(key>a[mid])
low=mid+1;//key大于中间,在右半区查找
else{
return mid;//找到
}
}
return -1;// 没找到
}
递归
int binary_search(int *arr, int left, int right, int key)
{
assert(arr != NULL);
if (left <= right)
{
int mid = left + ((right - left) >> 1);
if (key > arr[mid])
{
return binary_search(arr, mid+1, right, key);
}
else if (key < arr[mid])
{
return binary_search(arr, left, mid-1, key);
}
else
{
return mid;
}
}
else
{
return -1;
}
}
注意:
1. low<=high 最终low和high 会指向同一个数值。
如果target不一定存在的话,就要用low <= high,最终low大于high导致循环退出,说明target不存在。
如果我们要找的target一定存在的话,那用low < high
2. 如果不mid+1 mid-1 会死循环 严重注意:high=mid没关系,但low=mid有可能导致死循环。因为low=high-1时low=mid,如果又进入low=mid分支相当于搜索区间没缩小。但high一定大于mid,所以不会造成死循环。
动态查找:二叉排序树(查找和增删速度快)
左子树小于父节点,右子树大于父节点的二叉树就是二叉排序树。对其中序遍历,必为升序。
二叉排序树以链接方式存储,插入删除操作时,不用移动元素,只要找到合适位置,只需修改链接指针即可。
二叉树的二叉链表节点结构
struct BiTNode{
int data;
BiTNode* lchild,*rchild;
};//BitNode为结构体
// 二叉树节点生成
BiTNode* T,p;//T是结构体指针
// 查找(查找结果给p)
SearchBST(T,93,NULL,&p);//需要对p进行修改,索引用二重指针,指针的指针。
// 插入
InsertBST(&T,50);//需要对T进行修改,索引用二重指针,指针的指针。
// 删除
DeeteBST(&T,50);//需要对T进行修改
查找(递归)
//使用
BiTNode* T,p;//T是结构体指针
SearchBST(T,93,NULL,&p);//需要对p进行修改,索引用二重指针,指针的指针。
Status SearchBST(BiTNode* T,int key,BiTNode* f,BiTNode** p){//T是二叉排序树指针、f指向T的双亲,p为返回查找成功节点,二重指针
if(T==NULL){//叶节点,没找到
*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);//T->lchild指针
}
else{//右子树继续查
return SearchBST(T->rchild,key,T,p);
}
}
插入
- 先调用查找操作将要插入的关键字进行比较
- 如果在原有的二叉排序树中没有要插入的关键字,则将关键字与查找的结点p(在查找操作中返回的结点)的值进行比较
- 若p为空,则插入关键字赋值给该节点;
- 若小于结点p的值,则插入关键字作为结点p的左子树;
- 若大于结点p的值,则插入关键字作为结点p的右子树;
int InsertBST(BiTNode** T, int key){//这里的T是二重指针,修改时用*T即可。
BiTNode* p,s;//指针
if (!SearchBST( *T, key, NULL, &p)) { // 没找到key,p返回查找路径上最后一个节点
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)//p为NULL时,第一个节点作为根节点
*T = s; // 插入 s 为新的根结点,指针类型
else if (key < p->data)
p->lchild = s; //插入 s 为左孩子
else
p->rchild = s; // 插入 s 为右孩子
return TRUE;
}else
return FALSE;
}
int main(){
int i;
int a[10]={62,5,2,5,48,4,6,464,84,66};
BiTNode* T=NULL;//T是指针
for(int i=0;i<10;i++)
{
InsertBST(&T,a[i]);//输入&T为二重指针,需要修改T
}
}
//使用
BiTNode* T;//T是结构体指针
InsertBST(&T,50);//需要对T进行修改,索引用二重指针,指针的指针。
删除
二叉排序树的删除操作相对复杂,因为不能因为删除了结点,让这颗二叉排序树变得不满足二叉排序树的性质,所以对于二叉排序树的删除存在三种情况:
- 叶子结点;(很容易实现删除操作,直接删除结点即可)
- 删除节点仅有左或者右子树;(容易实现,删除结点后,将它的左子树或者右子树整个移动到删除结点的位置)
- 左右子树都有的结点。(实现删除操作很复杂)
对于要删除的结点同时存在左右子树的情况的解决办法
核心思想:将它的直接前驱或者直接后继作为删除结点的数据
/**
* 从二叉排序树中删除结点 p , 并重接它的左/右子树
*/
int Delete(BiTNode** p){//p是二重指针
BiTNode* q, s;//临时指针
// 1.只有左/右子树,直接重新链接即可
if ((*p)->rchild == NULL) { // 右子树空 则只需要重接它的左子树
q = *p;
*p = (*p)->lchild;// 关键!!!
free(q);
}else if ((*p)->lchild == NULL){ // 左子树空 则只需要重接它的右子树
q = *p;
*p = (*p)->rchild;
free(q);
}else{ // 2. 左右子树都不空,找左子树最大值替换。
// 2.1先给q、s初始化。q指向*p指针,s初始指向*p节点的左子树。
q = *p;
s = (*p)->lchild;
// 2.2找到左子数最右侧最大值,令节点s指向它;q指向s父节点。
while (s->rchild) {
q = s;
s = s->rchild;
}
// 2.3 欲删除节点*p数值更新
(*p)->data = s->data; // 更新节点数值。s 指向被删除结点的直接前驱
// 2.4 !!! 重新链接s节点左子树
if (q != *p)
q->rchild = s->lchild; // 一般情况下,需要连给q的右子树
else
q->lchild = s->lchild; // 少数:q和*p节点相同,那么就要连给q的左子树了
// 2.5 释放s节点空间
free(s);
}
return TRUE;
}
/**
* 二叉排序树的删除
* 当二叉排序树中存在关键字等于 key 的数据元素时,删除该数据元素并返回TRUE
*/
int DeleteBST(BiTNode** T, int key){//T是指针的指针
if (!*T) // 到叶节点还没找到,*T=NULL,不存在关键字等于 key 的元素
return FALSE;
else{
if (key == (*T)->data)//*T是指针
return Delete(T);//这里的T是二重指针
else if (key < (*T)->data)
return DeleteBST(&(*T)->lchild, key);
else
return DeleteBST(&(*T)->rchild, key);
}
}
//使用
BiTNode* T,p;//T是结构体指针
DeletehBST(&T,93);//需要对T进行修改,索引用二重指针,指针的指针。
综合测试:
#include <iostream>
using namespace std;
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100
//构建节点
struct BiTNode{
int data;
BiTNode* lchild,*rchild;//左右节点都是指针
};//结构体和指针
//中序递归遍历,左根右
void InOrderTraverse(BiTNode* T){//T是指针
if (!T)
return;//遇到叶节点,结束。
//左根右
InOrderTraverse(T->lchild);//输入的也是指针
printf("%d ", T->data);
InOrderTraverse(T->rchild);
}
//查找
Status SearchBST(BiTNode* T,int key,BiTNode* f,BiTNode** p){//T是二叉排序树指针、f指向T的双亲,p为返回查找成功节点,二重指针
if(T==NULL){//叶节点,没找到
*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);//T->lchild指针
}
else{//右子树继续查
return SearchBST(T->rchild,key,T,p);
}
}
//插入
int InsertBST(BiTNode** T, int key){//这里的T是二重指针,修改时用*T即可。
BiTNode* p,s;//指针
if (!SearchBST( *T, key, NULL, &p)) { // 没找到key,p返回查找路径上最后一个节点
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)//p为NULL时,第一个节点作为根节点
*T = s; // 插入 s 为新的根结点,指针类型
else if (key < p->data)
p->lchild = s; //插入 s 为左孩子
else
p->rchild = s; // 插入 s 为右孩子
return TRUE;
}else
return FALSE;
}
// 删除子函数
int Delete(BiTNode** p){//p是二重指针
BiTNode* q, s;//临时指针
// 1.只有左/右子树,直接重新链接即可
if ((*p)->rchild == NULL) { // 右子树空 则只需要重接它的左子树
q = *p;
*p = (*p)->lchild;// 关键!!!
free(q);
}else if ((*p)->lchild == NULL){ // 左子树空 则只需要重接它的右子树
q = *p;
*p = (*p)->rchild;
free(q);
}else{ // 2. 左右子树都不空,找左子树最大值替换。
// 2.1先给q、s初始化。q指向*p指针,s初始指向*p节点的左子树。
q = *p;
s = (*p)->lchild;
// 2.2找到左子数最右侧最大值,令节点s指向它;q指向s父节点。
while (s->rchild) {
q = s;
s = s->rchild;
}
// 2.3 欲删除节点*p数值更新
(*p)->data = s->data; // 更新节点数值。s 指向被删除结点的直接前驱
// 2.4 !!! 重新链接s节点左子树
if (q != *p)
q->rchild = s->lchild; // 一般情况下,需要连给q的右子树
else
q->lchild = s->lchild; // 少数:q和*p节点相同,那么就要连给q的左子树了
// 2.5 释放s节点空间
free(s);
}
return TRUE;
}
/**
* 删除
* 当二叉排序树中存在关键字等于 key 的数据元素时,删除该数据元素并返回TRUE
*/
int DeleteBST(BiTNode** T, int key){//T是指针的指针
if (!*T) // 到叶节点还没找到,*T=NULL,不存在关键字等于 key 的元素
return FALSE;
else{
if (key == (*T)->data)//*T是指针
return Delete(T);//这里的T是二重指针
else if (key < (*T)->data)
return DeleteBST(&(*T)->lchild, key);
else
return DeleteBST(&(*T)->rchild, key);
}
}
int main(int argc, const char * argv[]) {
int i;
int a[10] ={62,88,58,47,35,73,51,99,37,93};
BiTNode* T = NULL;//T是指针
for (i = 0; i < 10; i++) { // 通过插入操作来构建二叉排序树
InsertBST(&T, a[i]);
}
printf("中序递归遍历二叉排序树:\n");
InOrderTraverse(T);
printf("\n\n");
DeleteBST(&T, 93);//输入指向指针的指针
printf("删除结点 93 后的结果为:\n");
InOrderTraverse(T);
printf("\n\n");
printf("插入 91 后的结果为:\n");
InsertBST(&T, 91);//输入指向指针的指针
InOrderTraverse(T);
printf("\n\n");
return 0;
}
平衡二叉树 AVL树(关联容器)
举例:32145671098
易错点:找到最小不平衡子树,即举例插入节点最近的,bf绝对值大于1的节点。
二叉搜索树中,节点的存储位置由关键值大小决定,这是关联容器核心。
平衡因子BF:该节点左子树深度减去右子树深度的值。每一个节点的左子树和右子树的高度差不能超过1,BF<1。
构建AVL树思想:构建二叉排序树过程中,每插入一个节点,先检查是否破坏了树的平衡性,若是,找出最小不平衡子树。
在保持二叉排序树特性前提下,调整最小不平衡子树各节点之间链接关系,旋转后成为新的平衡子树。
查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logn)
定义节点
struct BSTNode{
2 int val;
3 int bf;//节点的平衡因子
4 BSTNode *lchild, *rchild;
5 };
失去平衡后进行的调整规律可以归纳为以下四种
- 单项右旋处理(bf>0)
- 单项左旋处理 (bf<0)
- 双向(先左后右)旋转处理(最小不平衡子树bf<0)
- 双向(先右后左)旋转处理(最小不平衡子树bf>0)
单项右旋(bf正):
void R_Roate(BiTNode** p){// 二重指针
// 先初始化并赋值L
BiTNode* L=(*p)->lchild;
(*p)->lchild=L->rchild;//1.L右子树变为P左子树
L->rchild=(*p);//2.P改成L右子树
*p=L;//3.L替代P成为新的根节点
}
LR(先左后右)
这个图是先左后右的,左边明显比右侧高。L的bf是-1,需要先左旋,P的bf是2,之后再右旋。
左平衡(左边明显高:单纯右旋和先左后右两种)
#define LH +1;//左高
#define EH 0;//等高
#define RH -1;//右高
32 void LeftBalance(BiTNode** T){//二重指针
// 1. 初始化左子树
33 BiTNode* L = (*T)->lchild;
// 2. 对左子树的bf进行判断
34 switch(L->bf){
// 2.1 左边的左边插入,单纯右旋,后bf因子都为0
35 case LH:{(*T)->bf = L->bf = EH; R_Roate(T); break;}
// 2.2 先左后右,左子树的右边插入。
36 case RH:{
37 BiTNode* Lr = L->rchild;//左子树的右节点Lr
// 3.对Lr的bf因子进行判断,更新L和根P的bf因子
38 switch(Lr->bf){
39 case LH:{L->bf = EH; (*T)->bf = RH; break;}
40 case EH:{L->bf = EH; (*T)->bf = EH; break;}
41 case RH:{L->bf = LH; (*T)->bf = EH; break;}
42 }
// 4.Lr作为新根,bf因子为0;进行先左后右旋转
43 Lr->bf = EH;
44 L_Roate(&(*T)->lchild);// 左旋针对根P的左子树
45 R_Roate(T);// 右旋针对根P
46 }
47 }
48 }
插入InsertAVL
//若在平衡的二叉排序树T中不存在和e有相同关键字的结点,
//则插入一个数据元素为e的新结点并返回1,否则返回0。若因插入而使二叉排序树失去平衡,
//则作平衡旋转处理,布尔变量taller反映T长高与否
Status InsertAVL(BiTree *T,int e,Status *taller)
{
if(!*T)
{ /*插入新结点,树“长高”,置taller为TRUE */
*T=(BiTree)malloc(sizeof(BiTNode));
(*T)->data = e;
(*T)->lchild=(*T)->rchild = NULL;
(*T)->bf=EH;
*taller = TRUE;
}
else
{
if(e==(*T)->data)
{ /*树中已存在和e相同关键字的结点则不再插入*/
*taller=FALSE;
return FALSE;
}
if(e<(*T)->data)
{ /*继续在T的左子树中进行搜索*/
if(!InsertAVL(&(*T)->lchild,e,taller))
return FALSE;
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;
}
}
}
else
{ /*继续在T的左子树中进行搜索*/
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 LH:
RightBalance(T);
*taller = FALSE;
break;
}
}
}
}
return TRUE;
}
综合测试
#include<iostream>
using namespace std;
/*
author: Lai XingYu
date: 2018/5/18
describe: AVL
*/
#define LH +1
#define EH 0
#define RH -1
typedef struct BSTNode{
int val;
int bf;
struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree;
void R_Roate(BSTree& p){
BSTree temp = p->lchild;
p->lchild = temp->rchild;
temp->rchild = p;
p = temp;
}
void L_Roate(BSTree& p){
BSTree temp = p->rchild;
p->rchild = temp->lchild;
temp->lchild = p;
p = temp;
}
void LeftBalance(BSTree& T){
BSTree temp = T->lchild;
switch(temp->bf){
case LH:{T->bf = temp->bf = EH; R_Roate(T); break;}
case RH:{
BSTree t = temp->rchild;
switch(t->bf){
case LH:{temp->bf = EH; T->bf = RH; break;}
case EH:{temp->bf = EH; T->bf = EH; break;}
case RH:{temp->bf = LH; T->bf = EH; break;}
}
t->bf = EH;
L_Roate(T->lchild);
R_Roate(T);
}
}
}
void RightBalance(BSTree& T){
BSTree temp = T->rchild;
switch(temp->bf){
case LH:{
BSTree t = temp->lchild;
switch(t->bf){
case LH:{T->bf = EH; temp->bf = RH; break;}
case EH:{T->bf = EH; temp->bf = EH; break;}
case RH:{T->bf = LH; temp->bf = EH; break;}
}
t->bf = EH;
R_Roate(T->rchild);
L_Roate(T);
}
case RH:{T->bf = temp->bf = EH; L_Roate(T); break;}
}
}
/*
taller标志插入新结点后,树的高度是否变高
如果val在二叉树中,则插入失败
如果插入结点让二叉树失去平衡,则要进行选择处理,让二叉树恢复平衡
*/
bool InsertAVL(BSTree& T, int val, bool& taller){
if(!T){//插入新结点,树变高
T = (BSTree) malloc(sizeof(BSTNode));
T->val = val; T->bf = EH; T->lchild = T->rchild = NULL;
taller = true;
}else{
if(val==T->val){taller = false; return false;} //如果val存在于二叉树中, 则插入失败
if(val<T->val){ //若果val比当前结点的值小,则把新结点插入到当前结点的左子树中
if(!InsertAVL(T->lchild, val, taller)) return false;
if(taller){
switch(T->bf){ //插入新结点后要对当前结点的平衡因子做出相应的修改
case LH:{LeftBalance(T); taller=false; break;}
case EH:{taller = true; T->bf = LH; break;}
case RH:{taller = false; T->bf = EH; break;}
}}
}else{
if(!InsertAVL(T->rchild, val, 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;
}
void inorder(BSTree T){
if(!T) return;
if(T->lchild) inorder(T->lchild);
cout<<T->val<<" ";
if(T->rchild) inorder(T->rchild);
}
int main(){
int t[] = {1,2,3,4,5,6,7};
int f[] = {1,2,5,3,7,6,4};
BSTree T = NULL;
bool taller;
for(int i=0; i<7; i++) InsertAVL(T, f[i], taller);
inorder(T);
return 0;}
结果:1234567
通过上面的例子可以看到,用相同的数字,不同顺序的序列来构建平衡二叉树,得到的结果是一样的
红黑树(关联容器set map mutliset mutlimap)
引出原因:严格的AVL树出现大量旋转调整以保证|BF|<1,红黑树保证最长路径<2*最短路径,没有严格的平衡并保证局部平衡。
红黑树和AVL树区别
1、AVL树追求"完全平衡",节点的 |balFact| <= 1,旋转调整计算量大。
红黑树用非严格的平衡来换取增删节点时候旋转次数的降低。实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL(高度平衡);如果搜索,插入删除次数几乎差不多,应该选择RB。
2、广泛用于C ++的STL中,地图和集都是用红黑树实现的;
红黑树特征:
1、根节点为黑色
2、父子不能都是红色
3、新增节点必须为红,新增节点的父节点必须为黑;节点为红,子节点必须为黑
4、任一节点到NULL的任何路径,所含黑节点数目相同。
插入(三种情况)
x是插入的新节点,p是x的双亲,g是p的双亲,q是x 的叔叔。
1、q是红色节点
(p x双红色)p 和 q变为黑色,g变为红色。
2、q是黑色节点,x是双亲p的外侧子女
以p为轴心右旋,旋转后p为根,(p x双红色)p变为黑色,g变为红色
3、q是黑色节点,x是双亲p的内侧子女
两次旋转:先以p为轴心左旋,在以g为中心右旋。(p x双红色)x变黑色,g变红色。
具体插入实例
2、15、12、4、10、8、9、35、25
1、插入12后情况3,违反了父子都是红色。插入的是内侧,两次旋转,先右旋再左旋,最后变色
2、插入4情况1,叔叔是红色,单纯改变颜色
3、插入10情况2 ,插入的是同一侧,一次左旋转后变色
哈希表(查找O(1))
key-value匹配对
散列函数构造方法
除留余数法:f(key)=key mod p (mod是取余数)
处理散列冲突方法
开放定址法:f(key)=(f(key)+di) mod m