首頁 web前端 js教程 在javascript中如何實現最長公共子序列

在javascript中如何實現最長公共子序列

Jun 07, 2018 pm 05:01 PM
js 最長公共子序列

最長公共子序列(longest common sequence)和最長公共子字串(longest common substring)不是一回事兒,下面這篇文章主要給大家介紹了關於javascript實現最長公共子序列的相關資料,需要的朋友可以參考下。

介紹

最長公共子序列(Longest Common Subsequence LCS)是從給定的兩個序列X和Y中取出盡可能多的一部分字符,按照它們在原序列排列的先後次序排列得到。 LCS問題的演算法用途廣泛,如在軟體不同版本的管理中,用LCS演算法找到新舊版本的異同處;在軟體測試中,用LCS演算法對錄製和回放的序列進行比較,在基因工程領域,用LCS演算法檢查病人DNA連與鍵康DNA鏈的異同;在防抄襲系統中,用LCS演算法檢查論文的抄襲率。 LCS演算法也可以用於程式碼相似度度量,人體運行的序列檢索,視頻段匹配等方面,所以對LCS演算法進行研究具有很高的應用價值。

基本概念

子序列(subsequence): 一個特定序列的子序列就是將給定序列中零個或多個元素去掉後得到的結果(不改變元素間相對次序)。例如序列的子序列有:

公共子序列(common subsequence): 給定序列X和Y,序列Z是X的子序列,也是Y的子序列,則Z是X和Y的公共子序列。例如X=[A,B,C,B,D,A,B],Y=[B,D,C,A,B,A[,則序列Z=[B,C,A]為X和Y的公共子序列,其長度為3。但Z不是X和Y的最長公共子序列,而序列[B,C,B,A]和[B,D,A,B]也均為X和Y的最長公共子序列,長度為4 ,而X和Y不存在長度大於等於5的公共子序列。對於序列[A,B,C]和序列[E,F,G]的公共子序列只有空序列[]。

最長公共子序列:給定序列X和Y,從它們的所有公共子序列中選出長度最長的那一個或幾個。
子字串: 將一個序列從最前面或最後或同時刪掉零個或幾個字元構成的新系列。區別與子序列,子序列是可以從中間摳掉字元的。 cnblogs這個字串中子序列有多少個呢?很顯然有27個,例如其中的cb,cgs等等都是其子序列

給一個圖再解釋一下:

我們可以看出子序列不見得一定是連續的,連續的是子字串。

問題分析

我們還是從矩陣開始分析,自己推導出狀態遷移方程式。

首先,我們把問題轉換成前端夠為熟悉的概念,不要序列序列地叫了,可以認為是陣列或字串。一切從簡,我們就估且認定是兩個字串做比較吧。

我們專注於留意」子序列「的概念,它可以刪掉多個或零個,也可以全部幹掉。這時我們的第一個子序列為 空字串 (如果我們的序列不是字串,我們還可以 )!這個真是千萬要注意到!許多人就是看不懂《演算法導論》的那張圖表,還有許多部落格的作者不懂裝懂。我們總是從左到右比較,當然了第一個字串,由於作為矩陣的高,就垂直放置了。

##DCABAA#BC#D
x""B
""

A假令X = "ABCDAB", Y="BDCABA",各自取出最短的序列,也就是空字串與空字串比較。 LCS的方程式解為一個數字,那麼這個表格也只能填數字。兩個空字串的公同區域的長度為0.""ABCD
Bx""B#DCABA
0
######A############B############

然後我們X不動,繼續讓空字符串出陣,Y讓“B”出陣,很顯然,它們的公共區域的長度為0. Y換成其他字符, D啊,C啊, 或者, 它們的連續組合DC、 DDC, 情況沒有變, 依然為0. 因此第一行都為0. 然後我們Y不動,Y只出空字任串,那麼與上面的分析一樣,都為0,第一列都是0.

#""0#00#ABCD##A0
x""BD#CABA
##0000
0
0
0
#0

B

0

x##A#""B0C#0D 0A0
#LCS問題與背包問題有點不一樣,背包問題還可以設定-1行,而最長公共子序列因為有空子序列的出現,一開始就把左邊與上邊固定死了。 然後我們再將問題放大些,這次雙方都出一個字符,顯然只有兩都相同時,才有存在不為空字符串的公共子序列,長度也理解數然為1。 A為"X", Y為"BDCA"的子序列的任一##""BDCAB
##0 #000000
##A0#0001

#B0#Bx""BAB""00#0#0 00#A00#00111
#繼續往右填空,該怎麼填?顯然,LCS不能大於X的長度,Y的從A字串開始的子序列與B的A序列相比,怎麼也能等於1。 ##DC
A

B0##B#如果X只從派出前面個字元A,B吧,亦即是“”,“A”, "B", "AB"這四種組合,前兩個已經解說過了。那我們先看B,${X_1} == ${Y_0}, 我們得到一個新的公共子字串了,應該要加1。為什麼呢?因為我們這個矩陣是一個狀態表,從左到右,從上到下描述狀態的遷移過程,而這些狀態是基於已有狀態 累加 出來的。現在我們要確認的是,現在我們要填的這個格子的值與它周圍已經填好的格子的值是存在何種關係 。目前,資訊太少,就是一個孤點,直接填1。 ##DCABA000
C0D 0A0#B0
x""B
""00
#0#0 0
#A
0#0
1### ###1######1################################################ #########B######0######1#############C######0################ ####D######0############A#######0###########B######0# ###########

然後我們讓Y多出一個D做幫手,{"",A,B,AB} vs {"",B,D,BD},顯然,繼續填1. 一直填到Y的第二個B之前,都是1。因為到BDCAB時,它們有另一個公共子序列,AB。

##DCABAB011##1# 2C#DA
x""B
""00#0#0 00
#A00#001 11
##1
##0
0
#0

B

0

到這一步,我們可以總結一些規則了,之後就是透過計算驗證我們的想法,加入新的規則或限定條件來完善。 Y將所有字符派上去,X依然是2個字符,經仔細觀察,還是填2.看五行,X再多派一個C,ABC的子序列集合比AB的子序列集合大一些,那麼它與Y的B子序列集合大一些,就算不大,就不能比原來的小。顯然新增的C不能成為戰力,不是兩者的公共字符,因此值應該等於AB的子序列集合。 ##DCABA0# 000A00#001110##120
×""B
""000
B
1#1##1 #2
#C
1###################D#####0#### ########A######0############B######0############

而且我們可以確定,如果兩個字串要比較的字元不一樣,那麼要填的格子是與其左邊或上邊有關,那邊大就取那個。

如果比較的字元一樣呢,稍安毋躁,剛好X的C要與Y的C進行比較,即ABC的子序列集合{"",A,B,C,AB,BC, ABC}與BDC的子序列集合{"",B,D,C,BD,DC,BDC}比較,得到公共子字串有「」,B,D 。這時還是與先前的結論一樣,當字元相等時,它對應的格子值等於左邊與右邊與左上角的值,並且左邊,上邊,左上邊總是相等的。這些奧秘需要更嚴格的數學知識來論證。

假設有兩個數組,A和B。 A[i]為A的第i個元素,A(i)為由A的第一個元素到第i個元素所組成的前綴。 m(i, j)為A(i)和B(j)的最長公共子序列長度。

由於演算法本身的遞推性質,其實只要證明,對於某個i和j:

  m(i, j) = m(i-1, j-1) 1 (當A[i] = B[j]時)

  m(i, j) = max( m(i-1, j), m(i, j-1) ) (當A[i ] != B[j]時)

第一個式子很好證明,即當A[i] = B[j]時。可以用反證,假設m(i, j) > m(i-1, j-1) 1 (m(i, j)不可能小於m(i-1, j-1) 1,原因很明顯) ,那麼可以推出m(i-1, j-1)不是最長的這一矛盾結果。

第二個有些trick。當A[i] != B[j]時,還是反證,假設m(i, j) > max( m(i-1, j), m(i, j-1) )。

由反證假設,可得m(i, j) > m(i-1, j)。這個可以推出A[i]一定在m(i, j)對應的LCS序列中(反證可得)。而由於A[i] != B[j],故B[j]一定不在m(i, j)對應的LCS序列中。所以可推出m(i, j) = m(i, j-1)。這就推出了與反正假設矛盾的結果。

得證。

我們現在用下面的方程式來繼續填表了。

程式實作

//by 司徒正美
function LCS(str1, str2){
  var rows = str1.split("")
  rows.unshift("")
  var cols = str2.split("")
  cols.unshift("")
  var m = rows.length 
  var n = cols.length 
  var dp = []
  for(var i = 0; i < m; i++){ 
   dp[i] = []
   for(var j = 0; j < n; j++){ 
    if(i === 0 || j === 0){
     dp[i][j] = 0
     continue
    }
    
    if(rows[i] === cols[j]){ 
     dp[i][j] = dp[i-1][j-1] + 1 //对角+1
    }else{
     dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1]) //对左边,上边取最大
    }
   }
   console.log(dp[i].join(""))//调试
  } 
  return dp[i-1][j-1]
 }
登入後複製

LCS可以進一步簡化,只要透過挪位置,省去新陣列的產生

//by司徒正美
function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 var dp = [new Array(n+1).fill(0)] //第一行全是0
 for(var i = 1; i <= m; i++){ //一共有m+1行
  dp[i] = [0] //第一列全是0
  for(var j = 1; j <= n; j++){//一共有n+1列
   if(str1[i-1] === str2[j-1]){ 
    //注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理
    dp[i][j] = dp[i-1][j-1] + 1 //对角+1
   } else {
     dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1]) 
   }
  }
 } 
 return dp[m][n];
}
登入後複製

#列印一個LCS

我們再來給列印函數,先看如何列印一個。我們從右下角開始尋找,一直找到最上一行終止。因此目標字串的建構是倒序。為了避免使用stringBuffer這樣麻煩的中間量,我們可以透過遞歸實現,每次執行程式時,只傳回一個字串,沒有則傳回一個空字串, 以 printLCS(x,y,...) str[i ] 相加,就可以得到我們要求的字串。

我們再寫出一個方法,來驗證我們得到的字串是否真正的LCS字串。作為一個已經工作的人,不能寫的程式碼像在校生那樣,不做單元測試就放到線上讓別人踩坑。

//by 司徒正美,打印一个LCS
function printLCS(dp, str1, str2, i, j){
 if (i == 0 || j == 0){
  return "";
 }
 if( str1[i-1] == str2[j-1] ){
  return printLCS(dp, str1, str2, i-1, j-1) + str1[i-1];
 }else{
  if (dp[i][j-1] > dp[i-1][j]){
   return printLCS(dp, str1, str2, i, j-1);
  }else{
   return printLCS(dp, str1, str2, i-1, j);
  }
 }
}
//by司徒正美, 将目标字符串转换成正则,验证是否为之前两个字符串的LCS
function validateLCS(el, str1, str2){
 var re = new RegExp( el.split("").join(".*") )
 console.log(el, re.test(str1),re.test(str2))
 return re.test(str1) && re.test(str2)
}
登入後複製

使用:

function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 //....略,自行补充
 var s = printLCS(dp, str1, str2, m, n)
 validateLCS(s, str1, str2)
 return dp[m][n]
}
var c1 = LCS( "ABCBDAB","BDCABA");
console.log(c1) //4 BCBA、BCAB、BDAB
var c2 = LCS("13456778" , "357486782" );
console.log(c2) //5 34678 
var c3 = LCS("ACCGGTCGAGTGCGCGGAAGCCGGCCGAA" ,"GTCGTTCGGAATGCCGTTGCTCTGTAAA" );
console.log(c3) //20 GTCGTCGGAAGCCGGCCGAA
登入後複製

#列印全部LCS

##想法與上面差不多,我們注意一下,在LCS方法有一個Math.max取值,這其實是整合了三種情況,因此可以分叉出三個字串。我們的方法將傳回一個es6集合對象,方便自動去掉。然後每次都用新的集合合併舊的集合的字任串。

//by 司徒正美 打印所有LCS
function printAllLCS(dp, str1, str2, i, j){
 if (i == 0 || j == 0){
  return new Set([""])
 }else if(str1[i-1] == str2[j-1]){
  var newSet = new Set()
  printAllLCS(dp, str1, str2, i-1, j-1).forEach(function(el){
   newSet.add(el + str1[i-1])
  })
  return newSet
 }else{
  var set = new Set()
  if (dp[i][j-1] >= dp[i-1][j]){
   printAllLCS(dp, str1, str2, i, j-1).forEach(function(el){
    set.add(el)
   })
  }
  if (dp[i-1][j] >= dp[i][j-1]){//必须用>=,不能简单一个else搞定
   printAllLCS(dp, str1, str2, i-1, j).forEach(function(el){
    set.add(el)
   })
  } 
  return set
 } 
 }
登入後複製

使用:

function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 //....略,自行补充
 var s = printAllLCS(dp, str1, str2, m, n)
 console.log(s)
 s.forEach(function(el){
  validateLCS(el,str1, str2)
  console.log("输出LCS",el)
 })
 return dp[m][n]
}
var c1 = LCS( "ABCBDAB","BDCABA");
console.log(c1) //4 BCBA、BCAB、BDAB
var c2 = LCS("13456778" , "357486782" );
console.log(c2) //5 34678 
var c3 = LCS("ACCGGTCGAGTGCGCGGAAGCCGGCCGAA" ,"GTCGTTCGGAATGCCGTTGCTCTGTAAA" );
console.log(c3) //20 GTCGTCGGAAGCCGGCCGAA
登入後複製

#空間最佳化

使用捲動陣列:

function LCS(str1, str2){
 var m = str1.length 
 var n = str2.length
 var dp = [new Array(n+1).fill(0)],now = 1,row //第一行全是0
 for(var i = 1; i <= m; i++){ //一共有2行
  row = dp[now] = [0] //第一列全是0
  for(var j = 1; j <= n; j++){//一共有n+1列
   if(str1[i-1] === str2[j-1]){ 
    //注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理
    dp[now][j] = dp[i-now][j-1] + 1 //对角+1
   } else {
    dp[now][j] = Math.max( dp[i-now][j], dp[now][j-1]) 
   }
  }
  now = 1- now; //1-1=>0;1-0=>1; 1-1=>0 ...
 } 
 return row ? row[n]: 0
}
登入後複製

危險的遞歸解法

#str1的一個子序列對應於下標序列{1, 2, …, m}的子序列,因此,str1共有${2^m}$個不同子序列(str2亦然,如${2^n}$),因此複雜度達到驚人的指數時間(${2^m * 2^ n}$)。

//警告,字符串的长度一大就会爆栈
function LCS(str1, str2, a, b) {
  if(a === void 0){
   a = str1.length - 1
  }
  if(b === void 0){
   b = str2.length - 1
  }
  if(a == -1 || b == -1){
   return 0
  } 
  if(str1[a] == str2[b]) {
   return LCS(str1, str2, a-1, b-1)+1;
  }
  if(str1[a] != str2[b]) {
   var x = LCS(str1, str2, a, b-1)
   var y = LCS(str1, str2, a-1, b)
   return x >= y ? x : y
  }
 }
登入後複製
上面是我整理給大家的,希望今後會對大家有幫助。

相關文章:

使用vue實作二級路由設定方法

#react專案開發

在Vue-Router2.X中實作多種路由實作#

以上是在javascript中如何實現最長公共子序列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

如何使用JS和百度地圖實現地圖平移功能 如何使用JS和百度地圖實現地圖平移功能 Nov 21, 2023 am 10:00 AM

如何使用JS和百度地圖實現地圖平移功能百度地圖是一款廣泛使用的地圖服務平台,在Web開發中經常用於展示地理資訊、定位等功能。本文將介紹如何使用JS和百度地圖API實作地圖平移功能,並提供具體的程式碼範例。一、準備工作使用百度地圖API前,首先需要在百度地圖開放平台(http://lbsyun.baidu.com/)上申請一個開發者帳號,並建立一個應用程式。創建完成

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟 股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟 Dec 17, 2023 pm 06:55 PM

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟,需要具體程式碼範例隨著網路和科技的快速發展,股票交易已成為許多投資者的重要途徑之一。而股票分析是投資人決策的重要一環,其中蠟燭圖被廣泛應用於技術分析。學習如何使用PHP和JS繪製蠟燭圖將為投資者提供更多直觀的信息,幫助他們更好地做出決策。蠟燭圖是一種以蠟燭形狀來展示股票價格的技術圖表。它展示了股票價格的

如何使用PHP和JS創建股票蠟燭圖 如何使用PHP和JS創建股票蠟燭圖 Dec 17, 2023 am 08:08 AM

如何使用PHP和JS創建股票蠟燭圖股票蠟燭圖是股票市場中常見的技術分析圖形,透過繪製股票的開盤價、收盤價、最高價和最低價等數據,幫助投資者更直觀地了解股票的價格波動情形。本文將教你如何使用PHP和JS創建股票蠟燭圖,並附上具體的程式碼範例。一、準備工作在開始之前,我們需要準備以下環境:1.一台運行PHP的伺服器2.一個支援HTML5和Canvas的瀏覽器3

如何使用JS和百度地圖實現地圖點擊事件處理功能 如何使用JS和百度地圖實現地圖點擊事件處理功能 Nov 21, 2023 am 11:11 AM

如何使用JS和百度地圖實現地圖點擊事件處理功能概述:在網路開發中,經常需要使用地圖功能來展示地理位置和地理資訊。而地圖上的點擊事件處理是地圖功能中常用且重要的一環。本文將介紹如何使用JS和百度地圖API來實現地圖的點擊事件處理功能,並給出具體的程式碼範例。步驟:匯入百度地圖的API檔案首先,要在HTML檔案中匯入百度地圖API的文件,可以透過以下程式碼實現:

如何使用JS和百度地圖實現地圖熱力圖功能 如何使用JS和百度地圖實現地圖熱力圖功能 Nov 21, 2023 am 09:33 AM

如何使用JS和百度地圖實現地圖熱力圖功能簡介:隨著互聯網和行動裝置的快速發展,地圖成為了普遍的應用場景。而熱力圖作為一種視覺化的展示方式,能夠幫助我們更直觀地了解數據的分佈。本文將介紹如何使用JS和百度地圖API來實現地圖熱力圖的功能,並提供具體的程式碼範例。準備工作:在開始之前,你需要準備以下事項:一個百度開發者帳號,並建立一個應用,取得到對應的AP

PHP與JS開發技巧:掌握繪製股票蠟燭圖的方法 PHP與JS開發技巧:掌握繪製股票蠟燭圖的方法 Dec 18, 2023 pm 03:39 PM

隨著網路金融的快速發展,股票投資已經成為了越來越多人的選擇。而在股票交易中,蠟燭圖是常用的技術分析方法,它能夠顯示股票價格的變動趨勢,幫助投資人做出更精準的決策。本文將透過介紹PHP和JS的開發技巧,帶領讀者了解如何繪製股票蠟燭圖,並提供具體的程式碼範例。一、了解股票蠟燭圖在介紹如何繪製股票蠟燭圖之前,我們首先需要先了解什麼是蠟燭圖。蠟燭圖是由日本人

js和vue的關係 js和vue的關係 Mar 11, 2024 pm 05:21 PM

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。

See all articles