数据结构十大习题
前言
本篇博客给出核心代码及其思路注释,还有其他不同的方法不一一讨论
一.一元多项式运算
实验内容:
该程序实现两个一元多项式的求和运算。程序已给出一元多项式存储结构的定义,建立存储结构的函数,输出一元多项式的函数,及主函数。需补充完成一元多项式的求和运算的函数编写。输入数据在文件exp01A.in中,其中数据依次为两个一元多项式各项的系数和指数,以指数从高到低为序
相关知识及解题思路:
解题思路:
以线性表来描述一元多项式,存储结构采用单链表,每个结点存储多项式中某一项的系数和指数,建立单链表时指数低的结点列于指数高的结点之后,即线性表的元素按指数递减有序排列。在多项式求和运算时,将指数相同的项的系数相加,其和非0则存储该项。乘积运算时,运用循环将2个多项式的各项交叉相乘,存储每2项的积(系数的积,指数的和)于新建立的结点,之后将这些结点中指数相同的各项系数之和非0的项合并为1个结点,最后释放多余的临时结点。
代码框架及实现:
框架:
#include <stdio.h>
#include <iostream>
#include <cmath>
using namespace std;
typedef
struct node // 定义单链表结点的数据类型 mulpoly
{ double coef; // 表示系数
int exp; // 表示指数
struct node *next; // 指向下一项结点的指针
}mulpoly;
mulpoly *create() // 指数从高到低,逐项输入系数和指数,建单链表
{ mulpoly *h, *p, *q;
h=new mulpoly; // 建立头结点
p=h; // 头结点作为初始的前驱
double x;
int y;
while(1)
{ cin>>x; // 输入系数
cin>>y; // 输入指数
if(fabs(x)>1E-8)
{ q=new mulpoly;
q->coef=x; q->exp=y;
p->next=q;
p=q;
}
if(y==0) break; // 输入指数是零时终止
}
p->next=NULL; // 最后一项结点无后继
return h; // 返回头指针
}
void output(mulpoly *h) // 遍历单链表,输出一元多项式
{ mulpoly *p;
cout<<"Y=";
p=h->next;
if(p==NULL) cout<<"0"; // 多项式为0
else // 输出多项式的第1项
{ cout<<p->coef;
if(p->exp!=0) cout<<"X";
if(p->exp>1) cout<<"^"<<p->exp;
p=p->next;
}
while(p!=NULL) // 逐项输出后续各项
{ if(p->coef>0) cout<<"+";
cout<<p->coef;
if(p->exp!=0) cout<<"X";
if(p->exp>1) cout<<"^"<<p->exp;
p=p->next;
}
cout<<endl;
}
mulpoly *poly_add(mulpoly *a,mulpoly *b) // 两个多项式求和函数
{//************************************************
//================================================
}
int main()
{ mulpoly *ha, *hb, *hc;
freopen("exp01A.in", "r", stdin);
freopen("exp01A.out", "w", stdout);
cout<<"Input each item of a multipoly:\n";
ha=create();
cout<<"Input each item of another multipoly:\n";
hb=create();
hc=poly_add(ha,hb);
cout<<"The sum of a multipoly: "; output(ha);
cout<<" and another one: "; output(hb);
cout<<" is: "; output(hc);
return 1;
}
实现:
mulpoly *poly_add(mulpoly *a,mulpoly *b) // 两个多项式求和函数
{//************************************************
mulpoly *c, *pa, *pb, *pc, *q;
c=new mulpoly; // 建立结果多项式单链表的头结点
pc=c;
pa=a->next; pb=b->next;
while((pa!=NULL)&&(pb!=NULL)) // 当两个多项式都有非零项时
{ if(pa->exp>pb->exp) // 被加项的指数较大
{ q=new mulpoly; // 创建一个新节点,存储被加项的系数和指数
q->coef=pa->coef; q->exp=pa->exp; pa=pa->next; // 将被加项的信息赋给新节点,并将被加项指针后移
pc->next=q; pc=q; // 将新节点链接到结果多项式,并将结果指针后移
}
else if(pa->exp<pb->exp) // 被加项的指数较小
{ q=new mulpoly; // 创建一个新节点,存储被加项的系数和指数
q->coef=pb->coef; q->exp=pb->exp; pb=pb->next; // 将被加项的信息赋给新节点,并将被加项指针后移
pc->next=q; pc=q; // 将新节点链接到结果多项式,并将结果指针后移
}
else // 指数相同,需要合并同类项
{ if(fabs(pa->coef+pb->coef)>1e-8) // 指数相同,系数之和非零,需要保留这一项
{ q=new mulpoly; // 创建一个新节点,存储合并后的系数和指数
q->coef=pa->coef+pb->coef; q->exp=pa->exp; // 将合并后的信息赋给新节点
pc->next=q; pc=q; // 将新节点链接到结果多项式,并将结果指针后移
}
pa=pa->next; pb=pb->next; // 将两个被加项指针都后移,继续比较下一对同类项
}
}
while(pa!=NULL) // 链接多项式a的剩余项
{ q=new mulpoly; // 创建一个新节点,存储剩余项的系数和指数
q->coef=pa->coef; q->exp=pa->exp; pa=pa->next; // 将剩余项的信息赋给新节点,并将剩余项指针后移
pc->next=q; pc=q; // 将新节点链接到结果多项式,并将结果指针后移
}
while(pb!=NULL) // 链接多项式b的剩余项
{ q=new mulpoly; // 创建一个新节点,存储剩余项的系数和指数
q->coef=pb->coef; q->exp=pb->exp; pb=pb->next; // 将剩余项的信息赋给新节点,并将剩余项指针后移
pc->next=q; pc=q; // 将新节点链接到结果多项式,并将结果指针后移
}
pc->next=NULL; // 结果多项式的最后一个节点的next置为NULL,表示结束
return c; // 返回头结点
}
二.迷宫探索
前言(动态内存分配c++语法):
1.new是一个操作符,它用于在堆上分配内存,并调用相应的构造函数。它的语法是:
new 类型名(参数列表)
它会返回一个指向新分配内存的指针,如果分配失败,会抛出一个bad_alloc异常。
2.delete也是一个操作符,它用于释放new分配的内存,并调用相应的析构函数。它的语法是:
delete 指针
它会将指针所指向的内存归还给系统,如果指针是NULL,不会有任何效果。如果指针不是由new分配的,或者已经被delete过,会导致未定义行为。
实验内容:
相关知识及解题思路:
相关知识:
栈的初始化、入栈、出栈、取栈顶等基本运算,栈的存储结构
解题思路:
1.运用栈(探索栈)来存储路径探索过程中的位置信息,并入口处初始入栈。在每个点上,存在8个探索方向,寻找第一个可能的下一个点。若存在这样的点,则前进一步,新进入的点及其周围未探索和已探索的点的信息入栈;若已没有可进一步的点,则当前位置出栈,然后取栈顶做新一轮的探索。如此深入探索或回溯,直至找到出口,或栈回溯至空(不存在走出迷宫的路径)。
由于探索栈出栈序列是从终点到出发点的序列,为了得到其逆序列,可将探索栈的出栈序列如另一个栈(路径栈),再由路径栈出栈即可得从入口到出口的坐标序列。
2.top和pos_top都是指针变量,它们的区别是指向的栈不同。top是指向原来的栈,也就是存储迷宫中可行走位置的栈,每个节点都包含一个位置的横纵坐标和一个方向。pos_top是指向位置栈,也就是存储走出迷宫的路径的栈,每个节点只包含一个位置的横纵坐标。原来的栈是在寻找路径的过程中使用的,位置栈是在回溯显示路径的过程中使用的。
3.第一个while循环是为了实现将原来的栈中的节点全部转移到位置栈中,从而得到一个反向的路径。因为原来的栈是按照进入迷宫的顺序存储的,所以最后进入栈的节点是最先进入迷宫的节点,也就是起点。而位置栈是按照出栈的顺序存储的,所以最后进入栈的节点是最后出栈的节点,也就是终点。所以,通过这个循环,就可以将原来的栈中的路径反转过来,从而得到一个从起点到终点的路径。
4.第二个while循环是为了实现将位置栈中的节点全部输出,并释放内存。因为位置栈中的节点是按照从起点到终点的顺序存储的,所以输出的时候就可以得到一个正确的路径。同时,为了避免内存泄漏,每输出一个节点,就要删除一个节点,直到位置栈为空。
5.假设原来的栈中有四个节点,分别是(1,1),(2,2),(3,3),(4,4),它们表示迷宫中的位置,其中(1,1)是终点,(4,4)是起点。位置栈一开始是空的。
第一个while循环的作用是将原来的栈中的节点全部转移到位置栈中,从而得到一个反向的路径。每次循环,都会创建一个新的位置节点,将原来栈顶节点的坐标赋值给它,然后将它插入到位置栈顶,同时删除原来的栈顶节点,并更新栈顶指针。循环结束后,原来的栈为空,位置栈中有四个节点,分别是(4,4),(3,3),(2,2),(1,1),它们表示从起点到终点的路径。
第二个while循环的作用是将位置栈中的节点全部输出,并释放内存。每次循环,都会输出位置栈顶节点的坐标,然后删除位置栈顶节点,并更新位置栈顶指针。循环结束后,位置栈为空,输出结果是(4,4) (3,3) (2,2) (1,1) ,这就是走出迷宫的路径。
代码框架及实现:
框架:
#include <iostream>
#include <stdio.h>
using namespace std;
int m, n; // 表示迷宫矩阵的行数,列数
int **maze; // 指向迷宫矩阵的指针
typedef
struct node
{ int ipos,jpos;
int direct[8];
struct node *next;
}node_type; // 探索栈结点类型
typedef
struct postp
{ int ipos,jpos;
struct postp *next;
}pos_type; // 路径栈结点类型
node_type *top; // 指向探索栈的栈顶指针
pos_type *pos_top; // 指向路径栈的栈顶指针
void init(int x0, int y0) // 初始化探索栈底,即入口坐标与其周围8个点状态
{ top=new node_type;
top->ipos=x0;
top->jpos=y0;
top->direct[0]=maze[x0-1][y0-1];
top->direct[1]=maze[x0-1][y0];
top->direct[2]=maze[x0-1][y0+1];
top->direct[3]=maze[x0][y0-1];
top->direct[4]=maze[x0][y0+1];
top->direct[5]=maze[x0+1][y0-1];
top->direct[6]=maze[x0+1][y0];
top->direct[7]=maze[x0+1][y0+1];
top->next=NULL;
}
void check_cycle(int ii, int jj, node_type *newp) // 检测新踏进点周围的某一点是否已在路径上
{ node_type *p=top;
while((p!=NULL)&&(!((p->ipos==ii)&&(p->jpos==jj))))
p=p->next;
if(p!=NULL)
switch(newp->ipos-ii)
{ case 1: switch(newp->jpos-jj)
{ case 1: newp->direct[0]=0; break;
case 0: newp->direct[1]=0; break;
case -1: newp->direct[2]=0;
}
break;
case 0: switch(newp->jpos-jj)
{ case 1: newp->direct[3]=0; break;
case -1: newp->direct[4]=0;
}
break;
case -1: switch(newp->jpos-jj)
{ case 1: newp->direct[5]=0; break;
case 0: newp->direct[6]=0; break;
case -1: newp->direct[7]=0;
}
}
}
void push(int ii, int jj) // 探索中的一次踏进(入栈)
{ node_type *p;
p=new node_type;
p->ipos=ii;
p->jpos=jj;
p->direct[0]=maze[ii-1][jj-1];
p->direct[1]=maze[ii-1][jj];
p->direct[2]=maze[ii-1][jj+1];
p->direct[3]=maze[ii][jj-1];
p->direct[4]=maze[ii][jj+1];
p->direct[5]=maze[ii+1][jj-1];
p->direct[6]=maze[ii+1][jj];
p->direct[7]=maze[ii+1][jj+1];
if(p->direct[0]) check_cycle(ii-1,jj-1,p);
if(p->direct[1]) check_cycle(ii-1,jj,p);
if(p->direct[2]) check_cycle(ii-1,jj+1,p);
if(p->direct[3]) check_cycle(ii,jj-1,p);
if(p->direct[4]) check_cycle(ii,jj+1,p);
if(p->direct[5]) check_cycle(ii+1,jj-1,p);
if(p->direct[6]) check_cycle(ii+1,jj,p);
if(p->direct[7]) check_cycle(ii+1,jj+1,p);
p->next=top;
top=p;
}
void pop() // 探索中的回退(一次出栈)
{ node_type *p;
p=top;
top=p->next;
delete p;
}
void show_maze() // 显示迷宫矩阵
{ int i,j;
cout<<"A maze shown as below: \n";
for(i=0; i<m; i++)
{ cout<<" ";
for(j=0; j<n; j++) cout<<maze[i][j]<<' ';
cout<<endl;
}
}
void show_pathway() // 回溯显示走出迷宫的路径
{//************************************************
//================================================
cout<<endl;
}
int main()
{ int i, j;
freopen("exp02.in", "r", stdin);
freopen("exp02.out", "w", stdout);
cin>>m;
cin>>n;
maze=new int *[m];
for(i=0; i<m; i++) maze[i]=new int [n];
for(i=0; i<m; i++)
for(j=0; j<n; j++) cin>>maze[i][j];
show_maze();
init(1,1);
while((top!=NULL)&&(!((top->ipos==m-2)&&(top->jpos==n-2))))
{ for(i=0; i<8; i++)
if(top->direct[i]==1)
{ top->direct[i]=0;
break;
}
switch(i)
{ case 0: push(top->ipos-1,top->jpos-1); break;
case 1: push(top->ipos-1,top->jpos); break;
case 2: push(top->ipos-1,top->jpos+1); break;
case 3: push(top->ipos,top->jpos-1); break;
case 4: push(top->ipos,top->jpos+1); break;
case 5: push(top->ipos+1,top->jpos-1); break;
case 6: push(top->ipos+1,top->jpos); break;
case 7: push(top->ipos+1,top->jpos+1); break;
default: pop();
}
}
if(top!=NULL)
{ cout<<"Pathway to get out off the maze: \n";
show_pathway();
}
else
cout<<"No pathway\n";
return 1;
}
实现:
void show_pathway() // 回溯显示走出迷宫的路径
{//************************************************
node_type *nodep=top; //定义一个指向原来栈顶的指针
pos_type *posp; //定义一个指向位置类型的指针
pos_top=NULL; //初始化位置位置栈为空
while(top!=NULL) //当原来的栈不为空时
{ posp=new pos_type; //创建一个新的位置节点
posp->ipos=nodep->ipos; //将栈顶节点的横坐标赋值给位置节点
posp->jpos=nodep->jpos; //将栈顶节点的纵坐标赋值给位置节点
posp->next=pos_top; //将位置节点插入到位置栈顶
pos_top=posp; //更新位置原来栈顶指针
top=nodep->next; //将位置栈顶指针后移一位
delete nodep; //删除原来的栈顶节点
nodep=top; //更新原来栈顶指针
}
while(pos_top!=NULL) //当位置栈不为空时
{ cout<<'('<<pos_top->ipos<<','<<pos_top->jpos<<") "; //输出位置节点的坐标
posp=pos_top; //保存位置栈顶指针
pos_top=pos_top->next; //将位置栈顶指针后移一位
delete posp; //删除原来的位置节点
}
//================================================
cout<<endl; //输出换行符
}
三.稀疏矩阵转置
实验内容:
相关知识及解题思路:
相关知识:
1.三元表是一种用来存储稀疏矩阵的压缩方式,也叫三元组表。它只存储矩阵中的非零元素,以及它们的行号和列号。三元表可以用数组或链表来实现。
一般来说,三元表能包含矩阵的所有信息,因为它记录了矩阵的行数、列数和非零元素的位置和值。但是,如果矩阵中有重复的非零元素,或者有特殊的结构或规律,那么三元表可能不是最优的存储方式,因为它会占用更多的空间或增加更多的运算复杂度。
2.三元表的优点是可以节省空间,避免存储大量的零元素,也可以方便地进行矩阵的加法和转置等运算 。三元表的缺点是不能直接进行矩阵的乘法和求逆等运算,也不适合存储非零元素较多或有规律分布的矩阵 。
解题思路:
假设原始矩阵A是这样的:
0 | 0 | 0 | 0 |
---|---|---|---|
1 | 0 | 0 | 2 |
0 | 3 | 0 | 4 |
0 | 0 | 5 | 0 |
它有4列,3行,5个非零元素。我们要将它转置为矩阵B,使得B的行数等于A的列数,B的列数等于A的行数,B的非零元素个数等于A的非零元素个数。具体步骤如下:
1. 创建两个数组num和pos,分别用于存储原始矩阵每一列的非零元素的个数和位置。num的长度为A的列数加1,pos的长度也为A的列数加1。这里加1是为了方便使用下标从1开始。
2. 遍历原始矩阵的所有非零元素,统计每一列的非零元素的个数,并存入num数组。遍历A的所有非零元素(A.num表示非零元素个数),对于每一个非零元素,它取出它的列号(A.data[k].j表示第k个非零元素的列号),然后在num数组中对应的位置加一(num[A.data[k].j]++表示num数组中第A.data[k].j个元素加一)。这样,当循环结束后,num数组就记录了A中每一列有多少个非零元素。例如,第一列有一个非零元素,所以num[1]=1;第二列有一个非零元素,所以num[2]=1;第三列有一个非零元素,所以num[3]=1;第四列有两个非零元素,所以num[4]=2。遍历完成后,num数组如下:
num[0] | num[1] | num[2] | num[3] | num[4] |
---|---|---|---|---|
- | 1 | 1 | 1 | 2 |
3. 根据num数组,计算每一列的第一个非零元素在转置矩阵中的位置,并存入pos数组。具体地,第一列的位置为0,其他列的位置等于前一列的位置加上前一列的非零元素的个数。例如,第一列的位置为pos[1]=0;第二列的位置为pos[2]=pos[1]+num[1]=0+1=1;第三列的位置为pos[3]=pos[2]+num[2]=1+1=2;第四列的位置为pos[4]=pos[3]+num[3]=2+1=3。计算完成后,pos数组如下:
pos[0] | pos[1] | pos[2] | pos[3] | pos[4] |
---|---|---|---|---|
- | 0 | 1 | 2 | 3 |
4. 再次遍历原始矩阵的所有非零元素,根据pos数组,将它们分配给转置矩阵的相应行。同时,将pos数组中的相应位置加1,为下一个同列元素做准备。转置过程中,元素的值不变,只是行下标和列下标互换。例如,原始矩阵中第一个非零元素是(1,0,1),它在转置矩阵中对应的位置是pos[0]=0,所以将它分配给转置矩阵中(0,1,1);然后将pos[0]加1,变为1;原始矩阵中第二个非零元素是(2,1,3),它在转置矩阵中对应的位置是pos[1]=1,所以将它分配给转置矩阵中(1,2,3);然后将pos[1]加1,变为2;以此类推,直到遍历完所有的非零元素。遍历完成后,转置矩阵B如下:
0 | 1 | 0 |
---|---|---|
0 | 3 | 0 |
0 | 0 | 5 |
0 | 2 | 4 |
这就是快速转置的每一个步骤。
代码框架及实现:
框架:
#include <iostream>
#include <stdio.h>
#define M 64
using namespace std;
typedef
struct
{ int i, j, v; // 表示行、列、值
}triple; // 三元组类型
typedef
struct
{ triple data[M];
int row, col, num;
}triGroup; // 三元组表类型
void input(triGroup &A) // 输入数据建立三元组表
{//************************************************
//================================================
}
void output(const triGroup &A) // 输出三元组表
{ int p;
cout<<A.row<<' '<<A.col<<' '<<A.num<<endl;
for(p=0; p<A.num; p++)
cout<<A.data[p].i<<' '<<A.data[p].j<<' '<<A.data[p].v<<endl;
}
void transSM(const triGroup &A, triGroup &B) // 转置矩阵
{//************************************************
//================================================
}
int main()
{ triGroup S, T;
freopen("exp04.in", "r", stdin);
freopen("exp04.out", "w", stdout);
input(S);
transSM(S,T);
output(T);
fclose(stdin);
fclose(stdout);
return 1;
}
实现:
part1:
void input(triGroup &A) // 输入数据建立三元组表
{ int p;
cin>>A.row; // 从标准输入读取矩阵的行数
cin>>A.col; // 从标准输入读取矩阵的列数
cin>>A.num; // 从标准输入读取矩阵的非零元素个数
for(p=0; p<A.num; p++) // 循环读取每个非零元素的信息
{ cin>>A.data[p].i; // 读取非零元素的行下标
cin>>A.data[p].j; // 读取非零元素的列下标
cin>>A.data[p].v; // 读取非零元素的值
}
}
part2:
void transSM(const triGroup &A, triGroup &B) // 转置矩阵
{//************************************************
B.row=A.col; B.col=A.row; B.num=A.num; // 将B的行数、列数和非零元素个数设为A的列数、行数和非零元素个数
if(A.num) // 如果A有非零元素
{ int k, p;
int *num=new int[A.col+1], *pos=new int[A.col+1]; // 动态分配两个数组,num用于存储每一列的非零元素个数,pos用于存储每一列的第一个非零元素在B中的位置
for(k=1; k<=A.col; k++) num[k]=0; // 初始化num数组为0
for(k=0; k<A.num; k++) num[A.data[k].j]++; // 遍历A的所有非零元素,统计每一列的非零元素个数
pos[1]=0; // 第一列的第一个非零元素在B中的位置为0
for(k=2; k<=A.col; k++) pos[k]=pos[k-1]+num[k-1]; // 计算其他列的第一个非零元素在B中的位置,等于前一列的位置加上前一列的非零元素个数
for(int k=0; k<A.num; k++) // 遍历A的所有非零元素
{
p=pos[A.data[k].j]++; // 找到该元素在B中对应的位置,同时将该位置加1,为下一个同列元素做准备
B.data[p].i=A.data[k].j; B.data[p].j=A.data[k].i; // 将该元素在B中的行下标和列下标分别设为A中的列下标和行下标,实现转置
B.data[p].v=A.data[k].v; // 将该元素在B中的值设为A中的值,保持不变
}
}
//================================================
}
未完待续…