【数据结构】折半查找和二叉排序树的构造与删除

Problem A 二分查找

7-1 二分查找

全屏浏览题目切换布局

作者 usx程序设计类课程组

单位 绍兴文理学院

对于输入的n个整数,先进行升序排序,然后进行二分查找。

输入格式:

测试数据有多组,处理到文件尾。每组测试数据第一行输入一个整数n(1≤n≤100),第二行输入n个各不相同的待排序的整数,第三行是查询次数m(1≤m≤100),第四行输入m个待查找的整数。

输出格式:

对于每组测试,分2行输出,第一行是排序后的升序的结果,每两个数据之间留一个空格;第二行是查找的结果,若找到则输出排序后元素的位置(从1开始,每两个数据之间留一个空格),否则输出0。

输入样例:

9
4 7 2 1 8 5 9 3 6
5
10 9 8 7 -1

输出样例:

1 2 3 4 5 6 7 8 9
0 9 8 7 0

思路

二分查找是针对有序序列查找元素的高效方法,可以通过while循环递归实现。while循环需要注意判断条件是low<=high,即当low>high时说明找不到要查找的元素,return 0即可;递归算法需要注意low和high需要放在函数的参数中,以便在递归的过程中传递参数。

代码

#include <bits/stdc++.h>
using namespace std;
int a[20], b[10], m, n;

/// @brief while循环版折半查找
/// @param key 
/// @return 
int binary_Search(int key) {
    
    
    int low = 0, high = m - 1;
    while (low <= high) {
    
    
        int mid = (low + high) / 2;
        if (key == a[mid]) {
    
    
            return mid + 1;
        } else if (key < a[mid]) {
    
    
            high = mid - 1;
        } else {
    
    
            low = mid + 1;
        }
    }
    return 0;
} // binary_Search 

/// @brief 递归版折半查找
/// @param low 
/// @param high 
/// @param key 
/// @return 
int Binary_Search(int low, int high, int key) {
    
    
    if (low > high) {
    
    
        return 0;
    } else {
    
    
        int mid = (low + high) / 2;
        if (key == a[mid]) {
    
    
            return mid + 1;
        } else if (key < a[mid]) {
    
    
            return Binary_Search(low, mid - 1, key);
        } else {
    
    
            return Binary_Search(mid + 1, high, key);
        }
    }
} // Binary_Search

int main() {
    
    
    while (scanf("%d", &m) != EOF) {
    
    
        for (int i = 0; i < m; ++i) {
    
    
            cin >> a[i];
        }
        sort(a, a + m);
        cin >> n;
        for (int i = 0; i < m; ++i) {
    
    
            cout << a[i] << (i == m - 1 ? "\n" : " ");
        }
        for (int i = 0; i < n; ++i) {
    
    
            cin >> b[i];
            // cout << binary_Search(b[i]) << (i == n - 1 ? "\n" : " ");
            cout << Binary_Search(0, m, b[i]) << (i == n - 1 ? "\n" : " ");
        }
    }
}

Problem B 构造二叉检索数

7-2 构造二叉检索树

全屏浏览题目切换布局

作者 唐艳琴

单位 中国人民解放军陆军工程大学

本题目构造一棵二叉检索树。要求读入n个整数,以0结束。最后输出这棵树的先序序列。

输入格式:

输入n个整数,以0表示结束,数据间以空格隔开。

输出格式:

输出这棵树的先序序列,以一个空格隔开,结尾也有一个空格。

输入样例:

34 50 23 12 30 23 0

输出样例:

34 23 12 23 30 50 

思路

构造二叉检索树的基本方法是:将要插入的结点与根节点比较,若比根节点小则与根节点的左孩子比较,否则和右孩子比较…直到找到一个可以放置的位置,即可把该结点插入到二叉检索树中。By the way,因为二叉排序树的中序遍历序列是有序的递增序列,因此二叉排序树的构造过程实际上也是一种排序过程。本题可以使用树的中序遍历来验证二叉排序树的构造过程是否正确。

代码

#include <bits/stdc++.h>
using namespace std;

typedef struct BiTree {
    
    
    int data;
    BiTree *leftChild, *rightChild;
} BiTree;

/// @brief 往二叉搜索树树中插入结点
/// @param bt 
/// @param node 
void InsertNode(BiTree* &bt, int node) {
    
    
    if (bt == NULL) {
    
    
        bt = (BiTree *)malloc(sizeof(BiTree));
        bt->data = node;
        bt->leftChild = NULL;
        bt->rightChild = NULL;
    } else {
    
    
        if (node <= bt->data) {
    
    
            InsertNode(bt->leftChild, node);
        } else {
    
    
            InsertNode(bt->rightChild, node);
        }
    }
} // InsertNode

/// @brief 初始化二叉搜索树
/// @param bt 
void InitBiTree(BiTree* &bt) {
    
    
    int num;
    cin >> num;
    while (num != 0) {
    
    
        InsertNode(bt, num);
        cin >> num;
    }
} // InitBiTree

/// @brief 先序遍历
/// @param bt 
void PreOrderTraverse(BiTree *bt) {
    
    
    if (bt == NULL) {
    
    
        return;
    } else {
    
    
        cout << bt->data << " ";
        PreOrderTraverse(bt->leftChild);
        PreOrderTraverse(bt->rightChild);
    }
} // PreOrderTraverse

int main() {
    
    
    BiTree *bt = NULL;
    InitBiTree(bt);
    PreOrderTraverse(bt);
}

Problem C 二叉搜索树的删除操作

7-3 二叉搜索树的删除操作

全屏浏览题目切换布局

作者 孔德桢

单位 浙大城市学院

给出一棵二叉搜索树(没有相同元素), 请输出其删除部分元素之后的层序遍历序列。

删除结点的策略如下:

  • 如果一个结点是叶子结点,则直接删除;
  • 如果一个结点的左子树不为空, 则将该结点的值设置为其左子树上各结点中的最大值,并继续删除其左子树上拥有最大值的结点;
  • 如果一个结点的左子树为空但右子树不为空,则将该结点的值设置为其右子树上各结点中的最小值,并继续删除其右子树上拥有最小值的结点。

输入格式:

每个输入文件包含一个测试用例。每个测试用例的第一行包含一个整数 N (0<N<=100),表示二叉搜索树中结点的个数。
第二行给出该二叉搜索树的先序遍历序列,由 N 个整数构成,以一个空格分隔。第三行给出一个整数K (0<K<N),表示待删除的结点个数。最后一行给出 K 个整数,表示待删除的各个结点上的值。必须按输入次序删除结点。题目保证结点一定能被删除。

输出格式:

在一行中输出删除结点后的层序遍历序列。序列中的数字以一个空格分隔,行末不得有多余空格。

输入样例:

7
4 2 1 3 6 5 7
2
3 6

输出样例:

4 2 5 1 7

思路

由于题目已经给出相应的删除策略,因此不能使用教材上的旋转法来删除:

  • 如果一个结点是叶子结点,则直接删除;
  • 如果一个结点的左子树不为空, 则将该结点的值设置为其左子树上各结点中的最大值,并继续删除其左子树上拥有最大值的结点;
  • 如果一个结点的左子树为空但右子树不为空,则将该结点的值设置为其右子树上各结点中的最小值,并继续删除其右子树上拥有最小值的结点。

我找遍了很多教材很多视频,发现基本都没有对代码中DeleteNode函数中的判断条件temp != node做出比较详细的解释。掉了几撮头发以后,下文我将阐述我对该条件的理解:

  • 首先理解temp什么时候等于node,什么时候不等于node?

temp != node的相关解释

定义node结点为要删除结点,temp指针指向node,left指针指向node的左孩子。如图1所示,不难发现,结点A已经是node左子树中的最大结点,left->rightChild == NULL,那么在DeleteNode函数中while循环不会执行,temp和left保持原位,此时的temp就等于node。而如图2所示,此时node的左子树的最大结点已经不是A而是C,left->rightChild != NULL,此时就会进入while循环,执行

while (left->rightChild != NULL) {
    
     // while循环找到最大结点
	temp = left;
    left = left->rightChild;
}

则temp就不等于node。所以判断的标准就转化为node的左孩子是否有右子树。以图为例,若temp != node,则说明左孩子有右子树,直接删除结点C;若temp == node,则说明左孩子没有子树,左孩子A自身就是最大节点,就直接删除结点A。

if (temp != node) {
    
     // 若左子树根节点有右子树 
	DeleteNode(temp->rightChild);
} else {
    
     // 若左子树根节点没有右子树
    DeleteNode(temp->leftChild);
}

代码

#include <bits/stdc++.h>
using namespace std;

#define MAX_QUEUE_SIZE 20

typedef struct BiTree {
    
    
    int data;
    struct BiTree *leftChild, *rightChild;
} BiTree;

typedef struct Queue {
    
    
    int head, tail;
    BiTree *base;
} Queue;

/// @brief 初始化循环队列
/// @param q 
void InitQueue(Queue &q) {
    
    
    q.head = 0;
    q.tail = 0;
    q.base = (BiTree *)malloc(MAX_QUEUE_SIZE * sizeof(BiTree));
} // InitQueue

/// @brief 元素入队列
/// @param q 
/// @param data 
void EnQueue(Queue &q, BiTree data) {
    
    
    q.base[q.tail] = data;
    q.tail = (q.tail + 1) % MAX_QUEUE_SIZE;
} // EnQueue

/// @brief 元素出队列
/// @param q 
void DeQueue(Queue &q) {
    
    
    q.head = (q.head + 1) % MAX_QUEUE_SIZE;   
} // DeQueue

/// @brief 获取队首元素
/// @param q 
/// @return 
BiTree Front(Queue q) {
    
    
    return q.base[q.head];
} // Front

/// @brief 判断循环队列是否为空
/// @param q 
/// @return 
bool QueueEmpty(Queue q) {
    
    
    return q.head == q.tail;
} // QueueEmpty

/// @brief 向二叉排序树插入结点
/// @param bt 
/// @param data 
void InsertNode(BiTree* &bt, int data) {
    
    
    if (bt == NULL) {
    
    
        bt = (BiTree *)malloc(sizeof(BiTree));
        bt->data = data;
        bt->leftChild = NULL;
        bt->rightChild = NULL;
        return;
    } 
    if (data < bt->data) {
    
    
        InsertNode(bt->leftChild, data);
    } else {
    
    
        InsertNode(bt->rightChild, data);
    }
} // InsertNode

/// @brief 构建二叉排序树
/// @param bt 
/// @param vexnum 
void CreateBiTree(BiTree* &bt, int vexnum) {
    
    
    for (int i = 1; i <= vexnum; ++i) {
    
    
        int vex;
        cin >> vex;
        InsertNode(bt, vex);
    }
} // CreateBiTree

/// @brief 二叉树的层序遍历
/// @param bt 
/// @param cnt 
void LayerTraverse(BiTree *bt, int cnt) {
    
    
    int count = 0;
    Queue q;
    InitQueue(q);
    EnQueue(q, *bt);
    while (!QueueEmpty(q)) {
    
    
        BiTree front = Front(q);
        cout << front.data << (count == cnt - 1 ? "" : " ");
        // cout << front.data << " ";
        count++;
        if (Front(q).leftChild) {
    
    
            EnQueue(q, *(Front(q).leftChild));
        }
        if (Front(q).rightChild) {
    
    
            EnQueue(q, *(Front(q).rightChild));
        }
        DeQueue(q);
    }
} // LayerTraverse 

/// @brief 删除二叉排序树的结点
/// @param node 
void DeleteNode(BiTree* &node) {
    
    
    BiTree* temp = node;
    // 若删除的结点是叶子结点。直接删除即可
    if (node->rightChild == NULL && node->leftChild == NULL) {
    
     
        node = NULL;
        free(temp);
    } else if (node->leftChild != NULL) {
    
     // 当该结点有左子树时(右子树可有可无),使用它左子树的最大结点替代要删除的节点
        BiTree* left = node->leftChild; // 左子树
        while (left->rightChild != NULL) {
    
     // while循环找到最大结点
            temp = left;
            left = left->rightChild;
        }
        node->data = left->data; //把这个最大结点值赋给要删除的结点
        // 删除掉最大结点
        if (temp != node) {
    
     // 若左子树根节点有右子树 
            DeleteNode(temp->rightChild);
        } else {
    
     // 若左子树根节点没有右子树
            DeleteNode(temp->leftChild);
        }
    } else if (node->leftChild == NULL && node->rightChild != NULL) {
    
    //右
        BiTree* right = node->rightChild;
        while (right->leftChild != NULL) {
    
     // while循环找到最大结点
            temp = right;
            right = right->leftChild;
        }
        node->data = right->data; // 把这个最大节点赋给要删除的结点
        // 删除掉最大结点
        if (temp != node) {
    
     // 若右子树根节点有左子树
            DeleteNode(temp->leftChild);
        } else {
    
     // 若右子树根节点没有左子树
            DeleteNode(temp->rightChild);
        }
    }
} // DeleteNode

/// @brief 删除二叉排序树中的元素
/// @param node 
/// @param data 
void Delete(BiTree* &node, int data) {
    
    
    if (node == NULL) {
    
    
        return;
    }
    if (data == node->data) {
    
    
        DeleteNode(node);
        return;
    }
    if (data < node->data){
    
    
        Delete(node->leftChild, data);
    } else {
    
    
        Delete(node->rightChild, data);
    }
} // Delete

int main() {
    
    
    BiTree *bt = NULL;
    int vexnum;
    cin >> vexnum;
    CreateBiTree(bt, vexnum);
    int deleteNum;
    cin >> deleteNum;
    for (int i = 0; i < deleteNum; ++i) {
    
    
        int data;
        cin >> data;
        Delete(bt, data);
    }
    LayerTraverse(bt, vexnum - deleteNum);
}

旋转法删除二叉排序树结点

// /// @brief 旋转法删除二叉排序树的结点
// /// @param node 
void DeleteNode(BiTree* &node) {
    
    
     BiTree *temp = node;
     // 如果要删除的结点是叶子结点,直接删除即可
     if (node->leftChild == NULL && node->rightChild == NULL) {
    
    
         node = NULL;
         free(temp);
     } else if (node->rightChild == NULL) {
    
    
         // 若要删除的结点只有左子树,则只需要用左子树的根节点替代这个要删除的结点(有啥用啥)
         node = node->leftChild;
         free(temp);
     } else if (node->leftChild == NULL) {
    
    
         // 若要删除的结点只有右子树
         node = node->rightChild;
         free(temp);
     } else if (node->leftChild != NULL && node->rightChild != NULL) {
    
    
         // 若要删除的结点即有左子树又有右子树
         BiTree *left = node->leftChild; // 定义左子树
         while (left->rightChild != NULL) {
    
    
             temp = left;
             left = left->rightChild;
         }
         node->data = left->data; // 找到最大结点后用它来替代要删除的结点
         if (temp != node) {
    
     // 若左子树的根节点没有右子树
             temp->rightChild = left->leftChild;
         } else {
    
     // 若左子树的根节点有右子树
             temp->leftChild = left->rightChild;
         }
         delete left;
     }
 } // DeleteNode

猜你喜欢

转载自blog.csdn.net/Keikei419/article/details/128328317