前言
最近看到一个帖子,是一个将数据的有序拟合的问题。有许多人留言讨论了,但并没有给出较为全面的证明。
原贴如下:
https://www.v2ex.com/t/442035
通过半天的努力,得到了较为可观的结果。
问题描述
一个单链表,每个节点里存储都是正整数,现在是无序的,可能会有重复数字,可以修改每个节点里的值,达到以下两个目标:
[1] 单链表变为有序的,从大到小,可以大于等于.
[2] 修改的△值最小.
举例:
1.单链表 2->4->1->3 ,可能改为 3->3->1->1 此时,此时链表有序,此时的△ = |(2 - 3)| + |(4 - 3)| + |(1 - 1)| + |(3 - 1)| = 4. 或者可以改为 2->2->2->2 此时的△ = |(2 - 2)| + |(4 - 2)| + |(1-2)| + |(3-2)| = 4.
2.单链表 1->19->2 ,可能改为 2->2->2,△ = 1 + 17 + 0 = 18.
阅题感触:
1.初看给人一种措手不及的感觉, 就比如 2->4->1->3 ,把第一个元素定位2那么后面每个元素都将受到影响,都得大于等于2,对于第一个元素就无从下手。
2,用穷举法:每个元素有M中可能,工N个元素,则复杂度位 0( N^M ),显然是糟糕的。
解题思路:
让新数据迎合给定的数据,能马上想到用最小二乘法去拟合,但是有一个前提,那就是拟合函数必须是递增的。
不妨假设给的数据是连续的,用函数f(x)表示,定义域(a,b),且f(x)在(a,b)上处处可倒。
拟合函数g(x),定义域(a,b),g(x)单调递增,且g(x)在(a,b)上处处可倒,g(x)的倒数恒大于等于0;
误差S等于f(x)-g(x)差的绝对值在(a,b)的积分
即求使得S最小时的g(x);
画个图观察:
误差S即为图像的面积
误差 Sg1=Sm+Sn
误差 Sg2=Sm
显然 Sg1>Sg2
通过上图可以看出g1是拟合的上限,g2是拟合的下限,目标L必然在g1与g2中间
显然拟合线是不唯一的
那么从s出发可以有两种方式,直线先沿着原曲线走一段距离在平行过去,曲线则直接拉过去,虽然终点有高有底,但实际目的都是合理划分与原曲线围成的面积,既然都一样,那就选简单直线吧。
已有 Sg1>Sg2
误差S=S1+S2
显然S与Sg1和Sg2是有大小区别的
不妨设 S<Sg2的
那么新的拟合线就在L与g2中间
如此,便可迭代下去,直至误差S收敛。
新的拟合线newL
将曲线画复杂一点
虽然曲线复杂了,但还是可以将它分为一个个上凸曲线(波)
对于A可以按照前面的方式拟合,但B和C呢
将B与C视作一个波 D
D对于A而言又是什么
D对于外部而言可以用平均值表示,即:D1
在这里便可以说明一下合并波的原理:
若此波(可以是一系列的并波)的均值比前波(可以是一系列的并波)的均值低,那么可以将它并入前波,前波更新均值。
回到问题上面 可以将每个值视作一个波,对于 2 ,4,1,3,如图
模拟算法过程
对于2,没有前波,均值为2
对于4,有前波2,均值为4大于2
对于1,有前波4,均值为1小于4
合并(4,1)均值为2.5
对于(4,1),有前波2,均值为2.5大于2
对于3,有前波(4,1),均值为3大于2.5
新的拟合值 2,2.5,2.5,3,S=0+1.5+1.5+0=3
对于4和1的选值有2,3
a 2,2,2,3 S=3
b 2,2,3,3 S=4
c 2,3,3,3 S=3
结论
1.最优解与整形取值有关,
2.可能解误差 >= 最优解误差
3.最优解误差 (不取整时等号恒成立)>= 拟合误差
代码实现
(抛弃整形束缚)
波峰
class Peak{
public Number getValue() {
return value;
}
public Number getFitValue() {
return fitValue;
}
public void setFitValue(Number fitValue) {
this.fitValue = fitValue;
}
public Peak(Number value) {
this.value=value;
}
private Number value;
private Number fitValue;
public double getError() {
return Math.abs(value.doubleValue()-fitValue.doubleValue());
}
public String toString() {
return "(value : "+value+"\tfitValue : "+fitValue+")\tError:\t"+getError()+"";
}
}
一系列波的并波
class Wave{
private ArrayList<Peak> peakList= new ArrayList<Peak>();
private double avg;
public Wave(Peak peak) {
addPeak(peak);
}
public void addPeak(Peak peak) {
peakList.add(peak);
avg=getAvg();
}
public void addWave(Wave wave) {
peakList.addAll(wave.peakList);
avg=getAvg();
}
public double getAvg() {
int sum=0;
for(Peak itme:peakList) {
sum+=itme.getValue().doubleValue();
}
return 1.0*sum/peakList.size();
}
public String toString() {
return "Wave\tavg : "+avg+"\n"+peakList;
}
public void fitting(){
peakList.forEach(e->e.setFitValue(avg));
}
}
拟合类
class OrderlyFitting {
private ArrayList<Wave> list= new ArrayList<Wave>();
public ArrayList<Peak> data= new ArrayList<Peak>();
public OrderlyFitting(double[] data5) {
for(int i=0;i<data5.length;i++) {
Peak peak=new Peak(data5[i]);
this.data.add(peak);
Wave wave=new Wave(peak);
this.list.add(wave);
}
link();
fitting();
}
public void link() {
int index=0;
while(index<list.size()) {
Wave wave=list.get(index);
Wave high = getHighfWave(wave);
if(high!=null) {
high.addWave(wave);
list.remove(wave);
index--;
}else {
index++;
}
}
}
public Wave getHighfWave(Wave wave) {
int index=list.indexOf(wave);
if(index>=1) {
Wave high = list.get(index-1);
if(wave.getAvg()<=high.getAvg()) {
return high;
}
}
return null;
}
public ArrayList<Wave> fitting(){
list.forEach(Wave::fitting);
return list;
}
public void show(){
double error=0;
for(int i=0;i<data.size();i++) {
Peak itme=data.get(i);
error+=itme.getError();
System.out.println("Peak "+(i+1)+" : "+itme);
}
System.out.println("error :\t"+error);
}
}
结果检验
public class Fit extends Application{
private LineChart chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
public void createSeries(){
double[] data1=new double[] {1,2,3,4};//{2,4,1,3};
double[] data2=new double[] {1,19,2};
double[] data3=new double[] {8,7,8,2,2,2};
double[] data4=new double[] {2,2,2,8,7,8};
double[] data5=new double[10];
for(int i=0;i<data5.length;i++) {
data5[i]=100*Math.random();
}
OrderlyFitting of=new OrderlyFitting(data1);
of.show();
ArrayList<Peak> data=of.data;
Series<Number, Number> series = new LineChart.Series<>();
Series<Number, Number> fit = new LineChart.Series<>();
series.setName("Data");
fit.setName("Fit");
for(int i=0;i<data.size();i++) {
series.getData().add(new Data<Number, Number>(i+1,data.get(i).getValue()));
fit.getData().add(new Data<Number, Number>(i+1,data.get(i).getFitValue()));
}
chart.getData().addAll(series,fit);
}
public Parent createContent() {
xAxis = new NumberAxis();
xAxis.setLabel("X-Axis");
yAxis = new NumberAxis();
yAxis.setLabel("Y-Axis");
chart = new LineChart(xAxis, yAxis);
chart.setId("linechart");
createSeries();
chart.setTitle("OrderlyFitting");
return chart;
}
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(createContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
测试数据 data1 [1 2 3 4]
原始数据以有序,完全拟合
测试数据 [2 4 1 3 ]
测试数据 [1,19,2 ]
测试数据 [ 8,7,8,2,2,2]
测试数据 [ 2,2,2,8,7,8]
测试数据 随机10个
读到这里想必你一定有个疑问,为什么一个波相对于外界要用平均值表示,而不以极大值,极小值,众数呢,读者可以自行研究。
还有需要说明的是,此过程只考虑递增的情况,如果拟合出来的是一条直线那么,很有可能数据是呈现总体递减的,被合并成了一个波,如需检验,将数据逆序输入即可。
我想说的话:
写了快两个月了,本想将博客作为求职的敲门砖,谁知却成了绊脚石。
许多东西网上都能查到,如果你质疑我,我只想说即便复制了这么多,并坚持了下来也应该有所收获吧。
能坚持看到这里,相逢即是有缘,你可以留下一个问题或者评论扣个1,我想看看是真的有人看了这篇文章,还是网站的自己给的阅读次数