第一阶段小组报告汇总
1.张帆
一 所选题目
6-2 最短路径(迪杰斯特拉算法)
7-4 先序和中序构造二叉树
7-14 矩阵运算
7-20 奇怪的电梯
二 程序源码及注释
6-2 最短路径(迪杰斯特拉算法)
void ShortestPath_DIJ(AMGraph G,int v0)
{
int S[100];
int n = G.vexnum;
for(int i=0;i<n;i++)
{
S[i] = 0;
D[i] = G.arcs[v0][i];
if(D[i]<MaxInt && i != v0)
Path[i] = v0;
else
Path[i] = -1;
}
S[v0] = 1;
D[v0] = 0;
for(int i = 1; i < n;i++)
{
int u = -1;
int min = MaxInt;
for(int j=0;j<n;j++)
{
if(S[j]==0&&D[j]<min)
{
min = D[j];
u = j;
}
}
if(u == -1)
{
break;
}
S[u] = 1;
for(int v=0;v<n;v++){
if(S[v]==0 && G.arcs[u][v] < MaxInt && D[u] + G.arcs[u][v] < D[v])
{
D[v] = D[u] + G.arcs[u][v];
Path[v] = u;
}
}
}
}
7-4 先序和中序构造二叉树
def build(pre,ino):
if not pre:
return []
root = pre[0]
pos = ino.index(root)
left = build(pre[1:pos+1],ino[:pos])
right = build(pre[pos+1:],ino[pos+1:])
return left+right+[root]
n=int(input())
pre=list(map(int,input().split()))
ino=list(map(int,input().split()))
res=build(pre,ino)
print(' '.join(map(str,res))+' ')
7-14 矩阵运算
n = int(input())
a = []
for i in range(n):
a.append(list(map(int,input().split())))
sum_val = 0
for i in range(n):
for j in range(n):
if in-1 or j n-1 or i+j ==n-1:
continue
sum_val += a[i][j]
print(sum_val)
7-20 奇怪的电梯
from collections import deque
def main():
N,A,B=map(int,input().split())
k=list(map(int,input().split()))
visited = [False] * (N+1)
q=deque()
q.append((A,0))
visited[A] = True
found = False
while q:
floor,step=q.popleft()
if floor ==B:
print(step)
found = True
break
up=floor+k[floor-1]
down=floor-k[floor-1]
for nxt in [up,down]:
if 1<= nxt <=N and not visited[nxt]:
visited[nxt]=True
q.append((nxt,step+1))
if not found:
print(-1)
if name=="main":
main()
三 数据测试及说明
6-2 最短路径(迪杰斯特拉算法)
6 8
012345
0 5 100
0 2 10
0 4 30
1 2 5
2 3 50
3 5 10
4 3 20
4 5 60
0 5
7-4 先序和中序构造二叉树
5
10 20 30 40 50
20 10 40 30 50
7-14 矩阵运算
4
2 3 4 1
5 6 1 1
7 1 8 1
1 1 1 1
7-20 奇怪的电梯
5 1 5
3 3 1 2 5
四 PTA提交截图
6-2 最短路径(迪杰斯特拉算法)
7-4 先序和中序构造二叉树
7-4 先序和中序构造二叉树
7-20 奇怪的电梯
五 个人体会
本次系列编程练习,我分别完成迪杰斯特拉最短路径、BFS电梯问题、矩阵运算、先序中序推导后序二叉树四道习题。通过实践,我理解了贪心、BFS、递归等算法思想,掌握邻接矩阵图存储、队列搜索、二维数组操作、二叉树序列重建等核心知识点。
编码调试过程中遇到多种典型错误:变量作用域冲突、语法书写失误、数组下标与切片混淆、区间判断条件写错、索引越界等,认识到算法逻辑看懂不等于代码能写对,语法细节、边界条件、下标处理极易出错。遇到输出异常时,不能直接照搬代码,需要带入样例手动推演,定位逻辑漏洞。
算法思想虽清晰,但代码细节繁杂。后续我会重视代码规范,加强边界模拟调试,多练习各类经典题型,夯实数据结构与算法的实践能力。
2.孙堃
一 所选题目
6.5
7.5
7-17
7-18
二 程序源码及注释
本题要求实现快速排序的一趟划分函数,待排序列的长度1<=n<=1000。
int Partition ( SqList L,int low, int high )
{
KeyType pivot = L.elem[low]; //选取low位置作为基准
while(low < high)
{
//从右往左找小于pivot的
while(low < high && L.elem[high] >= pivot)
high--;
L.elem[low] = L.elem[high];
//从左往右找大于pivot的
while(low < high && L.elem[low] <= pivot)
low++;
L.elem[high] = L.elem[low];
}
L.elem[low] = pivot; //基准归位
return low;
}
题目名称 编写一个哈夫曼编码译码程序。
按词频从小到大的顺序给出各个字符(大小写均有可能,不超过30个)的词频(整数或实数),根据词频构造哈夫曼树,给出每个字符的哈夫曼编码,并对给出的语句进行译码。
为确保构建的哈夫曼树唯一,本题做如下限定:
(1)选择根结点权值最小的两棵二叉树时,选取权值较小者作为左子树。
(2)若多棵二叉树根结点权值相等,按先后次序分左右,先出现的作为左子树,后出现的作为右子树。
生成哈夫曼编码时,哈夫曼树左分支标记为0,右分支标记为1。
include <stdio.h>
include <stdlib.h>
include <string.h>
include <ctype.h>
define MAX_NODES 100
define MAX_CHARS 30
define MAX_CODE 100
define MAX_ENCODE 1000
// 哈夫曼树节点
typedef struct Node {
char ch; // 字符,内部节点为'\0'
double freq; // 词频
struct Node* left;
struct Node* right;
} Node;
// 用于存储编码
typedef struct {
char ch;
char code[MAX_CODE];
} CodeEntry;
Node* nodes[MAX_NODES]; // 节点指针数组
int node_count = 0;
CodeEntry code_table[MAX_CHARS];
int code_count = 0;
// 创建新节点
Node* create_node(char ch, double freq) {
Node* new_node = (Node*)malloc(sizeof(Node));
new_node->ch = ch;
new_node->freq = freq;
new_node->left = NULL;
new_node->right = NULL;
return new_node;
}
// 在节点数组中找权值最小的两个节点索引
void find_two_min(int* idx1, int* idx2) {
int i;
// 找第一个最小
idx1 = 0;
for (i = 1; i < node_count; i++) {
if (nodes[i]->freq < nodes[idx1]->freq ||
(nodes[i]->freq == nodes[*idx1]->freq && i < *idx1)) {
*idx1 = i;
}
}
// 找第二个最小(排除idx1)
idx2 = -1;
for (i = 0; i < node_count; i++) {
if (i == idx1) continue;
if (idx2 == -1 || nodes[i]->freq < nodes[idx2]->freq ||
(nodes[i]->freq == nodes[*idx2]->freq && i < *idx2)) {
*idx2 = i;
}
}
}
// 构建哈夫曼树
Node* build_huffman_tree(char chars[], double freqs[], int n) {
int i;
int idx1, idx2;
// 初始化所有节点
for (i = 0; i < n; i++) {
nodes[i] = create_node(chars[i], freqs[i]);
}
node_count = n;
// 合并节点,直到只剩一个
while (node_count > 1) {
find_two_min(&idx1, &idx2);
// 确保idx1是左子树(权值小,或权值相等时索引小)
if (nodes[idx1]->freq > nodes[idx2]->freq ||
(nodes[idx1]->freq == nodes[idx2]->freq && idx1 > idx2)) {
int temp = idx1;
idx1 = idx2;
idx2 = temp;
}
// 创建父节点
Node* parent = create_node('\0', nodes[idx1]->freq + nodes[idx2]->freq);
parent->left = nodes[idx1];
parent->right = nodes[idx2];
// 从数组中删除两个子节点(先删大的索引,避免影响)
if (idx1 > idx2) {
// 将最后一个元素移到idx1位置
nodes[idx1] = nodes[node_count - 1];
nodes[idx2] = nodes[node_count - 2];
} else {
nodes[idx2] = nodes[node_count - 1];
nodes[idx1] = nodes[node_count - 2];
}
node_count -= 2;
// 添加父节点
nodes[node_count] = parent;
node_count++;
}
return nodes[0];
}
// 生成哈夫曼编码
void generate_codes(Node* root, char* code, int depth) {
if (root == NULL) return;
// 叶子节点
if (root->ch != '\0') {
code_table[code_count].ch = root->ch;
strncpy(code_table[code_count].code, code, depth);
code_table[code_count].code[depth] = '\0';
code_count++;
return;
}
// 左子树
if (root->left) {
code[depth] = '0';
generate_codes(root->left, code, depth + 1);
}
// 右子树
if (root->right) {
code[depth] = '1';
generate_codes(root->right, code, depth + 1);
}
}
// 先序遍历输出编码(先序:根->左->右)
void preorder_output(Node* root) {
if (root == NULL) return;
if (root->ch != '\0') {
// 找到对应的编码并输出
for (int i = 0; i < code_count; i++) {
if (code_table[i].ch == root->ch) {
printf("%c:%s\n", root->ch, code_table[i].code);
break;
}
}
return;
}
preorder_output(root->left);
preorder_output(root->right);
}
// 译码
void decode(char* encoded, Node* root) {
Node* cur = root;
int len = strlen(encoded);
printf("original:");
for (int i = 0; i < len; i++) {
if (encoded[i] == '0') {
cur = cur->left;
} else if (encoded[i] == '1') {
cur = cur->right;
}
if (cur->ch != '\0') {
printf("%c", cur->ch);
cur = root;
}
}
printf("\n");
}
// 从输入行中分离字符和词频
void parse_input(char* line, char* ch, double* freq) {
int i = 0;
char temp[100];
int j = 0;
// 提取字符部分(可能是多个字符,如 "m", "abc" 等,但题目中应该是一个字符)
while (line[i] && !isdigit(line[i]) && line[i] != '.') {
temp[j++] = line[i++];
}
temp[j] = '\0';
strcpy(ch, temp);
// 提取数字部分
j = 0;
while (line[i]) {
temp[j++] = line[i++];
}
temp[j] = '\0';
*freq = atof(temp);
}
int main() {
int n;
char chars[MAX_CHARS];
double freqs[MAX_CHARS];
char encoded[MAX_ENCODE];
char code[MAX_CODE];
Node* root;
// 读取字符个数
scanf("%d", &n);
getchar(); // 消耗换行符
// 读取字符和词频
for (int i = 0; i < n; i++) {
char line[100];
char ch_temp[10];
double freq;
fgets(line, sizeof(line), stdin);
// 去除末尾换行符
line[strcspn(line, "\n")] = '\0';
parse_input(line, ch_temp, &freq);
chars[i] = ch_temp[0]; // 取第一个字符
freqs[i] = freq;
}
// 读取编码串
scanf("%s", encoded);
// 构建哈夫曼树
root = build_huffman_tree(chars, freqs, n);
// 生成编码
generate_codes(root, code, 0);
// 按先序输出所有字符编码
preorder_output(root);
// 译码并输出
decode(encoded, root);
return 0;
}
根据维基百科的定义:
插入排序是迭代算法,逐一获得输入数据,逐步产生有序的输出序列。每步迭代中,算法从输入序列中取出一元素,将之插入有序序列中正确的位置。如此迭代直到全部元素有序。
归并排序进行如下迭代操作:首先将原始序列看成 N 个只包含 1 个元素的有序子序列,然后每次迭代归并两个相邻的有序子序列,直到最后只剩下 1 个有序的序列。
现给定原始序列和由某排序算法产生的中间序列,请你判断该算法究竟是哪种排序算法?
include
include
include
using namespace std;
// 插入排序,执行一轮,把pos位置元素插入前面有序区
void insertOneRound(vector
int tmp = a[pos];
int i;
for (i = pos; i > 0 && a[i-1] > tmp; i--) {
a[i] = a[i-1];
}
a[i] = tmp;
}
// 归并排序一轮,len是子序列长度
void mergeOneRound(vector
int n = arr.size();
int i;
for (i = 0; i <= n - 2len; i += 2len) {
merge(arr.begin()+i, arr.begin()+i+len, arr.begin()+i+len, arr.begin()+i+2len, temp.begin()+i);
}
// 剩下不足2len的部分
if (i + len < n) {
merge(arr.begin()+i, arr.begin()+i+len, arr.begin()+i+len, arr.end(), temp.begin()+i);
} else {
for(int j = i; j < n; j++) temp[j] = arr[j];
}
arr.swap(temp);
}
void print(const vector
for(int i = 0; i < v.size(); i++){
if(i>0) cout << " ";
cout << v[i];
}
cout << endl;
}
int main() {
int n;
cin >> n;
vector
for(int i=0; i<n; i++) cin >> origin[i];
for(int i=0; i<n; i++) cin >> mid[i];
vector
bool isInsert = false;
// 模拟插入排序每一轮
for(int i = 1; i < n; i++){
insertOneRound(ins, i);
if(ins == mid){
isInsert = true;
// 再迭代一轮
insertOneRound(ins, i+1);
break;
}
}
if(isInsert){
cout << "Insertion Sort" << endl;
print(ins);
}else{
cout << "Merge Sort" << endl;
vector
vector
int len = 1;
while(true){
mergeOneRound(mer, temp, len);
if(mer == mid){
mergeOneRound(mer, temp, len*2);
break;
}
len *= 2;
}
print(mer);
}
return 0;
}
根据维基百科的定义:
插入排序是迭代算法,逐一获得输入数据,逐步产生有序的输出序列。每步迭代中,算法从输入序列中取出一元素,将之插入有序序列中正确的位置。如此迭代直到全部元素有序。
堆排序也是将输入分为有序和无序两部分,迭代地从无序部分找出最大元素放入有序部分。它利用了大根堆的堆顶元素最大这一特征,使得在当前无序区中选取最大元素变得简单。
现给定原始序列和由某排序算法产生的中间序列,请你判断该算法究竟是哪种排序算法?
include <stdio.h>
// 大根堆调整
void heapAdjust(int a[], int low, int high) {
int i = low, j = 2 * i + 1;
int temp = a[i];
while (j <= high) {
if (j + 1 <= high && a[j+1] > a[j]) {
j++;
}
if (temp < a[j]) {
a[i] = a[j];
i = j;
j = 2 * i + 1;
} else {
break;
}
}
a[i] = temp;
}
// 打印数组
void printArr(int a[], int n) {
for(int i = 0; i < n; i++) {
if(i > 0) printf(" ");
printf("%d", a[i]);
}
printf("\n");
}
int main()
{
int n;
int origin[105], mid[105];
// (void) 消除scanf返回值警告,不用额外变量ret
(void)scanf("%d", &n);
for(int i = 0; i < n; i++){
(void)scanf("%d", &origin[i]);
}
for(int i = 0; i < n; i++){
(void)scanf("%d", &mid[i]);
}
// 判断插入排序:找第一个不满足有序的位置p
int p = 0;
while(p < n-1 && mid[p] <= mid[p+1]){
p++;
}
// p前面有序,p+1到末尾是否和原数组完全相等
int isInsert = 1;
for(int i = p+1; i < n; i++){
if(mid[i] != origin[i]){
isInsert = 0;
break;
}
}
if(isInsert) {
printf("Insertion Sort\n");
// 下一轮:把mid[p+1]插入前面0~p有序数组
int val = mid[p+1];
int idx = p;
while(idx >= 0 && mid[idx] > val) {
mid[idx+1] = mid[idx];
idx--;
}
mid[idx+1] = val;
printArr(mid, n);
} else {
printf("Heap Sort\n");
// ✅正确找堆边界:从后往前找已经排好的升序部分
int len = n - 1;
while (len >= 1 && mid[len-1] <= mid[len]) {
len--;
}
// 交换堆顶mid[0] 和堆末尾mid[len]
int t = mid[0];
mid[0] = mid[len];
mid[len] = t;
heapAdjust(mid, 0, len - 1);
printArr(mid, n );
}
return 0;
}
三 数据测试及说明
6-5
测试用例:10
5 2 4 1 8 9 10 12 3 6
7-5
测试用例:3
m1
n1
c2
10110
测试结果
7-17
测试用例:10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0
测试结果
7-18
测试用例:10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0测试结果
四 PTA提交截图
6-5
7-5
7-17
7-18
五 个人体会
本次算法与数据结构综合训练,我完成了快速排序划分、哈夫曼编码译码、排序算法识别四道编程题,巩固了排序算法与哈夫曼树的相关知识。
实现快速排序划分时,我加深了对分治思想的理解,指针移动、边界条件很容易出错,通过调试理清了一趟划分的完整逻辑。哈夫曼编码译码难度较高,在建树、选点合并、递归生成编码、译码的过程中,需要严格遵循题目对于左右子树的规则约束,多次调试才解决索引处理上的 bug,体会到树结构递归编程的要点。
两道排序判断题,要求模拟插入、归并、堆排序的执行过程,对比中间序列并输出下一轮结果。通过编码实践,我分清了几种排序算法各自的序列特征,明白了不能只会调用排序库函数,要掌握算法内部的执行流程。
调试过程中我发现,代码编译成功不等于结果正确,很多错误来源于下标、循环边界处理不当。同时 PTA 给出的编译警告也提醒我要注重代码书写规范。
这次训练让我意识到,看懂理论和动手写代码之间存在差距。今后我会多动手实现底层算法,学会拆分复杂问题、分步调试,进一步提升代码编写与排错能力,为后续的数据结构学习打好基础。
3.牛蕴韬
一 所选题目
6-4
7-3
7-16
7-21
二 程序源码及注释
题目名称:希尔排序的实现
void ShellInsert(SqList L,int dk)
{
int i, j;
for(i = dk + 1; i <= L.Length; i++)
{
if(L.elem[i] < L.elem[i - dk])
{
L.elem[0] = L.elem[i]; // 哨兵
for(j = i - dk; j > 0 && L.elem[0] < L.elem[j]; j -= dk)
{
L.elem[j + dk] = L.elem[j];
}
L.elem[j + dk] = L.elem[0];
}
}
}
题目名称:修建道路
include
include
include
using namespace std;
vector
int f(int x){return p[x]!=x?p[x]=f(p[x]):x;}
struct E{int u,v,w;bool operator<(E o)const{return w<o.w;}};
int main(){
int n;cin>>n;p.resize(n+1);
for(int i=1;i<=n;i++)p[i]=i;
vector
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
cin>>d;if(i<j)es.push_back({i,j,d});
}
int q,a,b;cin>>q;
while(q--){cin>>a>>b;p[f(b)]=f(a);}
sort(es.begin(),es.end());
int ans=0;
for(auto e:es)if(f(e.u)!=f(e.v))ans+=e.w,p[f(e.v)]=f(e.u);
cout<<ans;
return 0;
}
3.题目名称:重排链表
include <stdio.h>
define MAXADDR 100005
struct Node{
int data;
int next;
}node[MAXADDR];
int list[100005]; //保存原始链表结点地址顺序
int res[100005]; //重排之后新的输出顺序
int main()
{
int head, n;
scanf("%d%d",&head,&n);
int addr,data,next;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&addr,&data,&next);
node[addr].data=data;
node[addr].next=next;
}
//第一步:顺着链表取出所有结点地址,存入list
int p=head;
int cnt=0;
while(p!=-1)
{
list[cnt++]=p;
p=node[p].next;
}
//第二步:双指针生成新顺序 Ln L1 Ln‑1 L2 ...//res=list//=kj
int i=0, j=cnt-1;
int k=0;
while(i <= j)
{
if(i == j) //奇数个结点,最后剩下一个,只放一次
{
res[k++] = list[j];
break;
}
res[k++] = list[j];
res[k++] = list[i];
j--;
i++;
}
//第三步:输出res数组,依次设置next,最后一个结点next=-1//tk-1
for(int t=0;t<k;t++)
{
if(t != k-1)
{
printf("%05d %d %05d\n", res[t], node[res[t]].data, res[t+1]);
}
else
{
printf("%05d %d -1\n", res[t], node[res[t]].data);
}
}
return 0;
}
4.题目名称:抓住那头牛
include <stdio.h>
include <stdlib.h>
define MAX 200005
int vis[MAX];
//队列结点,存位置和步数
typedef struct{
int pos;//位置
int step;//步数
}Node;//初始化
Node q[MAX];
int head=0,tail=0;
int main()
{
int N,K;
scanf("%d%d",&N,&K);
//起点入队//nx[i]//q[tail++]=(node){nx[i],t+i}
q[tail++]=(Node){N,0};
vis[N]=1;
while(head<tail)
{
Node cur=q[head++];//队尾
int x=cur.pos;
int t=cur.step;
if(x==K)
{
printf("%d\n",t);
return 0;
}
//三个方向
int nx[3]={x-1,x+1,x*2};
for(int i=0;i<3;i++)
{
if(nx[i]>=0&&nx[i]<MAX&&!vis[nx[i]])//条件
{
vis[nx[i]]=1;//读取过
q[tail++]=(Node){nx[i],t+1};
}
}
}
return 0;
}
三 数据测试及说明
题目名称:希尔排序的实现
题目名称:修建道路
题目名称:重排链表
题目名称: 抓住那头牛
四 PTA提交截图
题目名称:希尔排序的实现
题目名称:修建道路
题目名称: 重排链表
题目名称:抓住那头牛
五 个人体会
经过这段时间图算法的练习,完成了农夫抓牛 BFS、村庄修路最小生成树等题目之后,我对搜索和最小生成树这一类算法有了更深的感悟。首先,我认识到算法没有万能的模板,不同问题适合不同的解法。求最短步数时适合用广度优先搜索,依靠队列一层一层向外扩展,可以保证最先找到的结果就是最短路径;而最小生成树问题适合克鲁斯卡尔算法,借助并查集管理连通分量,选出代价最小的边把所有结点连通起来。同时我也体会到模拟数据手动推演的重要性。遇到难懂的题目,不能只依靠代码直接运行,可以先在纸上走一遍流程,看清队列、双指针或者并查集的变化过程,卡点往往就能快速找到。
今后我打算继续加强算法基础练习,尽量做到先理清思路再动手写代码,写完之后主动测试边界情况,例如起点大于终点、结点数量为奇数、已经全部连通等特殊案例,减少程序出错的概率。
4.王煜航
一 所选题目
最短路径(弗洛伊德算法)
jmu-ds-实现KMP
AVL 树的根
双向循环链表应用
二 程序源码及注释
最短路径(弗洛伊德算法)
试实现弗洛伊德最短路径算法。
函数接口定义:
void ShortestPath_Floyed(AMGraph G);
其中 G 是基于邻接矩阵存储表示的有向图。
裁判测试程序样例:
include
using namespace std;
define MaxInt 32767
define MVNum 100
typedef char VerTexType;
typedef int ArcType;
int Path[MVNum][MVNum];
int D[MVNum][MVNum];
typedef struct{
VerTexType vexs[MVNum];
ArcType arcs[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
void CreateUDN(AMGraph &G);//实现细节隐藏
void ShortestPath_Floyed(AMGraph G);
void DisplayPath(AMGraph G , int begin ,int temp ){
if(Path[begin][temp] != -1){
DisplayPath(G , begin ,Path[begin][temp]);
cout << G.vexs[Path[begin][temp]] << "->";
}
}
int main(){
AMGraph G;
char start , destination;
int num_start , num_destination;
CreateUDN(G);
ShortestPath_Floyed(G);
cin >> start >> destination;
num_start = LocateVex(G , start);
num_destination = LocateVex(G , destination);
DisplayPath(G , num_start , num_destination);
cout << G.vexs[num_destination]<<endl;
cout << D[num_start][num_destination];
return 0;
}
/* 请在这里填写答案 */
双向循环链表应用
已知p指向双向循环链表中的一个结点,其结点结构为data、prior、next三个域,实现交换p所指向的结点和它的前缀结点的顺序。
输入格式:
第一行输入元素个数,第二行输入元素值,第三行输入要交换的元素值,第四行输出结果。
输出格式:
输出交换后的结果,中间不用空格分隔,若要交换的值:4,不在数组终,则输出“未找到4”
AVL 树的根
将给定的一系列数字插入初始为空的 AVL 树,请你输出最后生成的 AVL 树的根结点的值。
输入格式:
输入的第一行给出一个正整数 n(≤20),随后一行给出 n 个整形 int 范围内的、不同的整数,其间以空格分隔。
输出格式:
在一行中输出顺序插入上述整数到一棵初始为空的 AVL 树后,该树的根结点的值。
4, jmu-ds-实现KMP
给两个字符串A、B, 从A中找出第一次出现B的位置。
输入格式:
第一行输入一个整数n,表示测试数据的个数
对于每组测试数据,输入两个字符串S T,S和T中间用一个空格隔开,每组数据占一行。
输出格式:
对于每组测试数据,输出A中找出第一次出现B的位置,如果A不包含B,输出“not find!”
三 数据测试及说明
最短路径(弗洛伊德算法)
jmu-ds-实现KMP
AVL 树的根
双向循环链表应用
四 PTA提交截图
最短路径(弗洛伊德算法)
jmu-ds-实现KMP
AVL 树的根
双向循环链表应用
五 个人体会
本次实验我完成了 Floyd 最短路算法、KMP 字符串匹配、AVL 平衡二叉树以及双向循环链表四道综合编程题目,涵盖图、字符串、树、线性表四大核心数据结构,通过上机编码与调试,我对各类经典算法的原理、执行流程与代码实现有了更加深刻、完整的理解。
在图结构的 Floyd 多源最短路实验中,我掌握了利用邻接矩阵存储图结构的方法,理解了以中间节点 k 为核心的三层循环更新机制,明确了距离数组与路径前驱数组的作用。通过补全代码、调试路径输出逻辑,我认识到图算法对初始化、无穷大赋值、循环顺序的严格要求,任意细节错误都会导致最终结果异常,提升了我严谨编写图论代码的能力。
在 KMP 字符串匹配实验中,我深入理解了最长相等前后缀与 next 数组的构造原理,掌握了 KMP 算法避免主串回溯、提升匹配效率的核心优势。相较于暴力匹配,KMP 的逻辑更加抽象,通过手动模拟数组生成过程和调试代码,我克服了对哨兵值、边界条件、循环跳转逻辑的理解误区,熟练掌握了标准 KMP 算法的完整实现流程。
AVL 平衡二叉树实验让我系统掌握了二叉平衡树的插入、高度更新与四种旋转调整操作。我区分了 LL、RR、LR、RL 四种失衡情况对应的旋转方式,理解了递归插入后回溯调整平衡的机制。实验中我深刻意识到,平衡树对节点高度更新顺序、旋转步骤的准确性要求极高,微小疏漏就会导致树结构失衡,锻炼了我处理树形结构复杂逻辑、排查递归代码错误的能力。
双向循环链表的结点交换实验,让我进一步熟悉了双向链表前驱、后继指针的操作逻辑。相较于单链表,双向循环链表指针关系更加复杂,结点交换需要完整修改相邻节点的所有指针域。通过画图梳理指针指向、分步修改代码,我规避了断链、死循环等常见错误,熟练掌握了双向循环链表的遍历、查找与结点交换操作,深刻体会到线性表操作逻辑性与严谨性的重要性。
总体而言,本次实验巩固了我对链表、树、图、字符串匹配算法的理论与代码实现能力,暴露了我对复杂算法底层逻辑不熟、边界考虑不全面的问题。通过不断调试、模拟流程、改错优化,我的代码阅读能力、逻辑思维能力和问题排查能力得到明显提升,为后续数据结构综合应用与算法学习打下了扎实的实践基础。
5.梁有有
一 所选题目
1.6-6 堆排序
2.7-11 二路归并排序
3. 7-12 链式基数排序
4.7-22 图的深度优先遍历
二 程序源码及注释
1.题目名称:6-6 堆排序
void HeapAdjust(HeapType H, int s, int m) {
int rc = H.elem[s]; // 暂存根节点
int child;
for (child = 2 * s; child <= m; child *= 2) {
// 若右孩子存在且更大,则选择右孩子
if (child < m && H.elem[child] < H.elem[child + 1]) {
child++;
}
// 若根节点已经大于等于最大孩子,则满足堆性质
if (rc >= H.elem[child]) {
break;
}
// 将孩子节点上移
H.elem[s] = H.elem[child];
s = child; // 继续向下调整
}
H.elem[s] = rc; // 将暂存的根节点放入最终位置
}
2.题目名称 7-11 二路归并排序
include <stdio.h>
int n;
int a[105];
void print_array() {
for (int i = 0; i < n; i++) {
if (i) printf(" ");
printf("%d", a[i]);
}
printf("\n");
}
void merge(int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int L[105], R[105];
for (int i = 0; i < n1; i++)
L[i] = a[left + i];
for (int j = 0; j < n2; j++)
R[j] = a[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j])
a[k++] = L[i++];
else
a[k++] = R[j++];
}
while (i < n1)
a[k++] = L[i++];
while (j < n2)
a[k++] = R[j++];
// 每次归并完成后输出整个数组
print_array();
}
void merge_sort(int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
merge_sort(left, mid);
merge_sort(mid + 1, right);
merge(left, mid, right);
}
}
int main() {
while (scanf("%d", &n) != EOF) {
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
merge_sort(0, n - 1);
}
return 0;
}
3.题目名称 7-12 链式基数排序
include <stdio.h>
define MAXN 1000
typedef struct Node {
int key;
int next;
} Node;
Node nodes[MAXN];
int head, tail;
/* 计算最大关键字的位数,若最大值为0则位数为1 */
int get_max_digit(int max_key) {
if (max_key == 0) return 1;
int cnt = 0;
while (max_key > 0) {
max_key /= 10;
cnt++;
}
return cnt;
}
int main() {
int n;
scanf("%d", &n);
int max_key = 0;
for (int i = 0; i < n; i++) {
scanf("%d", &nodes[i].key);
if (nodes[i].key > max_key) max_key = nodes[i].key;
nodes[i].next = i + 1; // 初始按输入顺序链接
}
if (n > 0) {
nodes[n - 1].next = -1; // 尾结点置空
head = 0;
tail = n - 1;
}
int max_digit = get_max_digit(max_key);
int front[10], rear[10]; // 10个桶的头尾指针
int divisor = 1; // 当前处理的位权,1,10,100,...
for (int d = 0; d < max_digit; d++) {
// 1. 初始化桶
for (int i = 0; i < 10; i++) {
front[i] = rear[i] = -1;
}
// 2. 分配:遍历链表,按当前位放入对应桶
int cur = head;
while (cur != -1) {
int next = nodes[cur].next; // 保存原后继
int digit = (nodes[cur].key / divisor) % 10;
nodes[cur].next = -1; // 断开原链接
if (front[digit] == -1) {
front[digit] = rear[digit] = cur;
} else {
nodes[rear[digit]].next = cur;
rear[digit] = cur;
}
cur = next;
}
// 3. 收集:按桶顺序重新链接成一个链表
head = tail = -1;
for (int i = 0; i < 10; i++) {
if (front[i] != -1) {
if (head == -1) {
head = front[i];
tail = rear[i];
} else {
nodes[tail].next = front[i];
tail = rear[i];
}
}
}
if (tail != -1) {
nodes[tail].next = -1; // 新链表尾置空
}
// 4. 输出当前趟的结果
cur = head;
while (cur != -1) {
printf("%d ", nodes[cur].key);
cur = nodes[cur].next;
}
printf("\n");
divisor *= 10; // 处理下一位
}
return 0;
}
4.7-22 图的深度优先遍历
三 数据测试及说明
1.题目名称 6-6堆排序
测试用例说明
表格
用例编号 输入数组(下标 1 开始) 预期输出 测试目的
用例 1 49 38 65 97 76 13 13 38 49 65 76 97 普通乱序,验证筛选、建堆、排序完整流程
用例 2 1 2 3 4 5 1 2 3 4 5 输入已经有序,检验堆排序处理最坏输入
用例 3 5 4 3 2 1 1 2 3 4 5 输入已经是大顶堆,检验交换 + 筛选逻辑
用例 4 88 88 边界:n=1,无孩子节点,HeapAdjust直接不进入循环
样例输出
plaintext
【测试用例1】原始序列:49 38 65 97 76 13
排序结果:13 38 49 65 76 97
【测试用例2】原始升序:1 2 3 4 5
排序结果:1 2 3 4 5
【测试用例3】原始降序:5 4 3 2 1
排序结果:1 2 3 4 5
【测试用例4】单元素:88
排序结果:88
2.题目名称 7-11 二路归并排序
边界用例 1:n=1
输入
1
5
输出:无输出(left>=right不会进入 merge,没有归并操作)
边界用例 2:n=2
输入
2
2 1
输出
1 2
测试说明表格
表格
用例 输入 预期输出 测试目的
普通乱序 649 38 65 97 76 13 4 行输出如上 验证递归归并,每次归并输出
n=2 22 1 1 2 最小需要归并的规模
n=1 15 无输出 边界:只有一个元素,没有归并操作
已经有序 41 2 3 4 1 2 3 41 2 3 41 2 3 4 输入本身有序,仍要执行归并 + 输出
3.题目名称 7-12 链式基数排序
测试用例表
表格
用例 输入 预期输出 测试目的
普通乱序 6
49 38 65 97 76 13 13 38 49 65 76 97 常规多位数排序
边界 n=1 1
888 888 单元素
全部相同 4
22 22 22 22 22 22 22 22 重复关键字
位数参差不齐 5
5 123 99999 42 10000 5 42 123 10000 99999 1‑5 位混合数字
样例输入 4
plaintext
5
5 123 99999 42 10000
输出
plaintext
5 42 123 10000 99999
4.7-22 图的深度优先遍历
测试用例表
表格
用例 输入 输出 测试目的
普通连通图 5 5 1
1 2
1 3
2 4
2 5
3 5 1 2 4 5 3 优先访问小编号邻接点
单顶点 1 0 1 1 边界:N=1,没有边
不连通图 4 1 1
1 2 1 2 只遍历起点所在连通分量,3、4 访问不到
链状图 4 3 1
1 3
3 2
2 4 1 3 2 4 线性无向图 DFS
四 PTA提交截图
1.题目名称 6-6堆排序
2.题目名称 7-11 二路归并排序
3.题目名称 7-12 链式基数排序
4.7-22 图的深度优先遍历
五 个人体会
排序与图算法实验个人学习体会
这次实验我一共完成了堆排序、二路归并排序、链式基数排序以及图的深度优先遍历四道编程题目,做完之后收获很多,也看到了自己存在不少问题。
最开始做堆排序的时候,我主要是理解堆的筛选函数。一开始下标总是搞混,一会儿从0开始,一会儿从1开始,写出来的代码运行结果不对。反复看课本的大顶堆筛选逻辑,才明白堆排序是向下筛选,把不满足堆性质的节点一步步下沉。调试的时候,我一步步跟着测试样例走,才搞懂rc保存节点、孩子节点向下遍历的完整流程,也体会到堆排序不需要大量移动元素,靠下标调整完成排序的特点。
接着做二路归并排序,递归的思路一开始有点绕。递归先拆分,拆分到最小单元之后再合并。题目要求每完成一次归并就要输出数组,我一开始错误把打印写在了递归函数最后,输出结果完全不符合样例。后来才意识到,打印必须放在merge合并函数内部,每合并完一小段就输出一次。通过调试样例,我看懂递归的执行顺序,明白了归并排序是先分后合,依靠临时数组完成两段有序序列的合并。
链式基数排序对我来说难度最大,它不是普通指针链表,而是静态链表,用数组下标模拟链表,next存下标,用1代表链表结束。一开始我对分配Distribute和收集Collect两个函数理解模糊,分不清个位、十位的取数逻辑。写代码的时候,桶数组忘记初始化,出现链表循环的bug。反复调试测试用例,才理解基数排序不做大小比较,依靠多轮分配收集完成排序,也体会到静态链表节省内存、不需要动态申请节点的好处。
最后做图的深度优先遍历DFS。邻接矩阵构建无向图,题目要求遇到多个邻接点优先访问编号小的节点。我一开始循环邻接点的时候写反了循环顺序,输出遍历序列和样例不一致。修改成从小到大遍历顶点编号,结果才正确。DFS递归访问,visited数组标记访问过的顶点,防止重复访问死循环,我也理解深度优先就是一条路走到头,再回溯回来访问其他分支。
做完这四道题,我感受到数据结构不能只看课本看懂理论,真正动手写代码才会暴露很多细节问题。很多逻辑课堂上听着明白,落实到代码,下标、边界条件、循环顺序稍微出错,整个程序就错了。很多bug不是算法思路错,而是下标、初始化、边界处理这些小地方。
同时我也认识自己的不足:递归类的代码容易理不清执行流程;静态链表这种不常用的结构上手比较慢;读题的时候容易忽略题目要求,比如优先小编号、每归并一次输出等细节。
后续我打算,写代码之前先理清楚步骤,想好测试用例,写完之后用简单的小数据先手动模拟一遍,再上机运行。遇到报错不要盲目改代码,顺着流程调试,找到出错的位置。通过本次实验,我对内部排序算法和图的遍历有了更加实际的理解,动手编码的能力也得到锻炼。
6.梁子林
一 所选题目
-
试实现普里姆最小生成树算法。
-
本题要求实现二路归并排序中的归并操作,待排序列的长度1<=n<=1000。
3.假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
4.把题目给出中缀表达式转换为后缀表达式输出,并求后缀表达式的值。为简单起见,我们约定:1、输入的中缀表达式一定是合法的,并且只含数字,四种运算符+、-、*、/和小括号;2、运算数都是一位正整数(1~9);3、输入的中缀表达式不超过20个字符;4、除法运算的结果仍然是正整数。
二 程序源码及注释
1.题目名称:试实现普里姆最小生成树算法。
void Prim(AMGraph G, char u) {
int start = LocateVex(G, u);
// 初始化closedge数组
for (int i = 0; i < G.vexnum; ++i) {
if (i != start) {
closedge[i].adjvex = u;
closedge[i].lowcost = G.arcs[start][i];
} else {
closedge[i].lowcost = 0; // 将起始顶点加入已选集合
}
}// 需要进行 n-1 次选择
for (int i = 1; i < G.vexnum; ++i) {
int k = Min(G); // 找到权值最小的未选顶点
// 输出该边
cout << closedge[k].adjvex << "->" << G.vexs[k] << endl;
// 将顶点 k 加入已选集合
closedge[k].lowcost = 0;// 更新其他未选顶点的最小边权值
for (int j = 0; j < G.vexnum; ++j) {
if (j != k && G.arcs[k][j] < closedge[j].lowcost) {
closedge[j].adjvex = G.vexs[k];
closedge[j].lowcost = G.arcs[k][j];
}
}
}
}
2.题目名称:本题要求实现二路归并排序中的归并操作,待排序列的长度1<=n<=1000。
void Merge(SqList L, int low, int m, int high)
{
// 创建一个临时数组来存储合并后的结果
KeyType *temp = (KeyType *)malloc((high - low + 1) * sizeof(KeyType));
if (temp == NULL) return; // 内存分配失败则返回
int i = low; // 左子序列的起始索引
int j = m + 1; // 右子序列的起始索引
int k = 0; // 临时数组的索引
// 比较两个子序列的元素,将较小的放入临时数组
while (i <= m && j <= high) {
if (L.elem[i] <= L.elem[j]) {
temp[k++] = L.elem[i++];
} else {
temp[k++] = L.elem[j++];
}
}
// 如果左子序列还有剩余元素,将其复制到临时数组
while (i <= m) {
temp[k++] = L.elem[i++];
}
// 如果右子序列还有剩余元素,将其复制到临时数组
while (j <= high) {
temp[k++] = L.elem[j++];
}
// 将临时数组中的元素复制回原数组的对应位置
for (i = low, k = 0; i <= high; i++, k++) {
L.elem[i] = temp[k];
}
// 释放临时数组的内存
free(temp);
}
3.题目名称:假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集
include <stdio.h>
define MAXN 105
int main() {
int n;
scanf("%d", &n);
int graph[MAXN][MAXN] = {0};
int indegree[MAXN] = {0};
for (int i = 1; i <= n; i++) {
int k;
scanf("%d", &k);
for (int j = 0; j < k; j++) {
int dep;
scanf("%d", &dep);
// dep 是 i 的前置依赖,所以建边 dep -> i
if (!graph[dep][i]) {
graph[dep][i] = 1;
indegree[i]++;
}
}
}
// 拓扑排序
int queue[MAXN], head = 0, tail = 0;
for (int i = 1; i <= n; i++) {
if (indegree[i] == 0) {
queue[tail++] = i;
}
}
int count = 0;
while (head < tail) {
int u = queue[head++];
count++;
for (int v = 1; v <= n; v++) {
if (graph[u][v]) {
indegree[v]--;
if (indegree[v] == 0) {
queue[tail++] = v;
}
}
}
}
// 如果所有任务都能被执行,说明无环,方案可行
if (count == n) {
printf("1\n");
} else {
printf("0\n");
}
return 0;
}
4.题目名称:把题目给出中缀表达式转换为后缀表达式输出,并求后缀表达式的值。为简单起见,我们约定:1、输入的中缀表达式一定是合法的,并且只含数字,四种运算符+、-、*、/和小括号;2、运算数都是一位正整数(1~9);3、输入的中缀表达式不超过20个字符;4、除法运算的结果仍然是正整数。
include <stdio.h>
include <string.h>
include <stdlib.h>
define MAX_SIZE 25
// 栈结构
typedef struct {
char data[MAX_SIZE];
int top;
} CharStack;
typedef struct {
int data[MAX_SIZE];
int top;
} IntStack;
// 初始化字符栈
void initCharStack(CharStack *s) {
s->top = -1;
}
// 字符入栈
void pushChar(CharStack *s, char c) {
s->data[++(s->top)] = c;
}
// 字符出栈
char popChar(CharStack *s) {
return s->data[(s->top)--];
}
// 获取栈顶字符
char getTopChar(CharStack *s) {
return s->data[s->top];
}
// 判断字符栈是否为空
int isEmptyChar(CharStack *s) {
return s->top == -1;
}
// 初始化整数栈
void initIntStack(IntStack *s) {
s->top = -1;
}
// 整数入栈
void pushInt(IntStack *s, int num) {
s->data[++(s->top)] = num;
}
// 整数出栈
int popInt(IntStack *s) {
return s->data[(s->top)--];
}
// 获取运算符优先级
int priority(char op) {
switch(op) {
case '+':
case '-': return 1;
case '*':
case '/': return 2;
default: return 0;
}
}
// 判断是否是运算符
int isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
// 中缀转后缀并求值
void convertAndEvaluate(char *infix, char *postfix, int *result) {
CharStack opStack;
IntStack valStack;
initCharStack(&opStack);
initIntStack(&valStack);
int i, j = 0;
int len = strlen(infix);
for (i = 0; i < len; i++) {
char c = infix[i];
// 如果是数字
if (c >= '1' && c <= '9') {
postfix[j++] = c;
pushInt(&valStack, c - '0');
}
// 如果是左括号
else if (c == '(') {
pushChar(&opStack, c);
}
// 如果是右括号
else if (c == ')') {
while (!isEmptyChar(&opStack) && getTopChar(&opStack) != '(') {
char op = popChar(&opStack);
postfix[j++] = op;
// 计算
int b = popInt(&valStack);
int a = popInt(&valStack);
int val;
switch(op) {
case '+': val = a + b; break;
case '-': val = a - b; break;
case '*': val = a * b; break;
case '/': val = a / b; break;
}
pushInt(&valStack, val);
}
// 弹出左括号
if (!isEmptyChar(&opStack) && getTopChar(&opStack) == '(') {
popChar(&opStack);
}
}
// 如果是运算符
else if (isOperator(c)) {
while (!isEmptyChar(&opStack) && getTopChar(&opStack) != '(' &&
priority(getTopChar(&opStack)) >= priority(c)) {
char op = popChar(&opStack);
postfix[j++] = op;
// 计算
int b = popInt(&valStack);
int a = popInt(&valStack);
int val;
switch(op) {
case '+': val = a + b; break;
case '-': val = a - b; break;
case '*': val = a * b; break;
case '/': val = a / b; break;
}
pushInt(&valStack, val);
}
pushChar(&opStack, c);
}
}
// 弹出剩余运算符
while (!isEmptyChar(&opStack)) {
char op = popChar(&opStack);
if (op != '(' && op != ')') {
postfix[j++] = op;
// 计算
int b = popInt(&valStack);
int a = popInt(&valStack);
int val;
switch(op) {
case '+': val = a + b; break;
case '-': val = a - b; break;
case '*': val = a * b; break;
case '/': val = a / b; break;
}
pushInt(&valStack, val);
}
}
postfix[j] = '\0';
*result = popInt(&valStack);
}
int main() {
int N;
scanf("%d", &N);
getchar(); // 吸收换行符
for (int k = 0; k < N; k++) {
char infix[MAX_SIZE];
char postfix[MAX_SIZE];
int result;
fgets(infix, MAX_SIZE, stdin);
// 去掉换行符
int len = strlen(infix);
if (infix[len - 1] == '\n') {
infix[len - 1] = '\0';
}
convertAndEvaluate(infix, postfix, &result);
printf("%s %d\n", postfix, result);
}
return 0;
}
三 数据测试及说明
1.题目名称:试实现普里姆最小生成树算法
2.题目名称:本题要求实现二路归并排序中的归并操作,待排序列的长度1<=n<=1000。
3.题目名称:假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集
4.题目名称:把题目给出中缀表达式转换为后缀表达式输出,并求后缀表达式的值。为简单起见,我们约定:1、输入的中缀表达式一定是合法的,并且只含数字,四种运算符+、-、*、/和小括号;2、运算数都是一位正整数(1~9);3、输入的中缀表达式不超过20个字符;4、除法运算的结果仍然是正整数。
四 PTA提交截图
1.题目名称:试实现普里姆最小生成树算法
2.题目名称:本题要求实现二路归并排序中的归并操作,待排序列的长度1<=n<=1000。
3.题目名称:假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集
4.题目名称:题目名称:把题目给出中缀表达式转换为后缀表达式输出,并求后缀表达式的值。为简单起见,我们约定:1、输入的中缀表达式一定是合法的,并且只含数字,四种运算符+、-、*、/和小括号;2、运算数都是一位正整数(1~9);3、输入的中缀表达式不超过20个字符;4、除法运算的结果仍然是正整数。
五 个人体会
本次算法与数据结构综合训练,我一共完成了四道编程题目:普里姆最小生成树、二路归并的归并操作、任务调度拓扑排序、中缀表达式转后缀并求值。通过动手编写代码、调试程序、在 PTA 平台提交测试,我对图、排序、栈、队列这些核心数据结构有了更加深刻的理解,不再只停留在课本理论层面。
在实现普里姆算法时,我理解了最小生成树的生成逻辑,学会使用closedge数组记录顶点之间的最小权值,逐步把顶点加入生成树集合。调试过程中,我意识到初始化数组、及时更新权值信息是程序正确运行的关键,一个小的赋值错误就会导致生成的边完全错误。
二路归并排序的归并操作让我练习了数组的分片合并,借助临时数组完成两个有序子序列的归并。我体会到,使用动态内存分配时,一定要记得释放内存,避免内存泄漏;同时数组下标边界很容易出错,low、m、high 三个边界值需要仔细区分,否则会出现元素漏拷贝、数组越界的问题。
任务调度这道题考察拓扑排序。我采用邻接矩阵存储图结构,维护入度数组,借助队列完成拓扑序列的生成。做完这道题,我明白了拓扑排序的适用场景:判断有向图是否存在环,用来处理存在先后依赖关系的任务。只有入度为 0 的节点才可以优先执行,如果最终能够访问全部节点,代表任务没有循环依赖,可以全部执行。
中缀表达式转后缀表达式是本次训练难度最大的一题,核心是调度场算法,双重栈(运算符栈、数值栈)的使用让我收获很多。我搞懂了运算符优先级和结合性的意义:对于加减乘除这类左结合运算符,栈顶运算符判断条件要使用>=,如果只用>,遇到连续同级运算,例如8/4/2,运算顺序就会出错,得到错误结果。括号的处理也很关键,遇到右括号要不断弹出栈内运算符直到遇见左括号,并且左括号本身不输出到后缀表达式。同时程序一边生成后缀表达式,一边利用数值栈完成计算,加深了我对栈 “后进先出” 特性的理解。
在 PTA 提交过程中,遇到过编译警告、边界样例不通过等问题。我学会自己构造测试用例,手动推演程序执行流程,定位代码 bug。很多时候代码逻辑大体正确,但细节处理考虑不周,比如输入字符串需要处理换行符、数组末尾手动添加字符串结束符\0等,这些细节直接决定程序能否 AC。
总的来说,这次综合训练让我明白,学习数据结构不能只背原理,一定要动手写代码。理论看懂和代码写对中间还有很大差距。后续我会更加注重代码细节,加强边界测试,遇到算法多手动模拟执行过程,提升自己分析问题、调试程序的能力。
浙公网安备 33010602011771号