插值项目
一、项目说明
1、面向一维插值和线性拟合,含有牛顿插值、线性插值等四种插值方式;
2、设计了可用鼠标直接操作的GUI界面,比上次贪吃蛇项目的GUI更好;
二、代码说明
1、头文件
#include <graphics.h> // 引用图形库头文件
#include <conio.h> //控制台输入输出
#include <stdio.h>
#include <windows.h> //用到了定时函数sleep()
#include <math.h> //数学函数库
#include <string.h> //字符串处理函数
2、插值部分(只解释代码,原理部分的相关资料见文尾)
void nuton()//牛顿插值
{
event=0;
cleardevice();//清屏(取决于背景色)
int k,m;
//算出插商表
for(int i=0;i<non;i++){
numc[i][0]=numy[i];
}
for(k=1;k<non;k++)
for(m=k;m<non;m++)
{
numc[m][k]=(numc[m-1][k-1]-numc[m][k-1])/(numx[m-k]-numx[m-k+1]);
}
for(int i=0;i<nan;i++)
{
float t=1.0;
numry[i]=numc[0][0];
for(int j=1;j<=non;j++)
{
t=t*(numr[i]-numx[j-1]);
numry[i]=numry[i]+numc[j][j]*t;
}
}
//此后为配合图形界面输出结果
char msg[40]="所求的插值为";
for(int i=0;i<nan;i++)
{
char temp[30];
sprintf(temp,"%f",numry[i]);
sprintf(msg,"%s%s ",msg,temp);
}
MessageBox(NULL,TEXT(msg),TEXT("结果"),MB_OK);
exit(0);
};
可以直接通过牛顿插值公式写出代码。
void langrage()//拉格朗日插值
{
cleardevice();//清屏(取决于背景色)
int k,m;
float a[30];
for(int i=0;i<nan;i++){
for(k=0;k<non;k++)
{
a[k]=numy[k];
for(m=0;m<non;m++)
{
if(m!=k) a[k]=a[k]*((numr[i]-numx[m])/(numx[k]-numx[m]));
}
numry[i]+=a[k];
}
}
char msg[40]="所求的插值为";
for(int i=0;i<nan;i++)
{ char temp[30];
sprintf(temp,"%f",numry[i]);
sprintf(msg,"%s%s ",msg,temp);
}
MessageBox(NULL,TEXT(msg),TEXT("结果"),MB_OK);
exit(0);
};
void fenduan()//分段线性
{
for(int i=0;i<nan;i++)
{
for(int j=0;j<non;j++)
{
if(numr[i]>numx[j]&&numr[i]<numx[j+1])
{
numry[i]=numy[j]+((numy[j+1]-numy[j])/(numx[j+1]-numx[j]))*(numr[i]-numx[j]);
break;
}
}
}
char msg[40]="所求的插值为";
for(int i=0;i<nan;i++)
{ char temp[30];
sprintf(temp,"%f",numry[i]);
sprintf(msg,"%s%s ",msg,temp);
}
MessageBox(NULL,TEXT(msg),TEXT("结果"),MB_OK);
exit(0);
};
拉格朗日插值和分段线性插值代码结构与牛顿插值一致。
3、拟合部分
拟合采用了最小二乘法,对一维数据进行拟合
void nihe()//线性拟合
{
float a,b;
float up=0,down=0,sumx=0,sumy=0;
for(int i=0;i<non;i++)
{
sumx+=numx[i];
sumy+=numy[i];
up+=numx[i]*numy[i];
down+=numx[i]*numx[i];
}
b=(up-sumx*sumy/non)/(down-sumx*sumx/non);
a=sumy/non-b*sumx/non;
for(int i=0;i<nan;i++)
{
numry[i]=numr[i]*b+a;
}
char msg[40];
sprintf(msg,"所求的函数为Y=%fX+%f,所求的点的值为",b,a);
for(int i=0;i<nan;i++)
{ char temp[30];
sprintf(temp,"%f",numry[i]);
sprintf(msg,"%s%s ",msg,temp);
}
MessageBox(NULL,TEXT(msg),TEXT("结果"),MB_OK);
exit(0);
};
公式如下:
4、界面部分(最为繁杂,作为重点讲解)
#define maxwidth 480
#define maxheight 360//规定界面的大小
int event=0;
int r1[4][4]={{40,10,440,50},{160,100,320,140},{160,160,320,200},{160,220,320,260}};
RECT R11={r1[0][0],r1[0][1],r1[0][2],r1[0][3]};
RECT R12={r1[1][0],r1[1][1],r1[1][2],r1[1][3]};
RECT R13={r1[2][0],r1[2][1],r1[2][2],r1[2][3]};
RECT R14={r1[3][0],r1[3][1],r1[3][2],r1[3][3]};//第一个界面的按钮位置参数
int r2[6][4]={{40,10,440,50},{40,100,200,140},{280,100,440,140},{40,200,200,240},{280,200,440,240}};
RECT R21={r2[0][0],r2[0][1],r2[0][2],r2[0][3]};
RECT R22={r2[1][0],r2[1][1],r2[1][2],r2[1][3]};
RECT R23={r2[2][0],r2[2][1],r2[2][2],r2[2][3]};
RECT R24={r2[3][0],r2[3][1],r2[3][2],r2[3][3]};
RECT R25={r2[4][0],r2[4][1],r2[4][2],r2[4][3]};
MOUSEMSG m;//鼠标指针
void win_inter();
void win_enter();
void win_enter1();
void nuton();//牛顿插值
void langrage();//拉格朗日插值
void fenduan();//分段线性插值
void nihe();//线性拟合
//构建了三个设置字体大小的函数,可以直接用,l-large;m-middle;s-small
void textl()
{
LOGFONT f;//字体样式指针
gettextstyle(&f); //获取字体样式
f.lfHeight=48;
_tcscpy(f.lfFaceName,_T("宋体")); //设置字体为宋体
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); //BLACK在graphic.h头文件里面被定义为黑色的颜色常量
}
void textm()
{
LOGFONT f;//字体样式指针
gettextstyle(&f); //获取字体样式
f.lfHeight=30;
_tcscpy(f.lfFaceName,_T("宋体")); //设置字体为宋体
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); //BLACK在graphic.h头文件里面被定义为黑色的颜色
}
void texts()
{
LOGFONT f;//字体样式指针
gettextstyle(&f); //获取字体样式
f.lfHeight=20;
_tcscpy(f.lfFaceName,_T("宋体")); //设置字体为宋体
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); //BLACK在graphic.h头文件里面被定义为黑色的颜色
}
//两个判断函数,用于分别在两个界面中判断鼠标是否移动到按钮内
int judge_b1(int x,int y)
{
if(x>r1[1][0]&&x<r1[1][2]&&y>r1[1][1]&&y<r1[1][3])return 1;
if(x>r1[2][0]&&x<r1[2][2]&&y>r1[2][1]&&y<r1[2][3])return 2;
if(x>r1[3][0]&&x<r1[3][2]&&y>r1[3][1]&&y<r1[3][3])return 3;
else return 0;
}
int judge_b2(int x,int y)
{
if(x>r2[1][0]&&x<r2[1][2]&&y>r2[1][1]&&y<r2[1][3])return 1;
if(x>r2[2][0]&&x<r2[2][2]&&y>r2[2][1]&&y<r2[2][3])return 2;
if(x>r2[3][0]&&x<r2[3][2]&&y>r2[3][1]&&y<r2[3][3])return 3;
if(x>r2[4][0]&&x<r2[4][2]&&y>r2[4][1]&&y<r2[4][3])return 4;
else return 0;
}
void win_first()//第一个界面
{
for(int i=0;i<256;i++)
{
setbkcolor(RGB(i,i,i));
cleardevice();
Sleep(5);
}
textl();
//现在规定的按钮区域中写出文字
drawtext("Math Tool",&R11,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R1内输入文字,水平居中,垂直居中,单行显示
textm();
drawtext("插值",&R12,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R2内输入文字,水平居中,垂直居中,单行显示
drawtext("拟合",&R13,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R3内输入文字,水平居中,垂直居中,单行显示
drawtext("退出",&R14,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R3内输入文字,水平居中,垂直居中,单行显示
setlinecolor(BLACK);
//然后画出矩形,形成按钮
rectangle(r1[1][0],r1[1][1],r1[1][2],r1[1][3]);
rectangle(r1[2][0],r1[2][1],r1[2][2],r1[2][3]);
rectangle(r1[3][0],r1[3][1],r1[3][2],r1[3][3]);
while(true)
{
m=GetMouseMsg();//读取鼠标操作
switch(m.uMsg)
{
case WM_MOUSEMOVE:
setrop2(R2_XORPEN);
setlinecolor(LIGHTCYAN);//线条颜色为亮青色
setlinestyle(PS_SOLID, 3);//设置画线样式为实现,10磅
setfillcolor(WHITE);//填充颜色为白色
if(judge_b1(m.x,m.y)!=0)
{
if(event != judge_b1(m.x,m.y))
{
event =judge_b1(m.x,m.y);//记录这一次触发的按钮
fillrectangle(r1[event][0],r1[event][1],r1[event][2],r1[event][3]);//有框填充矩形(X1,Y1,X2,Y2)
}
}
else
{
if(event != 0)//上次触发的按钮未被修正为原来的颜色
{
fillrectangle(r1[event][0],r1[event][1],r1[event][2],r1[event][3]);//两次同或为原来颜色
event = 0;
}
}
break;
case WM_LBUTTONDOWN:
setrop2(R2_NOTXORPEN);//二元光栅——NOT(屏幕颜色 XOR 当前颜色)
for(int i=0;i<=10;i++)//当鼠标点击时会产生水波特效
{
setlinecolor(RGB(25*i,25*i,25*i));//设置圆颜色
circle(m.x,m.y,2*i);
Sleep(30);//停顿30ms
circle(m.x,m.y,2*i);//抹去刚刚画的圆
}
switch(judge_b1(m.x,m.y))//switch函数,规定每个按钮的去向
{
case 1: win_inter();break;
case 2: win_enter1();
nihe();
break;
case 3: closegraph();exit(0);
default:
FlushMouseMsgBuffer();//清空鼠标消息缓存区
break;
}
break;
}
}
}
void win_inter()//第二个界面,与第一个界面基本一致
{
event=0;
setbkcolor(RGB(255,255,255));
cleardevice();//清屏(取决于背景色)
textl();
drawtext("插值",&R21,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
textm();
drawtext("牛顿",&R22,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R2内输入文字,水平居中,垂直居中,单行显示
drawtext("拉格朗日",&R23,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R3内输入文字,水平居中,垂直居中,单行显示
drawtext("线性插值",&R24,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R3内输入文字,水平居中,垂直居中,单行显示
drawtext("返回",&R25,DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形区域R2内输入文字,水平居中,垂直居中,单行显示
setlinecolor(BLACK);
rectangle(r2[1][0],r2[1][1],r2[1][2],r2[1][3]);
rectangle(r2[2][0],r2[2][1],r2[2][2],r2[2][3]);
rectangle(r2[3][0],r2[3][1],r2[3][2],r2[3][3]);
rectangle(r2[4][0],r2[4][1],r2[4][2],r2[4][3]);
while(true)
{
m=GetMouseMsg();//读取鼠标操作
switch(m.uMsg)
{
case WM_MOUSEMOVE:
setrop2(R2_XORPEN);
setlinecolor(LIGHTCYAN);//线条颜色为亮青色
setlinestyle(PS_SOLID, 3);//设置画线样式为实现,10磅
setfillcolor(WHITE);//填充颜色为白色
if(judge_b2(m.x,m.y)!=0)
{
if(event != judge_b2(m.x,m.y))
{
event =judge_b2(m.x,m.y);//记录这一次触发的按钮
fillrectangle(r2[event][0],r2[event][1],r2[event][2],r2[event][3]);//有框填充矩形(X1,Y1,X2,Y2)
}
}
else
{
if(event != 0)//上次触发的按钮未被修正为原来的颜色
{
fillrectangle(r2[event][0],r2[event][1],r2[event][2],r2[event][3]);//两次同或为原来颜色
event = 0;
}
}
break;
case WM_LBUTTONDOWN:
setrop2(R2_NOTXORPEN);//二元光栅——NOT(屏幕颜色 XOR 当前颜色)
for(int i=0;i<=10;i++)
{
setlinecolor(RGB(25*i,25*i,25*i));//设置圆颜色
circle(m.x,m.y,2*i);
Sleep(30);//停顿30ms
circle(m.x,m.y,2*i);//抹去刚刚画的圆
}
switch(judge_b2(m.x,m.y))
{
case 1: win_enter();
nuton();
break;
case 2: win_enter();
langrage();
break;
case 3: win_enter();
fenduan();
break;
case 4: cleardevice();//清屏(取决于背景色)
event=0;
win_first();
default:
FlushMouseMsgBuffer();//清空鼠标消息缓存区
break;
}
break;
}
}
}
void win_enter()//输入参数时的界面
{
InputBox(s,30,"请输入点的个数");//弹出对话框
strcpy(c,s);
non=c[0]-'0';
char str[50];
for(int i=0;i<non;i++)
{
sprintf(str,"请输入第%d个点的x坐标和y坐标,用空格隔开,支持浮点数",i+1);
InputBox(s,30,str);
sscanf(s,"%f%f",&numx[i],&numy[i]);
}
InputBox(s,30,"请输入想要计算的插值点的个数");
strcpy(c,s);
nan=c[0]-'0';
for(int i=0;i<nan;i++)
{
sprintf(str,"请输入所要求的第%d个插值点的x坐标",i+1);
InputBox(s,30,str);
sscanf(s,"%f",&numr[i]);
}
}
void win_enter1()
{
InputBox(s,30,"请输入点的个数");
strcpy(c,s);
non=c[0]-'0';
char str[50];
for(int i=0;i<non;i++)
{
sprintf(str,"请输入第%d个点的x坐标和y坐标,用空格隔开,支持浮点数",i+1);
InputBox(s,30,str);
sscanf(s,"%f%f",&numx[i],&numy[i]);
}
InputBox(s,30,"请输入想要计算的点的个数");
strcpy(c,s);
nan=c[0]-'0';
for(int i=0;i<nan;i++)
{
sprintf(str,"请输入所要求的第%d个点的x坐标",i+1);
InputBox(s,30,str);
sscanf(s,"%f",&numr[i]);
}
}
三、所遇困难和现存不足
1、GUI仍然不够美观
2、只能处理一维数据
3、离群点未判断(难搞,放弃了)
4、最后显示数据的对话框不能弹出到最前,会被数据录入窗口遮挡,需要手动挪开第一个窗口才能看到数据输出。
四、参考资料
插值原理:https://www.cnblogs.com/duye/p/8671820.html
界面设计:https://blog.csdn.net/weixin_44044411/article/details/104276757
感谢所有为我提供帮助的人。
五、源代码
https://download.csdn.net/download/qzl19/12708237