字串按規則排序演算法

來源:互聯網
上載者:User
寫這個東西源自於公司組織的一次編程道場,最後的總結就是,盡量使用既有的庫,將問題轉化為既有庫演算法能解決的問題,可讀性第一,效率第二。老大們說的話總是讓人覺得醍醐灌頂,不要自己實現一個功能為了去榨取那麼一點點效能,最終還不一定能榨出來!不知道有沒有什麼特別的原因,最後幾位老大展示出的代碼竟然一模一樣,雖然語言不同,那就像直接的翻譯一般,難道編程有其道,而老大們均掌握了“道”?
       我也想出了那種“標準的做法”,只是我根本不會用什麼庫,也根本不瞭解庫,雖然有了虛擬碼,然而在將其轉化為C代碼的時候,遇到了無法突破的障礙,因為我根本不知道map或者vector之類的,更別提STL了,我除了知道點C++的一點文法之外,其它的什麼都不知道…有時候,我認為我根本不是一個程式員,而是一個網管。
       我沒有什麼技巧可炫,也沒有庫使用的知識可以利用,只好從零開始用標準C來實現了,雖然效率不一定很高,可讀性方面也可能只有我自己能看懂,然而不管怎麼說,實現了,而且所有的時間複雜度是可控的,因為整個代碼沒有掉進任何的庫實現的演算法黑洞,比如如果你不知道你所使用的sort是怎麼實現的,那麼它的時間複雜度就是不可控的。
       問題是這樣的:按照下列規則排序字串數組
1.F一定出現在最前面;
2.L一定出現在最後面;
3.B一定要在A前面;
4.所有相同的字串必須放在一起。
實際上再抽象一點就是,輸入字串是無序的,但是要確保輸出是有序的。正常的思路就是將字串的規則鍵轉化為一個數字,然後進行數字排序,然而要處理字串和索引的關係,這個如果不使用庫裡面的ADT還真麻煩,於是換一種思路,在掃描字串的過程中就將其各歸其位,各歸其位的含義就是根據規則的優先順序順序找到自己的位置,那麼二叉樹是一個理想的選擇。
       現在最關鍵的就是寫一個getprio函數以及一個compare函數,而這個是很好辦的。getprio函數的邏輯決定了最終的排序結果,這個函數可以做的很複雜,也可以很簡單,比如為了能實現不感興趣的字串按照自然順序輸出,並且相同的排在一起這樣的需求,可以為getprio函數儲存一個容器,儲存所有已經匹配到的不感興趣的字串的prio值。
    以下是全部的代碼:
#include <stdio.h>#include <stdlib.h>#define MAX    32//一個字串鏈表,儲存相同的prio的字串struct string_list {    char *str;    struct string_list *next;};//一個字串封裝,攜帶了一個priostruct string_wrap {    char *string;    int prio;};//最終決定字串位置的二叉樹struct string_node {    struct string_list  *string;    struct string_list  *last;    int prio;    struct string_node *left;    struct string_node *right;};//聲明要使用的函數struct string_node *add_node(struct string_node *, struct string_wrap *str, int (*cmp)(struct string_wrap *, struct string_node *));void print_result(struct string_node *);//比較函數,比較優先順序int normalcmp(struct string_wrap *n1, struct string_node *n2){    return  n1->prio - n2->prio;}//getprio函數,根據規則返回優先順序int getprio(char *s, int thread){    static int prio = 1;    if (!strcmp(s, "F"))        return 0;    if (!strcmp(s, "L"))        return 100;    if (!strcmp(s, "B"))        return 50;    if (!strcmp(s, "A"))        return 51;    //如果希望不想關的字串按照輸入順序輸出,則放開下面的注釋    //如果只是return prio++,那麼字串將按照原始順序輸出    //return prio++;    //返回自然順序,和上面注釋的意義一樣    return thread;}int main(int argc, char **argv){    struct string_node *root;    int thread = 0;    char string[MAX];    char *str[40] = {"L","F","A","dsfsfsg","L","B","A", "ss", "A"};        root = NULL;    int i = 0;    while (str[i]) {        int prio = getprio(str[i], ++thread);        struct string_wrap sw = {str[i], prio};        root = add_node(root, &sw, normalcmp);        i ++;    }    print_result(root);    return 0;}//標準的二叉樹插入struct string_node *add_node(struct string_node *p, struct string_wrap *new, int (*cmp)(struct string_wrap *n1, struct string_node *n2)){    int cmp_ret;        if (p == NULL) {        p = (struct string_node *)malloc(sizeof(struct string_node));        p->string = (struct string_list*)malloc(sizeof(struct string_list));        p->string->str = (char *)calloc(1, strlen(new->string));        strcpy(p->string->str, new->string);        p->last = p->string;        p->prio = new->prio;        p->left = p->right = NULL;    } else if ((cmp_ret = cmp(new, p)) == 0) {        struct string_list *next = (struct string_list*)calloc(1, sizeof(struct string_list));            next->str = (char *)calloc(1, strlen(new->string));        strcpy(next->str, new->string);        p->last->next = next;    } else if (cmp_ret < 0) {        p->left = add_node(p->left, new, cmp);    } else {        p->right = add_node(p->right, new, cmp);    }        return p;}//輸出結果,如果不使用printf,可以用一個容器來裝結果字串void print_result(struct string_node *p){    if (p != NULL) {        print_result(p->left);        for (; p->string; p->string = p->string->next) {            printf("%s\n", p->string->str);        }        print_result(p->right);        }}

最後應該總結一下,其實寫這類演算法,切忌一開始第一個就考慮時間複雜度,先把它實現了再慢慢最佳化,不管再複雜,只要計算可以終止,那就是一個思路,你要用代碼去實現自己的這個思路,然後再去想另一個思路,而不是先搞出一大堆圖示,一大堆虛擬碼,然後加以比較,拼接,最終費了好大的勁實現一個四不像的演算法。人的思路貴在第一印象,只要你能手工將淩亂的字串排序成規則的,那麼你一定有自己這麼做的思路,所謂的演算法就是用程式的語言將這個思路寫下來,既然你能用文字來描述,為何不能用C語言或者java語言來描述呢?大O是以後的事情了,它只是一個事後的評價,在你有多個方案的時候,給出一個評價而已,如果一開始就以大O為基準,那麼不會有好的結果,先把事情做了,再去考慮有沒有更好的辦法,而不是一開始就去考慮怎樣做最好。
       還有一個問題和大O相關,如果你使用C++的STL容器來完成這個演算法,首先你要知道這些容器操作的時間複雜度,然後才能計算你整個演算法的時間複雜度,別以為代碼簡單了就高效了,你用了一個lookup完成了一個尋找操作,假設STL中有lookup這個操作原語,而我用了10行C程式碼完成了同樣的操作,你的可讀性比我的好,效率也可能比我的好,然而真的100%比我的好嗎?你知道loopup原語的時間複雜度嗎?這好像又回到了最初的問題:
可讀性第一,效率第二,不要去自己實現一個操作,為了榨取一點效能。
可是我不是很同意這個觀點,作為一種精神,精益求精是每個程式員特別是有駭客精神的程式員所追求的,這種人特別希望將一切都置於自己的控制之下,即每件事都是可控的,代碼的可讀性僅僅是他們追求的一個方面而已,如果不是在做項目,不是為了及時交付,不是為了軟體工程,那麼可讀性以及“最好使用庫”就成了次要的。每一個精益求精的程式員追求的遠遠不只是軟體工程中的那一套,他們應該把實現演算法作為一種遊戲來看待,以下舉個例子。
       朋友遠道而來,小區對面有一家上好的餐館,好好吃上一頓算上酒水飲料要花300元,只需要你定好座位,付好錢,然後帶著朋友去即可。另一個選擇是,你去超市買上幾斤肉,一些蔬菜,加上一些佐料和素材,再買上一瓶好酒,然後回家自己做,等待朋友的到來,總的花費嘛,可能已經遠遠超過了300元。你會怎樣選擇呢?如果算時間成本和方便度的話,下館子是最好的辦法,現在也一直都在提倡這麼做,不是年夜飯也都這麼搞了嗎?但是如果你想找到一種成就感和對朋友的那份心意,而不僅僅是想顯示廚藝的話,我相信你會選擇自己動手的,雖然很有可能由於廚藝不好會糟蹋了上好的材料(我經曆了N多次),然而意義卻是不一樣的。

       從工程學角度看,社會分工早就不需要自己做飯請客了,然而從生活的角度,家裡還是要備一些廚具,最起碼的意義是不一樣的。程式員的感覺與此類似,雖然有那麼多的庫,那麼多的架構可以使用,然而如果不是為了項目,僅僅是自己實現一個小想法的話,還是自己動手會好一些,當然那些庫和架構也要會用,畢竟程式員的工作是為公司效力,而公司是完全站在軟體工程的角度上考慮問題的。因此,不應該讓大家總是使用庫和既有的實現,在項目中這麼做即可,平時自己做小玩意或者搞一點hack,還這麼做,那就不太合適了。

       聽科班出身的人說過,大學老師教導,初學編程,一定要先學命令列,然後再IDE,只是我不是科班出身,也沒有受過那樣的教導,雖如此,我還是很贊同的,如果不懂原理而只是簡單的庫拼接,那麼只是空中樓閣,這樣的人可能會是一個好的程式員,卻很難成為一個好的設計者,緊急排錯也最好不要找他們。我的觀點並不是讓大家都必須自己動手,說這句話也沒有任何驕傲的成分,我只是想說:知其然,知其所以然,勿不求甚解。不求甚解,浮光掠影,下策!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.