实验4-树、二叉树与查找

集美大学课程实验报告-实验4:二叉树与查找

项目名称 内容
课程名称 数据结构
实验项目名称 二叉树与查找
上机实践日期 4.30
上机实践时间 2学时

一、目的(本次实验所涉及并要求掌握的知识点)

以下内容请根据实际情况编写

  • 掌握的二叉树与查找 基本概念和应用。
  • 学习二叉树与查找 的实现与优化。
  • 理解二叉树与查找 在实际问题中的应用。

二、实验内容与设计思想

以下请根据实际情况编写

题目1:先序序列创建二叉树

函数相关伪代码

伪代码
// 节点结构
结构体 Node {
    字符 data
    Node left
    Node right
}

// 建树函数
函数 建树(字符串 s, 下标 i):
    如果 s[i] == '#' 或 i越界:
        i加1
        返回 空
    
    创建新节点 node
    node.data = s[i]
    i加1
    node.left = 建树(s, i)
    node.right = 建树(s, i)
    返回 node

// 中序遍历
函数 中序(node):
    如果 node == 空:
        返回
    中序(node.left)
    输出 node.data + " "
    中序(node.right)

// 主程序
主函数:
    循环 读取字符串 s:
        如果 读完了:
            退出循环
        i = 0
        root = 建树(s, i)
        中序(root)
        输出换行

函数代码

#include <iostream>
#include <string>
using namespace std;

// 二叉树节点结构
typedef struct Node {
    char data;
    struct Node* left;
    struct Node* right;
} Node, *Tree;

// 根据先序遍历字符串构建二叉树
Tree buildTree(string& preorder, int& index) {
    if (index >= preorder.length()) {
        return nullptr;
    }
    
    char ch = preorder[index++];
    if (ch == '#') {
        return nullptr;  // 空节点
    }
    
    Node* node = new Node();
    node->data = ch;
    node->left = buildTree(preorder, index);
    node->right = buildTree(preorder, index);
    
    return node;
}

// 中序遍历
void inorderTraversal(Tree root) {
    if (root == nullptr) {
        return;
    }
    
    // 左子树
    inorderTraversal(root->left);
    // 根节点
    cout << root->data << " ";
    // 右子树
    inorderTraversal(root->right);
}

// 释放二叉树内存
void deleteTree(Tree root) {
    if (root == nullptr) {
        return;
    }
    deleteTree(root->left);
    deleteTree(root->right);
    delete root;
}

int main() {
    string preorder;
    
    while (cin >> preorder) {
        int index = 0;
        Tree root = buildTree(preorder, index);
        inorderTraversal(root);
        cout << endl;
        deleteTree(root);
    }
    
    return 0;
}

时间复杂度
O(n)
空间复杂度
O(n)

题目2:先序输出叶结点

函数相关伪代码

 先序输出叶结点(节点):
    如果 节点 == 空:
        返回
    
    如果 节点的左孩子 == 空 且 节点的右孩子 == 空:
        输出 " " + 节点的值
    
    先序输出叶结点(节点的左孩子)
    先序输出叶结点(节点的右孩子)

函数代码

相关主要代码
void PreorderPrintLeaves(BinTree BT) {
    if (BT == NULL) {
        return;
    }
    
    // 判断是否为叶结点
    if (BT->Left == NULL && BT->Right == NULL) {
        printf(" %c", BT->Data);
    }
    
    // 先序遍历左子树
    PreorderPrintLeaves(BT->Left);
    // 先序遍历右子树
    PreorderPrintLeaves(BT->Right);
}

时间复杂度
O(n)
空间复杂度
最坏O(n)最好O(logn)

题目3:求二叉树高度

函数相关伪代码

伪代码函数 GetHeight(节点):
    如果 节点 == 空:
        返回 0
    
    左高 = GetHeight(节点.左孩子)
    右高 = GetHeight(节点.右孩子)
    
    返回 max(左高, 右高) + 1

函数代码

int GetHeight(BinTree BT) {
    if (BT == NULL) {
        return 0;
    }
    int leftHeight = GetHeight(BT->Left);
    int rightHeight = GetHeight(BT->Right);
    return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

时间复杂度
O(n)
空间复杂度
最坏O(n)最好O(logn)

题目4:二叉树层次遍历(广度优先)

函数相关伪代码

伪代码函数 建树(字符串 arr, 下标 index):
    如果 index 越界 或 arr[index] == '#':
        返回 空
    创建节点 node 值为 arr[index]
    node.left = 建树(arr, 2*index)
    node.right = 建树(arr, 2*index+1)
    返回 node

函数 层次遍历(root):
    如果 root 为空:
        输出 "NULL"
        返回
    
    创建队列 q
    q.push(root)
    
    当 q 不为空:
        cur = q.front(), q.pop()
        输出 cur.data(如果不是第一个,前面加空格)
        
        如果 cur.left 存在: q.push(cur.left)
        如果 cur.right 存在: q.push(cur.right)

主函数:
    读入字符串 input
    root = 建树(input, 1)
    层次遍历(root)

函数代码

相关主要代码#include <iostream>
#include <string>
#include <queue>
using namespace std;

typedef struct Node {
    char data;
    Node* left;
    Node* right;
    Node(char d) : data(d), left(nullptr), right(nullptr) {}
} Node;

// 根据顺序存储字符串构建二叉树(下标从1开始)
Node* buildTree(const string& arr, int index) {
    if (index >= arr.length() || arr[index] == '#') {
        return nullptr;
    }
    Node* root = new Node(arr[index]);
    root->left = buildTree(arr, 2 * index);
    root->right = buildTree(arr, 2 * index + 1);
    return root;
}

// 层次遍历
void levelOrder(Node* root) {
    if (root == nullptr) {
        cout << "NULL";
        return;
    }
    
    queue<Node*> q;
    q.push(root);
    bool first = true;
    
    while (!q.empty()) {
        Node* cur = q.front();
        q.pop();
        
        if (!first) {
            cout << " ";
        }
        cout << cur->data;
        first = false;
        
        if (cur->left) q.push(cur->left);
        if (cur->right) q.push(cur->right);
    }
}

int main() {
    string input;
    cin >> input;
    
    // 输入的字符串第一个字符是 '#' 占位,从下标1开始构建树
    Node* root = buildTree(input, 1);
    
    levelOrder(root);
    
    return 0;
}

时间复杂度
O(n)
空间复杂度
O(n)

题目5:创建二叉排序树并遍历

函数相关伪代码

伪代码结构体 Node {
    int data
    Node left
    Node right
}

函数 插入(root, val):
    如果 root == 空:
        root = 新节点(val)
        返回
    
    如果 val < root.data:
        插入(root.left, val)
    否则如果 val > root.data:
        插入(root.right, val)

函数 中序(root):
    如果 root == 空:
        返回
    中序(root.left)
    输出 root.data + " "
    中序(root.right)

主函数:
    root = 空
    循环 读入 val:
        如果 val == '#':
            跳出循环
        插入(root, val)
    中序(root)

函数代码

相关主要代码
#include <iostream>
using namespace std;

// 二叉树节点结构
typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node, *BSTree;

// 创建新节点
Node* createNode(int val) {
    Node* newNode = new Node();
    newNode->data = val;
    newNode->left = nullptr;
    newNode->right = nullptr;
    return newNode;
}

// 插入节点到二叉排序树
void insert(BSTree& root, int val) {
    if (root == nullptr) {
        root = createNode(val);
        return;
    }
    
    if (val < root->data) {
        insert(root->left, val);
    } else if (val > root->data) {
        insert(root->right, val);
    }
    // 等于的情况,根据题目要求可以不插入(二叉排序树一般不允许重复)
}

// 中序遍历(左 → 根 → 右)
void inorderTraversal(BSTree root) {
    if (root == nullptr) {
        return;
    }
    
    inorderTraversal(root->left);
    cout << root->data << " ";
    inorderTraversal(root->right);
}

int main() {
    BSTree root = nullptr;
    int val;
    
    // 读入数据,直到遇到 #
    while (cin >> val) {
        if (val == -1) {  // 如果题目用 -1 表示结束,根据实际情况调整
            break;
        }
        insert(root, val);
    }
    
    // 中序遍历输出
    inorderTraversal(root);
    cout << endl;
    
    return 0;
}

题目7:航空公司VIP客户查询

函数相关伪代码

伪代码// 哈希函数
h = id每个字符的加权累加 % 200003

// 查找
idx = h(id)
遍历 hashTable[idx] 链表:
    如果 id 匹配: 返回里程
返回 -1

// 插入
idx = h(id)
遍历链表:
    如果 id 匹配: 里程 += miles; 返回
头插法创建新节点

// 主流程
读 N, K
循环 N 次: 读 id, miles; 如果 miles<K 则 miles=K; 插入(id, miles)
读 M
循环 M 次: 读 id; 查找并输出结果或 "No Info"

函数代码

相关主要代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define HASH_SIZE 200003  // 一个大质数,减少冲突

// 哈希链节点
typedef struct Node {
    char id[19];           // 身份证号(18位 + '\0')
    int mileage;           // 累积里程
    struct Node* next;     // 指向下一个节点
} Node;

// 哈希表
Node* hashTable[HASH_SIZE];

// 哈希函数:简单累加取模
unsigned int hash(const char* id) {
    unsigned int h = 0;
    for (int i = 0; id[i]; i++) {
        h = (h * 31 + id[i]) % HASH_SIZE;
    }
    return h;
}

// 查找:返回里程,-1 表示未找到
int find(const char* id) {
    unsigned int idx = hash(id);
    Node* p = hashTable[idx];
    while (p) {
        if (strcmp(p->id, id) == 0) {
            return p->mileage;
        }
        p = p->next;
    }
    return -1;
}

// 插入或更新
void insert(const char* id, int miles) {
    unsigned int idx = hash(id);
    Node* p = hashTable[idx];

    // 查找是否已存在
    while (p) {
        if (strcmp(p->id, id) == 0) {
            p->mileage += miles;
            return;
        }
        p = p->next;
    }

    // 不存在,创建新节点(头插法)
    Node* newNode = (Node*)malloc(sizeof(Node));
    strcpy(newNode->id, id);
    newNode->mileage = miles;
    newNode->next = hashTable[idx];
    hashTable[idx] = newNode;
}

int main() {
    int N, K;
    scanf("%d %d", &N, &K);

    // 初始化哈希表
    for (int i = 0; i < HASH_SIZE; i++) {
        hashTable[i] = NULL;
    }

    // 读入 N 条飞行记录
    for (int i = 0; i < N; i++) {
        char id[19];
        int miles;
        scanf("%s %d", id, &miles);

        // 低于 K 公里的按 K 计算
        if (miles < K) {
            miles = K;
        }

        insert(id, miles);
    }

    // 读入 M 条查询
    int M;
    scanf("%d", &M);

    for (int i = 0; i < M; i++) {
        char queryId[19];
        scanf("%s", queryId);

        int result = find(queryId);
        if (result == -1) {
            printf("No Info\n");
        }
        else {
            printf("%d\n", result);
        }
    }

    // 释放内存(非必须,但良好习惯)
    for (int i = 0; i < HASH_SIZE; i++) {
        Node* p = hashTable[i];
        while (p) {
            Node* tmp = p;
            p = p->next;
            free(tmp);
        }
    }

    return 0;
}

平均时间复杂度 O(N + M),空间复杂度 O(N),其中 N 为不同身份证号数量,M 为查询次数

三、实验使用环境(本次实验所使用的平台和相关软件)

以下请根据实际情况编写


四、实验步骤和调试过程(实验步骤、测试数据设计、测试结果分析)

以下请根据实际情况编写

题目1:先序序列创建二叉树

PTA提交截图
image

题目2:先序输出叶节点

PTA提交截图image

题目3:求树的高度

PTA提交截图
image

题目4:二叉树层次遍历(广度优先)

PTA提交截图
image

题目5:创建二叉排序树并遍历

本机运行截图
image

题目7:航空公司VIP客户查询

PTA运行截图
image


五、实验小结(实验中遇到的问题及解决过程、实验体会和收获)

以下请根据实际情况编写

遇到的问题及解决方法:

  1. 问题:哈希函数导致大量冲突,查询速度慢。

    • 解决方法:采用大质数 200003 作为哈希表大小,并使用加权哈希(h = h * 31 + id[i]),使身份证号分布更均匀,减少冲突。
  2. 问题:处理低于 K 公里的里程时逻辑容易遗漏。

    • 解决方法:在读入记录后立即判断 if (miles < K) miles = K;,确保里程值正确后再插入哈希表。

实验体会和收获:

  • 哈希表应用:

深入理解了链地址法哈希表的实现原理

学会了设计哈希函数(加权累加取模大质数)

掌握了哈希表的插入、查找、删除操作。


六、附件(参考文献和相关资料)

以下请根据实际情况编写

  1. C++ Primer
  2. deepseek
posted @ 2026-05-10 18:54  一四一二二  阅读(10)  评论(0)    收藏  举报