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

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




時間、空間復雜度比較

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

//創(chuàng)建查詢數據
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;//將關鍵字作為一個數據元素存放到查找表的第一個位置,起監(jiān)視哨的作用
int len = str->length;
//從最后一個數據元素依次遍歷,一直遍歷到數組下標為0
for(int i=1; i<len+1; i++) //創(chuàng)建數據從數組下標為1開始,查詢也從1開始
{
if(str->elem[i].key == key)
{
return i;
}
}
//如果 i=0,說明查找失敗;查找成功返回要查找元素key的位置i
return 0;
}

int main()
{
SSTable *str;
int num;
printf("請輸入創(chuàng)建數據元素的個數:\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,則結束查找;否則,向下繼續(xù)。

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

說明

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

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

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

                         ——《大話數據結構》

代碼

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

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

//創(chuàng)建查詢數據
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;//初始狀態(tài) 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("請輸入創(chuàng)建數據元素的個數:\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,則結束查找;否則,向下繼續(xù)。

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

說明

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

代碼

#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

5 哈希查找

哈希表

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

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

哈希函數

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

算法思路

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

  1. 用給定的哈希函數構造哈希表;
  2. 根據選擇的沖突處理方法(常見方法:拉鏈法和線性探測法)解決地址沖突;
  3. 在哈希表的基礎上執(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;
}

// 哈希函數:除留余數法
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 二叉樹查找

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

算法思路

  1. 若b是空樹,則搜索失敗:
  2. 若x等于b的根節(jié)點的數據域之值,則查找成功:
  3. 若x小于b的根節(jié)點的數據域之值,則搜索左子樹:
  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樹運行每個節(jié)點保存1個或者兩個的值。對于普通的2節(jié)點(2-node),他保存1個key和左右兩個自己點。對應3節(jié)點(3-node),保存兩個Key,2-3查找樹的定義如下:

  1. 要么為空,要么:

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

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

算法思路:

要判斷一個鍵是否在樹中,我們先將它和根結點中的鍵比較。如果它和其中的任何一個相等,查找命中。否則我們就根據比較的結果找到指向相應區(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,從而保證了最壞情況下的時間復雜度。但是2-3樹實現(xiàn)起來比較復雜,于是就有了一種簡單實現(xiàn)2-3樹的數據結構,即紅黑樹(Red-Black Tree)。

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

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

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

2-3樹轉紅黑樹

為什么使用紅黑樹

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

紅黑樹性質

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

算法思路

紅黑樹的思想就是對2-3查找樹進行編碼,尤其是對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();

// 析構
tree.~bst();

getchar();
return 0;
}

9 B樹/B+樹

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

                             ——維基百科

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

  • 定義任意非葉子結點最多只有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)

算法思路

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

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

代碼

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

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

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

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

B+樹

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

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

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

如:(M=3)

1

算法思路

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

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

參考資料

  1. https://www.sohu.com/a/296278527_478315
  2. https://www.cnblogs.com/exzlc/p/12203744.html
  3. 部分配圖來源于網絡

最近原創(chuàng)推薦

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

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

C語言與C++面試知識總結
數據結構之堆棧

一文輕松理解內存對齊

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

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

數據結構之線性表


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

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