首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >数据结构与算法(十六)——静态查找&动态查找

数据结构与算法(十六)——静态查找&动态查找

作者头像
拉维
发布于 2022-06-15 04:31:56
发布于 2022-06-15 04:31:56
2.2K04
代码可运行
举报
文章被收录于专栏:iOS小生活iOS小生活
运行总次数:4
代码可运行

一、静态查找

静态查找指的是只对表执行查找操作,并不会动态添加元素。静态查找主要有顺序查找二分查找两大类,接下来我们依次讲解一下。

1,顺序查找

顺序查找指的是线性表中的元素查找,按照元素是否有序,可以分为【无序线性表的顺序查找】和【有序线性表的顺序查找】。接下来我所要介绍的两种算法都是针对的是无序线性表的顺序查找。

(1)原始算法

对于一般的无序线性表,其顺序查找的思想如下:

从线性表的一端开始,逐个检查关键字是否满足条件。若查到某个元素的关键字满足给定条件,则查找成功,并返回该元素在线性表中的位置;若已经找到线性表的另一端了,但是还是没有查找到符合给定条件的元素,则返回查找失败的信息

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
 array是待搜索的数组
 arrayCount是数组中元素个数
 searchKey是搜索字段
 返回值是匹配到的array中的元素的下标,这里使用-1来表示没有匹配到值
 */
int sequentialSearch(int *array, int arrayCount, int searchKey) {
  for (int i = 0; i < arrayCount; i++) {
    if (array[i] == searchKey) {
      return i;
    }
  }

  return -1;
}

(2)优化算法——哨兵顺序查找

在上面的顺序查找原始算法中,我们可以看到,每一层遍历实际上都有俩判断:数组越界的判断、条件匹配的判断。接下来,我们通过设置一个哨兵位来减少判断,进而提高程序效率。

具体的做法如下:

在待搜索的数组中设置一个哨兵位,一般设置第0位为哨兵位,并将该哨兵位的值设置为搜索条件值。

然后从数组的最后一个位置开始循环遍历,遍历之前需要新建一个变量来记录当前循环遍历到的位置下标index,循环继续的条件是没有找到指定的元素,在每一次循环遍历体中都令index减1。

这里并不需要进行数组越界的判断,因为在0号哨兵位肯定能够匹配得到,循环也就一定能够跳出。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
 array是待搜索的数组,这个数组中的0号位是哨兵位
 arrayCount是数组中除了哨兵位之外的元素的个数
 searchKey是搜索字段
 返回值是匹配到的array中的元素的下标,这里使用哨兵位来表示没有匹配到值
 */
int sentrySequentialSearch(int *array, int arrayCount, int searchKey) {
  array[0] = searchKey; // 将搜索字段设置为哨兵的值

  // 从数组的最后一个元素进行倒序遍历
  int index = arrayCount;
  while (array[index] != searchKey) {
    index--;
  }

  return index;
}

上面我们介绍了顺序查找以及哨兵顺序查找,他们都是针对的无序线性表,线性表中的元素是随机分布的。现在我们可以考虑一下,线性表中的元素被搜索的概率是一样的吗?肯定不是的吧。那么既然一个线性表中的各个元素被搜索的概率是不一样的,我如果事先按照搜索频率对表中的元素进行排序,那么在遍历查找的前期就更有可能找到,这样将会大大提高搜索的效率

(3)有序线性表的顺序查找

我前面说的都是针对无序线性表,那么如果是有序线性表的话,其实还可以在上边的算法基础上进一步进行优化。

如果在查找之前就已经知道了表中的数据是有序的,那么其实就不必非得在比较到表的另外一端的时候才能确定查找失败,而是在中间就可以判断出来(下面会做详细解释),进而减少线性表查找失败的平均查找长度。

如果表L是按照关键字从小到大排列的顺序表,现在从前往后查找关键字key,当查找到第i个元素的时候,发现第i个元素对应的关键字小于key,而第i+1个元素对应的关键字大于key,此时就可以返回查找失败的信息了,而不再需要继续往前查找了,因为第i个元素之后的关键字均大于key,所以表中是不存在关键字key的元素的

2,折半查找/二分查找

在前面介绍的顺序查找算法章节的最后,我介绍了有序线性表的顺序查找,可以减少查找失败的平均查找长度。即便如此,其实在有序顺序表中查找也是不会采取该方案的。接下来我继续来介绍一种更为高效的有序顺序表的搜索方案——二分查找法。

(1)原始算法

基本思路如下:

先找到有序顺序表中的中间元素middle,然后将其与搜索字段searchKey进行比较,这样就可以初步判断搜索的范围。既然搜索范围已经确定了,那么该搜索范围以外的其他元素就都可以不用再继续查找了(这样相较于顺序查找就可以省略一半的元素不用查找了,这就是效率啊!!!!)。然后按照前述步骤反复搜寻。

因为二分查找每一次查找都可以缩减一半的查找范围,因此二分查找的时间复杂度是O(log2(N)),而顺序查找的时间复杂度是O(log(N))。举个例子,比如某顺序表中一共有2^32个元素,那么采用二分查找法的话,最差的情况是查找32次就可以找到对应元素;但是如果是采用顺序查找法,最坏的情况是要查找2^32=4294967296次!!!可见,二分查找法的效率是非常高的~

一定要注意哦,二分查找的前提是:顺序表必须是有序的!

代码解析

① 构建一个有序顺序表,这里使用数组array

② 找到顺序表中的中间元素下标middleIndex,那么如何来找到这个中间元素呢?思路如下:通过对数组元素下标的计算来求得中间元素(中间元素下表middleIndex = (数组中最小边界下标lowIndex + 最大边界下标highIndex) / 2)

③ 取出中间元素array[middleIndex],将其与搜索字段searchKey进行比较。如果array[middleIndex] > searchKey,那么说明待搜索元素searchKey是在中间元素的左侧(假设左小右大),此时就可以调整最大边界下标highIndex为(middleIndex - 1);如果array[middleIndex] < searchKey,那么说明待搜索元素searchKey是在中间元素的右侧(假设左小右大),此时就可以调整最小边界下标lowIndex为(middleIndex + 1);如果array[middleIndex] == searchKey,那么就直接返回middleIndex。

④ 然后就是重复执行如下操作:找到中间元素array[middleIndex],比较array[middleIndex]和搜索字段searchKey的大小,然后更新searchKey新的范围,直到array[middleIndex] == searchKey(即找到searchKey)为止。那么要重复执行,就势必会使用到循环,那么循环结束的条件是什么呢?实际上,在上述循环执行过程中,lowIndex和highIndex会越来越靠近,甚至会指向同一处,在这个过程中,lowIndex会始终在highIndex的左边(即lowIndex < highIndex)。如果一直找不到指定的元素的话,俩下标必然会相互交错,即lowIndex > highIndex,此时循环结束。所以循环结束的条件就是lowIndex > highIndex。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
 二分查找的前提是:数组array中的元素是有序的,本例中假设其递增。
 */
int binarySearch(int *array, int arrayCount, int searchKey) {
  int lowIndex = 0;
  int highIndex = arrayCount - 1;
  int middleIndex;

  while (lowIndex < highIndex) {
    middleIndex = (lowIndex + highIndex) / 2;
    if (searchKey < array[middleIndex]) {
      highIndex = middleIndex - 1;
    } else if (searchKey > array[middleIndex]) {
      lowIndex = middleIndex + 1;
    } else {
      return  middleIndex;
    }
  }

  // 没有找到的话就返回-1
  return -1;
}

(2)优化算法——插值查找

在上面的二分查找中,中间元素是这样确定的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
middleIndex = (lowIndex + highIndex) / 2;

实际上,上述代码也可以变化一种方式,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 middleIndex = lowIndex + (highIndex - lowIndex) / 2;

可以看到,这里取的就是1/2的位置

如果有序线性表的数据量比较大,并且数据的分布比较均匀,那么其实这里的1/2数值的取值是可以优化的。我们可以将这里的1/2改为自适应,那么根据什么自适应呢?我们可以根据待查找元素在有序线性表中的所处位置来确定这个比例值,使得中间元素值array[middleIndex]的变化能够更靠近待查找元素searchKey,进而间接减少比较的次数。也就是说,我们是通过如下代码来确定中间元素的下标:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
middleIndex = lowIndex + ((searchKey - array[lowIndex]) / (array[highIndex] - array[lowIndex])) * (highIndex - lowIndex);

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
 插值查找
 */
int intepolationSearch(int *array, int arrayCount, int searchKey) {
  int lowIndex = 0;
  int highIndex = arrayCount - 1;
  int middleIndex;

  while (lowIndex < highIndex) {
    middleIndex = lowIndex + ((searchKey - array[lowIndex]) / (array[highIndex] - array[lowIndex])) * (highIndex - lowIndex);

    if (searchKey > array[middleIndex]) {
      lowIndex = middleIndex + 1;
    } else if (searchKey < array[middleIndex]) {
      highIndex = middleIndex - 1;
    } else {
      return  middleIndex;
    }
  }

  return -1;
} 

一定要注意哦,只有当查找有序线性表中的元素,并且线性表中的元素分布比较均匀的时候,插值排序才会比二分排序效率高;如果有序线性表的元素分配是不均匀的,那么插值排序的效率是不一定会比二分排序效率高的

(3)斐波那契查找

上面的差值搜索,是对元素均匀分布的有序线性表的二分查找的优化;那么,如果在有序线性表中,元素的分布是不均匀的,那么如何对其二分查找进行优化呢?答案是使用斐波那契黄金分割比例。我在《数据结构与算法(六)——栈结构》中简单介绍过斐波那契数列的求解,这里只是简单介绍下斐波那契的定义,具体求解不再赘述:

简而言之,斐波那契数列的特点就是:从第三项开始,每一项都等于它前面两项之和。我们既然知道了这个特点,那么就可以利用这个特点来做区间分割:将一个长度为F(n)的数组分为左右两段,左边一段长度是F(n-1),右边一段长度是F(n-2),如下图所示:

斐波那契搜索算法与二分查找、插值查找的基本思路是一致的,其中最重要的区别就是中间值的确定,斐波那契查找算法的中间值的计算方式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 middleIndex = lowIndex + F(n-1) - 1

斐波那契查找算法的基本思想如下:

首先,在斐波那契数列中找到第一个大于等于有序线性表中元素个数的数F(n)。

然后将原有序线性表的长度拓展为F(n),如果需要补充元素的话,则将下标arrayCount及其之后的所有元素都补充为array[arrayCount-1],一直到新数组的长度为F(n)为止。

对原来的有序线性表拓展完成之后,就进行斐波那契分割,也就是说,将F(n)个元素分割成前半部分F(n-1)个元素和后半部分F(n-2)个元素。

随后我们找出所要查找的元素在哪一部分,然后更改边界值。

循环上述步骤,直至找到对应元素或者所有元素循环完毕为止。

前面我介绍了斐波那契查找的基本思想,根据该基本思想,斐波那契查找的详细步骤可以分为如下几步:

① 构建一个斐波那契数列F(100)

② 找到斐波那契数列中大于等于arrayCount的第一个元素F(n)

③ 有必要的话,对原来的有序线性表array进行扩容,使其元素个数等于F(n)

④ 根据斐波那契特点对扩容后的有序线性表进行分割,确定查找的中间元素底标middleIndex = lowIndex + F(n-1) - 1。这里之所以减1,是因为数组的下标是从0开始的。

⑤ 获取到中间元素array(middleIndex),并将其与搜索值searchKey进行比较。

a. 如果array(middleIndex) > searchKey,即搜索值在中间元素的左侧,由于左区间的长度是F(n-1),所以需要更新n = n-1,并且更新highIndex = middlle - 1,然后再次执行④⑤步。

b. 如果array(middleIndex) < searchKey,即搜索值在中间元素的右侧,由于右区间的长度是F(n-2),所以需要更新n = n-2,并且更新lowIndex = middlle + 1,然后再次执行④⑤步。

c. 如果array(middleIndex) < searchKey,则说明找到了目标值,但是此时还不可以直接返回middleIndex,还需要判断该中间元素是原有序线性表中的元素还是填充元素。如果middleIndex<=arrayCount-1,则说明是原有序线性表中的元素,此时直接返回middleIndex即可;如果middleIndex>arrayCount-1,则说明是填充元素,此时返回原有序线性表中的最后一个元素的下标。

代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma mark - 斐波那契查找


// 构建斐波那契数列
void createFibonacciSequence(int arrayCount, int *fibonacciSequence, int *n) {
  fibonacciSequence[0] = 1;
  fibonacciSequence[1] = 1;
  *n = 1;
  while (fibonacciSequence[*n] < arrayCount) {
    (*n)++;
    fibonacciSequence[*n] = fibonacciSequence[*n - 1] + fibonacciSequence[*n - 2];
  }
}


int fibonacciSearch(int *array, int arrayCount, int searchKey) {
  // 1,构建斐波那契数列,并求解出第一个大于等于数组长度的斐波那契元素
  int fibonacciSequence[100]; // 斐波那契数列
  int n; // n是保证【fibonacciSequence[n]>=searchKey】的最小值
  createFibonacciSequence(arrayCount, fibonacciSequence, &n);

  // 2,对原来的有序线性表进行扩容填充
  for (int i = arrayCount; i < fibonacciSequence[n]; i++) {
    array[i] = array[arrayCount-1];
  }

  // 3,循环遍历搜索对应元素
  int lowIndex = 0;
  int highIndex = fibonacciSequence[n] - 1; // 之所以减1是因为下标从0开始
  int middleIndex; // 中间元素下标
  while (lowIndex <= highIndex) {
    middleIndex = lowIndex + fibonacciSequence[n-1] - 1;

    // 比较中间元素与搜索值
    if (searchKey > array[middleIndex]) {
      lowIndex = middleIndex + 1;
      n = n - 2;
    } else if (searchKey < array[middleIndex]) {
      highIndex = middleIndex - 1;
      n = n - 1;
    } else {
      return middleIndex <= arrayCount-1 ? middleIndex : arrayCount-1;
    }
  }

  return -1;
}


int main(int argc, const char * argv[]) {
  // insert code here...
  printf("Hello, World!\n");

  int array[100] = {1,2,3,4,5,6,7,8,9,10};
  int arrayCount = 10;
  printf("斐波那契查找:%d\n", fibonacciSearch(array, arrayCount, 10));

  return 0;
}

二、动态查找——二叉搜索树

前面我们已经知道,静态查找指的是只对表执行查找操作,并不会动态添加元素。接下来我们来介绍动态查找,也就是说,在动态查找过程中,如果没有找到对应元素的话,那么就向查找表中插入未找到的元素,或者从查找表中删除某个指定的元素

动态查找的方案就是二叉搜索树,又称为二叉排序树。

二叉排序树(BST,Binary Sort Tree),它要么是一棵空树,要么是一颗具有下列性质的二叉树:

① 每个节点都有唯一的值,且每个节点的值均不相同

② 若它的左子树不为空,则它的左子树的所有节点均小于根节点的值

③ 若它的右子树不为空,则它的右子树的所有节点均大于根节点的值

④ 它的左右子树均为二叉搜索树

1,二叉搜索树的查找

二叉搜索树其实就是一个排序之后的二叉树,所以其结构跟二叉树是完全一样的,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//
//  main.c
//  BinarySearchTree
//
//  Created by 拉维 on 2022/5/12.
//


#include <stdio.h>


// 操作的状态
typedef enum : int {
  Success,
  Error,
} Status;


// BST节点结构
typedef struct BSTNode {
  int data; // 数据域
  struct BSTNode *leftChild; // 左子节点
  struct BSTNode *rightChild; // 右子节点
} BSTNode;


// 二叉搜索树的结构
typedef struct BSTNode * BinarySearchTree;

二叉搜索树的查找其实很简单。

① 首先,找到二叉搜索树的根节点,并使用currentNode记录

② 将根节点的值与搜索值searchKey进行比较,如果正好匹配,则返回currentNode;如果searchKey小于当前节点值,则将当前节点currentNode更新为当前节点的左子节点;如果searchKey大于当前节点值,则将当前节点currentNode更新为当前节点的右子节点。

③ 循环执行上面第②步,一直到currentNode为空或者找到对应节点为止。

④ 如果到最后也没有找到,则返回NULL。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// BST的查找
/*
 如果找到了对应元素,则parentNode是目标节点的双亲节点;如果没有找到对应元素,则parentNode是最终查找的那个节点,此时就可以将新建节点添加为parentNode的子节点
 */
struct BSTNode * bstSearch(BinarySearchTree bstRootNode, int searchKey, BinarySearchTree *parentNode) {
  BinarySearchTree currentNode = bstRootNode;
  *parentNode = NULL;

  while (currentNode) {
    if (currentNode->data == searchKey) {
      break; // 跳出循环
    } else if (currentNode->data > searchKey) {
      *parentNode = currentNode;
      currentNode = currentNode->leftChild;
    } else {
      *parentNode = currentNode;
      currentNode = currentNode->rightChild;
    }
  }

  return currentNode;
}

2,二叉搜索树的插入

步骤如下:

1,首先,通过BST搜索来查找对应元素。在查找的过程中将最终查找的节点通过parentNode变量记录下来,如果找到了,则parentNode记录的是目标节点的父节点;如果没有找到,则parentNode记录的是最终查找到的那个节点。

2,只有在未找到目标节点的情况之下才会执行插入操作。

(1)首先新建对应节点node,并对其数值域进行赋值,左右指针均置空

(2)如果BST是一个空树,那么将BST的根节点设置为新建的node节点

(3)将插入字段insertValue与parentNode的值域进行比较,如果insertValue<parentNode.data,则将新节点插入到parentNode的左子节点的位置;如果insertValue>parentNode.data,则将新节点插入到parentNode的右子节点的位置。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// BST的插入


void bstInsert(BinarySearchTree *bst, int insertValue) {
  // 1,在二分搜索树中查找待插入的元素
  BinarySearchTree parentNode;
  BinarySearchTree toInsertNode = bstSearch(*bst, insertValue, &parentNode);

  // 2,如果能够找到待插入的元素,则直接略过;如果不能找到待插入的元素,则新建节点并插入到BST中
  if (!toInsertNode) {
    // 2.1,新建节点
    struct BSTNode *node = malloc(sizeof(BSTNode));
    node->data = insertValue;
    node->leftChild = node->rightChild = NULL;

    // 2.2,如果原BST是空树,那么将新节点设置为BST的根节点
    if (!parentNode) {
      *bst = node;
      return;
    }

    // 2.3,按照左小右大进行子节点的插入
    if (parentNode->data > insertValue) {
      parentNode->leftChild = node;
    } else if (parentNode->data < insertValue) {
      parentNode->rightChild = node;
    }
  }
}

3,二叉搜索树的节点删除

思路如下:

1,查找对应节点,如果找不到,则报错误信息;如果找到了,则执行接下来的删除操作。

2,如果待删除的节点没有左子节点,那么就可以直接将待删除节点指向其右子节点,然后销毁原待删除节点。

3,如果待删除的节点没有右子节点,那么就可以直接将待删除节点指向其左子节点,然后销毁原待删除节点。

4,如果待删除节点的左右子节点均存在,那么此时,我们这里不采取直接删除待删除节点的方式,而是选取一个合适的节点来填充到待删除节点的位置上,该合适的节点就是中序排列下的待删除节点的前驱结点

(1)查找待删除节点的左子树中的最右侧的那一个节点,即待删除节点的前驱结点preNode,并记录该前驱节点的双亲结点parentOfPreNode

(2)将前驱结点的值填充到待删除节点的位置上

(3)如果parentOfPreNode==待删除节点,那么说明待删除节点的左子节点是没有右子树的,此时将待删除节点的左子节点(即前驱结点)的双亲结点的左子节点的指向调整为前驱结点的左子节点,然后销毁原来的前驱结点

(4)如果parentOdPreNode!=待删除节点,那么说明待删除节点的左子节点是有右子树的,并且该前驱结点是没有右子节点的,因此将前驱结点的双亲节点的右子节点指针指向调整为前驱结点的左子节点,然后销毁原来的前驱节点。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma mark - BST的元素删除


void bstDeleteNode(BinarySearchTree *toDeleteNode) {
  BinarySearchTree tempNode;
  if (!(*toDeleteNode)->leftChild) {
    // 1,待删除节点的左子节点为空,此时只需要将待删除节点指向其右子节点即可
    tempNode = *toDeleteNode;
    *toDeleteNode = (*toDeleteNode)->rightChild;
    free(tempNode);
  } else if (!(*toDeleteNode)->rightChild) {
    // 2,待删除节点的右子节点为空,此时只需要将待删除节点指向其左子节点即可
    tempNode = *toDeleteNode;
    *toDeleteNode = (*toDeleteNode)->leftChild;
    free(tempNode);
  } else {
    // 3,待删除节点的左右子节点都存在
    /*
     实际上,BST就是中序排序的。因此,待删除节点的左子树中的最后侧的节点就是该左子树中值最大的节点,也就是待删除节点的前驱结点;待删除节点的右子树中的最左侧的节点就是该右子树中值最小的节点,也就是待删除节点的后继节点。
     如果要删除待删除节点toDeleteNode,那么其实是可以将待删除节点的前驱结点或者后继节点填充到该位置的,这里是以填充前驱结点为例进行讲解。
     注意咯,这里的【待删除节点toDeleteNode】是不会真正销毁的,我们选取一个合适的节点preNode来填充到该位置,然后调整preNode的双亲节点的指向,并将preNode销毁。【待删除节点toDeleteNode】只是名义上的移除,真正销毁的是填充到待删除节点位置上的前驱结点preNode。
     */
    // 3.1,找寻中序排序下的前驱结点,以及前驱结点的双亲结点
    BinarySearchTree preNode = (*toDeleteNode)->leftChild;
    BinarySearchTree parentOfPreNode = *toDeleteNode;
    while (preNode->rightChild) {
      preNode = preNode->rightChild;
      parentOfPreNode = preNode;
    }
    // 3.2,判断前驱结点的双亲节点的双亲节点是否为待删除节点自身
    tempNode = preNode;
    (*toDeleteNode)->data = preNode->data; // 将前驱结点的值填充到待删除节点的位置
    if (parentOfPreNode == *toDeleteNode) {
      /*
       3.2.1 如果待删除节点的前驱节点的双亲结点是待删除节点自身,说明待删除节点的左子节点是没有右子树的,此时将待删除节点的左子节点的值填充到待删除节点的位置,并且将待删除节点指向其左子节点的左子节点,然后删除待删除节点的左子节点即可。
       */
      (*toDeleteNode)->leftChild = preNode->leftChild;
    } else {
      /*
       3.2.2 如果待删除节点的前驱结点的双亲节点不是待删除节点自身,那么说明待删除节点的左子节点是有右子树的,并且该前驱节点没有右子节点,此时需要将前驱节点的值赋值到待删除节点中,并且将前驱结点的双亲结点的右子节点指向前驱结点的左子节点,最终销毁该前驱节点。
       */
      parentOfPreNode->rightChild = preNode->leftChild;
    }
    free(tempNode);
  }
}


void bstDelete(BinarySearchTree *bst, int toDeleteValue) {
  // 递归查找待删除的节点元素,如果找到指定元素,则删除;如果没有找到指定元素,则提示删除失败
  if (!*bst) {
    printf("删除失败,失败原因:未找到指定元素节点\n");
    return;
  }
  
  if ((*bst)->data == toDeleteValue) {
    bstDeleteNode(bst);
  } else if((*bst)->data > toDeleteValue) {
    bstDelete(&(*bst)->leftChild, toDeleteValue);
  } else {
    bstDelete(&(*bst)->rightChild, toDeleteValue);
  }
}

验证代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main(int argc, const char * argv[]) {
  // insert code here...
  printf("Hello, World!\n");
  
  int originalArray[10] = {62, 88, 58, 47, 35, 73, 51, 99, 37, 93};
  
  BinarySearchTree binarySearchTree = NULL;
  for (int i = 0; i < 10; i++) {
    bstInsert(&binarySearchTree, originalArray[i]);
  }
  
  BinarySearchTree parentNode;
  BinarySearchTree searchNode = bstSearch(binarySearchTree, 35, &parentNode);
  if (searchNode) {
    printf("搜索节点%d成功\n", searchNode->data);
  } else {
    printf("搜索节点失败\n");
  }
  
  bstDelete(&binarySearchTree, 74);
  searchNode = bstSearch(binarySearchTree, 73, &parentNode);
  if (searchNode) {
    printf("搜索节点%d成功\n", searchNode->data);
  } else {
    printf("搜索节点失败\n");
  }
  
  return 0;
}

验证结果如下:

4,二叉搜索树的弊端

二叉搜索树的搜索效率是与树的深度相关的,在极端情况下,二叉搜索树会退化成一条单链,如下图所示,这种情况下的搜索效率将会大大降低。

那么这种情况下该如何进行优化呢?答案是使用平衡二叉树。关于平衡二叉树的内容我会在接下来的文章中进行讲解。

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
微PE制作U盘启动盘,并安装Win10
微PE 是一款很好用的 WinPE (Windows预先安装环境(英语:Microsoft Windows Preinstallation Environment),简称Windows PE或WinPE)工具箱,可以用来制作一个随插随用的U盘启动盘,并且不影响U盘的日常使用,在 Windows 系统电脑的系统出问题时会是救命般的存在。下面就来介绍一下如何制作 PE启动盘 ,并使用它来安装 Win10 操作系统。
宋天伦
2020/09/29
31.7K0
微PE制作U盘启动盘,并安装Win10
详解Win10家庭版/专业版/企业版功能区别
之前曾经在今年更早时间解释了Win10各个版本之间有什么差别和不同之处,但是在Win10升级全知晓中发现依然有很多朋友还是在问,这里就再详细解释一下。
zhangdd
2018/08/01
2.3K0
U盘pe(理论大白菜、优启通、微PE都可以) 装ESXI方案 (非通用UltraISO重做启动U盘),省U盘「建议收藏」
此文是我发的一篇的准备工作,因为ESXi 6.7刚发布的原因,很多同学等着升级,故而先写了出来。原文如下:
全栈程序员站长
2022/10/03
9K0
U盘pe(理论大白菜、优启通、微PE都可以) 装ESXI方案 (非通用UltraISO重做启动U盘),省U盘「建议收藏」
Windows 10系统重装U盘启动工具制作方法实例演示,windows11镜像下载地址
Windows 系统重装U盘启动工具制作方法 U盘启动工具下载与制作流程演示 ① win10系统U盘启动工具下载 ② win11系统 iso 镜像下载 ③ win10系统U盘启动工具制作流程 [ 推荐文章 ] 一篇文章快速掌握 Linux 基本命令 U盘启动工具下载与制作流程演示 ① win10系统U盘启动工具下载 windows 10 U盘启动工具下载地址 ② win11系统 iso 镜像下载 windows 11安装助手及镜像下载地址 ③ win10系统U盘启动工具制作流程 这是下载后
小蓝枣
2022/04/01
1.2K0
Windows 10系统重装U盘启动工具制作方法实例演示,windows11镜像下载地址
通过U盘winpe进行系统安装或维护
大概思路就是找个正常电脑,用老毛桃或大白菜制作USB启动盘,制作好后在启动盘里放置最新的ISO,然后在需要安装系统或修复启动引导的电脑上,通过启动盘中的winpe进行系统安装或维护。
Windows技术交流
2024/06/19
6290
自带win10系统换win7的那些坑
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/135729.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/05
2.7K0
Windows + Deepin 双系统安装实践
这一周没有怎么写代码,玩弄了一番我的电脑。在不到一周的时间里装了不下于十次系统,这一段时间差点疯掉。不过最终结果还可以。现在电脑上有两个系统,Windows 10 和 Deepin。开机的时候可以进行系统之间的切换。
多云转晴
2019/11/05
9.4K0
Windows + Deepin 双系统安装实践
全步骤图,带你重装系统
使用工具: U盘一个+优启通3.6版本+win10系统包 公众号发送: 重装系统,就可获取这些资源以及KMS破解工具的具体下载地址
东边的大西瓜
2022/05/05
1.3K0
全步骤图,带你重装系统
我在B站学知识之三分钟完成U盘PE启动盘制作与Win11系统安装实践
作者: WeiyiGeek 原文地址: https://www.bilibili.com/read/cv16201487 温馨提示: 如图片显示不出请访问原文查看。
全栈工程师修炼指南
2022/09/29
3.9K0
我在B站学知识之三分钟完成U盘PE启动盘制作与Win11系统安装实践
系统安装||第三篇:U盘pe模式安装纯净系统,不带任何捆绑和劫持!
昨天发的文章有部分描述错误,可能大家也发现了,由于发的太晚,所以没有仔细检查,大家忽视即可。QAQ
FreeRonin
2019/11/04
3.1K0
系统安装||第三篇:U盘pe模式安装纯净系统,不带任何捆绑和劫持!
最新Win10专业版系统下载|Win10 22H2纯净版安装教程|UEFI引导+U盘分区优化+安全加固(含官方镜像文件)
Windows 10 是微软推出的一款具有重要影响力的操作系统。它在易用性和安全性上有显著提升,对云服务、智能移动设备等新技术进行融合,还优化支持了固态硬盘等硬件。其特点丰富,有 Cortana 个人助理、Edge 浏览器、多桌面和任务视图等。开始菜单回归经典且具高度可定制性,搜索栏功能强大。从 Windows Store 下载的应用可窗口化运行。
万里顾一诚
2025/06/09
9.7K0
最新Win10专业版系统下载|Win10 22H2纯净版安装教程|UEFI引导+U盘分区优化+安全加固(含官方镜像文件)
利用微PE装机工具制作U盘启动盘并重装系统详细教程
突然有这篇文章是因为最近给发小笔记本电脑更换了固态硬盘,而更换固态后直接把原装的机械硬盘挂咸鱼卖了,而系统数据都在机械硬盘,新硬盘是空的什么也没有,需要重新做个系统。
岳泽以
2022/10/26
44.4K0
利用微PE装机工具制作U盘启动盘并重装系统详细教程
如何用u盘安装一个纯净的win10系统
最近用了一段时间win10之后,有点对windows路转粉的意思,把所有的电脑都装成了win10系统,这里记录一下用U盘安装win10的过程和一些中间用到的工具,以及遇到的一些问题。
efonfighting
2019/11/08
2K0
如何用u盘安装一个纯净的win10系统
win10多合一原版系统_微软Win10专业版制作多合一系统安装盘教程
大家好,又见面了,我是你们的朋友全栈君。 微软Win10怎么制作多合一系统安装盘?和Win10家庭版、win10企业版,win10教育版相比,微软Win10专业版是最受大家喜欢的操作系统,那么在安装W
全栈程序员站长
2022/09/13
2.9K0
[735]利用UItraISO软碟通制作U盘启动盘安装Ubuntu16.04系统
第2,3可以不勾选:第2选项是默认.ISO文件都用软碟通打开,不经常使用软碟通没必要关联;第3选项会生产一个驱动器(空盘),类似于百度云盘的,不经常使用软碟通不建议勾选。
周小董
2020/01/13
3.9K0
[735]利用UItraISO软碟通制作U盘启动盘安装Ubuntu16.04系统
用Xboot制作多系统启动U盘
   平时同学想重装或者换系统,让帮忙整一下,有想用Xp的,有想用WIN7的,还有想用WIN8,来回的制U盘启动盘也是个麻烦事,后来发现一款软件——Xboot,一个U盘可以制作成多系统启动的,感觉挺好,给大家推荐、推荐。
令仔很忙
2018/09/14
2.2K0
用Xboot制作多系统启动U盘
Windows 10版本business_editions和consumer_editions的区别?
consumer_editions 版本包含:Home(家庭版); Education(教育版) ; Professional(专业版); business_editions 版本包含:Education(教育版); Enterprise (企业版); Professional(专业版);
小狐狸说事
2022/11/17
3.4K0
多启动优盘制作
多启动优盘是什么? 你想要一个优盘,不用格式化就可以安装win7,win10,linux,不需要每换一次系统格式化一次优盘吗?文章就是这么一个目的了. 目录 如何实现多启动? 这里从底层的一些设置的话
@坤的
2018/07/05
1.9K0
windows安装双系统教程
2016-04-2222:19:23 发表评论 1,001℃热度 下载windows系统 安装windows系统 目录 装双系统其实mac下更难,windows电脑装双windows系统很简单,但
timhbw
2018/05/03
4.6K0
Windows10常用配置
Q: Windows 10版本 business_editions和consumer_editions的区别?
全栈工程师修炼指南
2022/09/28
2.5K0
Windows10常用配置
推荐阅读
相关推荐
微PE制作U盘启动盘,并安装Win10
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档