zoukankan      html  css  js  c++  java
  • manacher-线性查找算法-(最长回文子串问题)

    manacher-线性查找算法
    manacher算法中需要知道的概念:
       回文半径: 回文中心 到 回文边界的距离.
       回文半径数组: radius[i]表示以 i 为回文中心的最大回文半径.
       回文最右边界: 出现的回文边界中最右的位置.
       首次回文中心: 回文最右边界首次出现时的回文中心.
       首次回文左边界: 回文最右边界首次出现时的回文左边界.
       i镜像点i': 若i位置在长回文串中, 那么i位置 以 长回文串的回文中心 作出的 对称点.
    
    manacher 流程:
       预处理字符串: 例如将s1="sasd"处理成s2="#s#a#s#d#".
       将回文最右边界和首次回文中心都置为最左边界即-1索引.
       遍历处理后的字符串s2, 根据 i位置 与 回文最右边界 的关系可分为2种情况:
    	  i位置在回文最右边界外面:
    		 radius[i] = 暴力中心扩展后的回文半径.
    	  i位置在回文最右边界里面(包括回文最右边界), 则根据 i镜像点i'的回文左边界 与 首次回文左边界 的关系又可分为3种情况:
    		 i'的回文左边界在首次回文左边界里面(不包括首次回文左边界):
    			radius[i] = i镜像点i'的最长回文半径(即是radius[i']).
    		 i'的回文左边界在首次回文左边界外面:
    			radius[i] = 回文最右边界与i位置的距离.
    		 i'的回文左边界在首次回文左边界的边界上:
    			radius[i] = 暴力中心扩展后的回文半径.
    	  经过上面得到radius[i]后,根据radius[i]的状况更新回文最右边界,首次回文中心,首次回文左边界,全局最大回文半径等等信息.
    




    创建3个文件:manacher.h、manacherArray.c、manacherArrayTest.c。




    manacherArray.h
    #ifndef MANACHER_ARRAY_H_
    #define MANACHER_ARRAY_H_
    
    // 功能: 最长回文子串的长度.
    // 参数: s(字符串首地址).
    // 返回: 最长回文子串的长度.
    // 注意: 当 s=NULL 时, 将错误退出程序.
    extern int32_t manacherArray( char s[] );
    
    #endif
    

    manacherArray.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include "manacherArray.h"
    
    // 功能: 打印错误信息后就错误退出程序.
    // 参数: expression(错误判断表达式), message(需打印的错误信息).
    // 返回: 无.
    // 注意: 当表达式 expression 为真时, 才触发.
    #define ERROR_EXIT( expression, message )                                    
    if( (expression) ) {                                                         
    	fprintf( stderr, "
    error location: file = %s, func = %s, line = %d.
    ", 
    	                 __FILE__, __func__, __LINE__ );                         
    	fprintf( stderr, "error  message: %s%s.
    a",                            
    	                 (message) != NULL ? (message) : __func__,               
    	                 (message) != NULL ? "" : " function error" );           
    	exit( EXIT_FAILURE );                                                    
    }
    
    • 萌新版本。
      int32_t manacherArray( char s[] ) {
      	char *tempA = NULL, ch = '#';
      	int *radiusA = NULL; // 回文半径数组.
      	int right = -1;      // 回文最右边界.
      	int center = -1;     // 首次回文中心.
      	int left = -1;       // 以 center 为回文中心的回文左边界.
      	int mirror = -1;     // i 以 center 为中心的镜像 i'.
      	int mirrorLeft = -1; // 以 i' 为回文中心的回文左边界.
      	int i = 0, j = 1, answer = 0, slen = 0;
      
      	ERROR_EXIT( s == NULL, "NullPointerException" );
      	slen = strlen( s );
      	tempA = malloc( sizeof(*tempA) * (slen * 2 + 2) );
      	for( tempA[0] = ch; (tempA[j++] = s[i++]) != ''; tempA[j++] = ch ) {}
      	radiusA = malloc( sizeof(*radiusA) * (slen * 2 + 1) );
      
      	for( i = 0; tempA[i] != ''; ++i ) {
      		if( i > right ) {
      			// i位置在回文最右边界外, 暴力扩.
      			for( j = 1; i - j >= 0 && tempA[i - j] == tempA[i + j]; ++j ) {}
      			radiusA[i] = j - 1;
      		} else {
      			left = center - radiusA[center];       // 以 center 为回文中心的回文左边界.
      			mirror = center * 2 - i;               // i 以 center 为中心的镜像 i'.
      			mirrorLeft = mirror - radiusA[mirror]; // 以 i' 为回文中心的回文左边界.
      			// i位置在回文最右边界里面,根据以 i' 为回文中心的回文左边界又可分为3种情况.
      			if( mirrorLeft == left ) {
      				for( j = right - i + 1; i - j >= 0 && tempA[i - j] == tempA[i + j]; ++j ) {}
      				radiusA[i] = j - 1;
      			} else if( mirrorLeft < left ) {
      				radiusA[i] = right - i;
      			} else {
      				radiusA[i] = radiusA[mirror];
      			}
      		}
      		if( i + radiusA[i] > right ) { // 出现较右的回文右边界.
      			center = i;
      			right = i + radiusA[i];
      		}
      		if( radiusA[i] > answer ) {    // 出现较大的回文半径.
      			answer = radiusA[i];
      		}
      	}
      	free( radiusA );
      	free( tempA );
      
      	return answer;
      }
      
    • 左神版本。
      char *manacherArray( char s[] ) {
      	char *ta = NULL, ch = '#';
      	int *radius = NULL;  // 回文半径数组.
      	int right = -1;      // 出现过的回文最右边界.
      	int center = -1;     // 第一次出现回文最右边界时的回文中心.
      	int answer = -1;
      	int i = 0, j = 1, slen = 0;
      
      	ERROR_EXIT( s == NULL, "NullPointerException" );
      	slen = strlen( s );
      	ta = malloc( sizeof(*ta) * (slen * 2 + 2) );
      	for( ta[0] = ch; (ta[j++] = s[i++]) != ''; ta[j++] = ch ) {}
      	radius = malloc( sizeof(*radius) * (slen * 2 + 1) );
      
      	for( i = 0; ta[i] != ''; ++i ) {
      		// i在回文最右边界外,让 radius[i] 默认取只有自己本身距离1.
      		// i在回文最右边界内,让 radius[i] 默认取较短的距离, 因为较长距离会越界或不是回文.
      		// center * 2 - i 表示 i位置的镜像i'.
      		radius[i] = i >= right ? 1 : MIN( radius[center * 2 - i], right - i );
      
      		// 为什么 while 循环 不用判断 i + radius[i] < strlen( ta ) 呢?
      		// ∵ 循环中判断了 i - radius[i] >= 0.
      		// ∴ 在循环中 i + radius[i] 是合法索引不会引发越界.
      		// ∵ i 和 radius[i] 不会是负数 且 radius[i]在循环开始前的默认最小值是1.
      		// ∴ i - radius[i] 位置 永远在 i + radius[i] 位置 前面.
      		// ∵ radius[i] 在循环中的增长值是1.
      		// ∴ ''出现的位置一定是在 i + radius[i] 位置.
      		while( i - radius[i] >= 0 && ta[i - radius[i]] == ta[i + radius[j]] ) {
      			++radius[i];
      		}
      
      		--radius[i]; // 减去i位置自己本身距离1.
      
      		if( i + radius[i] > right ) { // 出现较右的回文右边界.
      			center = i;
      			right = i + radius[i];
      		}
      		if( radius[i] > answer ) {    // 出现较大的回文半径.
      			answer = radius[i];
      		}
      	}
      	free( radius );
      	free( ta );
      
      	return answer;
      }
      

    manacherArrayTest.c
    实现对数器
    

    manacherArrayTest.sh
    # !/bin/bash
    
    for(( i = 1; i <= 21; ++i )) do
    	printf "%02d" ${i}
    	echo -n ______________
    	./manacherArrayTest
    done
    

    参考: https://www.zhihu.com/question/37289584
    参考: https://segmentfault.com/a/1190000008484167

    参考书籍:
    [1] 严蔚敏,吴伟民.数据结构(C语言版)[M].北京:清华大学出版社,2007.
    [2] 高一凡.《数据结构》算法实现及解析[M].第二版.西安:西安电子科技大学出版社,2004.
    [3] Andrew Binstock,John Rex著,陈宗斌等译.Practical Algorithms for Programmers.北京:机械工业出版社,2009.

    重点参考人物: 左神.



  • 相关阅读:
    初识 Rabbitmq
    Lambda表达式(C语言-gcc编译器)
    二叉树转换成双向链表
    进程的内存分布
    Linux shell之数组
    Ubuntu 使用Gparted工具扩大第一分区方法步骤
    Android源码编译出错解决办法
    IIC总线解析
    VirtualBox Ubuntu虚拟机串口编程
    ubuntu虚拟机上解决克隆github代码慢的方法
  • 原文地址:https://www.cnblogs.com/hujunxiang98/p/12934173.html
Copyright © 2011-2022 走看看