www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導(dǎo)讀]時間、空間復(fù)雜度比較 查找算法 平均時間復(fù)雜度 空間復(fù)雜度 查找條件 順序查找 O(n) O(1) 無序或有序 二分查找(折半查找) O(log2n) O(1) 有序 插值查找 O(log2(log2n)) O(1) 有序 斐波那契查找 O(log2n) O(1) 有序 哈希查找 O(1) O(n) 無序或有序 二叉查找




時間、空間復(fù)雜度比較

查找算法 平均時間復(fù)雜度 空間復(fù)雜度 查找條件
順序查找 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 順序查找

算法思路

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

代碼

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

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

//創(chuàng)建查詢數(shù)據(jù)
void Create(SSTable **st,int length)
{
(*st)=(SSTable*)malloc(sizeof(SSTable));
(*st)->length=length;
(*st)->elem =(ElemType*)malloc((length+1)*sizeof(ElemType));
printf("輸入表中的數(shù)據(jù)元素:\n");
//根據(jù)查找表中數(shù)據(jù)元素的總長度,在存儲時,從數(shù)組下標(biāo)為 1 的空間開始存儲數(shù)據(jù)
for (int i=1; i<=length; i++)
{
scanf("%d",&((*st)->elem[i].key));
}
}

//順序查找函數(shù),其中key為要查找的元素
int Search_seq(SSTable *str,keyType key)
{
//str->elem[0].key=key;//將關(guān)鍵字作為一個數(shù)據(jù)元素存放到查找表的第一個位置,起監(jiān)視哨的作用
int len = str->length;
//從最后一個數(shù)據(jù)元素依次遍歷,一直遍歷到數(shù)組下標(biāo)為0
for(int i=1; i<len+1; i++) //創(chuàng)建數(shù)據(jù)從數(shù)組下標(biāo)為1開始,查詢也從1開始
{
if(str->elem[i].key == key)
{
return i;
}
}
//如果 i=0,說明查找失?。徊檎页晒Ψ祷匾檎以豮ey的位置i
return 0;
}

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

運行結(jié)果

查找成功
查找失敗

2 二分查找(折半查找)

算法思路

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

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

  3. 若amid<x,說明待查找的元素值只可能在比中項元素大的范圍內(nèi),則把mid+1的值賦給low,并重新計算mid,轉(zhuǎn)去執(zhí)行步驟2;若mid>x,說明待查找的元素值只可能在比中項元素小的范圍內(nèi),則把mid-1的值賦給higt,并重新計算mid,轉(zhuǎn)去執(zhí)行步驟2。

說明

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

  • 在做查找的過程中,如果 low 指針和 high 指針的中間位置在計算時位于兩個關(guān)鍵字中間,即求得 mid 的位置不是整數(shù),需要統(tǒng)一做取整操作。

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

                         ——《大話數(shù)據(jù)結(jié)構(gòu)》

代碼

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

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

//創(chuàng)建查詢數(shù)據(jù)
void Create(SSTable **st,int length)
{
(*st)=(SSTable*)malloc(sizeof(SSTable));
(*st)->length=length;
(*st)->elem =(ElemType*)malloc((length+1)*sizeof(ElemType));
printf("輸入表中的數(shù)據(jù)元素:\n");
//根據(jù)查找表中數(shù)據(jù)元素的總長度,在存儲時,從數(shù)組下標(biāo)為 1 的空間開始存儲數(shù)據(jù)
for (int i=1; i<=length; i++)
{
scanf("%d",&((*st)->elem[i].key));
}
}

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

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

運行結(jié)果

查找成功
沒有查找到

3 插值查找

插值查找基于二分查找算法,將查找點的選擇改進(jìn)為自適應(yīng)選擇,可以提高查找效率。當(dāng)然,差值查找也屬于有序查找

算法思路

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

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

  3. 若amid<x,說明待查找的元素值只可能在比中項元素大的范圍內(nèi),則把mid+1的值賦給low,并重新計算mid,轉(zhuǎn)去執(zhí)行步驟2;若mid>x,說明待查找的元素值只可能在比中項元素小的范圍內(nèi),則把mid-1的值賦給higt,并重新計算mid,轉(zhuǎn)去執(zhí)行步驟2。

說明

  • 插值查找是基于折半查找進(jìn)行了優(yōu)化的查找方法。
  • 當(dāng)表長較大,而關(guān)鍵字分布又比較均勻的查找表來說,插值查找算法的平均性能要比折半查找要好得多。

代碼

#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("比較次數(shù): %d\n", --comparisons);
return index;
}

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

運行結(jié)果

運行結(jié)果

4 斐波那契查找

斐波那契查找與折半查找很相似,他是根據(jù)斐波那契序列的特點對有序表進(jìn)行分割的。他要求開始表中記錄的個數(shù)為某個斐波那契數(shù)小1,及n=F(k)-1;開始將k值與第F(k-1)位置的記錄進(jìn)行比較(及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]范圍內(nèi),k-=2 說明范圍[mid+1,high]內(nèi)的元素個數(shù)為n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1個,所以可以遞歸的應(yīng)用斐波那契查找。

代碼

#include "stdafx.h"
#include <memory>
#include <iostream>
using namespace std;

const int max_size=20;//斐波那契數(shù)組的長度

/*構(gòu)造一個斐波那契數(shù)組*/
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為要查找的數(shù)組,n為要查找的數(shù)組長度,key為要查找的關(guān)鍵字
{
int low=0;
int high=n-1;

int F[max_size];
Fibonacci(F);//構(gòu)造一個斐波那契數(shù)組F

int k=0;
while(n>F[k]-1)//計算n位于斐波那契數(shù)列的位置
++k;

int * temp;//將數(shù)組a擴(kuò)展到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則說明是擴(kuò)展的數(shù)值,返回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;
}

運行結(jié)果

47的位置為5

5 哈希查找

哈希表

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

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

哈希函數(shù)

規(guī)則:通過某種轉(zhuǎn)換關(guān)系,使關(guān)鍵字適度的分散到指定大小的的順序結(jié)構(gòu)中,越分散,則以后查找的時間復(fù)雜度越小,空間復(fù)雜度越高。

算法思路

如果所有的鍵都是整數(shù),那么就可以使用一個簡單的無序數(shù)組來實現(xiàn):將鍵作為索引,值即為其對應(yīng)的值,這樣就可以快速訪問任意鍵的值。這是對于簡單的鍵的情況,我們將其擴(kuò)展到可以處理更加復(fù)雜的類型的鍵。

  1. 用給定的哈希函數(shù)構(gòu)造哈希表;
  2. 根據(jù)選擇的沖突處理方法(常見方法:拉鏈法和線性探測法)解決地址沖突;
  3. 在哈希表的基礎(chǔ)上執(zhí)行哈希查找;

代碼

#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;
}

// 哈希函數(shù):除留余數(shù)法
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");
}

// 函數(shù)聲明:插入哈希表
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++]);
//把所有元素,按照新哈希函數(shù)放到新表中
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)
{ //沖突次數(shù)未達(dá)到上線
//插入代碼
H.rcd[p].key = key;
H.tag[p] = 1;
H.count++;
return SUCCESS;
}
else recreateHash(H); //重構(gòu)哈希表
}
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 二叉樹查找

二叉查找樹是先對待查找的數(shù)據(jù)進(jìn)行生成樹,確保樹的左分支的值小于右分支的值,然后在就行和每個節(jié)點的父節(jié)點比較大小,查找最適合的范圍。這個算法的查找效率很高,但是如果使用這種查找方法要首先創(chuàng)建樹。

算法思路

  1. 若b是空樹,則搜索失?。?
  2. 若x等于b的根節(jié)點的數(shù)據(jù)域之值,則查找成功:
  3. 若x小于b的根節(jié)點的數(shù)據(jù)域之值,則搜索左子樹:
  4. 查找右子樹。

代碼

遞歸和非遞歸

//非遞歸查找算法
BSTNode *BST_Search(BiTree T,ElemType key,BSTNode *&p)
{
//查找函數(shù)返回指向關(guān)鍵字值為key的結(jié)點指針,不存在則返回NULL
p=NULL;
while(T!=NULL&&key!=T->data)
{
p=T; //指向被查找結(jié)點的雙親
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指向數(shù)據(jù)元素結(jié)點,返回true;
//若失敗,p指向查找路徑上訪問的最后一個結(jié)點并返回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樹運行每個節(jié)點保存1個或者兩個的值。對于普通的2節(jié)點(2-node),他保存1個key和左右兩個自己點。對應(yīng)3節(jié)點(3-node),保存兩個Key,2-3查找樹的定義如下:

  1. 要么為空,要么:

  2. 對于2節(jié)點,該節(jié)點保存一個key及對應(yīng)value,以及兩個指向左右節(jié)點的節(jié)點,左節(jié)點也是一個2-3節(jié)點,所有的值都比key要小,右節(jié)點也是一個2-3節(jié)點,所有的值比key要大。

  3. 對于3節(jié)點,該節(jié)點保存兩個key及對應(yīng)value,以及三個指向左中右的節(jié)點。左節(jié)點也是一個2-3節(jié)點,所有的值均比兩個key中的最小的key還要?。恢虚g節(jié)點也是一個2-3節(jié)點,中間節(jié)點的key值在兩個跟節(jié)點key值之間;右節(jié)點也是一個2-3節(jié)點,節(jié)點的所有key值比兩個key中的最大的key還要大。

算法思路:

要判斷一個鍵是否在樹中,我們先將它和根結(jié)點中的鍵比較。如果它和其中的任何一個相等,查找命中。否則我們就根據(jù)比較的結(jié)果找到指向相應(yīng)區(qū)間的鏈接,并在其指向的子樹中遞歸地繼續(xù)查找。如果這是個空鏈接,查找未命中。

2-3 樹中查找鍵為H的節(jié)點
2-3 樹中查找鍵為B的節(jié)點

代碼

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查找樹能保證在插入元素之后能保持樹的平衡狀態(tài),最壞情況下即所有的子節(jié)點都是2-node,樹的高度為lgn,從而保證了最壞情況下的時間復(fù)雜度。但是2-3樹實現(xiàn)起來比較復(fù)雜,于是就有了一種簡單實現(xiàn)2-3樹的數(shù)據(jù)結(jié)構(gòu),即紅黑樹(Red-Black Tree)。

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

現(xiàn)在我們對2-3樹進(jìn)行改造,改造成一個二叉樹。怎么改造呢?對于2節(jié)點,保持不變;對于3節(jié)點,我們首先將3節(jié)點中左側(cè)的元素標(biāo)記為紅色,然后我們將其改造成圖3的形式;

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

2-3樹轉(zhuǎn)紅黑樹

為什么使用紅黑樹

  • 紅黑樹是一種平衡樹,他復(fù)雜的定義和規(guī)則都是為了保證樹的平衡性。如果樹不保證他的平衡性就是下圖:很顯然這就變成一個鏈表了。
  • 保證平衡性的最大的目的就是降低樹的高度,因為樹的查找性能取決于樹的高度。所以樹的高度越低搜索的效率越高!
  • 這也是為什么存在二叉樹、搜索二叉樹等,各類樹的目的。

紅黑樹性質(zhì)

  • 每個節(jié)點要么是黑色,要么是紅色。
  • 根節(jié)點是黑色。
  • 每個葉子節(jié)點(NIL)是黑色。
  • 每個紅色結(jié)點的兩個子結(jié)點一定都是黑色。
  • 任意一結(jié)點到每個葉子結(jié)點的路徑都包含數(shù)量相同的黑結(jié)點。

算法思路

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

代碼

#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;
// 創(chuàng)建紅黑樹
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();

// 析構(gòu)
tree.~bst();

getchar();
return 0;
}

9 B樹/B+樹

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

                             ——維基百科

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

  • 定義任意非葉子結(jié)點最多只有M個兒子;且M>2;
  • 根結(jié)點的兒子數(shù)為[2, M];
  • 除根結(jié)點以外的非葉子結(jié)點的兒子數(shù)為[M/2, M];
  • 每個結(jié)點存放至少M/2-1(取上整)和至多M-1個關(guān)鍵字;(至少2個關(guān)鍵字)
  • 非葉子結(jié)點的關(guān)鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
  • 非葉子結(jié)點的指針:P[1], P[2], …, P[M];其中P[1]指向關(guān)鍵字小于K[1]的 子樹,P[M]指向關(guān)鍵字大于K[M-1]的子樹,其它P[i]指向關(guān)鍵字屬于(K[i-1], K[i])的子樹;
  • 所有葉子結(jié)點位于同一層;

如:(M=3)

算法思路

從根結(jié)點開始,對結(jié)點內(nèi)的關(guān)鍵字(有序)序列進(jìn)行二分查找,如果命中則結(jié)束,否則進(jìn)入查詢關(guān)鍵字所屬范圍的兒子結(jié)點;重復(fù),直到所對應(yīng)的兒子指針為空,或已經(jīng)是葉子結(jié)點;

  • 關(guān)鍵字集合分布在整顆樹中;
  • 任何一個關(guān)鍵字出現(xiàn)且只出現(xiàn)在一個結(jié)點中;
  • 搜索有可能在非葉子結(jié)點結(jié)束;
  • 其搜索性能等價于在關(guān)鍵字全集內(nèi)做一次二分查找;
  • 自動層次控制;

代碼

BTNode* BTree_recursive_search(const BTree tree, KeyType key, int* pos)
{
int i = 0;
while (i < tree->keynum && key > tree->key[i])
{
++i;
}

// 查找關(guān)鍵字
if (i < tree->keynum && tree->key[i] == key)
{
*pos = i;
return tree;
}

// tree 為葉子節(jié)點,找不到 key,查找失敗返回
if (tree->isLeaf)
{
return NULL;
}

// 節(jié)點內(nèi)查找失敗,但 tree->key[i - 1]< key < tree->key[i],
// 下一個查找的結(jié)點應(yīng)為 child[i]

// 從磁盤讀取第 i 個孩子的數(shù)據(jù)
disk_read(&tree->child[i]);

// 遞歸地繼續(xù)查找于樹 tree->child[i]
return BTree_recursive_search(tree->child[i], key, pos);
}

B+樹

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

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

  • 非葉子結(jié)點的子樹指針與關(guān)鍵字個數(shù)相同;
  • 非葉子結(jié)點的子樹指針P[i],指向關(guān)鍵字值屬于[K[i], K[i+1])的子樹, B樹是開區(qū)間
  • 為所有葉子結(jié)點增加一個鏈指針;
  • 所有關(guān)鍵字都在葉子結(jié)點出現(xiàn);

如:(M=3)

1

算法思路

B+的搜索與B樹也基本相同,區(qū)別是B+樹只有達(dá)到葉子結(jié)點才命中(B樹可以在 非葉子結(jié)點命中),其性能也等價于在關(guān)鍵字全集做一次二分查找;

  • 所有關(guān)鍵字都出現(xiàn)在葉子結(jié)點的鏈表中(稠密索引),且鏈表中的關(guān)鍵字恰好是有序的;
  • 不可能在非葉子結(jié)點命中;
  • 非葉子結(jié)點相當(dāng)于是葉子結(jié)點的索引(稀疏索引),葉子結(jié)點相當(dāng)于是存儲(關(guān)鍵字)數(shù)據(jù)的數(shù)據(jù)層;
  • 更適合文件索引系統(tǒng);

參考資料

  1. https://www.sohu.com/a/296278527_478315
  2. https://www.cnblogs.com/exzlc/p/12203744.html
  3. 部分配圖來源于網(wǎng)絡(luò)

最近原創(chuàng)推薦

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

十大經(jīng)典排序算法(動態(tài)演示+代碼)

C語言與C++面試知識總結(jié)
數(shù)據(jù)結(jié)構(gòu)之堆棧

一文輕松理解內(nèi)存對齊

一文讀懂C語言與C++動態(tài)內(nèi)存

面試中常見的C語言與C++區(qū)別的問題

數(shù)據(jù)結(jié)構(gòu)之線性表


免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉