垃圾代碼評析——關於《C程序設計伴侶》9.4——鏈表(三)


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

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM