註:本課程筆記參考CS50官方課程和官方notes。
本週的課程是數據結構(data structure)。
當解決複雜的問題,需要面對大量的數據的時候,就需要先想想怎麼組織這些數據。比如很多app用戶量上億,這麼大的數量,怎麼存儲用戶名和密碼,然後讓用戶花最少的時間登陸自己的帳號,這就涉及按什麼邏輯處理這些數據,可以讓用戶啊很快的log in。這個處理背後的邏輯就是數據結構。
課程筆記:
課程總共花費約9個小時(課程看三遍,某些short看三遍)。
1.Array
優點:back to back,無論在在stack或heap上數據存儲都是連續的。
缺點:如果存在stack上存儲大小是固定的,無法增加。
如果想要增加,必須使用malloc分配動態存儲空間存在heap上面。也可直接使用realloc,既分配存儲空間,同時直接複製原本陣列的數據。
list[0] = 1;
list[1] = 2;
list[2] = 3;
int *tmp = realloc(list, 4 * sizeof(int));
free(list);
list = tmp;
// Add number to list
list[3] = 4;
複雜度:
- search:O(log n)
- insert:O(n)
2.linked list
linked list是一串link在一起的node組成的數據結構。每個node包含兩部分:變量本身和下一個node的地址。
重點在這個linked list不能斷鏈,這個教授在課程中具體有一步步code說明,還特別請同學上台很形象的展示了。新的node要插入的話,必須先讓n->next先指向,而不是讓已有的linked list來指向新的node,因為會產生斷鏈問題而導致失去後面鏈接的node。
如何在linked list裡插入數值。除了課程講的,google出來的這個視頻List in C/C++ - Inserting a node at beginning也很有幫助。
typedef struct node
{
int number;
struct node *next;
}
node;
//第一步 創立空的list
node *list = NULL;
//第二步 創立暫時的變量n的地址
node *n = malloc(sizeof(node));
//第三步 臨時變量賦值
if(n != NULL)
{
n->number = 1;
n->next = NULL;
}
//第四步 臨時變量變成list
list = n;
複雜度:
- search:O(n)。
- insert:如果需要排序的話O(n),如果不需要排序直接插入,O(1)。
3.hash table
hash table本質上就是很多linked list組成的array。像是撲克牌,可以52張混在一起變成一個linked list,也可以把四種不同的花色分成四個桶子,每一個桶子13張牌組成一個linked list,這四個linked list組成的array就是hash table。
當不介意數據是否是排序好的時候,可以用hash table。
本週的作業實作了hash table,增加理解。
複雜度:
- search:理論上是O(n),但是實際上,可以大幅增加效率,比如把人名按26個字母分為26個linked list,這樣搜索的效率就是26倍。
作業心得(Speller):
總共花費8個小時。
這週的作業難度又加大了,要求寫一個查文件裡錯誤詞彙的程式,如果沒有walkthrough簡直就是不可能完成的任務。CS50的作業設置的很難,學生很容易直接放棄;但是作業又提供有詳細講解的walkthrough,這真是一大陷阱,讓人覺得努力一下還是可以解題的。walkthrough又是點到為止,只是給個線索,還是要自己加深學習才能運用,就這樣在walkthrough的帶領下一步步試試看,不行改改再試試,就這樣一步步在不停的錯了再改中實踐正課的學習。最後一開始看似不可能完成的任務就這樣一步一步的完成了,好像玩一個很難的遊戲闖關成功一樣,收穫滿滿的成就感,接下來再闖下一關。如果CS50的作業沒有這麼精心的設計,我估計早就放棄了。
花了好幾天看了幾遍課程和短片,看了很多遍walkthrough並跟著寫出了程式,結果可以compile但是不能運行。
1.Segmentation fault (core dumped)
運行help50:
$ help50 valgrind ./speller texts/cat.txt
提示如下錯誤:
問題應該出在對hash table和linked list理解,再複習下shorts,google了insert in linked list。改正了如下錯誤:
1)hash table邏輯問題
如果用首兩個字母的話,會漏掉只有一個字母的詞,比如a和i,也會漏掉i‘m等。
2)hash table的數字問題
hash table的數字要大於等於0,小於等於N-1,不能超過這個範圍。
3)fopen沒有fclose
解決了這三個問題,發現程式可以運行,且沒有了memory leak的問題。
2.Could not unload dictionaries/large.
google了後發現問題出在unload function不小心return了false。
改正了這個錯誤再次運行。
3.case-insensitive的問題,根源應該出在check function。
我把check function的for loop改為while loop,問題依然存在。最後翻看了txt的文件,發現裡面大小寫都有,而我的hash function 沒有把大小寫區別對待。再次修改hash function,最後成功解題。
不過我的總運行時間是0.22s,遠遠超過CS50老師們的運行時間0.09s,顯示我的hash table還有很多提升空間。
下面是幾個重要的function:
//hash function
unsigned int hash(const char *word)
{
int sum = 0;
for(int i = 0; i<strlen(word);i++)
{
sum = sum+ tolower(word[i]);
}
return sum%500;
}
//load function
FILE *file = NULL;
bool load(const char *dictionary)
{
// open dictionary file:
file=fopen(dictionary,"r");
if (!dictionary)
{
return 1;
}
// read one at a time:
char word2[LENGTH + 1];
while(fscanf(file,"%s",word2)!=EOF)
{
node *n=malloc(sizeof(node));
if(n == NULL)
{
return 1;
}
strcpy(n->word,word2);
int x = hash(word2);
counter++;
n->next = table[x];
table[x] = n;
}
fclose(file);
return true;
}
//unload function
bool unload(void)
{
// TODO
for(int i = 0; i < N; i++)
{
node *cursor = table[i];
while(cursor!=NULL)
{
node *tmp=cursor;
cursor=cursor->next;
free(tmp);
}
}
return true;
}
學習心得:
本週課程對我很難,很難的東西,一次學一點,分開多次學,讓我們的大腦慢慢把這些知識組塊(chunk)。所以我花了很久學這節課,也看了很多遍。最後大腦吸收的越來越多,一開始很難的東西好像變得沒有那麼難了。
推薦學習coursera課程:學習如何學習。