最近在學習《c和指針》這本書,感覺書中有很多的編程技巧,之前在學習過程中,沒有學習到,這裏做個簡單的記錄。
對於c語言的鏈表,簡單來說,可以有單鏈表和雙鏈表兩種,每種都有實際運用中的優勢所在。但是鏈表因爲操作的是指針,因此也存在更大的風險。對於鏈表與數組的優劣不是本文的重點,本文主要是想以鏈表的插入函數舉例,講解對於代碼的部分優化、簡化,完成代碼實現方式的改變和減小代碼體積。
下面是未優化的單鏈表的插入函數的實現方式:
#include <stdio.h>
#include <stdlib.h>
#define FAILE 0
#define TRUE 1
typedef struct NODE{
struct NODE *link;
int value;
}Node;
int sll_insert(Node **rootp, int new_value)
{
Node *current;
Node *previous;
Node *new_node;
current = *rootp;
previous = NULL;
/*尋找正確的插入的位置,方法是按序訪問鏈表*/
while(current != NULL && current->value < new_value)
{
previous = current;
current = current->link;
}
/*爲新節點分配內存*/
new_node = (Node *)malloc(sizeof(Node));
if (new_node == NULL)
{
return FAILE;
}
new_node->value = new_value;
/*把新節點插入鏈表中*/
new_node->link = current;
if (previous == NULL)
{
*rootp = new_node;
}
else
{
previous->link = new_node
}
return TRUE;
}
單鏈表的操作是單向性的,對於插入操作,要正確的插入一個節點,就需要保存上一個節點的指針。但是這也就存在一個問題了,如果插入的位置是首個節點,就需要判斷上一個節點previous是不是NULL,從而得知該插入的位置是首個節點。但是這部分實現能不能被優化,即不用理會該插入的位置是不是首個節點,答案是肯定的。
消除這個特殊情況的關鍵在於:必須認識到鏈表中的每個節點都有一個指向它的指針。對於首個節點,指向它的指針就是根指針,而對於其他節點,這個指針就是前一個節點的link字段。而且對於c語言來說,允許讀取指針的地址,也就是操作符&,其他語言可能不允許有這樣的操作存在。
下面給出實現優化後的單鏈表的代碼:
#include <stdio.h>
#include <stdlib.h>
#define FAILE 0
#define TRUE 1
typedef struct NODE{
struct NODE *link;
int value;
}Node;
int sll_insert(register Node **linkp, int new_value)
{
register Node *current;
register Node *new_node;
/*尋找正確的插入的位置,方法是按序訪問鏈表*/
while((current = *linkp) != NULL && current->value < new_value)
{
linkp = ¤t->link;
}
/*爲新節點分配內存*/
new_node = (Node *)malloc(sizeof(Node));
if (new_node == NULL)
{
return FAILE;
}
new_node->value = new_value;
/*把新節點插入鏈表中*/
new_node->link = current;
*linkp = new_node;
return TRUE;
}
而對於雙鏈表,相比於單鏈表,雙鏈表可以實現以任何方向遍歷鏈表。對於在一條雙鏈表中插入一個節點,需要考慮的情況有4種:
1、新值可能必須插入到鏈表的中間位置;
2、新值可能必須插入到鏈表的起始位置;
3、新值可能必須插入到鏈表的結束位置;
4、新值可能必須即插入到起始位置,又插入到結束位置(即原鏈表爲空);
在實際編程中當然可以針對這4種情況進行代碼實現。下面給出實現的代碼:
#include <stdio.h>
#include <stdlib.h>
typedef struct NODE{
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;
int dll_insert(Node *rootp, int value)
{
Node *prev;
Node *next;
Node *newnode;
/*prev將指向應該在新節點之前的那個節點,next將指向應該在新節點之後的那個節點*/
for(prev = rootp; (next = prev->fwd) != NULL; prev = next)
{
if (next->value == value)
{
return 0;
}
if (next->value > value)
{
break;
}
}
newnode = (Node *)malloc(sizeof(Node));
if (newnode == NULL)
{
return -1;
}
newnode->value = value;
if (next != NULL)
{
/*情況1或2:並非位於鏈表尾部*/
if (prev != rootp)
{
/*情況1:並非位於鏈表起始位置*/
newnode->fwd = next;
prev->fwd = newnode;
newnode->bwd = prev;
next->bwd = newnode;
}
else
{
/*情況2:位於鏈表起始位置*/
newnode->fwd = next;
rootp->fwd = newnode;
newnode->bwd = NULL;
next->bwd = newnode;
}
}
else
{
/*情況3或4:位於鏈表的尾部*/
if (prev != rootp)
{
/*情況3:並非位於鏈表的起始位置*/
newnode->fwd = NULL;
prev->fwd = newnode;
newnode->bwd = prev;
prev->bwd = newnode;
}
else
{
/*情況4:位於鏈表的起始位置*/
newnode->fwd = NULL;
rootp->fwd = newnode;
newnode->bwd = NULL;
rootp->bwd = newnode;
}
}
return 1;
}
仔細查看代碼,可以發現在各個嵌套的if語句中存在相似之處。因此就可以對其進行提煉、簡化。這裏將會依次用到兩個技巧。
第1個技巧:
if (x == 3)
{
i = 1;
do_something;
j = 2;
}
else
{
i = 1;
do_something_different;
j = 2;
}
轉換等價於:
i = 1;
if (x == 3)
{
do_something;
}
else
{
do_something_different;
}
j = 2;
第2個技巧:
if (pointer != NULL)
{
field = pointer;
}
else
{
field = NULL;
}
等價於:
field = pointer;
對於這兩個技巧的運用,關鍵在於把看上去不一樣但實際上執行了相同任務的語句進行改寫,寫成同一種形式。下面給出最終的簡化結果:
#include <stdio.h>
#include <stdlib.h>
typedef struct NODE{
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;
int dll_insert(Node *rootp, int value)
{
Node *prev;
Node *next;
Node *newnode;
/*prev將指向應該在新節點之前的那個節點,next將指向應該在新節點之後的那個節點*/
for(prev = rootp; (next = prev->fwd) != NULL; prev = next)
{
if (next->value == value)
{
return 0;
}
if (next->value > value)
{
break;
}
}
newnode = (Node *)malloc(sizeof(Node));
if (newnode == NULL)
{
return -1;
}
newnode->value = value;
newnode->fwd = next;
prev->fwd = newnode;
if (prev != rootp)
{
newnode->bwd = prev;
}
else
{
newnode->bwd = NULL;
}
if (next != NULL)
{
next->bwd = newnode;
}
else
{
rootp->bwd = newnode;
}
return 1;
}
問題來了,上面的代碼能不能繼續簡化,答案是可以的,比如:
/*把節點添加到鏈表中*/
newnode->fwd = next;
prev->fwd = newnode;
newnode->bwd = prev != rootp ? prev :NULL;
(next != NULL ? next : rootp)->bwd = newnode;
總結:
對於代碼的優化和簡化在一定程序上可以提高程序的運行速度和減小程序的體積,但是另一方面,優化或簡化代碼會降低程序的可讀性,難於維護。因此對於一個程序員來說,權衡兩者的利弊就是其該做的事。致力於編寫高效且易維護的程序纔是一名程序員要追求的方向之一。