前文鏈接:http://www.cnblogs.com/pmer/archive/2012/11/22/2783672.html
【樣本】
——陳良喬 ,《C程序設計伴侶》,人民郵電出版社,2012年10月,p237
【評析】
實際上譚浩強的書講到了鏈表的查找、刪除和插入。這一點要實事求是,不能因為譚書有大量錯誤就不顧事實。
【樣本】
——陳良喬 ,《C程序設計伴侶》,人民郵電出版社,2012年10月,p237
【評析】
所謂“最常見的一個處理就是對鏈表中的數據進行排序”純屬無稽之談。事實上對鏈表排序,多半是一個沒有意義的問題。因為鏈表在建立時可以很容易建成有序的。譬如,對前文中建立鏈表的代碼,只要稍微修改一下insert()函數並提供一個用於比較的lessthan()函數就可以輕而易舉地實現建立有序鏈表的功能。
int lessthan ( data_t * , data_t * );
int lessthan ( data_t *pd1 , data_t *pd2 ) { return pd1->score < pd2->score ; }
void insert( node_t ** p_next , node_t * p_node) { if( ( * p_next == NULL ) || lessthan ( &p_node -> item , &(* p_next)-> item) ) { p_node->next = * p_next ; * p_next = p_node ; } else { insert ( &(* p_next)->next , p_node ) ; } }
既然建立鏈表時可以輕易地把鏈表建成有序的,而且相對於數組來說鏈表付出了額外存儲鏈接指針的代價,那么在建立時不考慮順序問題,而在鏈表建立之后再對鏈表排序就顯得是沒事找事了。因此對鏈表排序在很大程度上是一個沒有意義的問題。所以,絕大部分講解算法的書籍都只關注數組的排序問題,從來不考慮鏈表的排序問題。
但是考慮到結點的數據部分可能由多個數據項組成,由於在建立鏈表時只能根據一個數據項排序,如果問題要求在鏈表建立后再根據另一個數據項進行排序,那么這種鏈表的排序問題也許會稍微有點意義。
但是選擇冒泡法對鏈表排序則屬於荒謬之舉。因為冒泡法排序基本上是通過反復交換數據實現的,而交換則通常是在數組排序中一種不得已的選擇。在數組中數據是一個挨一個緊密排放的,有點像一排人滿滿登登地擠在一張大通鋪上,改變任何一個元素的位置都必然涉及到其他的元素位置的改變,所以有時只能通過交換這種手段來改變元素的位置。但是鏈表則與數組截然不同,鏈表的結點可以很容易地拿出來並插入到其他位置,但是交換對於鏈表來說則是笨重無比的一個操作。因此選擇以交換為主的算法對鏈表排序有點像選擇用飯桶吃飯,方法也許可行但卻非常不智。
【樣本】
——陳良喬 ,《C程序設計伴侶》,人民郵電出版社,2012年10月,p237
【評析】
這個函數充分說明了下面這種數據結構的設計為什么非常幼稚。
//定義表示學生信息結點的結構體 typedef struct _student { char name[20]; float score; //定義指向鏈表中下一個結點的指針 struct _student* next; }student;
首先swap()函數交換的是兩個結點數據項部分,但卻不得不把兩個參數寫成了指向結點的student *類型,名不副實。而如果設計成
typedef struct { char name[20]; float score; } student_t; typedef struct node { student_t data; //定義指向鏈表中下一個結點的指針 struct node * next; }node_t;
則可以名副其實地把swap()的兩個參數寫成指向結點中數據的student_t *類型。
其次,在后一種方案下,swap()可以簡單地寫為
void swap(student_t * ps1 , student_t * ps2) { student_t temp = *ps1; *ps1 = *ps2 ; *ps2 = temp ; }
而且這種方案沒有維護方面的后顧之憂,即使student_t類型有所改動,這個函數也不需要做任何改動。
【樣本】
——陳良喬 ,《C程序設計伴侶》,人民郵電出版社,2012年10月,p237~238
【評析】
這個函數有一個顯而易見的錯誤,就是當head為NULL亦即對空鏈表排序時,因為
student *next = node->next ;
中的node->next是一個無意義的運算而可能引起程序崩潰。顯然這是因為作者把while語句寫成了do-while語句所致。
前面說過,這種交換結點數據的辦法對鏈表來說非常笨拙,因此作者給出了另一種寫法。
【樣本】
——陳良喬 ,《C程序設計伴侶》,人民郵電出版社,2012年10月,p237~238
【評析】
這里的sort()函數居然居然用了6個變量操作鏈表。函數結構也極其復雜,亂作一團,慘不忍讀,比前一個寫法更爛。這種函數是廢品,根本不值得細看。盡管如此,這里還是提一下其中明顯的錯誤和瑕疵。
在這個函數的do-while語句中的
student *next = node->next ;
在head==NULL時是沒有意義的,可能會導致嚴重的錯誤。
一個明顯的瑕疵是內層循環中的if語句
if(cmp(node,next)) { /*……*/ next = node->next; } else { /*……*/ next = node->next; }
為什么會把代碼寫得如此混亂不堪呢?很大一部分原因就在於使用冒泡法對鏈表排序。
鏈表有很多好的性質,比如可以很方便地刪除、插入一個結點而不用移動其他結點,可以很很容易地建立一個有序的鏈表,但是卻不適宜交換結點。用冒泡法對鏈表排序是用鏈表做鏈表所不適宜的操作,這就像抱着飯桶吃飯一樣,必然會把自己弄得手忙腳亂、狼狽不堪。
如果真的確實需要對鏈表排序的話,其實倒是把鏈表的結點一個一個拆下來,然后按照有序的方式重新建立鏈表更容易。這看起來仿佛是一種“異地”排序,但是由於鏈表本身的特點,這樣其實並不需要額外的內存空間。
【重構】
#include <stdlib.h> #include <stdio.h> #include <string.h> //----------通用函數---------------------// void *my_malloc( size_t ); //--------------------------------------// //----------“數據”類型----------------// typedef struct { char name[20]; float score; } data_t; //-----------關於“數據”的函數-----------// int input ( data_t * ) ; void output ( data_t * ) ; int lessthan_score ( data_t * , data_t * ); int lessthan_name ( data_t * , data_t * ); //---------------------------------------// //---------“結點”類型------------------// typedef struct node { data_t item ; struct node *next ; } node_t; //--------“鏈表”操作函數---------------// void create( node_t ** , int (*) ( data_t * , data_t * )); void insert( node_t ** , node_t * , int (*) ( data_t * , data_t * ) ); void print ( node_t * ); void sort( node_t ** , int (*) ( data_t * , data_t * ) ); //--------------------------------------// int main( void ) { node_t *head = NULL ; //空鏈表 puts("建立一有序鏈表"); create( &head , lessthan_score ); //測試 puts("輸出鏈表"); print( head ); //按姓名排序
puts("按姓名排序"); sort( &head , lessthan_name ); print( head ); return 0; } //-------通用函數定義-------------------// void *my_malloc( size_t size ) { void *p = malloc( size ); if( p == NULL ) { puts("內存不足"); exit(1); } return p; } //--------------------------------------// //---------“數據”操作函數定義---------// int input ( data_t *p_data ) { puts("請輸入姓名、成績:"); if( scanf( "%20s%f" , p_data->name , &p_data->score ) != 2 ) //20很重要 { while( (getchar()) != '\n' ) //清空輸入緩存 ; return 0 ; } return !0 ; } void output ( data_t *p_data ) { printf ("姓名:%s 成績:%.2f\n" , p_data->name , p_data->score ) ; } int lessthan_name ( data_t *pd1 , data_t *pd2 ) { return strcmp ( pd1->name , pd2->name ) < 0 ; } int lessthan_score ( data_t *pd1 , data_t *pd2 ) { return pd1->score < pd2->score ; } //--------------------------------------// //----------“鏈表”操作函數定義--------// void print( node_t *p_node ) { if( p_node != NULL ) { output ( &p_node->item ); print( p_node->next ); } } void sort( node_t **p_next , int (*less) ( data_t * , data_t * ) ) { node_t * new_head = NULL ; node_t * temp ; while( (temp = *p_next) != NULL ) //記錄第一個結點 { *p_next = (*p_next)->next ; //切下一個結點 insert ( &new_head , temp , less ); //將切下的結點插入到新鏈表 } *p_next = new_head ; } void insert( node_t ** p_next , node_t * p_node , int (*less) ( data_t * , data_t * ) ) { if( ( * p_next == NULL ) || less ( &p_node -> item , &(* p_next)-> item) ) { p_node->next = * p_next ; * p_next = p_node ; } else { insert ( &(* p_next)->next , p_node , less ) ; } } void create( node_t **p_next , int (*less) ( data_t * , data_t * )) { data_t data; if( input ( & data ) != 0 ) { node_t *p_node = my_malloc( sizeof (*p_node) ); p_node->item = data ; insert( p_next , p_node , less);//插入結點 create( p_next , less ); //繼續 } } //--------------------------------------//