数据结构十大习题|part1

前言


本篇博客给出核心代码及其思路注释,还有其他不同的方法不一一讨论


一.一元多项式运算

实验内容:

该程序实现两个一元多项式的求和运算。程序已给出一元多项式存储结构的定义,建立存储结构的函数,输出一元多项式的函数,及主函数。需补充完成一元多项式的求和运算的函数编写。输入数据在文件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是这样的:

0000
1002
0304
0050

它有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]
-1112

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]
-0123

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如下:

010
030
005
024

这就是快速转置的每一个步骤。

代码框架及实现:

框架:

#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中的值,保持不变
		}
	}
 //================================================
}

未完待续…

  • 17
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值