《算法》-字符串[字符串排序]

引入

  • 字符串方便比較嗎任柜?不方便

  • 怎么辦呢?把每一個字符對應成一個數(shù)字 toIndex(

c)

  • 一共有多少個字符? R個
  • 數(shù)字R需要幾個二進制位來表示? lgR個
    如擴展ASCII碼共256個字符板壮,需要8位二進數(shù)來表示。

  • 區(qū)別
    Alphabet.toChar(index) 把數(shù)字對應成字符合住。這個是字母表的第i位

    String.charAt(index) 字符串的第i位是什么字符绰精。這個是字符串的第i位。 字符表API


    在這里插入圖片描述

標準字符表

在這里插入圖片描述

鍵索引計數(shù)

輸入字符串和字符串對應的組別(組別也是字符串的鍵)
在滿足組別有小到大排序的情況下透葛,將字符串按字母順序排序
[圖片上傳失敗...(image-24066d-1600017488526)]

算法步驟

第一步笨使,記錄組別的頻率
(為了得到某個字符串在排序后的范圍,比如組別2肯定在組別1后面僚害,在組別3前面硫椰,把每個組別有多少個人記錄下來,方便我們定位)

  • 共 5 組萨蚕,從第 0 組到第 4 組靶草。 創(chuàng)建數(shù)組大小為 6( = 5 + 1 )。int[] count=new count[6];
  • count[]記錄頻率
  • 記錄的位置是鍵值+1门岔,加1是方便后期更新鍵的位置起點
    [圖片上傳失敗...(image-70a43b-1600017488526)]
    第二步爱致,轉化為索引
    (得到每個組別的位置起點)
    [圖片上傳失敗...(image-56fc38-1600017488526)]

第三步,分類

  • 創(chuàng)建一個副本(因為在遍歷正本寒随,正本當前不能被覆蓋)
  • 按組別 丟進副本里,丟到該組別的位置起點處
    • 當前的數(shù)據(jù)是有序的
    • 下面是個人的小思考,可不用看
    • 如果原先的數(shù)據(jù)是有序的妻往,那么在每個組別中的數(shù)據(jù)也將會是有序的
    • 如果原先的數(shù)據(jù)是無序的互艾,那么先排序
    • 有種遞歸的思想
      • 外面先排好序,里面一層一層的去排序
      • 里面先排好序讯泣,外面一層一層的去排序
  • 該組別的位置起點 向后挪一位 (因為當前位被用了)

第四步纫普,復制

  • 把副本的數(shù)據(jù)拷貝回正本


    在這里插入圖片描述

KeyIndexedCounting 代碼

  • 復雜度
    • 訪問數(shù)組11N+4R+1次
  • 索引計數(shù)法是穩(wěn)定的
int N = a.length;
String[] aux = new String[N]; //訪問數(shù)組N次
int[] count = new int[R+1]; //訪問數(shù)組R+1次
// Compute frequency counts.
for(int i = 0;i<N;i++) //訪問數(shù)組2N次
    count[a[i].key()+1]++;
// Transform counts to indices.
for(int r = 0;r<R;r++) //訪問數(shù)組2R次,進行R次加法
    count[r+1]+=count[r];
// Distribute the records.
for(int i = 0;i<N;i++) //訪問數(shù)組3N次好渠,使計數(shù)器值增大N次并移動數(shù)據(jù)N次
    aux[count[a[i].key()]++]=a[i];
// Copy back.
for(int i = 0;i<N;i++) //訪問數(shù)組2N次昨稼,移動數(shù)據(jù)N次
    a[i]=aux[i];

低位優(yōu)先排序

結合索引排序,從字符串的低位(從右面開始)拳锚,從右到左假栓,每個字符都當一次該字符串的鍵,給整個字符串排序

以下代碼的局限性:每個字符串的長度是相等的霍掺。稍作修改可適應不等長的字符串匾荆。

LSD 代碼

  • 復雜度
    • 訪問數(shù)組
      • 最壞情況:~7WN + 3WR 次
      • 最好情況:8N+3R 次
    • 空間: R+N
public class LSD {
    public static void sort(String[] a, int W) { // Sort a[] on leading W characters.
        int N = a.length;
        int R = 256;
        String[] aux = new String[N];
        for (int d = W - 1; d >= 0; d--) { // Sort by key-indexed counting on dth char.
            int[] count = new int[R + 1];  // 創(chuàng)建數(shù)組大小為R+1
            for (int i = 0; i < N; i++) // Compute frequency counts. 頻率
                count[a[i].charAt(d) + 1]++;
            for (int r = 0; r < R; r++) // Transform counts to indices. 索引
                count[r + 1] += count[r];
            for (int i = 0; i < N; i++) // Distribute. 按組別丟到副本里去
                aux[count[a[i].charAt(d)]++] = a[i];
            for (int i = 0; i < N; i++) // Copy back. 賦回正本
                a[i] = aux[i];
        }
    }
}

高位優(yōu)先排序

考慮不等長字符串的比較

  • e.g. as 排在 aspect 前面。因此增加一個組別杆烁,記錄字符為空的頻次牙丽。
  • 這個組別應該在最前面,為count[0]
    - 怎么讓字符為空落到count[0]里呢兔魂?
    - 字符為空時烤芦,對應數(shù)字為0(具體實現(xiàn)的時候為返回-1,再在-1的基礎上+1)
    - 其他字符對應的數(shù)字在原來基礎上+1(就是給0騰個位置出來析校,不占用0拍棕,所有位次順移)
  • int[] count=new int[R+2];
    - 原為R+1
    - 再在原來的基礎上+1,即為R+2
  • 字符為空勺良,也即搜尋的時候超出字符串的原來長度

MSD 代碼

public class MSD {
    private static int R = 256; // radix 256個字符
    private static final int M = 15; // cutoff for small subarrays 數(shù)組小到多少的時候用插入排序绰播?
    private static String[] aux; // auxiliary array for distribution 副本
 
    private static int charAt(String s, int d) {
        if (d < s.length())
            return s.charAt(d);
        else
            return -1;
    }
 
    public static void sort(String[] a) {
        int N = a.length;
        aux = new String[N];
        sort(a, 0, N - 1, 0);
    }
 
    // Sort from a[lo] to a[hi], starting at the dth character.
    private static void sort(String[] a, int lo, int hi, int d) { 
        //如果數(shù)組較小,插入排序尚困,具體實現(xiàn)略
        if (hi <= lo + M) {
            Insertion.sort(a, lo, hi, d);
            return;
        }
        
        int[] count = new int[R + 2]; // 數(shù)組大小R+2
        for (int i = lo; i <= hi; i++)// Compute frequency counts.頻次蠢箩,只累計了hi-lo+1次
            count[charAt(a[i], d) + 2]++; // 每個對應數(shù)字在原來基礎上+1
        for (int r = 0; r < R + 1; r++) // Transform counts to indices. 索引
            count[r + 1] += count[r];
        for (int i = lo; i <= hi; i++) // Distribute.丟到對應組別里去
            aux[count[charAt(a[i], d) + 1]++] = a[i]; // 每個對應數(shù)字在原來基礎上+1
                                                      // aux的賦值從aux[0]開始,到aux[hi-lo]結束
                                                      // 在這里count會發(fā)生變化事甜。原來這里的count只是為了移動到下一位為下一個元素找位置用谬泌,現(xiàn)在這里的count[i]還可以通過是否到達count[i+1]來判斷是否可以結束遞歸
        for (int i = lo; i <= hi; i++) // Copy back. 注意aux的起終點和a的對應關系
            a[i] = aux[i - lo];
        // Recursively sort for each character value.
        for (int r = 0; r < R; r++) //私認為初始化條件r=1更好,因為r=0都是字符為空的子字符串
            sort(a, lo + count[r], lo + count[r + 1] - 1, d + 1); // 將當前相同字符的分為一組逻谦,每組以下一位字符為比較對象排序
    }
}
  • LSD
    • 從右到左掌实,每次都是N個字符作為一組,整體進行排序
  • MSD
    • 從從到右邦马,每次是第i位相同的字符串分成一組贱鼻,按第i+1位排序

三向字符串快速排序

  • 可以處理等值鍵宴卖,較長公共前綴,小數(shù)組邻悬,取值范圍較小的鍵
  • 避免創(chuàng)建大量空數(shù)組症昏,不需要額外空間

Quick3string 代碼

  • 復雜度
    • 平均: 2NlnN
public class Quick3string {
    private static int charAt(String s, int d) {
        if (d < s.length())
            return s.charAt(d);
        else
            return -1;
    }
 
    public static void sort(String[] a) {
        sort(a, 0, a.length - 1, 0);
    }
 
    private static void sort(String[] a, int lo, int hi, int d) {
        if (hi <= lo)
            return;
        int lt = lo, gt = hi; // 低位指針,高位指針
        int v = charAt(a[lo], d); // 切分值
        int i = lo + 1; // 從第二個字符串的d位開始
        while (i <= gt) {
            int t = charAt(a[i], d);
            if (t < v) // 比切分值小父丰,放到切分值前面去
                exch(a, lt++, i++);
            else if (t > v) // 比切分值大肝谭,放到最后去
                exch(a, i, gt--);
            else
                i++;
        }
 
        // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]
        sort(a, lo, lt - 1, d);
        if (v >= 0) // d位字母相同且不為空,則這部分從下一位開始再比較
            sort(a, lt, gt, d + 1);
        sort(a, gt + 1, hi, d);
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蛾扇,一起剝皮案震驚了整個濱河市攘烛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镀首,老刑警劉巖坟漱,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蘑斧,居然都是意外死亡靖秩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門竖瘾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沟突,“玉大人,你說我怎么就攤上這事捕传』菔茫” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵庸论,是天一觀的道長职辅。 經常有香客問我,道長聂示,這世上最難降的妖魔是什么域携? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鱼喉,結果婚禮上秀鞭,老公的妹妹穿的比我還像新娘。我一直安慰自己扛禽,他們只是感情好锋边,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著编曼,像睡著了一般豆巨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掐场,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天往扔,我揣著相機與錄音贩猎,去河邊找鬼。 笑死瓤球,一個胖子當著我的面吹牛融欧,可吹牛的內容都是我干的敏弃。 我是一名探鬼主播卦羡,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼麦到!你這毒婦竟也來了绿饵?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瓶颠,失蹤者是張志新(化名)和其女友劉穎拟赊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粹淋,經...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡吸祟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桃移。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屋匕。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖借杰,靈堂內的尸體忽然破棺而出过吻,到底是詐尸還是另有隱情,我是刑警寧澤蔗衡,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布纤虽,位于F島的核電站,受9級特大地震影響绞惦,放射性物質發(fā)生泄漏逼纸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一济蝉、第九天 我趴在偏房一處隱蔽的房頂上張望杰刽。 院中可真熱鬧,春花似錦堆生、人聲如沸专缠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涝婉。三九已至,卻和暖如春蔗怠,著一層夾襖步出監(jiān)牢的瞬間墩弯,已是汗流浹背吩跋。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留渔工,地道東北人锌钮。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像引矩,于是被迫代替她去往敵國和親梁丘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354