查找算法之順序查找,折半查找,二叉查找樹

查找表的概念

  查找表是由同一類型的數據元素構成的集合。例如電話號碼簿和字典均可以看做是一張查找表。
  在查找表中只作查找操做,而不改動表中數據元素,稱此類查找表爲靜態查找表;反之,在查找表中作查找操做的同時進行插入數據或者刪除數據的操做,稱此類表爲動態查找表。算法

順序查找

  順序查找的查找過程爲:從表中的最後一個數據元素開始,逐個同記錄的關鍵字作比較,若是匹配成功,則查找成功;反之,若是直到表中第一個關鍵字查找完也沒有成功匹配,則查找失敗
同時,在程序中初始化建立查找表時,因爲是順序存儲,因此將全部的數據元素存儲在數組中,可是把第一個位置留給了用戶用於查找的關鍵字。例如,在順序表{1,2,3,4,5,6}中查找數據元素值爲 7 的元素,則添加後的順序表爲:
在這裏插入圖片描述
            圖1
  順序表的一端添加用戶用於搜索的關鍵字,稱做「監視哨」。
  圖 1 中監視哨的位置也可放在數據元素 6 的後面(這種狀況下,整個查找的順序應有逆向查找改成順序查找)。
  放置好監視哨以後,順序表遍歷從沒有監視哨的一端依次進行,若是查找表中有用戶須要的數據,則程序輸出該位置;反之,程序會運行至監視哨,此時匹配成功,程序中止運行,可是結果是查找失敗。
代碼實現:數組

/*
 * @Description: 順序查找算法
 * @Version: V1.0
 * @Autor: Carlos
 * @Date: 2020-05-22 15:52:11
 * @LastEditors: Carlos
 * @LastEditTime: 2020-06-03 16:56:06
 */ 
#include <stdio.h>
#include <stdlib.h>
#define keyType int
typedef struct {
    //查找表中每一個數據元素的值
    keyType key;
    //若是須要,還能夠添加其餘屬性
}ElemType;

typedef struct{
    //存放查找表中數據元素的數組
    ElemType *elem;
    //記錄查找表中數據的總數量
    int length;
}SSTable;
/**
 * @Description: 建立查找表
 * @Param: SSTable **st 指向結構體指針的指針,即指針變量的指針,int length 建立的二叉樹的長度
 * @Return: 無
 * @Author: Carlos
 */
void Create(SSTable **st,int length){
    (*st)=(SSTable*)malloc(sizeof(SSTable));
    (*st)->length=length;
    //結構體指針分配空間
    (*st)->elem =(ElemType*)malloc((length+1)*sizeof(ElemType));
    printf("輸入表中的數據元素:\n");
    //根據查找表中數據元素的總長度,在存儲時,從數組下標爲 1 的空間開始存儲數據
    for (int i=1; i<=length; i++) {
        scanf("%d",&((*st)->elem[i].key));
    }
}
/**
 * @Description: 查找表查找的功能函數,其中key爲關鍵字
 * @Param: SSTable *st指向結構體變量的指針,keyType key 要查找的元素
 * @Return: key在查找表中的位置
 * @Author: Carlos
 */
int Search_seq(SSTable *st,keyType key){
    //將關鍵字做爲一個數據元素存放到查找表的第一個位置,起監視哨的做用
    st->elem[0].key=key;
    int i=st->length;
    //從查找表的最後一個數據元素依次遍歷,一直遍歷到數組下標爲0
    while (st->elem[i].key!=key) {
        i--;
    }
    //若是 i=0,說明查找失敗;反之,返回的是含有關鍵字key的數據元素在查找表中的位置
    return i;
}
int main(int argc, const char * argv[]) {
    SSTable *st;
    Create(&st, 6);
    getchar();
    printf("請輸入查找數據的關鍵字:\n");
    int key;
    scanf("%d",&key);
    int location=Search_seq(st, key);
    if (location==0) {
        printf("查找失敗");
    }else{
        printf("數據在查找表中的位置爲:%d",location);
    }
    return 0;
}

折半查找

  折半查找,也稱二分查找,在某些狀況下相比於順序查找,使用折半查找算法的效率更高。可是該算法的使用的前提是靜態查找表中的數據必須是有序的。
  例如,在{5,21,13,19,37,75,56,64,88 ,80,92}這個查找表使用折半查找算法查找數據以前,須要首先對該表中的數據按照所查的關鍵字進行排序:{5,13,19,21,37,56,64,75,80,88,92}。
  在折半查找以前對查找表按照所查的關鍵字進行排序的意思是:若查找表中存儲的數據元素含有多個關鍵字時,使用哪一種關鍵字作折半查找,就須要提早以該關鍵字對全部數據進行排序。函數

折半查找算法

  對靜態查找表{5,13,19,21,37,56,64,75,80,88,92}採用折半查找算法查找關鍵字爲 21 的過程爲:
在這裏插入圖片描述
              圖2
後一個關鍵字,指針 mid 指向處於 low 和 high 指針中間位置的關鍵字。在查找的過程當中每次都同 mid 指向的關鍵字進行比較,因爲整個表中的數據是有序的,所以在比較以後就能夠知道要查找的關鍵字的大體位置。
  例如在查找關鍵字 21 時,首先同 56 做比較,因爲21 < 56,並且這個查找表是按照升序進行排序的,因此能夠斷定若是靜態查找表中有 21 這個關鍵字,就必定存在於 low 和 mid 指向的區域中間。
  所以,再次遍歷時須要更新 high 指針和 mid 指針的位置,令 high 指針移動到 mid 指針的左側一個位置上,同時令 mid 從新指向 low 指針和 high 指針的中間位置。如圖3所示:
在這裏插入圖片描述
              圖3
  一樣,用 21 同 mid 指針指向的 19 做比較,19 < 21,因此能夠斷定 21 若是存在,確定處於 mid 和 high 指向的區域中。因此令 low 指向 mid 右側一個位置上,同時更新 mid 的位置。
在這裏插入圖片描述
              圖4
  當第三次作判斷時,發現 mid 就是關鍵字 21 ,查找結束。
  注意:在作查找的過程當中,若是 low 指針和 high 指針的中間位置在計算時位於兩個關鍵字中間,即求得 mid 的位置不是整數,須要統一作取整操做。
  折半查找的實現代碼:3d

/*
 * @Description: 折半查找.前提是靜態查找表中的數據必須是有序的。
 * @Version: V1.0
 * @Autor: Carlos
 * @Date: 2020-05-22 16:09:01
 * @LastEditors: Carlos
 * @LastEditTime: 2020-06-03 16:58:14
 */ 
#include <stdio.h>
#include <stdlib.h>
#define keyType int
typedef struct {
    //查找表中每一個數據元素的值
    keyType key;
    //若是須要,還能夠添加其餘屬性
}ElemType;

typedef struct{
    //存放查找表中數據元素的數組
    ElemType *elem;
    //記錄查找表中數據的總數量
    int length;
}SSTable;
/**
 * @Description: 建立查找表
 * @Param: SSTable **st 指向結構體指針的指針,即指針變量的指針,int length 建立的二叉樹的長度
 * @Return: 無
 * @Author: Carlos
 */
void Create(SSTable **st,int length){
    (*st)=(SSTable*)malloc(sizeof(SSTable));
    (*st)->length=length;
    (*st)->elem = (ElemType*)malloc((length+1)*sizeof(ElemType));
    printf("輸入表中的數據元素:\n");
    //根據查找表中數據元素的總長度,在存儲時,從數組下標爲 1 的空間開始存儲數據
    for (int i=1; i<=length; i++) {
        scanf("%d",&((*st)->elem[i].key));
    }
}
//折半查找算法
/**
 * @Description: 折半查找算法
 * @Param: SSTable *ST 指向結構體的指針,keyType key 要插入的元素
 * @Return: 成功的返回key在查找表中的位置,不然返回0
 * @Author: Carlos
 */
int Search_Bin(SSTable *ST,keyType key){
    //初始狀態 low 指針指向第一個關鍵字
    int low=1;
    //high 指向最後一個關鍵字
    int high=ST->length;
    int mid;
    while (low<=high) {
        //int 自己爲整形,因此,mid 每次爲取整的整數
        mid=(low+high)/2;
        //若是 mid 指向的同要查找的相等,返回 mid 所指向的位置
        if (ST->elem[mid].key==key)
        {
            return mid;
        }
        //若是mid指向的關鍵字較大,則更新 high 指針的位置
        else if(ST->elem[mid].key>key)
        {
            high=mid-1;
        }
        //反之,則更新 low 指針的位置
        else{
            low=mid+1;
        }
    }
    return 0;
}

int main(int argc, const char * argv[]) {
    SSTable *st;
    Create(&st, 11);
    getchar();
    printf("請輸入查找數據的關鍵字:\n");
    int key;
    scanf("%d",&key);
    int location=Search_Bin(st, key);
    //若是返回值爲 0,則證實查找表中未查到 key 值,
    if (location==0) {
        printf("查找表中無該元素");
    }else{
        printf("數據在查找表中的位置爲:%d",location);
    }
    return 0;
}

二叉查找樹

  動態查找表中作查找操做時,若查找成功能夠對其進行刪除;若是查找失敗,即表中無該關鍵字,能夠將該關鍵字插入到表中。
  動態查找表的表示方式有多種,本節介紹一種使用樹結構表示動態查找表的實現方法——二叉排序樹(又稱爲「二叉查找樹」)。指針

二叉查找樹概念

  二叉排序樹要麼是空二叉樹,要麼具備以下特色:code

  • 二叉排序樹中,若是其根結點有左子樹,那麼左子樹上全部結點的值都小於根結點的值;
  • 二叉排序樹中,若是其根結點有右子樹,那麼右子樹上全部結點的值都大小根結點的值;
  • 二叉排序樹的左右子樹也要求都是二叉排序樹;

  例如,圖 5 就是一個二叉排序樹:
在這裏插入圖片描述
              圖5blog

使用二叉排序樹查找關鍵字

  二叉排序樹中查找某關鍵字時,查找過程相似於次優二叉樹,在二叉排序樹不爲空樹的前提下,首先將被查找值同樹的根結點進行比較,會有 3 種不一樣的結果:排序

  1. 若是相等,查找成功;
  2. 若是比較結果爲根結點的關鍵字值較大,則說明該關鍵字可能存在其左子樹中;
  3. 若是比較結果爲根結點的關鍵字值較小,則說明該關鍵字可能存在其右子樹中;
    實現函數爲:(運用遞歸的方法)
/**
 * @Description: 二叉排序樹查找算法
 * @Param: BiTree T  KeyType key  BiTree f BiTree *p
 * @Return: 刪除成功 TRUE 刪除失敗 FALSE
 * @Author: Carlos
 */
int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p)
{
    //若是 T 指針爲空,說明查找失敗,令 p 指針指向查找過程當中最後一個葉子結點,並返回查找失敗的信息
    if (!T)
    {
        *p = f;
        return FALSE;
    }
    //若是相等,令 p 指針指向該關鍵字,並返回查找成功信息
    else if (key == T->data)
    {
        *p = T;
        return TRUE;
    }
    //若是 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹
    else if (key < T->data)
    {
        return SearchBST(T->lchild, key, T, p);
    }
    else
    {
        return SearchBST(T->rchild, key, T, p);
    }
}

二叉排序樹中插入關鍵字

  二叉排序樹自己是動態查找表的一種表示形式,有時會在查找過程當中插入或者刪除表中元素,當由於查找失敗而須要插入數據元素時,該數據元素的插入位置必定位於二叉排序樹的葉子結點,而且必定是查找失敗時訪問的最後一個結點的左孩子或者右孩子。
  例如,在圖 1 的二叉排序樹中作查找關鍵字 1 的操做,當查找到關鍵字 3 所在的葉子結點時,判斷出表中沒有該關鍵字,此時關鍵字 1 的插入位置爲關鍵字 3 的左孩子。
  因此,二叉排序樹表示動態查找表作插入操做,只須要稍微更改一下上面的代碼就能夠實現,具體實現代碼爲:遞歸

/**
 * @Description: 二叉排序樹查找算法
 * @Param: BiTree T  KeyType key  BiTree f BiTree *p
 * @Return: 刪除成功 TRUE 刪除失敗 FALSE
 * @Author: Carlos
 */
int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p)
{
    //若是 T 指針爲空,說明查找失敗,令 p 指針指向查找過程當中最後一個葉子結點,並返回查找失敗的信息
    if (!T)
    {
        *p = f;
        return FALSE;
    }
    //若是相等,令 p 指針指向該關鍵字,並返回查找成功信息
    else if (key == T->data)
    {
        *p = T;
        return TRUE;
    }
    //若是 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹
    else if (key < T->data)
    {
        return SearchBST(T->lchild, key, T, p);
    }
    else
    {
        return SearchBST(T->rchild, key, T, p);
    }
}
/**
 * @Description: 二叉樹插入函數
 * @Param: BiTree *T 二叉樹結構體指針的指針  ElemType e 要插入的元素
 * @Return: 刪除成功 TRUE 刪除失敗 FALSE
 * @Author: Carlos
 */
int InsertBST(BiTree *T, ElemType e)
{
    BiTree p = NULL;
    //若是查找不成功,需作插入操做
    if (!SearchBST((*T), e, NULL, &p))
    {
        //初始化插入結點
        BiTree s = (BiTree)malloc(sizeof(BiTree));
        s->data = e;
        s->lchild = s->rchild = NULL;
        //若是 p 爲NULL,說明該二叉排序樹爲空樹,此時插入的結點爲整棵樹的根結點
        if (!p)
        {
            *T = s;
        }
        //若是 p 不爲 NULL,則 p 指向的爲查找失敗的最後一個葉子結點,只須要經過比較 p 和 e 的值肯定 s 究竟是 p 的左孩子仍是右孩子
        else if (e < p->data)
        {
            p->lchild = s;
        }
        else
        {
            p->rchild = s;
        }
        return TRUE;
    }
    //若是查找成功,不須要作插入操做,插入失敗
    return FALSE;
}

  經過使用二叉排序樹對動態查找表作查找和插入的操做,同時在中序遍歷二叉排序樹時,能夠獲得有關全部關鍵字的一個有序的序列。
  例如,假設原二叉排序樹爲空樹,在對動態查找表 {3,5,7,2,1} 作查找以及插入操做時,能夠構建出一個含有表中全部關鍵字的二叉排序樹,過程如圖6 所示:
在這裏插入圖片描述
              圖6
  經過不斷的查找和插入操做,最終構建的二叉排序樹如圖 6(5) 所示。當使用中序遍歷算法遍歷二叉排序樹時,獲得的序列爲:1 2 3 5 7 ,爲有序序列。
一個無序序列能夠經過構建一棵二叉排序樹,從而變成一個有序序列。圖片

二叉排序樹中刪除關鍵字

  在查找過程當中,若是在使用二叉排序樹表示的動態查找表中刪除某個數據元素時,須要在成功刪除該結點的同時,依舊使這棵樹爲二叉排序樹。
  假設要刪除的爲結點 p,則對於二叉排序樹來講,須要根據結點 p 所在不一樣的位置做不一樣的操做,有如下 3 種可能:

  1. 結點 p 爲葉子結點,此時只須要刪除該結點,並修改其雙親結點的指針便可;
  2. 結點 p 只有左子樹或者只有右子樹,此時只須要將其左子樹或者右子樹直接變爲結點 p 雙親結點的左子樹便可;
  3. 結點 p 左右子樹都有,此時有兩種處理方式:
      (1).令結點 p 的左子樹爲其雙親結點的左子樹;結點 p 的右子樹爲其自身直接前驅結點的右子樹,如圖7所示;
    在這裏插入圖片描述
                  圖7
      (2)用結點 p 的直接前驅(或直接後繼)來代替結點 p,同時在二叉排序樹中對其直接前驅(或直接後繼)作刪除操做。如圖 8 爲使用直接前驅代替結點 p:
    在這裏插入圖片描述
                  圖8
      圖 8中,在對左圖進行中序遍歷時,獲得的結點 p 的直接前驅結點爲結點 s,因此直接用結點 s 覆蓋結點 p,因爲結點 s 還有左孩子,根據第 2 條規則,直接將其變爲雙親結點的右孩子。
      具體實現代碼:(可運行)
/*
 * @Description: 二叉查找樹
 * @Version: V1.0
 * @Autor: Carlos
 * @Date: 2020-06-02 15:50:31
 * @LastEditors: Carlos
 * @LastEditTime: 2020-06-03 16:49:46
 */
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define ElemType int
#define KeyType int
/* 二叉排序樹的節點結構定義 */
typedef struct BiTNode
{
    int data;
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
/**
 * @Description: 二叉排序樹查找算法
 * @Param: BiTree T  KeyType key  BiTree f BiTree *p
 * @Return: 刪除成功 TRUE 刪除失敗 FALSE
 * @Author: Carlos
 */
int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p)
{
    //若是 T 指針爲空,說明查找失敗,令 p 指針指向查找過程當中最後一個葉子結點,並返回查找失敗的信息
    if (!T)
    {
        *p = f;
        return FALSE;
    }
    //若是相等,令 p 指針指向該關鍵字,並返回查找成功信息
    else if (key == T->data)
    {
        *p = T;
        return TRUE;
    }
    //若是 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹
    else if (key < T->data)
    {
        return SearchBST(T->lchild, key, T, p);
    }
    else
    {
        return SearchBST(T->rchild, key, T, p);
    }
}
/**
 * @Description: 二叉樹插入函數
 * @Param: BiTree *T 二叉樹結構體指針的指針  ElemType e 要插入的元素
 * @Return: 刪除成功 TRUE 刪除失敗 FALSE
 * @Author: Carlos
 */
int InsertBST(BiTree *T, ElemType e)
{
    BiTree p = NULL;
    //若是查找不成功,需作插入操做
    if (!SearchBST((*T), e, NULL, &p))
    {
        //初始化插入結點
        BiTree s = (BiTree)malloc(sizeof(BiTree));
        s->data = e;
        s->lchild = s->rchild = NULL;
        //若是 p 爲NULL,說明該二叉排序樹爲空樹,此時插入的結點爲整棵樹的根結點
        if (!p)
        {
            *T = s;
        }
        //若是 p 不爲 NULL,則 p 指向的爲查找失敗的最後一個葉子結點,只須要經過比較 p 和 e 的值肯定 s 究竟是 p 的左孩子仍是右孩子
        else if (e < p->data)
        {
            p->lchild = s;
        }
        else
        {
            p->rchild = s;
        }
        return TRUE;
    }
    //若是查找成功,不須要作插入操做,插入失敗
    return FALSE;
}
/**
 * @Description: 刪除節點的函數
 * @Param: BiTree *p 指向結構體指針的指針
 * @Return: 刪除成功 TRUE
 * @Author: Carlos
 */
int Delete(BiTree *p)
{
    BiTree q, s;
    //狀況 1,結點 p 自己爲葉子結點,直接刪除便可
    if (!(*p)->lchild && !(*p)->rchild)
    {
        *p = NULL;
    }
    //左子樹爲空,只需用結點 p 的右子樹根結點代替結點 p 便可;
    else if (!(*p)->lchild)
    {
        q = *p;
        *p = (*p)->rchild;
        free(q);
        q = NULL;
    }
    //右子樹爲空,只需用結點 p 的左子樹根結點代替結點 p 便可;
    else if (!(*p)->rchild)
    {
        q = *p;
        //這裏不是指針 *p 指向左子樹,而是將左子樹存儲的結點的地址賦值給指針變量 p
        *p = (*p)->lchild;
        free(q);
        q = NULL;
    }
    //左右子樹均不爲空,採用第 2 種方式
    else
    {
        q = *p;
        s = (*p)->lchild;
        //遍歷,找到結點 p 的直接前驅
        while (s->rchild)
        {
            //指向p節點左子樹最右邊節點的前一個。保留下來
            q = s;
            s = s->rchild;
        }
        //直接改變結點 p 的值
        (*p)->data = s->data;
        //判斷結點 p 的左子樹 s 是否有右子樹,分爲兩種狀況討論  若是有右子樹,s必定會指向右子樹的葉子節點。q 此時指向的是葉子節點的父節點。 q != *p兩者不等說明有右子樹
        if (q != *p)
        {
            //如有,則在刪除直接前驅結點的同時,令前驅的左孩子結點改成 q 指向結點的孩子結點
            q->rchild = s->lchild;
        }
        else
        //q == *p ==NULL 說明沒有右子樹
        {
            //不然,直接將左子樹上移便可
            q->lchild = s->lchild;
        }
        free(s);
        s = NULL;
    }
    return TRUE;
}
/**
 * @Description: 刪除二叉樹中的元素
 * @Param: BiTree *T 指向二叉樹結構體的指針 int key 要刪除的元素
 * @Return: 刪除成功 TRUE 刪除失敗 FALSE
 * @Author: Carlos
 */
int DeleteBST(BiTree *T, int key)
{
    if (!(*T))
    { //不存在關鍵字等於key的數據元素
        return FALSE;
    }
    else
    {
        if (key == (*T)->data)
        {
            Delete(T);
            return TRUE;
        }
        else if (key < (*T)->data)
        {
            //使用遞歸的方式
            return DeleteBST(&(*T)->lchild, key);
        }
        else
        {
            return DeleteBST(&(*T)->rchild, key);
        }
    }
}
/**
 * @Description: 中序遍歷輸出二叉樹
 * @Param: BiTree t 結構體變量
 * @Return: 無
 * @Author: Carlos
 */
void order(BiTree t) 
{
    if (t == NULL)
    {
        return;
    }
    order(t->lchild);
    printf("%d ", t->data);
    order(t->rchild);
}
int main()
{
    int i;
    int a[5] = {3, 4, 2, 5, 9};
    BiTree T = NULL;
    for (i = 0; i < 5; i++)
    {
        InsertBST(&T, a[i]);
    }
    printf("中序遍歷二叉排序樹:\n");
    order(T);
    printf("\n");
    printf("刪除3後,中序遍歷二叉排序樹:\n");
    DeleteBST(&T, 3);
    order(T);
}

有任何問題,都可經過公告中的二維碼聯繫我

相關文章
相關標籤/搜索