前文鏈接: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 ); //繼續
}
}
//--------------------------------------//

