亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Java后綴數組之求sa數組的實例代碼

2024-07-14 08:40:14
字體:
來源:轉載
供稿:網友

后綴數組的一些基本概念請自行百度,簡單來說后綴數組就是一個字符串所有后綴大小排序后的一個集合,然后我們根據后綴數組的一些性質就可以實現各種需求。

public class MySuffixArrayTest { public char[] suffix;//原始字符串 public int n;//字符串長度 public int[] rank;// Suffix[i]在所有后綴中的排名 public int[] sa;// 滿足Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名為i的后綴為Suffix[SA[i]]     // (與Rank是互逆運算) public int[] height;// 表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最長公共前綴,也就是排名相鄰的兩個后綴的最長公共前綴 public int[] h;// 等于Height[Rank[i]],也就是后綴Suffix[i]和它前一名的后綴的最長公共前綴 public int[] ws;// 計數排序輔助數組 public int[] y;// 第二關鍵字rank數組 public int[] x;// rank的輔助數組}

以下的講解都以"aabaaaab"這個字符串為例,先展示一下結果,請參考這個結果進行理解分析(這個結果圖我復制別人的,請各位默認下標減1,因為我的數組從下標0開始的)

java,后綴數組,sa

suffix:原始字符串數組  假設原始字符串是"aabaaaab"  那這個數組對應的值應該是{'a','a','b','a','a','a','a','b'}
n:字符串長度 這里n是8
rank: 后綴數組的名次數組  相當于存的是第i個后綴對應的名次是多少  比如rank[0]就是指"aabaaaab"這個后綴的名次  rank[1]指"abaaaab"這個后綴的名次
sa: 這個是和rank數組互逆的一個數組  存的是第x名的是哪個后綴  還是舉例子來說明  sa[0]指的是排名第一的后綴數組即為3  也就是"aaaab"這個數組  他對應的rank[3]就是0。 sa[rank[i]]=i 這個式子請務必理解,理解了這個sa和rank的關系你應該也搞懂了 
height: height[i]就是sa[i]后綴數組和sa[i-1]后綴數組的最大公共前綴的長度  height[1]指的是排名第2和排名第1的最大公共前綴 sa[1]與sa[0]即"aaab"與"aaaab"的最大公共前綴 自然一眼看出 height[1]=3
h: h[i]指的是第i個后綴與他前一名的最大公共前綴  h[0]指的就是第一個后綴數組即"aabaaaab"與他前一名即"aab"的最大公共前綴 也就是height[rank[0]]=height[3]=3 這個有點不好理解 可以暫時不理解 繼續往下看
ws: 沒什么好說的,計數排序的輔助數組
y: 存的是第二關鍵字排序  相當于第二關鍵字的sa數組
x: 你可以理解為rank數組的備份,他最開始是rank數組備份,之后記錄每次循環后的rank數組

首先來看下求sa數組的代碼,我會一段一段的說明代碼作用并在后面附上總代碼

rank = new int[n];    sa = new int[n];    ws = new int[255];    y = new int[n];    x = new int[n];    // 循環原字符串轉換int值放入rank數組    for (int i = 0; i < n; i++) {      rank[i] = (int) suffix[i];    }

上面這段代碼的作用就是初始化數組以及進行第一次計數排序,第一次循環是對rank數組賦初值,執行完后rank數組對應值為{97,97,98,97,97,97,97,98},大家應該看得出來rank數組的初值就是字母對應的ascii碼。

接下來的三段循環就是第一次計數排序了,不理解計數排序的請百度。我說下這三段循環運行的過程

for (int i = 0; i < n; i++) {      ws[rank[i]]++;      x[i] = rank[i];    }for (int i = 1; i < ws.length; i++) {      ws[i] += ws[i - 1];    }

這兩段循環做的是對所有出現值計數,并備份rank數組至x數組,第一個循環運行完后ws[97]=6,ws[98]=2,第二個循環運行完后ws[97]=6,ws[98]=8

 for (int i = n - 1; i >= 0; i--) {       sa[--ws[rank[i]]] = i;    }

上面這段就是具體的計數排序求sa數組的代碼,大家第一次看的時候肯定是蒙的,這怎么就求出了sa呢。我第一次也是蒙的,但請保持耐心,仔細理解這段代碼。還記得前面說的公式嗎 sa[rank[i]]=i  舉個例子對于后綴"b"我們求他的sa  即sa[rank[7]]=sa[98]=7  顯然sa[98]不存在 但我們將98出現的次數已經記錄在ws數組了  那么ws[98]應該就是"b"對應的名次了  注意不要忘記計數減1  就變成了 sa[--ws[rank[i]]] = i。至于為什么要從后向前遍歷,這里你需要仔細理解一下,否則后面根據第二關鍵字進行排序的方式你肯定會完全蒙蔽。如果有兩個rank值相同的你怎么排序呢?肯定是先出現的在sa數組的前面,仔細思考這個循環以及ws數組值的變化,你會明白,for循環的順序實際上代表了rank值相同時的排列順序。從后向前遍歷代表了rank值相同時靠后的后綴排名也靠后。

以上只是第一次計數排序,相當于只比較每個后綴數組的首字母求出了一個sa,對應的結果如下圖

java,后綴數組,sa

// 循環組合排序    for (int j = 1, p = 0; j <= n; j = j << 1) {      // 需要補位的先加入排序數組y      p = 0;      for (int i = n - j; i < n; i++) {        y[p++] = i;      }      // 根據第一關鍵字sa排出第二關鍵字      for (int i = 0; i < n; i++) {        if (sa[i] >= j) {          y[p++] = sa[i] - j;        }      }      // 合并兩個關鍵字的排序      for (int i = 0; i < ws.length; i++) {        ws[i] = 0;      }      for (int i : x) {        ws[i]++;      }      for (int i = 1; i < ws.length; i++) {        ws[i] += ws[i - 1];      }      for (int i = n - 1; i >= 0; i--) {        sa[--ws[x[y[i]]]] = y[i];        y[i] = 0;      }      // 根據sa算出rank數組      int xb[] = new int[n];// x數組備份      for (int i = 0; i < n; i++) {        xb[i] = x[i];      }      int number = 1;      x[sa[0]] = 1;      for (int i = 1; i < n; i++) {        if (xb[sa[i]] != xb[sa[i - 1]]) {          x[sa[i]] = ++number;        } else if (sa[i] + j >= n && sa[i - 1] + j >= n) {          x[sa[i]] = number;        } else if (sa[i] + j < n && sa[i - 1] + j >= n) {          x[sa[i]] = ++number;        } else if (xb[sa[i] + j] != xb[sa[i - 1] + j]) {          x[sa[i]] = ++number;        } else {          x[sa[i]] = number;        }        if (number >= n)          break;      }    }

 這是求sa數組最難以理解的一段代碼,首先大家需要理解一下倍增算法的思路。第一次計數排序后我們是不是已經知道了所有后綴數組第一個首字母的排序,既然我們知道了第一個首字母的排序那是不是相當于我們也知道了他第二個字母的順序(注意排序和順序的區別,排序是我們知道他固定的排在第幾名,順序是我們只知道他出現的次序,但并不知道他具體排第幾名),這是當然的,因為他們本來就是出自一個字符串,對于每個后綴他同時也可以作為他之前后綴的后綴。說起來繞口,舉個例子,比如對于"baaaab"他首字母的順序是不是對應"abaaaab"的第二關鍵字順序。我們有了第一關鍵字的排序和第二關鍵字的排序就能求出兩個關鍵字的組合排序,跟據組合排序的結果我們還是可以延用之前的想法,對于"baaaab"第一次組合排序后我們排出來他頭兩個字母"ba"的排序,那么他同時他也可以作為"aabaaaab"的第二關鍵字的順序。整個排序的邏輯參考下圖

java,后綴數組,sa

然后我們來分段的分析代碼

for (int i = n - j; i < n; i++) {        y[p++] = i;      }      // 根據第一關鍵字sa排出第二關鍵字for (int i = 0; i < n; i++) {        if (sa[i] >= j) {          y[p++] = sa[i] - j;        }      }

以上代碼就是求第二關鍵字的sa也就是y數組,p初始值為0,第一段循環是將需要補位的后綴排在數組最前面。

第二個循環的邏輯你需要結合前面的邏輯圖進行理解了,我們對第一關鍵字的排序結果sa進行遍歷,if(sa[i] >=j )判斷該后綴能否作為其他后綴的第二關鍵字,以第一次循環j=1為例,當sa[i]=0時代表后綴數組"aabaaaab",顯然它無法作為其他后綴的第二關鍵字。對于可以作為其他后綴第二關鍵字的,他sa的順序就是對應的第二關鍵字的順序,sa[i] - j 求出他作為第二關鍵字的后綴放在y數組下,并且p++。這里你需要慢慢理解。

// 合并兩個關鍵字的排序      for (int i = 0; i < ws.length; i++) {        ws[i] = 0;      }      for (int i : x) {        ws[i]++;      }      for (int i = 1; i < ws.length; i++) {        ws[i] += ws[i - 1];      }      for (int i = n - 1; i >= 0; i--) {        sa[--ws[x[y[i]]]] = y[i];        y[i] = 0;      }

以上是根據第一關鍵字排序sa和第二關鍵字排序y求出其組合排序,這段代碼相當的晦澀難懂。我們可以先不理解代碼,先理解一個思路,對于兩個關鍵字排序,實際規則和兩個數字排序差不多,比如11和12比較大小,10位就是第一關鍵字,個位就是第二關鍵字,比較完10位我們求得11=12,再比較個位我們知道11<12,10位相同的話其個位的順序就是大小順序。我上面第一次計數排序時說過,計數排序for循環的順序實際上代表了rank值相同時的排列順序,那么這里我們怎么一次計數排序就求出兩個關鍵字合并后的順序呢?我說下我的理解,一次計數排序實際上包含了兩次排序,一次是數值的排序,一次是出現次序的排序,其規則就相當于前面11和12比較的例子,數值的排序是10位,出現次序的排序是個位。到這里我們就有思路了,數值的排序用第一關鍵字的排序,出現次序的排序用第二關鍵字的排序,這樣就能一次計數排序求得兩個關鍵字合并后的排序。上面的代碼就是這個思路的實現。x數組就是第一關鍵字的rank數組,我們對他進行計數。

 for (int i = n - 1; i >= 0; i--) {        sa[--ws[x[y[i]]]] = y[i];        y[i] = 0;      }

這段循環就是上面所有思路的實現,我們對第二關鍵字數組y從后進行遍歷,對于y[i]我們求出他第一關鍵字的計數排名,這個計數排名就是y[i]的排名,最后計數減1。合并關鍵字的排序成功求出。

相信你如果理解了上面所有的代碼后肯定會拍案叫絕,我本人在一遍遍琢磨這段代碼時也是熱血澎湃,簡直拜服了。這就是算法的魅力吧。

有了sa數組我們就可以求rank數組,這并不難,也就不講解了。下面附上求sa的所有代碼。

public static void main(String[] args) {    String str = "aabaaaab";    MySuffixArrayTest arrayTest = new MySuffixArrayTest(str.toString());    arrayTest.initSa();// 求sa數組  }  public void initSa() {    rank = new int[n];    sa = new int[n];    ws = new int[255];    y = new int[n];    x = new int[n];    // 循環原字符串轉換int值放入rank數組    for (int i = 0; i < n; i++) {      rank[i] = (int) suffix[i];    }    // 第一次計數排序    for (int i = 0; i < n; i++) {      ws[rank[i]]++;      x[i] = rank[i];    }    for (int i = 1; i < ws.length; i++) {      ws[i] += ws[i - 1];    }    for (int i = n - 1; i >= 0; i--) {      sa[--ws[rank[i]]] = i;    }    // 循環組合排序    for (int j = 1, p = 0; j <= n; j = j << 1) {      // 需要補位的先加入排序數組y      p = 0;      for (int i = n - j; i < n; i++) {        y[p++] = i;      }      // 根據第一關鍵字sa排出第二關鍵字      for (int i = 0; i < n; i++) {        if (sa[i] >= j) {          y[p++] = sa[i] - j;        }      }      // 合并兩個關鍵字的排序      for (int i = 0; i < ws.length; i++) {        ws[i] = 0;      }      for (int i : x) {        ws[i]++;      }      for (int i = 1; i < ws.length; i++) {        ws[i] += ws[i - 1];      }      for (int i = n - 1; i >= 0; i--) {        sa[--ws[x[y[i]]]] = y[i];        y[i] = 0;      }      // 根據sa算出rank數組      int xb[] = new int[n];// x數組備份      for (int i = 0; i < n; i++) {        xb[i] = x[i];      }      int number = 1;      x[sa[0]] = 1;      for (int i = 1; i < n; i++) {        if (xb[sa[i]] != xb[sa[i - 1]]) {          x[sa[i]] = ++number;        } else if (sa[i] + j >= n && sa[i - 1] + j >= n) {          x[sa[i]] = number;        } else if (sa[i] + j < n && sa[i - 1] + j >= n) {          x[sa[i]] = ++number;        } else if (xb[sa[i] + j] != xb[sa[i - 1] + j]) {          x[sa[i]] = ++number;        } else {          x[sa[i]] = number;        }        if (number >= n)          break;      }    }  }

總結

以上所述是小編給大家介紹的Java后綴數組之求sa數組的實例代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VeVb武林網網站的支持!


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91精品视频大全| 欧美一级在线亚洲天堂| 精品久久久国产精品999| 日韩精品在线视频观看| 亚洲片国产一区一级在线观看| 国产成人久久久| 精品国内亚洲在观看18黄| 久久精品在线视频| 欧美丰满少妇xxxxx| 中文字幕精品av| 国产在线观看91精品一区| 国产欧美久久久久久| 中文字幕日本精品| 中文字幕日韩av电影| 欧美亚洲伦理www| 97国产suv精品一区二区62| 欧美日韩国产在线| 欧美大人香蕉在线| 欧美高清激情视频| 久久久久久久久久久成人| 久久电影一区二区| 亚洲四色影视在线观看| 福利视频导航一区| 亚洲国产精品久久精品怡红院| 国产在线精品自拍| 亚洲欧洲午夜一线一品| 精品欧美aⅴ在线网站| 精品亚洲va在线va天堂资源站| 中文字幕av一区二区三区谷原希美| 亚洲美女av黄| 中文字幕成人精品久久不卡| 91日韩在线播放| 日韩中文字幕免费视频| 亚洲乱码国产乱码精品精| 啊v视频在线一区二区三区| 国产精品美女午夜av| 国产日产欧美精品| 欧美精品中文字幕一区| 中文字幕日韩欧美| 日韩在线观看免费全| 岛国av一区二区在线在线观看| 热re91久久精品国99热蜜臀| 色婷婷久久一区二区| 狠狠色狠狠色综合日日小说| 亚洲欧美激情在线视频| 最近2019中文字幕大全第二页| 国产一区二中文字幕在线看| 国产不卡一区二区在线播放| 91精品国产自产91精品| 国产精品欧美一区二区| 欧美第一黄网免费网站| 亚洲电影免费观看高清完整版在线| 国产成人一区二区在线| 浅井舞香一区二区| 日韩在线视频二区| 在线看日韩av| 欧美野外猛男的大粗鳮| 欧美日韩免费看| 欧美精品中文字幕一区| 一区二区欧美日韩视频| 中文字幕亚洲激情| 成人两性免费视频| 国产欧美精品日韩| 视频在线观看99| 日韩av网站大全| 国产精品中文久久久久久久| 日韩欧美在线视频日韩欧美在线视频| 中文字幕成人精品久久不卡| 97在线视频免费播放| 国产精品69久久| 国产精品久久77777| 欧美激情视频一区二区| 国产精品一二三视频| 国产精品久久久久av| 91视频国产一区| 在线视频日本亚洲性| 一本一本久久a久久精品综合小说| 欧美精品做受xxx性少妇| 国产精品久久久久久久9999| 久久精品亚洲国产| 久久精品国产亚洲| 亚洲国产天堂久久国产91| 亚洲韩国日本中文字幕| 亚洲精品mp4| 日韩精品欧美国产精品忘忧草| 国产精品v日韩精品| 影音先锋日韩有码| 色黄久久久久久| 91在线无精精品一区二区| 亚洲欧洲高清在线| 日韩黄色在线免费观看| 欧美激情精品久久久久久黑人| 久久免费精品视频| 国产一区二区三区在线视频| 欧洲亚洲女同hd| 日本精品中文字幕| 欧美性感美女h网站在线观看免费| 精品亚洲一区二区三区四区五区| 国产在线视频欧美| 国产91露脸中文字幕在线| 国语自产精品视频在线看抢先版图片| 成人国内精品久久久久一区| 欧美激情视频网| 国产一区二区三区视频在线观看| 伊人伊人伊人久久| 久久99久国产精品黄毛片入口| 亚洲人成网7777777国产| 亚洲石原莉奈一区二区在线观看| 在线不卡国产精品| 精品国产成人在线| 久久精品中文字幕电影| 91亚洲精品在线观看| 亚洲欧美中文日韩v在线观看| 在线观看日韩www视频免费| 亚洲天堂日韩电影| 久久天天躁狠狠躁夜夜躁2014| 这里只有精品视频在线| 国产99久久精品一区二区| 国产精品高清免费在线观看| 日韩av不卡电影| 精品视频一区在线视频| 中文字幕欧美精品日韩中文字幕| 成人av色在线观看| 亚洲欧美一区二区三区四区| 亚洲爱爱爱爱爱| 精品亚洲夜色av98在线观看| 久久久伊人日本| 国产精品无av码在线观看| 国产精品www色诱视频| 激情久久av一区av二区av三区| 亚洲va国产va天堂va久久| 欧美日韩激情视频| 日韩一区二区久久久| 国产成人精品最新| 久久久综合免费视频| 亚洲最大成人免费视频| 狠狠色香婷婷久久亚洲精品| 日韩av综合中文字幕| 精品一区二区三区三区| 亚洲精品久久久一区二区三区| 97精品视频在线| 91精品国产自产在线观看永久| 91精品免费看| 久久国产精品亚洲| 色综合久综合久久综合久鬼88| 久久精品中文字幕| 欧美一区二区视频97| 午夜精品久久久久久久99黑人| 富二代精品短视频| 亚洲天堂开心观看| 亚洲精品福利在线观看| 欧美猛交ⅹxxx乱大交视频| 91在线免费网站| 中文字幕亚洲情99在线| 色综合五月天导航| 性欧美暴力猛交69hd| 国产成人精品国内自产拍免费看| 亚洲人成电影网站色| 国产精品高清免费在线观看| 国产日韩欧美在线播放| 国产美女扒开尿口久久久| 伊人伊人伊人久久| 久久影视电视剧凤归四时歌| 欧美午夜激情视频|