1. 程式人生 > 實用技巧 >Scrapy 爬取重大注意事項!! 因為這個困擾了我4天,頭髮都掉光了。。

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;
}