指定序的排序問題,記一個學生的問題

主要內容

  1. 問題的分析與劃歸
  2. 排序算法映射,算法解析
  3. 不考慮重複字符串的實現代碼

 

  最近在傳智論壇遇到一個算法的問題,想了一下,有一個我認爲比較有趣的解法. 下面算 法或許不是最優的,可是能夠參考一下.算法

問題:

=======================================================================
給定兩個字符串,僅由小寫字母組成,它們包含了相同字符。求把第一個字符串變
成第二個字符串的最小操做次數,且每次操做只能對第一個字符串中的某個字符移
動到此字符串中的開頭。數組

例如給定兩個字符串「abcd" "bcad" ,輸出:2,由於須要操做2次才能把"abcd"變
成「bcad" ,方法是:abcd->cabd->bcad。數據結構

 

我開始用廣度優先搜索作。但是好比abcdefg 和 gfedcba 遞歸下去就是7的7次方的
時間複雜度 而後再在變換位置的函數裏循環 循環次數報表。函數

而後我用List記錄 abcdefg出現的東東 把重複的去掉了,也就是7*6*5*4*3*2
可是每次判斷list.contain()在內部貌似又要循環時間和上面差很少.優化

有沒有比較好的解決方式.

========================================================================spa

這個問題比較有趣,這裏我使用C#來完成,主要是考慮微軟提供了許多數據結構 不用再從新實現數據結構和相關方法了. code

問題分析

  開始拿到這個問題,先分析一下須要作的事兒:就是將一個字符串,變成一個指 定的字符串. 抽象一下,將目標字符串當作一個有序集的結構,能夠從新一個有序集. blog

再將原來的字符串當作一個等待排序的數據結構,就能夠將問題簡化爲簡單的排序算
法了. 即:指定序的排序問題. 排序

  舉個例子:將字符串"abcd"變成"bcad". 那麼字符集"bcad"構成一個有序集,能夠獲得映射表,以下:
{ ('b', 0), ('c', 1), ('a', 2), ('d', 3) } 所以須要排序的字符串就變成了一個數字集:{ 2, 0, 1, 3 }. 即 須要將這個數據集排序爲 { 0, 1, 2, 3 },須要求最小的步驟. 因
此將問題轉化爲排序最小步驟問題. 遞歸

  另外一方面,問題考慮了算法實現的要求,即每次只能將一個數據 取出,插入到開始的地方. 也就是說具體操做就是一個固定的函數:
將第i項插入到0項,原來前i-1項的數據依次後移一位. 故方法很簡單:

1 // 注意到須要處理的是字符串,因爲字符串是不可變的
2 // 這裏考慮使用字符數組
3 public void jk_remove(char[] s, int index) {
4     char temp = s[index];
5     for (int i = index - 1; i >= 0; i--) {
6         s[i + 1] = s[i];
7     }
8     s[0] = temp;
9 }

 


那麼這個函數調用幾回,就表示操做了幾回.
下面考慮另外一個問題. 就是何時調用這個函數. 也就是如何操做纔算是較優. 這個問題有點麻煩. 起初我打算從現成的算法中找一個. 不過現成的都不太友好. 想到
了使用"逆序"的方法.
所謂最少的步驟. 就是不要移動多餘的步驟. 在前面已經提到,問題已經劃歸成排 序問題,下面姑且使用升序.
要將一個數字序列排序,又只能將數據往前放,那麼最小的步驟就是每次移動一個 數據而且這個數據就是一個良序的,也就是說這個數字不會再次移動. 那麼一個長度爲
n的字符串,最多移動n-1次. 我想這個應該是最少的步驟了吧!!!
下面看看怎麼移動會比較好.
首先考慮移動的特徵就是每次移動數據都放在最開始的地方. 同時下一次移動開始 後這個已經被移動過的數據就會自動擠到後面去. 那麼再也不重複移動這個數據的辦法就
是首先移動最大的數. 那麼在移動剩下的最大數,就能夠保證移動完成之後結果就是良
序的了.
那麼什麼樣的數據須要移動呢?很簡單逆序(注1)的. 舉個例子:
延續前面的例子,數字2013. 第一個數字不須要考慮,第二個數字0與2構成一個逆 序,所以0是逆序數. 第三個數字1與2構成逆序,即1爲逆序數. 那麼首先移動較大的,
移動1獲得1203. 因爲數據排列發生變化,須要從新計算逆序. 所以獲得0爲逆序數,移
動0,獲得結果0123.
這裏就獲得一個結論,就是判斷逆序,移動最大的逆序數便可. 由此獲得僞代碼:

 1 // 這裏依舊使用升序排列
 2 public void jk_sort(char[] s) {
 3     char temp = '\0';    // 用來存放最大的逆序數
 4     // 因爲第一個數字不須要考慮逆序,所以從1開始循環
 5     for(int i = 1; i < s.length; i++) {
 6         for(int j = 0; j < i; j++) {
 7             // 後一個數比前一個數小,那麼構成逆序
 8             if(s[i] < s[j]) {
 9                 // 判斷記錄最大的逆序數
10                 if(temp == '\0' || temp < s[i]) {
11                     temp = s[i];
12                 } 
13                 // 跳出循環,看下一個數是否爲逆序
14                 break;
15             }
16         }
17     }
18     // 移動逆序數
19     if(temp != '\0') {
20         jk_remove(s, temp);
21         // 若是存在逆序數就遞歸繼續判斷,不然離開函數排序完成
22         jk_sort( s );
23     }
24 }        

 

這裏使用了遞歸,算法複雜度提高了,具體的優化能夠之後再說. 那麼有了這個思
路之後,就能夠實現了.


代碼實現

第一種狀況,字符串中沒有重複的字符

首先寫一個類

1 public class JKSort {
2 
3 }

 

提供靜態方法,移動數據

1 private static void jk_remove(char[] s, int index) {
2     char temp = s[index];
3     for (int i = index - 1; i >= 0; i--) {
4         s[i + 1] = s[i];
5     }
6     s[0] = temp;
7 }

 


提供映射,能夠考慮使用一個鍵值對

1 private Dictionary<char, int> dic; // 使用構造函數初始化
2 private JKSort(char[] chs) {
3     dic = new Dictionary<char, int>();
4     for(int i = 0; i < chs.Length; i++) {
5         dic.Add(chs[i], i);
6     }
7 }

 

提供方法實現排序

 1 private static void internal_jk_sort(char[] s, Dictionary<char, int> dic) {
 2     int temp = -1;    // 記錄最大逆序數的索引
 3 
 4     for(int i = 1; i < s.Length; i++) {
 5         for(int j = 0; j < i; j++) {
 6 
 7             if(dic[s[i]] < dic[s[j]]) {
 8 
 9                 if(temp == -1 || dic[s[temp]] < dic[s[i]]) {
10                     temp = i;
11                 } 
12 
13                 break;
14             }
15         }
16     }
17     // 移動逆序數
18     if(temp != -1) {
19         jk_remove(s, temp);
20         // 若是存在逆序數就遞歸繼續判斷,不然離開函數排序完成
21         internal_jk_sort( s, dic );
22     }
23 }

 


實現對外公開的方法

 1 public static string JK_Sort(string str, string strObj) {
 2     char[] chs = str.ToCharArray();
 3     char[] obj = strObj.ToCharArray();
 4 
 5     JKSort j = new JKSort(obj);
 6 
 7     internal_jk_sort(chs, j.dic);
 8 
 9 
10     return new string(chs);
11 }

 

那麼就完成了排序. 這裏提供的算法沒有實現優化,如想了解優化與帶有重複字符
串的解決辦法,請等待下文.
睡覺,2013年12月18日凌晨0時.

注1 逆序:在一個數字排列中,若是其中的兩個數前面的一個比後面的一個大,那麼就稱 它們構成一個逆序. 相關逆序的結論與問題,能夠參考《高等代數》或《線性代數》 的教材.

相關文章
相關標籤/搜索