垃圾代码评析——关于《C程序设计伴侣》9.4——链表(四)
【样本】
——陈良乔 ,《C程序设计伴侣》,人民邮电出版社,2012年10月,p241~242
【评析】
这段代码的基本能算及格。在链表中查找,无非是从头到尾一个一个的比较。
代码中的node是一个多余的变量。写链表操作代码时很多人都有这种恶习。实际上对于链表的操作者来说,由于链表是一种递归结构,所以只要是单调的从前到后的操作,任何一个结点的next成员都可以视为一个head。除非在操作过程中需要“回头”,没有任何必要保留初始的head值。
此外,代码中的break语句不如改成return node;。最后一句的return node;不如写成return NULL;。这样代码的意义更加明确。
student* find(student* node,char* key) { while( node != NULL ) { if(strcmp( node->name , key )==0) { return node ; } node = node->nex t; } return NULL ; }
【样本】
——陈良乔 ,《C程序设计伴侣》,人民邮电出版社,2012年10月,p242
【评析】
这段议论实在能把人雷得外焦里嫩。顺序查找效率确实不算高,但对于链表来说实在是无奈之举,因为这是链表本身的结构特性所决定的,除此之外别无他法也是无可奈何的事情。二分查找效率确实不错,但是那是有前提条件的,第一数据要有序,第二通常只能用于数组,因为数组的中间元素可以直接访问。但是对于链表来说,由于只能顺序访问各个结点而且根本无法回头,所以寻找链表的终点几乎是一件和顺序查找同样费劲的事情,这还没算上为链表排序的巨大成本。所以用二分法对链表查找就好像是说,剃须刀刮胡子有些慢,买把锋利的王麻子菜刀刮胡子会更有效。
下面就欣赏一下如何用菜刀刮胡子。
【样本】
——陈良乔 ,《C程序设计伴侣》,人民邮电出版社,2012年10月,p244
【评析】
首先这里的head=sort(head.cmpname);的成本比顺序查找还要高,因为冒泡排序需要比较的次数是N平方量级的,而顺序查找需要比较的次数是N的一次方量级的。
其次这里的tail=gettail(head);找到链表的结尾同样需要比较N次。链表的尾巴一向是深藏不露的,为揪出链表的尾巴而专门写了一个函数,历史上这还是首次。
再看一下midfind()函数:
【样本】
——陈良乔 ,《C程序设计伴侣》,人民邮电出版社,2012年10月,p243
【评析】
这里明显有个漏洞,就是当head、tail都为NULL时,亦即链表为空时,代码是错误的,程序很可能因此而崩溃。
二分法的关键在于确定中间结点,这里的student* mid =getmid(head,tail);又是如何得到的呢?再来看一下getmid()函数:
【样本】
——陈良乔 ,《C程序设计伴侣》,人民邮电出版社,2012年10月,p242~243
【评析】
天哪!先从头走到尾,数出链表一共有多少个结点,然后再从头走到中间,一共需要移动3/2N次。问题在于这样的过程不是一回而是多次。
通俗地讲一下。假如有一队人,你要从中找一个人,你本可以从前走到队尾挨个问一下。但是这里的样本代码却嫌这种方法太慢。他“采用了一种更加有效的查找算法”:首先,用两两交换的办法让队伍排好队(注意这本身就比从前走到尾费事、费时);然后从开头走到队尾数一共有多少人;然后再按照人数从开头走到队伍中间,了解一下要找的人在前面还是在后面;如果在前面就再从前走到中间数人数然后再从前面走到队伍前部的中间……
试问,天下还有比这更愚蠢的“更加有效的查找算法”吗?
【重构】
这次还是免了吧。用二分法对链表查找,俺实在丢不起那人。