数据的有序拟合

前言

最近看到一个帖子,是一个将数据的有序拟合的问题。有许多人留言讨论了,但并没有给出较为全面的证明。
原贴如下:
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,我想看看是真的有人看了这篇文章,还是网站的自己给的阅读次数

猜你喜欢

转载自blog.csdn.net/qq_39464369/article/details/89483151