Scrapy 爬取重大注意事項!! 因為這個困擾了我4天,頭髮都掉光了。。
樹
樹屬於非線性結構。邏輯上的樹指的是:一堆資料中包含一個稱之為根的節點,其他的節點又組成了若干棵樹,成為根節點的後繼。
如上圖所示,根節點與子樹只是相對概念,在任何一棵樹中都有一個根節點,而這棵樹本身又可以是別的樹的子樹。
樹的基本概念
雙親(parent)和孩子(children):一個節點的後繼節點被稱為該節點的孩子,相應地該節點被稱為這些孩子的雙親,比如上圖中的A是B,C,D的雙親。
兄弟(sibling):擁有共同雙親的節點互為兄弟節點,比如上圖中B,C,D和F,G。
節點的度(degree):一個節點的孩子個數,稱為該節點的度,比如A的度為3,B的度為0,C的度為1,D的度為2。
節點的層次(level):人為規定樹的根節點的層次為 1,他的後代節點的層次依次加 1,比如A的層次為1,B,C,D的層次為2,E,F,G的層次為3,以此類推。
樹的高度(height):樹中節點層次的最大值,如上圖中樹的高度為4。
終端節點(terminal):度為 0 的節點,比如B,E,H,G都是葉子。
二叉樹
在各種不同種類的樹種,二叉樹是最重要的,下列為各種不同的二叉樹。
二叉搜尋樹
二叉搜尋樹除了是任意節點的度小於等於2的樹外,還必須是嚴格區分左右節點次序的,這樣就能夠操作到想要操作的某個節點。
如上圖所示,將大於雙親節點的值往右邊存放(右孩),小於雙親節點的值往左存放(左孩)。
實驗程式
節點設計
&emsp使用一個結構體來構造一個二叉搜尋樹。
p_tree new_node(p_tree new, int data) { new = calloc(1, sizeof(my_tree)); if ( NULL== new) { perror("Memory allocation failure\n"); } new->data = data; new->L = new->R = NULL; return new; }
初始化根節點
// 讓使用者輸入新節點的資料 int input_msg(char * msg) { int num ; printf("%s:\n" , msg); scanf("%d" , &num); while(getchar() != '\n'); return num; } p_tree init_root(p_tree new_root) { new_root = calloc(1, sizeof(my_tree)); if ( NULL== new_root) { perror("root node Memory allocation failure\n"); } new_root->data = input_msg("Please enter the root node data"); new_root->L = new_root->R = NULL; return new_root; }
插入節點
使用遞迴的方法,找到樹中合適的位置將新節點插入進去。
p_tree insert_node(p_tree root, p_tree new)
{
if (NULL == root)
return new;
if (new->data < root->data)
{
root->L = insert_node(root->L, new);
}
else
{
root->R = insert_node(root->R, new);
}
}
如上圖所,如果在該樹中新增一個數據70,函式insert_node的呼叫過程如棕色箭頭所示,返回過程如藍色箭頭所示。
中序遍歷(從小到大)
使用中序遍歷的方法來列印樹中的資料。
p_tree traverse_tree(p_tree root)
{
if (NULL == root)
{
return NULL;
}
traverse_tree(root->L);
printf("%d\t", root->data);
traverse_tree(root->R);
}
刪除節點
將樹中的指定資料刪除
p_tree del_node(p_tree root, int del_data)
{
if (NULL == root)
{
return root;
}
//如果需要刪除的資料比根節點的資料小往左邊找,否則右邊找
if (del_data < root->data)
{
root->L = del_node(root->L, del_data);
}
else if (del_data > root->data)
{
root->R = del_node(root->R, del_data);
}
else if (del_data == root->data) //當找到需要刪除的資料時,執行這個程式碼塊
{
p_tree tmp;
if (root->L != NULL)//如果有左孩子,那就在左孩子中找一個最大的來替換
{
for (tmp = root->L; tmp->R != NULL; tmp=tmp->R); //通過迴圈找到root2左邊最右的節點(右腳為空)
root->data = tmp->data; //替換
root->L = del_node(root->L, tmp->data); //再次遞迴,來刪除原資料
}
else if (root->R != NULL)如果沒有左孩子,那就在右孩子中找一個最小的來替換
{
for (tmp = root->R; tmp->L != NULL; tmp=tmp->L); //如果沒有左孩子,那就在右孩子中找一個最小的來替換
root->data = tmp->data;
root->R = del_node(root->R, tmp->data);
}
else
{
free(root);
printf("Data deletion successful\n");
return NULL;
}
}
perror("The data could not be found\n");
return root;
}
假設需要刪除資料90,過程如圖所示,首先經過遞迴找到需要刪除的資料。當找到這個資料時,就以這個節點作為根節點,來查詢合適替換掉這個根節點的值。經過推理,當這個根節點的左孩子存在時,就在該左孩子的右孩子中查詢最大值;當左孩子不存在時,就在該右孩子的左孩子中查詢最小值。最後將查找出的值替換掉需要被刪除的節點。
如圖所示,經過for迴圈找到88是個合適替換掉90的值。
如圖所示,當將數值88替換掉90後,就需要將原來的資料88刪除,以root2的左孩子為根節點再次遞迴呼叫自己,經過遞迴當到達需要刪除的88,將該節點指向NULL
如圖所示,這是整個遞迴呼叫過程。
測試函式
int main(int argc, char const *argv[])
{
p_tree root;
int data[] = {60, 50 ,40, 65, 62, 90, 80, 75, 70, 85, 88, 120, 110, 115, 130, 140};
root = init_root(root);
p_tree new;
for (int i = 0; i < sizeof(data)/sizeof(int); i++)
{
new = new_node(new, data[i]);
insert_node(root, new);
}
traverse_tree(root);
printf("\n");
int del_data = input_msg("Please enter the data you want to delete");
del_node(root, del_data);
traverse_tree(root);
printf("\n");
return 0;
}