c语言模拟实现DTW(动态时间规整算法)

关于DTW算法的原理这篇博客写的很好https://blog.csdn.net/aa8568849/article/details/53841189?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase
作者对原文的c程序进行注释和改进如下:


#include "stdio.h"
#include "windows.h" 

struct pointOritation//保存当前节点方向,用来回溯每个W点
{
    int frontI,frontJ;
};


/*求最小累计距离的二维数组g,
传入参数p为标准模板和测试模板各元素之间的欧氏距的二维数组的首地址
n,m为数组的行数和列数
g为所求最小累积距离数组的首地址 
pr为保存g各个元素方向的结构体数组的首地址 */
void gArray(int *p,int n,int m,int *g,struct pointOritation *pr)
{
	//定义行标和列标变量i,j
	int i, j; 
	
    *(g+(n-1)*m)=(*(p+(n-1)*m))*2;	//计算g起始点的值及其上个节点的行标和列标(最左下角的点)即g[5][0] 
    (pr+(n-1)*m)->frontI = -1;
	(pr+(n-1)*m)->frontJ = -1;

    for (i=1; i<m; i++)//循环m-1次计算g最下面一横的值,(除g[5][0])(只有一个方向) 
    {
        *(g+(n-1)*m+i)=*(g+(n-1)*m+i-1)+*(p+(n-1)*m+i);  //求值 
        (pr+(n-1)*m+i)->frontI=n-1; 	// 求当前节点的最小累计距离的方向 
        (pr+(n-1)*m+i)->frontJ=i-1;     //保存方法为保存当前节点的上个节点的数组下标 
        // frontI为行标,frontJ为列标 
    }

    for (i=n-2; i>=0; i--)//循环n-1次最左边的一竖的值, (除g[5][0])(只有一个方向)
    {
        *(g+i*m)=*(g+(i+1)*m)+*(p+i*m);
        (pr+i*m)->frontI=i+1;
        (pr+i*m)->frontJ=0;
    }
    
    //计算剩余网格的G值   i为行标,j为列标 
    for (i=n-2; i>=0; i--)
    {
        for (j=1; j<m; j++)
        {
            int left,under,incline;  //分别表示当前节点三个方向上的累计距离值 
            left=*(g+i*m+j-1)+*(p+i*m+j);
            under=*(g+(i+1)*m+j)+*(p+i*m+j);
            incline=*(g+(i+1)*m+j-1)+(*(p+i*m+j))*2;

            //从左、下、斜三个方向选出最小的
            int min=left;
            *(g+i*m+j)=min;
            (pr+i*m+j)->frontI=i;
            (pr+i*m+j)->frontJ=j-1;

            if (min>under)
            {
                min=under;
                *(g+i*m+j)=min;
                (pr+i*m+j)->frontI=i+1;
                (pr+i*m+j)->frontJ=j;
            }
            if (min>incline)
            {
                min=incline;
                *(g+i*m+j)=min;
                (pr+i*m+j)->frontI=i+1;
                (pr+i*m+j)->frontJ=j-1;
            }
        }
    }
    
    //输出G数组
    printf("g矩阵如下:\n"); 
    for (i=0; i<n; i++)
    {
        for (j=0; j<m; j++)
        {
            printf("%d\t",*(g+i*m+j));
        }
		printf("\n");
    }
    
    //输出方向数组
    printf("方向矩阵如下:\n");
    for (i=0; i<n; i++)
    {
        for (j=0; j<m; j++)
        {
            printf("(%d,%d)\t",(pr+i*m+j)->frontI,(pr+i*m+j)->frontJ);
        }
		printf("\n");
    }
}

void printPath(struct pointOritation *po,int n,int m,int *g)
{
	//由于最短路径是从终点回溯找到起点,所以这里利用一个结构体数组实现正序输出路径
	struct temp_save{
		int i, j, distance;
	};
	struct temp_save temp[10];
	int t = 0;
	printf("路径如下:\n");
    int i=0,j=m-1; 
    while (1)
    {	
        int ii=(po+i*m+j)->frontI,jj=(po+i*m+j)->frontJ;
        temp[t].i = i;
        temp[t].j = j;
        temp[t].distance = *(g+i*m+j);
        t++;
        i=ii;
        j=jj;
        if(i==-1 && j==-1)
            break;
    }
    t--;
    while(t>=0){
    	printf("(%d, %d): 当前最小累积距离: %d\n", temp[t].i, temp[t].j, temp[t].distance);
    	t--;
	}
	printf("标准模板与匹配模板的最小累积距离=%d\n", temp[t+1].distance);
}
int main()
{
	//标准模板和匹配模板各元素之间的欧氏距离矩阵 
    int  d[6][4]={
                    {2,1,7,5},
                    {1,5,1,6},
                    {4,7,2,4},
                    {5,2,4,3},
                    {3,4,8,2},
                    {2,1,5,1},
                };
                
    //存放累积最小距离的矩阵g 
    int g[6][4];
    
    struct pointOritation pOritation[6][4];//用来存放g中当前节点的来源路径的结构体数组 
    
    //求出g和pOritation并打印 
    gArray(*d,6,4,*g,*pOritation);
    
    //打印路径即标准模板和匹配模板之间各元素的映射关系 
    printPath(*pOritation, 6, 4,*g);
    
    system("pause");
    return 0;
}

运行结果如下:
在这里插入图片描述

评论 4 您还未登录,请先 登录 后发表或查看评论
在日常的生活中我们最经常使用的距离毫无疑问应该是欧式距离,但是对于一些特殊情况,欧氏距离存在着其很明显的缺陷,比如说时间序列,举个比较简单的例子,序列A:1,1,1,10,2,3,序列B:1,1,1,2,10,3,如果用欧氏距离,也就是distance[i][j]=(b[j]-a[i])*(b[j]-a[i])来计算的话,总的距离和应该是128,应该说这个距离是非常大的,而实际上这个序列的图像是十分相似的,这种情况下就有人开始考虑寻找新的时间序列距离的计算方法,然后提出了DTW算法,这种方法在语音识别,机器学习方便有着很重要的作用。 这个算法是基于动态规划(DP)的思想,解决了发音长短不一的模板匹配问题,简单来说,就是通过构建一个邻接矩阵,寻找最短路径和。 还以上面的2个序列作为例子,A中的10和B中的2对应以及A中的2和B中的10对应的时候,distance[3]以及distance[4]肯定是非常大的,这就直接导致了最后距离和的膨胀,这种时候,我们需要来调整下时间序列,如果我们让A中的10和B中的10 对应 ,A中的1和B中的2对应,那么最后的距离和就将大大缩短,这种方式可以看做是一种时间扭曲,看到这里的时候,我相信应该会有人提出来,为什么不能使用A中的2与B中的2对应的问题,那样的话距离和肯定是0了啊,距离应该是最小的吧,但这种情况是不允许的,因为A中的10是发生在2的前面,而B中的2则发生在10的前面,如果对应方式交叉的话会导致时间上的混乱,不符合因果关系。 接下来,以output[6][6](所有的记录下标从1开始,开始的时候全部置0)记录A,B之间的DTW距离,简单的介绍一下具体的算法,这个算法其实就是一个简单的DP,状态转移公式是output[i] [j]=Min(Min(output[i-1][j],output[i][j-1]),output[i-1][j-1])+distance[i] [j];最后得到的output[5][5]就是我们所需要的DTW距离.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

zjLOVEcyj

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值