九大查找算法

時間、空間複雜度比較

查找算法 平均時間複雜度 空間複雜度 查找條件
順序查找 O(n) O(1) 無序或有序
二分查找(折半查找) O(log2n) O(1) 有序
插值查找 O(log2(log2n)) O(1) 有序
斐波那契查找 O(log2n) O(1) 有序
哈希查找 O(1) O(n) 無序或有序
二叉查找樹(二叉搜索樹查找) O(log2n)    
紅黑樹 O(log2n)    
2-3樹 O(log2n - log3n)    
B樹/B+樹 O(log2n)    

1 順序查找

算法思路

對於任意一個序列,從一端開始,順序掃描,依次將掃描到的結點關鍵字與給定值k相比較,若相等則表示查找成功;若掃描結束仍沒有找到關鍵字等於k的結點,表示查找失敗。

代碼

#include <stdio.h>
#include <stdlib.h>
#define keyType int
//2020.05.24
typedef struct 
{
	keyType key;//查找表中每個數據元素的值
}ElemType;

typedef struct
{
	ElemType *elem;//存放查找表中數據元素的數組
	int length;//記錄查找表中數據的總數量
}SSTable;

//創建查詢數據
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));
	}
}

//順序查找函數,其中key爲要查找的元素
int Search_seq(SSTable *str,keyType key)
{
	//str->elem[0].key=key;//將關鍵字作爲一個數據元素存放到查找表的第一個位置,起監視哨的作用
	int len = str->length;   
	//從最後一個數據元素依次遍歷,一直遍歷到數組下標爲0
	for(int i=1; i<len+1; i++)   //創建數據從數組下標爲1開始,查詢也從1開始
	{
		if(str->elem[i].key == key)
		{
			return i;
		}
	}
	//如果 i=0,說明查找失敗;查找成功返回要查找元素key的位置i
	return 0;
}

int main() 
{
	SSTable *str;
	int num;
	printf("請輸入創建數據元素的個數:\n");
	scanf("%d",&num);
	Create(&str, num);   
	getchar();
	printf("請輸入要查找的數據:\n");
	int key;
	scanf("%d",&key);
	int location=Search_seq(str, key);
	if (location==0) {
		printf("查找失敗");
	}else{
		printf("要查找的%d的順序爲:%d",key,location);
	}
	return 0;
}

運行結果

查找成功

查找成功

查找失敗

查找失敗

2 二分查找(折半查找)

算法思路

  1. 確定查找範圍low=0,high=N-1,計算中項mid=(low+high)/2。

  2. 若mid==x或low>=high,則結束查找;否則,向下繼續。

  3. 若amid<x,說明待查找的元素值只可能在比中項元素大的範圍內,則把mid+1的值賦給low,並重新計算mid,轉去執行步驟2;若mid>x,說明待查找的元素值只可能在比中項元素小的範圍內,則把mid-1的值賦給higt,並重新計算mid,轉去執行步驟2。

說明

  • 查找元素必須是有序的,如果是無序的則要先進行排序操作。

  • 在做查找的過程中,如果 low 指針和 high 指針的中間位置在計算時位於兩個關鍵字中間,即求得 mid 的位置不是整數,需要統一做取整操作。

折半查找的前提條件是需要有序表順序存儲,對於靜態查找表,一次排序後不再變化,折半查找能得到不錯的效率。但對於需要頻繁執行插入或刪除操作的數據集來說,維護有序的排序會帶來不小的工作量,那就不建議使用。

                         ——《大話數據結構》

代碼

#include <stdio.h>
#include <stdlib.h>
#define keyType int
typedef struct 
{
	keyType key;//查找表中每個數據元素的值
}ElemType;

typedef struct
{
	ElemType *elem;//存放查找表中數據元素的數組
	int length;//記錄查找表中數據的總數量
}SSTable;

//創建查詢數據
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));
	}
}

//折半查找函數 key爲要查找的元素
int Search_Bin(SSTable *str,keyType key)
{
	int low=1;//初始狀態 low 指針指向第一個關鍵字
	int high=str->length;//high 指向最後一個關鍵字
	int mid;
	while (low<=high) 
	{
		mid=(low+high)/2;//int 本身爲整形,所以,mid 每次爲取整的整數
		if(str->elem[mid].key==key)//如果 mid 指向的同要查找的相等,返回 mid 所指向的位置
		{
			return mid;
		}
        else if(str->elem[mid].key>key)//如果mid指向的關鍵字較大,則更新 high 指針的位置
		{
			high=mid-1;
		}
		//反之,則更新 low 指針的位置
		else
		{
			low=mid+1;
		}
	}
	return 0;
}

int main() 
{
	SSTable *str;
	int num;
	printf("請輸入創建數據元素的個數:\n");
	scanf("%d",&num);
	Create(&str, num);   
	getchar();
	printf("請輸入要查找的數據:\n");
	int key;
	scanf("%d",&key);
	int location=Search_Bin(str, key);
	if (location==0) {
		printf("沒有查找到");
	}else{
		printf("要查找的%d的順序爲:%d",key,location);
	}
	return 0;
}

運行結果

查找成功

查找成功

沒有查找到

沒有查找到

3 插值查找

插值查找基於二分查找算法,將查找點的選擇改進爲自適應選擇,可以提高查找效率。當然,差值查找也屬於有序查找

算法思路

  1. 確定查找範圍low=0,high=N-1,計算中項mid=low+(key-a[low])/(a[high]-a[low])*(high-low)。

  2. 若mid==x或low>=high,則結束查找;否則,向下繼續。

  3. 若amid<x,說明待查找的元素值只可能在比中項元素大的範圍內,則把mid+1的值賦給low,並重新計算mid,轉去執行步驟2;若mid>x,說明待查找的元素值只可能在比中項元素小的範圍內,則把mid-1的值賦給higt,並重新計算mid,轉去執行步驟2。

說明

  • 插值查找是基於折半查找進行了優化的查找方法。

  • 當表長較大,而關鍵字分佈又比較均勻的查找表來說,插值查找算法的平均性能要比折半查找要好得多。

代碼

#include<stdio.h>

int array[10] = { 1, 4, 9, 16, 27, 31, 33, 35, 45, 64 };

int InsertionSearch(int data) 
{
    int low = 0;
    int high = 10 - 1;
    int mid = -1;
    int comparisons = 1;      
    int index = -1;
    
    while(low <= high) 
    {
       printf("比較 %d 次\n" , comparisons );
       printf("low : %d, list[%d] = %d\n", low, low, array[low]);
       printf("high : %d, list[%d] = %d\n", high, high, array[high]);

       comparisons++;
       mid = low + (((double)(high - low) / (array[high] - array[low])) * (data - array[low]));
       printf("mid = %d\n",mid);
       
       // 沒有找到
       if(array[mid] == data) 
       {
	   index = mid;
           break;
        }
	else 
	{
	   if(array[mid] < data) 
           {
               low = mid + 1;
           } 
           else 
	   {
	       high = mid - 1;
	    }
	 }               
     }

     printf("比較次數: %d\n", --comparisons);
     return index;
}

int main() 
{
    int location = InsertionSearch(27);  //測試代,查27,可以找到
    if(location != -1)
    {
	printf("查找元素順序爲: %d\n" ,(location+1));
     }
     else
     {
         printf("沒有查找到");
     }
     return 0;
}

運行結果

運行結果

運行結果

4 斐波那契查找

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

算法思路

  1. 相等,mid位置的元素即爲所求

  2. 大於,low=mid+1,k-=2;

  3. 小於,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;
}

運行結果

47的位置爲5

47的位置爲5

5 哈希查找

哈希表

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

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

哈希函數

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

算法思路

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

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

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

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

代碼

#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 二叉樹查找

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

算法思路

  1. 若b是空樹,則搜索失敗:

  2. 若x等於b的根節點的數據域之值,則查找成功:

  3. 若x小於b的根節點的數據域之值,則搜索左子樹:

  4. 查找右子樹。

代碼

遞歸和非遞歸

//非遞歸查找算法
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);   //遞歸查找右子樹
    
}

7 2-3樹

2-3樹運行每個節點保存1個或者兩個的值。對於普通的2節點(2-node),他保存1個key和左右兩個自己點。對應3節點(3-node),保存兩個Key,2-3查找樹的定義如下:

  1. 要麼爲空,要麼:

  2. 對於2節點,該節點保存一個key及對應value,以及兩個指向左右節點的節點,左節點也是一個2-3節點,所有的值都比key要小,右節點也是一個2-3節點,所有的值比key要大。

  3. 對於3節點,該節點保存兩個key及對應value,以及三個指向左中右的節點。左節點也是一個2-3節點,所有的值均比兩個key中的最小的key還要小;中間節點也是一個2-3節點,中間節點的key值在兩個跟節點key值之間;右節點也是一個2-3節點,節點的所有key值比兩個key中的最大的key還要大。

算法思路:

要判斷一個鍵是否在樹中,我們先將它和根結點中的鍵比較。如果它和其中的任何一個相等,查找命中。否則我們就根據比較的結果找到指向相應區間的鏈接,並在其指向的子樹中遞歸地繼續查找。如果這是個空鏈接,查找未命中。

2-3 樹中查找鍵爲H的節點

2-3 樹中查找鍵爲H的節點

2-3 樹中查找鍵爲B的節點

2-3 樹中查找鍵爲B的節點

代碼

two_three *search23(two_three *t, element x)
{
    while(t) 
    {
        if (x < t->data_l) 
        {
            t = t->left_child;
        }
        else if (x > t->data_l && x < t->data_r) 
        {
            t = t->middle_child;
        } 
        else if (x > t->data_r) 
        {
            t = t->right_child;
        } 
        else 
        {
            return t;
        }
    }
    return NULL;
}

8 紅黑樹

2-3查找樹能保證在插入元素之後能保持樹的平衡狀態,最壞情況下即所有的子節點都是2-node,樹的高度爲lgn,從而保證了最壞情況下的時間複雜度。但是2-3樹實現起來比較複雜,於是就有了一種簡單實現2-3樹的數據結構,即紅黑樹(Red-Black Tree)。

理解紅黑樹一句話就夠了:紅黑樹就是用紅鏈接表示3-結點的2-3樹

現在我們對2-3樹進行改造,改造成一個二叉樹。怎麼改造呢?對於2節點,保持不變;對於3節點,我們首先將3節點中左側的元素標記爲紅色,然後我們將其改造成圖3的形式;

再將3節點的位於中間的子節點的父節點設置爲父節點中那個紅色的節點,如圖4的所示;最後我們將圖4的形式改爲二叉樹的樣子,如圖5所示。圖5是不是很熟悉,沒錯,這就是我們常常提到的大名鼎鼎的紅黑樹了。如下圖所示。

2-3樹轉紅黑樹

2-3樹轉紅黑樹

爲什麼使用紅黑樹

  • 紅黑樹是一種平衡樹,他複雜的定義和規則都是爲了保證樹的平衡性。如果樹不保證他的平衡性就是下圖:很顯然這就變成一個鏈表了。

  • 保證平衡性的最大的目的就是降低樹的高度,因爲樹的查找性能取決於樹的高度。所以樹的高度越低搜索的效率越高!

  • 這也是爲什麼存在二叉樹、搜索二叉樹等,各類樹的目的。

紅黑樹性質

  • 每個節點要麼是黑色,要麼是紅色。

  • 根節點是黑色。

  • 每個葉子節點(NIL)是黑色。

  • 每個紅色結點的兩個子結點一定都是黑色。

  • 任意一結點到每個葉子結點的路徑都包含數量相同的黑結點。

算法思路

紅黑樹的思想就是對2-3查找樹進行編碼,尤其是對2-3查找樹中的3-nodes節點添加額外的信息。紅黑樹中將節點之間的鏈接分爲兩種不同類型,紅色鏈接,他用來鏈接兩個2-nodes節點來表示一個3-nodes節點。黑色鏈接用來鏈接普通的2-3節點。特別的,使用紅色鏈接的兩個2-nodes來表示一個3-nodes節點,並且向左傾斜,即一個2-node是另一個2-node的左子節點。這種做法的好處是查找的時候不用做任何修改,和普通的二叉查找樹相同

代碼

#define BLACK 1
#define RED 0
#include <iostream>

using namespace std;

class bst 
{
private:

	struct Node 
	{
		int value;
		bool color;
		Node *leftTree, *rightTree, *parent;

		Node() : value(0), color(RED), leftTree(NULL), rightTree(NULL), parent(NULL) { }

		Node* grandparent() 
		{
			if (parent == NULL) 
			{
				return NULL;
			}
			return parent->parent;
		}

		Node* uncle() 
		{
			if (grandparent() == NULL) 
			{
				return NULL;
			}
			if (parent == grandparent()->rightTree)
				return grandparent()->leftTree;
			else
				return grandparent()->rightTree;
		}

		Node* sibling() 
		{
			if (parent->leftTree == this)
				return parent->rightTree;
			else
				return parent->leftTree;
		}
	};

	void rotate_right(Node *p) 
	{
		Node *gp = p->grandparent();
		Node *fa = p->parent;
		Node *y = p->rightTree;

		fa->leftTree = y;

		if (y != NIL)
			y->parent = fa;
		p->rightTree = fa;
		fa->parent = p;

		if (root == fa)
			root = p;
		p->parent = gp;

		if (gp != NULL) 
		{
			if (gp->leftTree == fa)
				gp->leftTree = p;
			else
				gp->rightTree = p;
		}

	}

	void rotate_left(Node *p) 
	{
		if (p->parent == NULL) 
		{
			root = p;
			return;
		}
		Node *gp = p->grandparent();
		Node *fa = p->parent;
		Node *y = p->leftTree;

		fa->rightTree = y;

		if (y != NIL)
			y->parent = fa;
		p->leftTree = fa;
		fa->parent = p;

		if (root == fa)
			root = p;
		p->parent = gp;

		if (gp != NULL) 
		{
			if (gp->leftTree == fa)
				gp->leftTree = p;
			else
				gp->rightTree = p;
		}
	}

	void inorder(Node *p) 
	{
		if (p == NIL)
			return;

		if (p->leftTree)
			inorder(p->leftTree);

		cout << p->value << " ";

		if (p->rightTree)
			inorder(p->rightTree);
	}

	string outputColor(bool color) 
	{
		return color ? "BLACK" : "RED";
	}

	Node* getSmallestChild(Node *p) 
	{
		if (p->leftTree == NIL)
			return p;
		return getSmallestChild(p->leftTree);
	}

	bool delete_child(Node *p, int data) 
	{
		if (p->value > data) 
		{
			if (p->leftTree == NIL) 
			{
				return false;
			}
			return delete_child(p->leftTree, data);
		}
		else if (p->value < data) 
		{
			if (p->rightTree == NIL) 
			{
				return false;
			}
			return delete_child(p->rightTree, data);
		}
		else if (p->value == data) 
		{
			if (p->rightTree == NIL) 
			{
				delete_one_child(p);
				return true;
			}
			Node *smallest = getSmallestChild(p->rightTree);
			swap(p->value, smallest->value);
			delete_one_child(smallest);

			return true;
		}
		else 
		{
			return false;
		}
	}

	void delete_one_child(Node *p) 
	{
		Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree;
		if (p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL) 
		{
			p = NULL;
			root = p;
			return;
		}

		if (p->parent == NULL) 
		{
			delete  p;
			child->parent = NULL;
			root = child;
			root->color = BLACK;
			return;
		}

		if (p->parent->leftTree == p) 
		{
			p->parent->leftTree = child;
		}
		else 
		{
			p->parent->rightTree = child;
		}
		child->parent = p->parent;

		if (p->color == BLACK) 
		{
			if (child->color == RED) 
			{
				child->color = BLACK;
			}
			else
				delete_case(child);
		}

		delete p;
	}

	void delete_case(Node *p) 
	{
		if (p->parent == NULL) 
		{
			p->color = BLACK;
			return;
		}
		if (p->sibling()->color == RED) 
		{
			p->parent->color = RED;
			p->sibling()->color = BLACK;
			if (p == p->parent->leftTree)
				rotate_left(p->sibling());
			else
				rotate_right(p->sibling());
		}
		if (p->parent->color == BLACK && p->sibling()->color == BLACK
			&& p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) 
		{
			p->sibling()->color = RED;
			delete_case(p->parent);
		}
		else if (p->parent->color == RED && p->sibling()->color == BLACK
			&& p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) 
		{
			p->sibling()->color = RED;
			p->parent->color = BLACK;
		}
		else 
		{
			if (p->sibling()->color == BLACK) 
			{
				if (p == p->parent->leftTree && p->sibling()->leftTree->color == RED
					&& p->sibling()->rightTree->color == BLACK) 
				{
					p->sibling()->color = RED;
					p->sibling()->leftTree->color = BLACK;
					rotate_right(p->sibling()->leftTree);
				}
				else if (p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK
					&& p->sibling()->rightTree->color == RED) 
				{
					p->sibling()->color = RED;
					p->sibling()->rightTree->color = BLACK;
					rotate_left(p->sibling()->rightTree);
				}
			}
			p->sibling()->color = p->parent->color;
			p->parent->color = BLACK;
			if (p == p->parent->leftTree) 
			{
				p->sibling()->rightTree->color = BLACK;
				rotate_left(p->sibling());
			}
			else 
			{
				p->sibling()->leftTree->color = BLACK;
				rotate_right(p->sibling());
			}
		}
	}

	void insert(Node *p, int data) 
	{
		if (p->value >= data) 
		{
			if (p->leftTree != NIL)
				insert(p->leftTree, data);
			else 
			{
				Node *tmp = new Node();
				tmp->value = data;
				tmp->leftTree = tmp->rightTree = NIL;
				tmp->parent = p;
				p->leftTree = tmp;
				insert_case(tmp);
			}
		}
		else 
		{
			if (p->rightTree != NIL)
				insert(p->rightTree, data);
			else 
			{
				Node *tmp = new Node();
				tmp->value = data;
				tmp->leftTree = tmp->rightTree = NIL;
				tmp->parent = p;
				p->rightTree = tmp;
				insert_case(tmp);
			}
		}
	}

	void insert_case(Node *p) 
	{
		if (p->parent == NULL) 
		{
			root = p;
			p->color = BLACK;
			return;
		}
		if (p->parent->color == RED) 
		{
			if (p->uncle()->color == RED) 
			{
				p->parent->color = p->uncle()->color = BLACK;
				p->grandparent()->color = RED;
				insert_case(p->grandparent());
			}
			else 
			{
				if (p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) 
				{
					rotate_left(p);
					rotate_right(p);
					p->color = BLACK;
					p->leftTree->color = p->rightTree->color = RED;
				}
				else if (p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) 
				{
					rotate_right(p);
					rotate_left(p);
					p->color = BLACK;
					p->leftTree->color = p->rightTree->color = RED;
				}
				else if (p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) 
				{
					p->parent->color = BLACK;
					p->grandparent()->color = RED;
					rotate_right(p->parent);
				}
				else if (p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) 
				{
					p->parent->color = BLACK;
					p->grandparent()->color = RED;
					rotate_left(p->parent);
				}
			}
		}
	}

	void DeleteTree(Node *p) 
	{
		if (!p || p == NIL) 
		{
			return;
		}
		DeleteTree(p->leftTree);
		DeleteTree(p->rightTree);
		delete p;
	}
public:

	bst() 
	{
		NIL = new Node();
		NIL->color = BLACK;
		root = NULL;
	}

	~bst() 
	{
		if (root)
			DeleteTree(root);
		delete NIL;
	}

	void inorder() 
	{
		if (root == NULL)
			return;
		inorder(root);
		cout << endl;
	}

	void insert(int x) 
	{
		if (root == NULL) 
		{
			root = new Node();
			root->color = BLACK;
			root->leftTree = root->rightTree = NIL;
			root->value = x;
		}
		else 
		{
			insert(root, x);
		}
	}

	bool delete_value(int data) 
	{
		return delete_child(root, data);
	}
private:
	Node *root, *NIL;
};

int main()
{
	cout << "---【紅黑樹】---" << endl;
	// 創建紅黑樹
	bst tree;

	// 插入元素
	tree.insert(2);
	tree.insert(9);
	tree.insert(-10);
	tree.insert(0);
	tree.insert(33);
	tree.insert(-19);

	// 順序打印紅黑樹
	cout << "插入元素後的紅黑樹:" << endl;
	tree.inorder();

	// 刪除元素
	tree.delete_value(2);

	// 順序打印紅黑樹
	cout << "刪除元素 2 後的紅黑樹:" << endl;
	tree.inorder();

	// 析構
	tree.~bst();

	getchar();
	return 0;
}

9 B樹/B+樹

在計算機科學中,B樹(B-tree)是一種樹狀數據結構,它能夠存儲數據、對其進行排序並允許以O(log n)的時間複雜度運行進行查找、順序讀取、插入和刪除的數據結構。B樹,概括來說是一個節點可以擁有多於2個子節點的二叉查找樹。與自平衡二叉查找樹不同,B-樹爲系統最優化大塊數據的讀和寫操作。B-tree算法減少定位記錄時所經歷的中間過程,從而加快存取速度。普遍運用在數據庫和文件系統。

                             ——維基百科

B 樹可以看作是對2-3查找樹的一種擴展,即他允許每個節點有M-1個子節點。

  • 定義任意非葉子結點最多隻有M個兒子;且M>2;

  • 根結點的兒子數爲[2, M];

  • 除根結點以外的非葉子結點的兒子數爲[M/2, M];

  • 每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)

  • 非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

  • 非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的 子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;

  • 所有葉子結點位於同一層;

如:(M=3) 1

算法思路

從根結點開始,對結點內的關鍵字(有序)序列進行二分查找,如果命中則結束,否則進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的兒子指針爲空,或已經是葉子結點;

  • 關鍵字集合分佈在整顆樹中;

  • 任何一個關鍵字出現且只出現在一個結點中;

  • 搜索有可能在非葉子結點結束;

  • 其搜索性能等價於在關鍵字全集內做一次二分查找;

  • 自動層次控制;

代碼

BTNode* BTree_recursive_search(const BTree tree, KeyType key, int* pos) 
{ 
    int i = 0; 
    while (i < tree->keynum && key > tree->key[i]) 
    { 
        ++i; 
    } 
   
    // 查找關鍵字 
    if (i < tree->keynum && tree->key[i] == key) 
    { 
        *pos = i; 
        return tree; 
    } 
   
    // tree 爲葉子節點,找不到 key,查找失敗返回 
    if (tree->isLeaf) 
    { 
        return NULL; 
    } 
   
    // 節點內查找失敗,但 tree->key[i - 1]< key < tree->key[i], 
    // 下一個查找的結點應爲 child[i] 
   
    // 從磁盤讀取第 i 個孩子的數據 
    disk_read(&tree->child[i]); 
   
    // 遞歸地繼續查找於樹 tree->child[i] 
    return BTree_recursive_search(tree->child[i], key, pos); 
}

B+樹

B+樹是B樹的變體,也是一種多路搜索樹:

其定義基本與B-樹同,除了:

  • 非葉子結點的子樹指針與關鍵字個數相同;

  • 非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹, B樹是開區間

  • 爲所有葉子結點增加一個鏈指針;

  • 所有關鍵字都在葉子結點出現;

如:(M=3)

1

1

算法思路

B+的搜索與B樹也基本相同,區別是B+樹只有達到葉子結點才命中(B樹可以在 非葉子結點命中),其性能也等價於在關鍵字全集做一次二分查找;https://blog.csdn.net/u013171283/article/details/82951039

  • 所有關鍵字都出現在葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字恰好是有序的;

  • 不可能在非葉子結點命中;

  • 非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是存儲(關鍵字)數據的數據層;

  • 更適合文件索引系統;

參考資料

  1. https://www.sohu.com/a/296278527_478315

  2. https://www.cnblogs.com/exzlc/p/12203744.html

  3. 部分配圖來源於網絡

最近原創推薦

如何定義一個只能在(堆/棧)上生成對象的類

十大經典排序算法(動態演示+代碼)

C語言與C++面試知識總結

數據結構之堆棧

一文輕鬆理解內存對齊

一文輕鬆理解打印有效日誌

一文讀懂C語言與C++動態內存

面試中常見的C語言與C++區別的問題

數據結構之線性表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章