飞道的博客

九大查找算法,值得收藏

394人阅读  评论(0)

时间、空间复杂度比较

查找算法 平均时间复杂度 空间复杂度 查找条件
顺序查找 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)    

节跳动50道高频算法

程序员相关的宝贵资料,免费送给大家,点击获取

1 顺序查找

算法思路

对于任意一个序列,从一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。

代码


  
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define keyType int
  4. //2021.05.24
  5. typedef struct
  6. {
  7. keyType key; //查找表中每个数据元素的值
  8. }ElemType;
  9. typedef struct
  10. {
  11. ElemType *elem; //存放查找表中数据元素的数组
  12. int length; //记录查找表中数据的总数量
  13. }SSTable;
  14. //创建查询数据
  15. void Create(SSTable **st,int length)
  16. {
  17. (*st)=(SSTable*) malloc( sizeof(SSTable));
  18. (*st)->length=length;
  19. (*st)->elem =(ElemType*) malloc((length+ 1)* sizeof(ElemType));
  20. printf( "输入表中的数据元素:\n");
  21. //根据查找表中数据元素的总长度,在存储时,从数组下标为 1 的空间开始存储数据
  22. for ( int i= 1; i<=length; i++)
  23. {
  24. scanf( "%d",&((*st)->elem[i].key));
  25. }
  26. }
  27. //顺序查找函数,其中key为要查找的元素
  28. int Search_seq(SSTable *str,keyType key)
  29. {
  30. //str->elem[0].key=key;//将关键字作为一个数据元素存放到查找表的第一个位置,起监视哨的作用
  31. int len = str->length;
  32. //从最后一个数据元素依次遍历,一直遍历到数组下标为0
  33. for( int i= 1; i<len+ 1; i++) //创建数据从数组下标为1开始,查询也从1开始
  34. {
  35. if(str->elem[i].key == key)
  36. {
  37. return i;
  38. }
  39. }
  40. //如果 i=0,说明查找失败;查找成功返回要查找元素key的位置i
  41. return 0;
  42. }
  43. int main()
  44. {
  45. SSTable *str;
  46. int num;
  47. printf( "请输入创建数据元素的个数:\n");
  48. scanf( "%d",&num);
  49. Create(&str, num);
  50. getchar();
  51. printf( "请输入要查找的数据:\n");
  52. int key;
  53. scanf( "%d",&key);
  54. int location=Search_seq(str, key);
  55. if (location== 0) {
  56. printf( "查找失败");
  57. } else{
  58. printf( "要查找的%d的顺序为:%d",key,location);
  59. }
  60. return 0;
  61. }

运行结果

查找成功

查找失败

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 的位置不是整数,需要统一做取整操作。

折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。


   
  1. ——《大话数据结构》

代码


  
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define keyType int
  4. typedef struct
  5. {
  6. keyType key; //查找表中每个数据元素的值
  7. }ElemType;
  8. typedef struct
  9. {
  10. ElemType *elem; //存放查找表中数据元素的数组
  11. int length; //记录查找表中数据的总数量
  12. }SSTable;
  13. //创建查询数据
  14. void Create(SSTable **st,int length)
  15. {
  16. (*st)=(SSTable*) malloc( sizeof(SSTable));
  17. (*st)->length=length;
  18. (*st)->elem =(ElemType*) malloc((length+ 1)* sizeof(ElemType));
  19. printf( "输入表中的数据元素:\n");
  20. //根据查找表中数据元素的总长度,在存储时,从数组下标为 1 的空间开始存储数据
  21. for ( int i= 1; i<=length; i++)
  22. {
  23. scanf( "%d",&((*st)->elem[i].key));
  24. }
  25. }
  26. //折半查找函数 key为要查找的元素
  27. int Search_Bin(SSTable *str,keyType key)
  28. {
  29. int low= 1; //初始状态 low 指针指向第一个关键字
  30. int high=str->length; //high 指向最后一个关键字
  31. int mid;
  32. while (low<=high)
  33. {
  34. mid=(low+high)/ 2; //int 本身为整形,所以,mid 每次为取整的整数
  35. if(str->elem[mid].key==key) //如果 mid 指向的同要查找的相等,返回 mid 所指向的位置
  36. {
  37. return mid;
  38. }
  39. else if(str->elem[mid].key>key) //如果mid指向的关键字较大,则更新 high 指针的位置
  40. {
  41. high=mid -1;
  42. }
  43. //反之,则更新 low 指针的位置
  44. else
  45. {
  46. low=mid+ 1;
  47. }
  48. }
  49. return 0;
  50. }
  51. int main()
  52. {
  53. SSTable *str;
  54. int num;
  55. printf( "请输入创建数据元素的个数:\n");
  56. scanf( "%d",&num);
  57. Create(&str, num);
  58. getchar();
  59. printf( "请输入要查找的数据:\n");
  60. int key;
  61. scanf( "%d",&key);
  62. int location=Search_Bin(str, key);
  63. if (location== 0) {
  64. printf( "没有查找到");
  65. } else{
  66. printf( "要查找的%d的顺序为:%d",key,location);
  67. }
  68. return 0;
  69. }

运行结果

查找成功

没有查找到

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。

说明

  • 插值查找是基于折半查找进行了优化的查找方法。

  • 当表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能要比折半查找要好得多。

代码


  
  1. #include<stdio.h>
  2. int array[ 10] = { 1, 4, 9, 16, 27, 31, 33, 35, 45, 64 };
  3. int InsertionSearch(int data)
  4. {
  5. int low = 0;
  6. int high = 10 - 1;
  7. int mid = -1;
  8. int comparisons = 1;
  9. int index = -1;
  10. while(low <= high)
  11. {
  12. printf( "比较 %d 次\n" , comparisons );
  13. printf( "low : %d, list[%d] = %d\n", low, low, array[low]);
  14. printf( "high : %d, list[%d] = %d\n", high, high, array[high]);
  15. comparisons++;
  16. mid = low + ((( double)(high - low) / ( array[high] - array[low])) * (data - array[low]));
  17. printf( "mid = %d\n",mid);
  18. // 没有找到
  19. if( array[mid] == data)
  20. {
  21. index = mid;
  22. break;
  23. }
  24. else
  25. {
  26. if( array[mid] < data)
  27. {
  28. low = mid + 1;
  29. }
  30. else
  31. {
  32. high = mid - 1;
  33. }
  34. }
  35. }
  36. printf( "比较次数: %d\n", --comparisons);
  37. return index;
  38. }
  39. int main()
  40. {
  41. int location = InsertionSearch( 27); //测试代,查27,可以找到
  42. if(location != -1)
  43. {
  44. printf( "查找元素顺序为: %d\n" ,(location+ 1));
  45. }
  46. else
  47. {
  48. printf( "没有查找到");
  49. }
  50. return 0;
  51. }

运行结果

运行结果

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个,所以可以递归的应用斐波那契查找。

代码


  
  1. #include "stdafx.h"
  2. #include <memory>
  3. #include <iostream>
  4. using namespace std;
  5. const int max_size= 20; //斐波那契数组的长度
  6. /*构造一个斐波那契数组*/
  7. void Fibonacci(int * F)
  8. {
  9. F[ 0]= 0;
  10. F[ 1]= 1;
  11. for( int i= 2;i<max_size;++i)
  12. F[i]=F[i -1]+F[i -2];
  13. }
  14. /*定义斐波那契查找法*/
  15. int FibonacciSearch(int *a, int n, int key) //a为要查找的数组,n为要查找的数组长度,key为要查找的关键字
  16. {
  17. int low= 0;
  18. int high=n -1;
  19. int F[max_size];
  20. Fibonacci(F); //构造一个斐波那契数组F
  21. int k= 0;
  22. while(n>F[k] -1) //计算n位于斐波那契数列的位置
  23. ++k;
  24. int * temp; //将数组a扩展到F[k]-1的长度
  25. temp= new int [F[k] -1];
  26. memcpy(temp,a,n* sizeof( int));
  27. for( int i=n;i<F[k] -1;++i)
  28. temp[i]=a[n -1];
  29. while(low<=high)
  30. {
  31. int mid=low+F[k -1] -1;
  32. if(key<temp[mid])
  33. {
  34. high=mid -1;
  35. k-= 1;
  36. }
  37. else if(key>temp[mid])
  38. {
  39. low=mid+ 1;
  40. k-= 2;
  41. }
  42. else
  43. {
  44. if(mid<n)
  45. return mid; //若相等则说明mid即为查找到的位置
  46. else
  47. return n -1; //若mid>=n则说明是扩展的数值,返回n-1
  48. }
  49. }
  50. delete [] temp;
  51. return 0;
  52. }
  53. int main()
  54. {
  55. int a[] = { 0, 1, 4, 35, 47, 53, 62, 78, 88, 99};
  56. int key= 47;
  57. int index=FibonacciSearch(a, sizeof(a)/ sizeof( int),key);
  58. if(index == 0)
  59. {
  60. cout<< "没有找到"<<key;
  61. }
  62. else
  63. {
  64. cout<<key<< " 的位置是:"<<index;
  65. }
  66. return 0;
  67. }

运行结果

47的位置为5

5 哈希查找

哈希表

我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素"分类",然后将这个元素存储在相应"类"所对应的地方。但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了"冲突",换句话说,就是把不同的元素分在了相同的"类"之中。后面我们将看到一种解决"冲突"的简便做法。

"直接定址"与"解决冲突"是哈希表的两大特点。

哈希函数

规则:通过某种转换关系,使关键字适度的分散到指定大小的的顺序结构中,越分散,则以后查找的时间复杂度越小,空间复杂度越高。

算法思路

如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。

  1. 用给定的哈希函数构造哈希表;

  2. 根据选择的冲突处理方法(常见方法:拉链法和线性探测法)解决地址冲突;

  3. 在哈希表的基础上执行哈希查找;

代码


  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define SUCCESS 1
  4. #define UNSUCCESS 0
  5. #define OVERFLOW -1
  6. #define OK 1
  7. #define ERROR -1
  8. #define MAXNUM 9999 // 用于初始化哈希表的记录 key
  9. typedef int Status;
  10. typedef int KeyType;
  11. // 哈希表中的记录类型
  12. typedef struct
  13. {
  14. KeyType key;
  15. }RcdType;
  16. // 哈希表类型
  17. typedef struct
  18. {
  19. RcdType *rcd;
  20. int size;
  21. int count;
  22. int *tag;
  23. }HashTable;
  24. // 哈希表每次重建增长后的大小
  25. int hashsize[] = { 11, 31, 61, 127, 251, 503 };
  26. int index = 0;
  27. // 初始哈希表
  28. Status InitHashTable(HashTable &H, int size)
  29. {
  30. int i;
  31. H.rcd = (RcdType *) malloc( sizeof(RcdType)*size);
  32. H.tag = ( int *) malloc( sizeof( int)*size);
  33. if ( NULL == H.rcd || NULL == H.tag) return OVERFLOW;
  34. KeyType maxNum = MAXNUM;
  35. for (i = 0; i < size; i++)
  36. {
  37. H.tag[i] = 0;
  38. H.rcd[i].key = maxNum;
  39. }
  40. H.size = size;
  41. H.count = 0;
  42. return OK;
  43. }
  44. // 哈希函数:除留余数法
  45. int Hash(KeyType key, int m)
  46. {
  47. return ( 3 * key) % m;
  48. }
  49. // 处理哈希冲突:线性探测
  50. void collision(int &p, int m)
  51. {
  52. p = (p + 1) % m;
  53. }
  54. // 在哈希表中查询
  55. Status SearchHash(HashTable H, KeyType key, int &p, int &c)
  56. {
  57. p = Hash(key, H.size);
  58. int h = p;
  59. c = 0;
  60. while (( 1 == H.tag[p] && H.rcd[p].key != key) || -1 == H.tag[p])
  61. {
  62. collision(p, H.size); c++;
  63. }
  64. if ( 1 == H.tag[p] && key == H.rcd[p].key) return SUCCESS;
  65. else return UNSUCCESS;
  66. }
  67. //打印哈希表
  68. void printHash(HashTable H)
  69. {
  70. int i;
  71. printf( "key : ");
  72. for (i = 0; i < H.size; i++)
  73. printf( "%3d ", H.rcd[i].key);
  74. printf( "\n");
  75. printf( "tag : ");
  76. for (i = 0; i < H.size; i++)
  77. printf( "%3d ", H.tag[i]);
  78. printf( "\n\n");
  79. }
  80. // 函数声明:插入哈希表
  81. Status InsertHash(HashTable &H, KeyType key);
  82. // 重建哈希表
  83. Status recreateHash(HashTable &H)
  84. {
  85. RcdType *orcd;
  86. int *otag, osize, i;
  87. orcd = H.rcd;
  88. otag = H.tag;
  89. osize = H.size;
  90. InitHashTable(H, hashsize[index++]);
  91. //把所有元素,按照新哈希函数放到新表中
  92. for (i = 0; i < osize; i++)
  93. {
  94. if ( 1 == otag[i])
  95. {
  96. InsertHash(H, orcd[i].key);
  97. }
  98. }
  99. return OK;
  100. }
  101. // 插入哈希表
  102. Status InsertHash(HashTable &H, KeyType key)
  103. {
  104. int p, c;
  105. if (UNSUCCESS == SearchHash(H, key, p, c))
  106. { //没有相同key
  107. if (c* 1.0 / H.size < 0.5)
  108. { //冲突次数未达到上线
  109. //插入代码
  110. H.rcd[p].key = key;
  111. H.tag[p] = 1;
  112. H.count++;
  113. return SUCCESS;
  114. }
  115. else recreateHash(H); //重构哈希表
  116. }
  117. return UNSUCCESS;
  118. }
  119. // 删除哈希表
  120. Status DeleteHash(HashTable &H, KeyType key)
  121. {
  122. int p, c;
  123. if (SUCCESS == SearchHash(H, key, p, c))
  124. {
  125. //删除代码
  126. H.tag[p] = -1;
  127. H.count--;
  128. return SUCCESS;
  129. }
  130. else return UNSUCCESS;
  131. }
  132. int main()
  133. {
  134. printf( "-----哈希表-----\n");
  135. HashTable H;
  136. int i;
  137. int size = 11;
  138. KeyType array[ 8] = { 22, 41, 53, 46, 30, 13, 12, 67 };
  139. KeyType key;
  140. //初始化哈希表
  141. printf( "初始化哈希表\n");
  142. if (SUCCESS == InitHashTable(H, hashsize[index++])) printf( "初始化成功\n");
  143. //插入哈希表
  144. printf( "插入哈希表\n");
  145. for (i = 0; i <= 7; i++)
  146. {
  147. key = array[i];
  148. InsertHash(H, key);
  149. printHash(H);
  150. }
  151. //删除哈希表
  152. printf( "删除哈希表中key为12的元素\n");
  153. int p, c;
  154. if (SUCCESS == DeleteHash(H, 12))
  155. {
  156. printf( "删除成功,此时哈希表为:\n");
  157. printHash(H);
  158. }
  159. //查询哈希表
  160. printf( "查询哈希表中key为67的元素\n");
  161. if (SUCCESS == SearchHash(H, 67, p, c)) printf( "查询成功\n");
  162. //再次插入,测试哈希表的重建
  163. printf( "再次插入,测试哈希表的重建:\n");
  164. KeyType array1[ 8] = { 27, 47, 57, 47, 37, 17, 93, 67 };
  165. for (i = 0; i <= 7; i++)
  166. {
  167. key = array1[i];
  168. InsertHash(H, key);
  169. printHash(H);
  170. }
  171. getchar();
  172. return 0;
  173. }

6 二叉树查找

二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。

算法思路

  1. 若b是空树,则搜索失败:

  2. 若x等于b的根节点的数据域之值,则查找成功:

  3. 若x小于b的根节点的数据域之值,则搜索左子树:

  4. 查找右子树。

代码

递归和非递归


  
  1. //非递归查找算法
  2. BSTNode *BST_Search(BiTree T,ElemType key,BSTNode *&p)
  3. {
  4. //查找函数返回指向关键字值为key的结点指针,不存在则返回NULL
  5. p= NULL;
  6. while(T!= NULL&&key!=T->data)
  7. {
  8. p=T; //指向被查找结点的双亲
  9. if(key<T->data)
  10. T=T->lchild; //查找左子树
  11. else
  12. T=T->rchild; //查找右子树
  13. }
  14. return T;
  15. }
  16. //递归算法
  17. Status Search_BST(BiTree T, int key, BiTree f, BiTree *p)
  18. {
  19. //查找BST中是否存在key,f指向T双亲,其初始值为NULL
  20. //若查找成功,指针p指向数据元素结点,返回true;
  21. //若失败,p指向查找路径上访问的最后一个结点并返回false
  22. if(!T)
  23. {
  24. *p=f;
  25. return false;
  26. }
  27. else if(key==T->data)
  28. { //查找成功
  29. *p=T;
  30. return true;
  31. }
  32. else if(key<T->data)
  33. return Search_BST(T->lchild,key,T,p); //递归查找左子树
  34. else
  35. return Search_BST(T->rchild,key,T,p); //递归查找右子树
  36. }

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 树中查找键为B的节点

代码


  
  1. two_three *search23(two_three *t, element x)
  2. {
  3. while(t)
  4. {
  5. if (x < t->data_l)
  6. {
  7. t = t->left_child;
  8. }
  9. else if (x > t->data_l && x < t->data_r)
  10. {
  11. t = t->middle_child;
  12. }
  13. else if (x > t->data_r)
  14. {
  15. t = t->right_child;
  16. }
  17. else
  18. {
  19. return t;
  20. }
  21. }
  22. return NULL;
  23. }

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树转红黑树

为什么使用红黑树

  • 红黑树是一种平衡树,他复杂的定义和规则都是为了保证树的平衡性。如果树不保证他的平衡性就是下图:很显然这就变成一个链表了。

  • 保证平衡性的最大的目的就是降低树的高度,因为树的查找性能取决于树的高度。所以树的高度越低搜索的效率越高!

  • 这也是为什么存在二叉树、搜索二叉树等,各类树的目的。

红黑树性质

  • 每个节点要么是黑色,要么是红色。

  • 根节点是黑色。

  • 每个叶子节点(NIL)是黑色。

  • 每个红色结点的两个子结点一定都是黑色。

  • 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

算法思路

红黑树的思想就是对2-3查找树进行编码,尤其是对2-3查找树中的3-nodes节点添加额外的信息。红黑树中将节点之间的链接分为两种不同类型,红色链接,他用来链接两个2-nodes节点来表示一个3-nodes节点。黑色链接用来链接普通的2-3节点。特别的,使用红色链接的两个2-nodes来表示一个3-nodes节点,并且向左倾斜,即一个2-node是另一个2-node的左子节点。这种做法的好处是查找的时候不用做任何修改,和普通的二叉查找树相同

代码


  
  1. #define BLACK 1
  2. #define RED 0
  3. #include <iostream>
  4. using namespace std;
  5. class bst
  6. {
  7. private:
  8. struct Node
  9. {
  10. int value;
  11. bool color;
  12. Node *leftTree, *rightTree, *parent;
  13. Node() : value( 0), color(RED), leftTree( NULL), rightTree( NULL), parent( NULL) { }
  14. Node* grandparent()
  15. {
  16. if (parent == NULL)
  17. {
  18. return NULL;
  19. }
  20. return parent->parent;
  21. }
  22. Node* uncle()
  23. {
  24. if (grandparent() == NULL)
  25. {
  26. return NULL;
  27. }
  28. if (parent == grandparent()->rightTree)
  29. return grandparent()->leftTree;
  30. else
  31. return grandparent()->rightTree;
  32. }
  33. Node* sibling()
  34. {
  35. if (parent->leftTree == this)
  36. return parent->rightTree;
  37. else
  38. return parent->leftTree;
  39. }
  40. };
  41. void rotate_right(Node *p)
  42. {
  43. Node *gp = p->grandparent();
  44. Node *fa = p->parent;
  45. Node *y = p->rightTree;
  46. fa->leftTree = y;
  47. if (y != NIL)
  48. y->parent = fa;
  49. p->rightTree = fa;
  50. fa->parent = p;
  51. if (root == fa)
  52. root = p;
  53. p->parent = gp;
  54. if (gp != NULL)
  55. {
  56. if (gp->leftTree == fa)
  57. gp->leftTree = p;
  58. else
  59. gp->rightTree = p;
  60. }
  61. }
  62. void rotate_left(Node *p)
  63. {
  64. if (p->parent == NULL)
  65. {
  66. root = p;
  67. return;
  68. }
  69. Node *gp = p->grandparent();
  70. Node *fa = p->parent;
  71. Node *y = p->leftTree;
  72. fa->rightTree = y;
  73. if (y != NIL)
  74. y->parent = fa;
  75. p->leftTree = fa;
  76. fa->parent = p;
  77. if (root == fa)
  78. root = p;
  79. p->parent = gp;
  80. if (gp != NULL)
  81. {
  82. if (gp->leftTree == fa)
  83. gp->leftTree = p;
  84. else
  85. gp->rightTree = p;
  86. }
  87. }
  88. void inorder(Node *p)
  89. {
  90. if (p == NIL)
  91. return;
  92. if (p->leftTree)
  93. inorder(p->leftTree);
  94. cout << p->value << " ";
  95. if (p->rightTree)
  96. inorder(p->rightTree);
  97. }
  98. string outputColor(bool color)
  99. {
  100. return color ? "BLACK" : "RED";
  101. }
  102. Node* getSmallestChild(Node *p)
  103. {
  104. if (p->leftTree == NIL)
  105. return p;
  106. return getSmallestChild(p->leftTree);
  107. }
  108. bool delete_child(Node *p, int data)
  109. {
  110. if (p->value > data)
  111. {
  112. if (p->leftTree == NIL)
  113. {
  114. return false;
  115. }
  116. return delete_child(p->leftTree, data);
  117. }
  118. else if (p->value < data)
  119. {
  120. if (p->rightTree == NIL)
  121. {
  122. return false;
  123. }
  124. return delete_child(p->rightTree, data);
  125. }
  126. else if (p->value == data)
  127. {
  128. if (p->rightTree == NIL)
  129. {
  130. delete_one_child(p);
  131. return true;
  132. }
  133. Node *smallest = getSmallestChild(p->rightTree);
  134. swap(p->value, smallest->value);
  135. delete_one_child(smallest);
  136. return true;
  137. }
  138. else
  139. {
  140. return false;
  141. }
  142. }
  143. void delete_one_child(Node *p)
  144. {
  145. Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree;
  146. if (p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL)
  147. {
  148. p = NULL;
  149. root = p;
  150. return;
  151. }
  152. if (p->parent == NULL)
  153. {
  154. delete p;
  155. child->parent = NULL;
  156. root = child;
  157. root->color = BLACK;
  158. return;
  159. }
  160. if (p->parent->leftTree == p)
  161. {
  162. p->parent->leftTree = child;
  163. }
  164. else
  165. {
  166. p->parent->rightTree = child;
  167. }
  168. child->parent = p->parent;
  169. if (p->color == BLACK)
  170. {
  171. if (child->color == RED)
  172. {
  173. child->color = BLACK;
  174. }
  175. else
  176. delete_case(child);
  177. }
  178. delete p;
  179. }
  180. void delete_case(Node *p)
  181. {
  182. if (p->parent == NULL)
  183. {
  184. p->color = BLACK;
  185. return;
  186. }
  187. if (p->sibling()->color == RED)
  188. {
  189. p->parent->color = RED;
  190. p->sibling()->color = BLACK;
  191. if (p == p->parent->leftTree)
  192. rotate_left(p->sibling());
  193. else
  194. rotate_right(p->sibling());
  195. }
  196. if (p->parent->color == BLACK && p->sibling()->color == BLACK
  197. && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK)
  198. {
  199. p->sibling()->color = RED;
  200. delete_case(p->parent);
  201. }
  202. else if (p->parent->color == RED && p->sibling()->color == BLACK
  203. && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK)
  204. {
  205. p->sibling()->color = RED;
  206. p->parent->color = BLACK;
  207. }
  208. else
  209. {
  210. if (p->sibling()->color == BLACK)
  211. {
  212. if (p == p->parent->leftTree && p->sibling()->leftTree->color == RED
  213. && p->sibling()->rightTree->color == BLACK)
  214. {
  215. p->sibling()->color = RED;
  216. p->sibling()->leftTree->color = BLACK;
  217. rotate_right(p->sibling()->leftTree);
  218. }
  219. else if (p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK
  220. && p->sibling()->rightTree->color == RED)
  221. {
  222. p->sibling()->color = RED;
  223. p->sibling()->rightTree->color = BLACK;
  224. rotate_left(p->sibling()->rightTree);
  225. }
  226. }
  227. p->sibling()->color = p->parent->color;
  228. p->parent->color = BLACK;
  229. if (p == p->parent->leftTree)
  230. {
  231. p->sibling()->rightTree->color = BLACK;
  232. rotate_left(p->sibling());
  233. }
  234. else
  235. {
  236. p->sibling()->leftTree->color = BLACK;
  237. rotate_right(p->sibling());
  238. }
  239. }
  240. }
  241. void insert(Node *p, int data)
  242. {
  243. if (p->value >= data)
  244. {
  245. if (p->leftTree != NIL)
  246. insert(p->leftTree, data);
  247. else
  248. {
  249. Node *tmp = new Node();
  250. tmp->value = data;
  251. tmp->leftTree = tmp->rightTree = NIL;
  252. tmp->parent = p;
  253. p->leftTree = tmp;
  254. insert_case(tmp);
  255. }
  256. }
  257. else
  258. {
  259. if (p->rightTree != NIL)
  260. insert(p->rightTree, data);
  261. else
  262. {
  263. Node *tmp = new Node();
  264. tmp->value = data;
  265. tmp->leftTree = tmp->rightTree = NIL;
  266. tmp->parent = p;
  267. p->rightTree = tmp;
  268. insert_case(tmp);
  269. }
  270. }
  271. }
  272. void insert_case(Node *p)
  273. {
  274. if (p->parent == NULL)
  275. {
  276. root = p;
  277. p->color = BLACK;
  278. return;
  279. }
  280. if (p->parent->color == RED)
  281. {
  282. if (p->uncle()->color == RED)
  283. {
  284. p->parent->color = p->uncle()->color = BLACK;
  285. p->grandparent()->color = RED;
  286. insert_case(p->grandparent());
  287. }
  288. else
  289. {
  290. if (p->parent->rightTree == p && p->grandparent()->leftTree == p->parent)
  291. {
  292. rotate_left(p);
  293. rotate_right(p);
  294. p->color = BLACK;
  295. p->leftTree->color = p->rightTree->color = RED;
  296. }
  297. else if (p->parent->leftTree == p && p->grandparent()->rightTree == p->parent)
  298. {
  299. rotate_right(p);
  300. rotate_left(p);
  301. p->color = BLACK;
  302. p->leftTree->color = p->rightTree->color = RED;
  303. }
  304. else if (p->parent->leftTree == p && p->grandparent()->leftTree == p->parent)
  305. {
  306. p->parent->color = BLACK;
  307. p->grandparent()->color = RED;
  308. rotate_right(p->parent);
  309. }
  310. else if (p->parent->rightTree == p && p->grandparent()->rightTree == p->parent)
  311. {
  312. p->parent->color = BLACK;
  313. p->grandparent()->color = RED;
  314. rotate_left(p->parent);
  315. }
  316. }
  317. }
  318. }
  319. void DeleteTree(Node *p)
  320. {
  321. if (!p || p == NIL)
  322. {
  323. return;
  324. }
  325. DeleteTree(p->leftTree);
  326. DeleteTree(p->rightTree);
  327. delete p;
  328. }
  329. public:
  330. bst()
  331. {
  332. NIL = new Node();
  333. NIL->color = BLACK;
  334. root = NULL;
  335. }
  336. ~bst()
  337. {
  338. if (root)
  339. DeleteTree(root);
  340. delete NIL;
  341. }
  342. void inorder()
  343. {
  344. if (root == NULL)
  345. return;
  346. inorder(root);
  347. cout << endl;
  348. }
  349. void insert(int x)
  350. {
  351. if (root == NULL)
  352. {
  353. root = new Node();
  354. root->color = BLACK;
  355. root->leftTree = root->rightTree = NIL;
  356. root->value = x;
  357. }
  358. else
  359. {
  360. insert(root, x);
  361. }
  362. }
  363. bool delete_value(int data)
  364. {
  365. return delete_child(root, data);
  366. }
  367. private:
  368. Node *root, *NIL;
  369. };
  370. int main()
  371. {
  372. cout << "---【红黑树】---" << endl;
  373. // 创建红黑树
  374. bst tree;
  375. // 插入元素
  376. tree.insert( 2);
  377. tree.insert( 9);
  378. tree.insert( -10);
  379. tree.insert( 0);
  380. tree.insert( 33);
  381. tree.insert( -19);
  382. // 顺序打印红黑树
  383. cout << "插入元素后的红黑树:" << endl;
  384. tree.inorder();
  385. // 删除元素
  386. tree.delete_value( 2);
  387. // 顺序打印红黑树
  388. cout << "删除元素 2 后的红黑树:" << endl;
  389. tree.inorder();
  390. // 析构
  391. tree.~bst();
  392. getchar();
  393. return 0;
  394. }

9 B树/B+树

在计算机科学中,B树(B-tree)是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和删除的数据结构。B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树。与自平衡二叉查找树不同,B-树为系统最优化大块数据的读和写操作。B-tree算法减少定位记录时所经历的中间过程,从而加快存取速度。普遍运用在数据库和文件系统。


   
  1. ——维基百科

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)
  2. {
  3. int i = 0;
  4. while (i < tree->keynum && key > tree->key[i])
  5. {
  6. ++i;
  7. }
  8. // 查找关键字
  9. if (i < tree->keynum && tree->key[i] == key)
  10. {
  11. *pos = i;
  12. return tree;
  13. }
  14. // tree 为叶子节点,找不到 key,查找失败返回
  15. if (tree->isLeaf)
  16. {
  17. return NULL;
  18. }
  19. // 节点内查找失败,但 tree->key[i - 1]< key < tree->key[i],
  20. // 下一个查找的结点应为 child[i]
  21. // 从磁盘读取第 i 个孩子的数据
  22. disk_read(&tree->child[i]);
  23. // 递归地继续查找于树 tree->child[i]
  24. return BTree_recursive_search(tree->child[i], key, pos);
  25. }

B+树

B+树是B树的变体,也是一种多路搜索树:

其定义基本与B-树同,除了:

  • 非叶子结点的子树指针与关键字个数相同;

  • 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树, B树是开区间

  • 为所有叶子结点增加一个链指针;

  • 所有关键字都在叶子结点出现;

如:(M=3)

1

算法思路

B+的搜索与B树也基本相同,区别是B+树只有达到叶子结点才命中(B树可以在 非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;https://blog.csdn.net/u013171283/article/details/82951039

  • 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;

  • 不可能在非叶子结点命中;

  • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;

  • 更适合文件索引系统;

LeetCode101题解

参考资料

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

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

  3. 部分配图来源于网络


转载:https://blog.csdn.net/weixin_41055260/article/details/117219407
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场