找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 4118|回复: 0

字符串按规则排序算法 |dog250

[复制链接]
发表于 2011-12-8 09:59:07 | 显示全部楼层 |阅读模式
写这个东西源自于公司组织的一次编程道场,最后的总结就是,尽量使用既有的库,将问题转化为既有库算法能解决的问题,可读性第一,效率第二。老大们说的话总是让人觉得醍醐灌顶,不要自己实现一个功能为了去榨取那么一点点性能,最终还不一定能榨出来!不知道有没有什么特别的原因,最后几位老大展示出的代码竟然一模一样,虽然语言不同,那就像直接的翻译一般,难道编程有其道,而老大们均掌握了“道”?
       我也想出了那种“标准的做法”,只是我根本不会用什么库,也根本不了解库,虽然有了伪代码,然而在将其转化为C代码的时候,遇到了无法突破的障碍,因为我根本不知道map或者vector之类的,更别提STL了,我除了知道点C++的一点语法之外,其它的什么都不知道…有时候,我认为我根本不是一个程序员,而是一个网管。
       我没有什么技巧可炫,也没有库使用的知识可以利用,只好从零开始用标准C来实现了,虽然效率不一定很高,可读性方面也可能只有我自己能看懂,然而不管怎么说,实现了,而且所有的时间复杂度是可控的,因为整个代码没有掉进任何的库实现的算法黑洞,比如如果你不知道你所使用的sort是怎么实现的,那么它的时间复杂度就是不可控的。
       问题是这样的:按照下列规则排序字符串数组
1.F一定出现在最前面;
2.L一定出现在最后面;
3.B一定要在A前面;
4.所有相同的字符串必须放在一起。
实际上再抽象一点就是,输入字符串是无序的,但是要确保输出是有序的。正常的思路就是将字符串的规则键转化为一个数字,然后进行数字排序,然而要处理字符串和索引的关系,这个如果不使用库里面的ADT还真麻烦,于是换一种思路,在扫描字符串的过程中就将其各归其位,各归其位的含义就是根据规则的优先级顺序找到自己的位置,那么二叉树是一个理想的选择。
       现在最关键的就是写一个getprio函数以及一个compare函数,而这个是很好办的。getprio函数的逻辑决定了最终的排序结果,这个函数可以做的很复杂,也可以很简单,比如为了能实现不感兴趣的字符串按照自然顺序输出,并且相同的排在一起这样的需求,可以为getprio函数保存一个容器,保存所有已经匹配到的不感兴趣的字符串的prio值。
    以下是全部的代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define MAX    32
  4. //一个字符串链表,保存相同的prio的字符串
  5. struct string_list {
  6.     char *str;
  7.     struct string_list *next;
  8. };
  9. //一个字符串包装,携带了一个prio
  10. struct string_wrap {
  11.     char *string;
  12.     int prio;
  13. };
  14. //最终决定字符串位置的二叉树
  15. struct string_node {
  16.     struct string_list  *string;
  17.     struct string_list  *last;
  18.     int prio;
  19.     struct string_node *left;
  20.     struct string_node *right;
  21. };
  22. //声明要使用的函数
  23. struct string_node *add_node(struct string_node *, struct string_wrap *str, int (*cmp)(struct string_wrap *, struct string_node *));
  24. void print_result(struct string_node *);
  25. //比较函数,比较优先级
  26. int normalcmp(struct string_wrap *n1, struct string_node *n2)
  27. {
  28.     return  n1->prio - n2->prio;
  29. }
  30. //getprio函数,根据规则返回优先级
  31. int getprio(char *s, int thread)
  32. {
  33.     static int prio = 1;
  34.     if (!strcmp(s, "F"))
  35.         return 0;
  36.     if (!strcmp(s, "L"))
  37.         return 100;
  38.     if (!strcmp(s, "B"))
  39.         return 50;
  40.     if (!strcmp(s, "A"))
  41.         return 51;
  42.     //如果希望不想关的字符串按照输入顺序输出,则放开下面的注释
  43.     //如果只是return prio++,那么字符串将按照原始顺序输出
  44.     //return prio++;
  45.     //返回自然顺序,和上面注释的意义一样
  46.     return thread;
  47. }
  48. int main(int argc, char **argv)
  49. {
  50.     struct string_node *root;
  51.     int thread = 0;
  52.     char string[MAX];
  53.     char *str[40] = {"L","F","A","dsfsfsg","L","B","A", "ss", "A"};
  54.    
  55.     root = NULL;
  56.     int i = 0;
  57.     while (str[i]) {
  58.         int prio = getprio(str[i], ++thread);
  59.         struct string_wrap sw = {str[i], prio};
  60.         root = add_node(root, &sw, normalcmp);
  61.         i ++;
  62.     }
  63.     print_result(root);
  64.     return 0;
  65. }
  66. //标准的二叉树插入
  67. struct string_node *add_node(struct string_node *p, struct string_wrap *new, int (*cmp)(struct string_wrap *n1, struct string_node *n2))
  68. {
  69.     int cmp_ret;
  70.    
  71.     if (p == NULL) {
  72.         p = (struct string_node *)malloc(sizeof(struct string_node));
  73.         p->string = (struct string_list*)malloc(sizeof(struct string_list));
  74.         p->string->str = (char *)calloc(1, strlen(new->string));
  75.         strcpy(p->string->str, new->string);
  76.         p->last = p->string;
  77.         p->prio = new->prio;
  78.         p->left = p->right = NULL;
  79.     } else if ((cmp_ret = cmp(new, p)) == 0) {
  80.         struct string_list *next = (struct string_list*)calloc(1, sizeof(struct string_list));   
  81.         next->str = (char *)calloc(1, strlen(new->string));
  82.         strcpy(next->str, new->string);
  83.         p->last->next = next;
  84.     } else if (cmp_ret < 0) {
  85.         p->left = add_node(p->left, new, cmp);
  86.     } else {
  87.         p->right = add_node(p->right, new, cmp);
  88.     }
  89.    
  90.     return p;
  91. }
  92. //输出结果,如果不使用printf,可以用一个容器来装结果字符串
  93. void print_result(struct string_node *p)
  94. {
  95.     if (p != NULL) {
  96.         print_result(p->left);
  97.         for (; p->string; p->string = p->string->next) {
  98.             printf("%s\n", p->string->str);
  99.         }
  100.         print_result(p->right);   
  101.     }
  102. }
复制代码
最后应该总结一下,其实写这类算法,切忌一开始第一个就考虑时间复杂度,先把它实现了再慢慢优化,不管再复杂,只要计算可以终止,那就是一个思路,你要用代码去实现自己的这个思路,然后再去想另一个思路,而不是先搞出一大堆图示,一大堆伪代码,然后加以比较,拼接,最终费了好大的劲实现一个四不像的算法。人的思路贵在第一印象,只要你能手工将凌乱的字符串排序成规则的,那么你一定有自己这么做的思路,所谓的算法就是用程序的语言将这个思路写下来,既然你能用文字来描述,为何不能用C语言或者java语言来描述呢?大O是以后的事情了,它只是一个事后的评价,在你有多个方案的时候,给出一个评价而已,如果一开始就以大O为基准,那么不会有好的结果,先把事情做了,再去考虑有没有更好的办法,而不是一开始就去考虑怎样做最好。
       还有一个问题和大O相关,如果你使用C++的STL容器来完成这个算法,首先你要知道这些容器操作的时间复杂度,然后才能计算你整个算法的时间复杂度,别以为代码简单了就高效了,你用了一个lookup完成了一个查找操作,假设STL中有lookup这个操作原语,而我用了10行C代码完成了同样的操作,你的可读性比我的好,效率也可能比我的好,然而真的100%比我的好吗?你知道loopup原语的时间复杂度吗?这好像又回到了最初的问题:
可读性第一,效率第二,不要去自己实现一个操作,为了榨取一点性能。
可是我不是很同意这个观点,作为一种精神,精益求精是每个程序员特别是有黑客精神的程序员所追求的,这种人特别希望将一切都置于自己的控制之下,即每件事都是可控的,代码的可读性仅仅是他们追求的一个方面而已,如果不是在做项目,不是为了及时交付,不是为了软件工程,那么可读性以及“最好使用库”就成了次要的。每一个精益求精的程序员追求的远远不只是软件工程中的那一套,他们应该把实现算法作为一种游戏来看待,以下举个例子。
       朋友远道而来,小区对面有一家上好的餐馆,好好吃上一顿算上酒水饮料要花300元,只需要你定好座位,付好钱,然后带着朋友去即可。另一个选择是,你去超市买上几斤肉,一些蔬菜,加上一些佐料和素材,再买上一瓶好酒,然后回家自己做,等待朋友的到来,总的花费嘛,可能已经远远超过了300元。你会怎样选择呢?如果算时间成本和方便度的话,下馆子是最好的办法,现在也一直都在提倡这么做,不是年夜饭也都这么搞了么?但是如果你想找到一种成就感和对朋友的那份心意,而不仅仅是想显示厨艺的话,我相信你会选择自己动手的,虽然很有可能由于厨艺不好会糟蹋了上好的材料(我经历了N多次),然而意义却是不一样的。
       从工程学角度看,社会分工早就不需要自己做饭请客了,然而从生活的角度,家里还是要备一些厨具,最起码的意义是不一样的。程序员的感觉与此类似,虽然有那么多的库,那么多的框架可以使用,然而如果不是为了项目,仅仅是自己实现一个小想法的话,还是自己动手会好一些,当然那些库和框架也要会用,毕竟程序员的工作是为公司效力,而公司是完全站在软件工程的角度上考虑问题的。因此,不应该让大家总是使用库和既有的实现,在项目中这么做即可,平时自己做小玩意或者搞一点hack,还这么做,那就不太合适了。
作者:dog250 发表于2011-12-7 21:54:10 原文链接
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-4-28 14:39 , Processed in 0.012190 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表