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?
定义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