希望通曉如何使用算法的開發人員首先要學習如何操作數據。而後,他們根據自己的需要應用算法技術來表示數據。通過算法操作數據主要涉及的是在內存中表示數據的技術。怎樣存儲、訪問數據以及怎樣轉換數據以便最高效的解決給定問題?大多數問題要求開發人員能夠熟練地掌握基本數據結構。
如下圖是具有三個節點的鏈表:
在C語言中,鏈表的表示如下:
struct Node{
char *city;
int temp;
struct Node *Next;
};
typedef struct Node * Link;
Link Head;
假如需要編寫一個程序,從輸入文件中讀取城市的名稱及氣溫信息。最後按照溫度和城市進行排序,並確定中間氣溫。對於這個問題,使用數組並不是一個好的選擇。因爲你不知道應該創建多大的數組才合適。或許可以聲明一個認爲足夠大的數組,但是這會浪費內存空間,並且還有輸入文件超出預期的風險。一種方案是讀兩遍輸入文件,第一遍確定大小,第二遍再進行數據處理。但是因爲磁盤I/O是非常慢的(它幾乎是總是程序中最慢的),這樣效率非常低,並不可取。一個好的方法是利用鏈表,它按接收到的數據來存儲它們。
下面是一個利用鏈表,讀入輸入文件,並按照氣溫城市進行排序的算法。citytemp.c從數據文件中讀取城市和氣溫,將記錄插入到一個鏈表中(按氣溫和城市名稱的升序進行排序),丟棄重複記錄,打印該有序鏈表,並指出位於中間的條目。數據記錄時文本文件中簡單的行,它前三個字符表示氣溫,其後接着最多124個字符表示城市名稱。
打印城市和氣溫的有序鏈表代碼如下:
/*--- citytemp.c--------------------------- Listing 2-1 ---------
* Reads a text file of cities and temperatures in the
* following format: TempCity
* where Temp is a number of three digits or
* a sign and two digits; City is a string of length < 124
* Examples: -10Duluth
* 096Phoenix
* The records are read into a singly linked list by order
* of temperature and city; duplicates are discarded. At EOF,
* the whole list is printed with an indication of the median
* temperature. And then, the list is progressively shortened
* and reprinted showing the median.
* Usage: citytemp filename.ext
*-------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*--- data definitions ---*/
struct Node { /* a node in our linked list */
char *City;
int Temp;
struct Node *Next;
};
typedef struct Node * Link; /* Links are pointers to nodes */
Link Head; /* head of our linked list */
int NodeCount; /* how many nodes in the list */
/*--- functions declarations for linked lists ---*/
int AddNodeAscend ( Link ); /* add a node */
void CreateList ( void ); /* initialize list */
int DeleteNode ( Link ); /* delete a node */
int DuplicateNode ( Link, Link ); /* handle duplicate inserts */
void FreeNode ( Link ); /* free a node's memory */
void ShowNodes ( void ); /* show list of nodes */
int NodeCmp ( Link, Link ); /* compare two nodes */
/*--- function definitions ---*/
int AddNodeAscend ( Link to_add )
{
Link pn, /* local copy of node to be added 指向將被插入的節點*/
prev, /* points to previous node 指向當前被檢查節點的前一節點*/
curr; /* points to node being examined 指向當前被檢查的節點*/
struct Node dummy;
int i;
/* Make a copy of the input node拷貝插入節點 */
pn = ( Link ) malloc ( sizeof ( struct Node ));
if ( pn == NULL )
return 0;
memcpy ( pn, to_add, sizeof ( struct Node ));
/* set up a dummy node to simplify logic建立頭節點,使邏輯更簡單 */
dummy.Next = Head;
prev = &dummy;
curr = Head;
/* insert node pn 插入pn指向的節點*/
for ( ;; prev = curr, curr = curr->Next )
{
if ( curr == NULL )
break; /* reached the end到達鏈表尾 */
i = NodeCmp ( pn, curr );//比較pn節點與curr節點
if ( i <= 0 )
break; /* pn precedes curr pn節點值小於當前節點curr */
}
if ( curr && i == 0 ) /* we have a duplicate 判斷是否重複節點*/
if ( DuplicateNode ( curr, pn ) == 0 )
return ( 1 ); /* bail out if DuplicateNode says to釋放重複節點的空間 */
//插入代碼
prev->Next = pn;
pn->Next = curr;
Head = dummy.Next;
NodeCount+=1;//yyw
return ( 1 );
}
/*--------------------------------------------------------------
* Handle the duplicate node. In this program,
* we just delete the duplicate.
*-------------------------------------------------------------*/
int DuplicateNode ( Link inlist, Link duplicate )//處理重複節點
{
FreeNode ( duplicate );//調用FreeNode,釋放重複節點的空間
return ( 0 );
}
int DeleteNode ( Link to_delete )
{
Link curr, /* the current node指向當前節點 */
prev; /* the previous node 向當前節點的前一節點*/
int i;
/*--- Is there anything in the list? ---判斷是不是空表*/
if ( Head == NULL )
return ( 0 );
/*--- If so, step through the list looking for the node ---非空,尋找要刪除的節點*/
for ( prev = NULL, curr = Head;
curr != NULL && ( i = NodeCmp ( to_delete, curr )) > 0;
prev = curr, curr = curr->Next )
/* loop around */ ;
/*--- Found a match, so delete it ---找到匹配條件的,刪除*/
if ( curr != NULL && i == 0 )//compare之後,若是相同的節點,返回值是0
{
if ( prev )
prev->Next = curr->Next;
else /* deleting Head */
Head = curr->Next;//第一個節點就是匹配待刪除的節點
FreeNode ( curr );//釋放內存
NodeCount -= 1;//節點數量減1
return ( 1 );
}
return ( 0 );
}
//按溫度、城市的規則比較兩個節點
int NodeCmp ( Link a, Link b )
{
/* returns 1, 0, -1, depending on whether the data in
* a is greater than, equal, or less than b.
*/
/* if temps are unequal, return based on temp 如果溫度不同,按溫度排序*/
if ( a->Temp != b->Temp )
return ( a->Temp - b->Temp );
/* else, return based on city's name溫度相同,按城市排序 */
return strcmp ( a->City, b->City );
}
//創建空鏈表(並沒有頭結點)
void CreateList ( void )
{
Head = NULL;
NodeCount = 0;
}
//釋放節點內存
void FreeNode ( Link n )
{
free ( n->City );
free ( n );
}
//展示節點羣
void ShowNodes( void )
{
Link pn;
int count, median;
// /* count the nodes */
// for ( count = 0, pn = Head; pn; pn = pn->Next )
// count += 1;
// /* compute the median node */
// median = count / 2 + 1;
///* compute the median node */
median = NodeCount/2+1;//yyw
/* step through the list printing cities and
* temperatures. Announce the median temperature.
遍歷鏈表城市與溫度,並指出中點
*/
if ( NodeCount ) /* only print if there's a node且僅當存在節點才展示 */
{
/* initialize the needed variables */
count = 0; /* count of nodes we've printed計算打印的節點 */
for ( pn = Head; pn; pn = pn->Next )
{
printf ( "%-20s: %3d", pn->City, pn->Temp );
count += 1;
if ( count == median )
printf ( " --Median--" );
printf ( "\n" );
}
}
else
printf ( "Empty list\n" );
}
/*--- main line ---*/
int main ( int argc, char *argv[] )
{
FILE *fin; /* file we'll be reading from */
char buffer[128]; /* where we'll read the file into */
struct Node n; /* the node we add each time */
if ( argc != 2 )
{
fprintf ( stderr, "Usage: citytemp filename.ext\n" );
exit ( EXIT_FAILURE );
}
fin = fopen ( argv[1], "rt" );
if ( fin == NULL )
{
fprintf ( stderr, "Cannot open/find %s\n", argv[2] );
exit ( EXIT_FAILURE );
}
/* Create and initialize the linked list to empty創建並初始化鏈表*/
CreateList();
/*--- main loop ---*/
while ( ! feof ( fin ))//循環直到文件流末EOF
{
/* read a record consisting of a line of text */
if ( fgets ( buffer, 127, fin ) == NULL )//從文件指針fin中讀取127-1個字符,
//存到以buff爲起始地址的空間裏,直到讀完一行,如果成功則返回s的指針,否則返回NULL。
break;
/* get rid of the trailing carriage return 行末字符爲結束符*/
buffer [ strlen ( buffer ) - 1 ] = '\0';
/* copy the city name to the node to be added 複製從buff+3開始的字符串*/
n.City = strdup ( buffer + 3 );
/* mark off the temperature and convert to int置行上第4個字符爲結束符 */
buffer[3] = '\0';
n.Temp = atoi ( buffer );//把行上前3個數字字符轉換成整型數
/* add the node to the list 設置好n節點後,插入鏈表*/
if ( AddNodeAscend ( &n ) == 0 )
{
fprintf ( stderr, "Error adding node. Aborting\n" );
exit ( EXIT_FAILURE );
}
}
ShowNodes();
/* Now, delete something */
printf( "\n" );
DeleteNode ( Head );
ShowNodes();
//從第一個節點開始,依次刪除一個節點並展示節點羣
while (Head && Head->Next)
{
printf ( "\n" );
DeleteNode ( Head->Next );
ShowNodes();
}
printf ( "\n" );
DeleteNode ( Head );
ShowNodes();
fclose ( fin );//關閉流
return ( EXIT_SUCCESS );
}
涉及的一些函數:
(1)void *memcpy(void *dest, const void *src, int n)
從源src所指的內存地址的起始位置開始拷貝n個字節到目標dest所指的內存地址的起始位置中.函數返回一個指向dest的指針。
(2)char *fgets(char *s, int n, FILE *stream)
參數: *s: 字符型指針,指向將存儲到的數據地址 n: 整型數據,將從流中讀取 n - 1 個字符 *stream: 指針數據,欲讀取的流。從文件指針stream中讀取n-1個字符,存到以s爲起始地址的空間裏,直到讀完一行,如果成功則返回s的指針,否則返回NULL。
(3)int atoi(const char *nptr)
把字符串轉換成整型數,參數nptr字符串,如果第一個非空格字符不存在或者不是數字也不是正負號則返回零,否則開始做類型轉換,之後檢測到非數字(包括結束符 \0) 字符時停止轉換,返回整型數。
(4)extern char *strdup(char *s)
複製字符串s,strdup()在內部調用了malloc()爲變量分配內存,當程序結束後,必須用free()釋放相應的內存空間,否則會造成內存泄漏
(5)extern int strcmp(const char *s1,const char * s2)
比較字符串s1和s2,一般形式:strcmp(字符串1,字符串2)。當s1<s2時,返回值<0,當s1=s2時,返回值=0,當s1>s2時,返回值>0。兩個字符串自左向右逐個字符相比(按ASCII值大小相比較),直到出現不同的字符或遇'\0'爲止。
用於該程序的一個實例數據文件如下:
算法運行截圖:
該程序顯示瞭如何創建以及遍歷鏈表、按順序添加節點,以及比較兩個節點。還包括函數DeleteNode()刪除一個節點,刪除節點需要遍歷一個鏈表,直到找到刪除的節點爲止。程序通過free()函數把當前節點佔用的內存返回給系統。