查找算法:二分查找、二叉排序树、平衡二叉树(AVL)、红黑树、哈希表

查找

查找表按照操作方式来分有两大种:静态查找表动态查找表。静态查找表只作查找操作的查找表。动态查找表在查找过程中同时插入查找表中不存在的数据元素, 或者从查找表中删除已经存在的某个数据元素。

顺序表查找(查找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 };

失去平衡后进行的调整规律可以归纳为以下四种

  1. 单项右旋处理(bf>0)
  2. 单项左旋处理  (bf<0)
  3. 双向(先左后右)旋转处理(最小不平衡子树bf<0)
  4. 双向(先右后左)旋转处理(最小不平衡子树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

发布了176 篇原创文章 · 获赞 84 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/try_again_later/article/details/89382892