天天看點

九大查找算法,值得收藏(二)

4 斐波那契查找

斐波那契查找與折半查找很相似,他是根據斐波那契序列的特點對有序表進行分割的。他要求開始表中記錄的個數為某個斐波那契數小1,及n=F(k)-1;開始将k值與第F(k-1)位置的記錄進行比較(及mid=low+F(k-1)-1).

算法思路:

相等,mid位置的元素即為所求

大于,low=mid+1,k-=2;

小于,high=mid-1,k-=1。

說明:

low=mid+1說明待查找的元素在[mid+1,high]範圍内,k-=2 說明範圍[mid+1,high]内的元素個數為n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1個,是以可以遞歸的應用斐波那契查找。

代碼:

#include "stdafx.h"
#include <memory>
#include  <iostream>
using namespace std;
 
const int max_size=20;//斐波那契數組的長度
 
/*構造一個斐波那契數組*/ 
void Fibonacci(int * F)
{
    F[0]=0;
    F[1]=1;
    for(int i=2;i<max_size;++i)
        F[i]=F[i-1]+F[i-2];
}
 
/*定義斐波那契查找法*/  
int FibonacciSearch(int *a, int n, int key)  //a為要查找的數組,n為要查找的數組長度,key為要查找的關鍵字
{
  int low=0;
  int high=n-1;
  
  int F[max_size];
  Fibonacci(F);//構造一個斐波那契數組F 
 
  int k=0;
  while(n>F[k]-1)//計算n位于斐波那契數列的位置
      ++k;
 
  int  * temp;//将數組a擴充到F[k]-1的長度
  temp=new int [F[k]-1];
  memcpy(temp,a,n*sizeof(int));
 
  for(int i=n;i<F[k]-1;++i)
     temp[i]=a[n-1];
  
  while(low<=high)
  {
    int mid=low+F[k-1]-1;
    if(key<temp[mid])
    {
      high=mid-1;
      k-=1;
    }
    else if(key>temp[mid])
    {
     low=mid+1;
     k-=2;
    }
    else
    {
       if(mid<n)
           return mid; //若相等則說明mid即為查找到的位置
       else
           return n-1; //若mid>=n則說明是擴充的數值,傳回n-1
    }
  }  
  delete [] temp;
  return 0;
}
 
int main()
{
    int a[] = {0,1,4,35,47,53,62,78,88,99};
    int key=47;
    int index=FibonacciSearch(a,sizeof(a)/sizeof(int),key);
    if(index == 0)
    {
       cout<<"沒有找到"<<key;
    }
    else
    {
       cout<<key<<" 的位置是:"<<index;
    }
    return 0;
}

      

5 哈希查找

哈希表:

我們使用一個下标範圍比較大的數組來存儲元素。可以設計一個函數(哈希函數, 也叫做散列函數),使得每個元素的關鍵字都與一個函數值(即數組下标)相對應,于是用這個數組單元來存儲這個元素;也可以簡單的了解為,按照關鍵字為每一個元素"分類",然後将這個元素存儲在相應"類"所對應的地方。但是,不能夠保證每個元素的關鍵字與函數值是一一對應的,是以極有可能出現對于不同的元素,卻計算出了相同的函數值,這樣就産生了"沖突",換句話說,就是把不同的元素分在了相同的"類"之中。後面我們将看到一種解決"沖突"的簡便做法。

"直接定址"與"解決沖突"是哈希表的兩大特點。

哈希函數:

規則:通過某種轉換關系,使關鍵字适度的分散到指定大小的的順序結構中,越分散,則以後查找的時間複雜度越小,空間複雜度越高。

如果所有的鍵都是整數,那麼就可以使用一個簡單的無序數組來實作:将鍵作為索引,值即為其對應的值,這樣就可以快速通路任意鍵的值。這是對于簡單的鍵的情況,我們将其擴充到可以處理更加複雜的類型的鍵。

用給定的哈希函數構造哈希表;

根據選擇的沖突處理方法(常見方法:拉鍊法和線性探測法)解決位址沖突;

在哈希表的基礎上執行哈希查找;

#include<stdio.h>
#include<stdlib.h>
 
#define SUCCESS 1
#define UNSUCCESS 0
#define OVERFLOW -1
#define OK 1
#define ERROR -1
#define MAXNUM 9999     // 用于初始化哈希表的記錄 key
 
typedef int Status;
typedef int KeyType;
 
// 哈希表中的記錄類型
typedef struct 
{
    KeyType key;
}RcdType;
 
// 哈希表類型
typedef struct 
{
    RcdType *rcd;
    int size;
    int count;
    int *tag;
}HashTable;
 
// 哈希表每次重建增長後的大小
int hashsize[] = { 11, 31, 61, 127, 251, 503 };
int index = 0;
 
// 初始哈希表
Status InitHashTable(HashTable &H, int size) 
{
    int i;
    H.rcd = (RcdType *)malloc(sizeof(RcdType)*size);
    H.tag = (int *)malloc(sizeof(int)*size);
    if (NULL == H.rcd || NULL == H.tag) return OVERFLOW;
    KeyType maxNum = MAXNUM;
    for (i = 0; i < size; i++) 
    {
        H.tag[i] = 0;
        H.rcd[i].key = maxNum;
    }
    H.size = size;
    H.count = 0;
    return OK;
}
 
// 哈希函數:除留餘數法
int Hash(KeyType key, int m) 
{
    return (3 * key) % m;
}
 
// 處理哈希沖突:線性探測
void collision(int &p, int m) 
{
    p = (p + 1) % m;
}
 
// 在哈希表中查詢
Status SearchHash(HashTable H, KeyType key, int &p, int &c) 
{
    p = Hash(key, H.size);
    int h = p;
    c = 0;
    while ((1 == H.tag[p] && H.rcd[p].key != key) || -1 == H.tag[p]) 
    {
        collision(p, H.size);  c++;
    }
 
    if (1 == H.tag[p] && key == H.rcd[p].key) return SUCCESS;
    else return UNSUCCESS;
}
 
//列印哈希表
void printHash(HashTable H)
{
    int  i;
    printf("key : ");
    for (i = 0; i < H.size; i++)
        printf("%3d ", H.rcd[i].key);
    printf("\n");
    printf("tag : ");
    for (i = 0; i < H.size; i++)
        printf("%3d ", H.tag[i]);
    printf("\n\n");
}
 
// 函數聲明:插入哈希表
Status InsertHash(HashTable &H, KeyType key);
 
// 重建哈希表
Status recreateHash(HashTable &H) 
{
    RcdType *orcd;
    int *otag, osize, i;
    orcd = H.rcd;
    otag = H.tag;
    osize = H.size;
 
    InitHashTable(H, hashsize[index++]);
    //把所有元素,按照新哈希函數放到新表中
    for (i = 0; i < osize; i++) 
    {
        if (1 == otag[i]) 
        {
            InsertHash(H, orcd[i].key);
        }
    }
    return OK;
}
 
// 插入哈希表
Status InsertHash(HashTable &H, KeyType key) 
{
    int p, c;
    if (UNSUCCESS == SearchHash(H, key, p, c)) 
    { //沒有相同key
        if (c*1.0 / H.size < 0.5) 
        { //沖突次數未達到上線
            //插入代碼
            H.rcd[p].key = key;
            H.tag[p] = 1;
            H.count++;
            return SUCCESS;
        }
        else recreateHash(H); //重構哈希表 
    }
    return UNSUCCESS;
}
 
// 删除哈希表
Status DeleteHash(HashTable &H, KeyType key) 
{
    int p, c;
    if (SUCCESS == SearchHash(H, key, p, c)) 
    {
        //删除代碼
        H.tag[p] = -1;
        H.count--;
        return SUCCESS;
    }
    else return UNSUCCESS;
}
 
int main()
{
    printf("-----哈希表-----\n");
    HashTable H;
    int i;
    int size = 11;
    KeyType array[8] = { 22, 41, 53, 46, 30, 13, 12, 67 };
    KeyType key;
 
    //初始化哈希表
    printf("初始化哈希表\n");
    if (SUCCESS == InitHashTable(H, hashsize[index++])) printf("初始化成功\n");
 
    //插入哈希表
    printf("插入哈希表\n");
    for (i = 0; i <= 7; i++) 
    {
        key = array[i];
        InsertHash(H, key);
        printHash(H);
    }
 
    //删除哈希表
    printf("删除哈希表中key為12的元素\n");
    int p, c;
    if (SUCCESS == DeleteHash(H, 12)) 
    {
        printf("删除成功,此時哈希表為:\n");
        printHash(H);
    }
 
    //查詢哈希表
    printf("查詢哈希表中key為67的元素\n");
    if (SUCCESS == SearchHash(H, 67, p, c)) printf("查詢成功\n");
 
    //再次插入,測試哈希表的重建
    printf("再次插入,測試哈希表的重建:\n");
    KeyType array1[8] = { 27, 47, 57, 47, 37, 17, 93, 67 };
    for (i = 0; i <= 7; i++) 
    {
        key = array1[i];
        InsertHash(H, key);
        printHash(H);
    }
 
    getchar();
    return 0;
}

      

6 二叉樹查找

二叉查找樹是先對待查找的資料進行生成樹,確定樹的左分支的值小于右分支的值,然後在就行和每個節點的父節點比較大小,查找最适合的範圍。 這個算法的查找效率很高,但是如果使用這種查找方法要首先建立樹。

若b是空樹,則搜尋失敗:

若x等于b的根節點的資料域之值,則查找成功:

若x小于b的根節點的資料域之值,則搜尋左子樹:

查找右子樹。

遞歸和非遞歸

//非遞歸查找算法
BSTNode *BST_Search(BiTree T,ElemType key,BSTNode *&p)
{
    //查找函數傳回指向關鍵字值為key的結點指針,不存在則傳回NULL
    p=NULL;     
    while(T!=NULL&&key!=T->data)
    {
        p=T;                          //指向被查找結點的雙親  
        if(key<T->data)
            T=T->lchild;              //查找左子樹
        else
            T=T->rchild;              //查找右子樹
    }
    return T;
}
 
//遞歸算法
Status Search_BST(BiTree T, int key, BiTree f, BiTree *p)
{
    //查找BST中是否存在key,f指向T雙親,其初始值為NULL
    //若查找成功,指針p指向資料元素結點,傳回true;
    //若失敗,p指向查找路徑上通路的最後一個結點并傳回false
    if(!T)
    {
        *p=f;
        return false;
    }
    else if(key==T->data)
    {                      //查找成功
        *p=T;
        return true;
    }
    else if(key<T->data)
        return Search_BST(T->lchild,key,T,p);   //遞歸查找左子樹
    else
        return Search_BST(T->rchild,key,T,p);   //遞歸查找右子樹
    
}
 

      

繼續閱讀