簡體   English   中英

在C中優雅實現循環單鏈表?

[英]Elegant implementation of circular singly-linked list in C?

經歷了經典的數據結構並停止在鏈表上。只是實現了一個循環的單鏈表,但我印象深刻的是這個列表可以用更優雅的方式表達,特別是remove_node函數。 牢記效率和代碼可讀性,任何人都可以為單鏈循環列表提供更簡潔有效的解決方案嗎?

#include <stdio.h>
#include <stdlib.h>


struct node{
    struct node* next;
    int value;
};


struct list{
    struct node* head;
};


struct node* init_node(int value){
    struct node* pnode;
    if (!(pnode = (struct node*)malloc(sizeof(struct node)))){
        return NULL;
    }
    else{
        pnode->value = value;   
    }
    return pnode;
}

struct list* init_list(){
    struct list* plist;
    if (!(plist = (struct list*)malloc(sizeof(struct list)))){
        return NULL;        
    }
    plist->head = NULL;
    return plist;
}


void remove_node(struct list*a plist, int value){

    struct node* current, *temp;
    current = plist->head;
    if (!(current)) return; 
    if ( current->value == value ){
        if (current==current->next){
            plist->head = NULL; 
            free(current);
        }
        else {
            temp = current;
            do {    
                current = current->next;    
            } while (current->next != plist->head);

            current->next = plist->head->next;
            plist->head = current->next;
            free(temp);
        }
    }
    else {
        do {
            if (current->next->value == value){
                temp = current->next;
                current->next = current->next->next;
                free(temp);
            }
            current = current->next;
        } while (current != plist->head);
    }
}

void print_node(struct node* pnode){
    printf("%d %p %p\n", pnode->value, pnode, pnode->next); 
}
void print_list(struct list* plist){

    struct node * current = plist->head;

    if (!(current)) return;
    if (current == plist->head->next){
        print_node(current);
    }
    else{
        do {
            print_node(current);
            current = current->next;

        } while (current != plist->head);
    }

}

void add_node(struct node* pnode,struct list* plist){

    struct node* current;
    struct node* temp;
    if (plist->head == NULL){
        plist->head = pnode;
        plist->head->next = pnode;
    }
    else {
        current = plist->head;
        if (current == plist->head->next){
            plist->head->next = pnode;
            pnode->next = plist->head;      
        }
        else {
            while(current->next!=plist->head)
                current = current->next;

            current->next = pnode;
            pnode->next = plist->head;
        }

    }
}

看一下Linux內核源代碼中的循環鏈表: http//lxr.linux.no/linux+v2.6.36/include/linux/list.h

它的美妙之處在於你沒有一個特殊的結構來使你的數據適合列表,你只需要在你希望作為列表的結構中包含struct list_head * 用於訪問列表中項目的宏將處理偏移量計算,以從struct list_head指針獲取數據。

可以在kernelnewbies.org/FAQ/LinkedLists上找到對內核中使用的鏈表的更詳細說明(對不起,我沒有足夠的業力來發布兩個超鏈接)。

編輯:嗯,列表是一個雙鏈表,而不是像你一樣的單鏈表,但你可以采用這個概念並創建自己的單鏈表。

當您將列表頭部視為列表的元素(所謂的“哨兵”)時,列表處理(特別是循環列表)變得更容易。 很多特殊情況都消失了。 您可以為sentinel使用虛擬節點,但如果下一個指針在結構中是第一個,則您甚至不需要這樣做。 另一個重要的技巧是在修改列表時保持指向前一個節點的下一個指針(以便稍后可以修改它)。 總而言之,你得到這個:

struct node* get_sentinel(struct list* plist)
{
    // use &plist->head itself as sentinel!
    // (works because struct node starts with the next pointer)
    return (struct node*) &plist->head;
}

struct list* init_list(){
    struct list* plist;
    if (!(plist = (struct list*)malloc(sizeof(struct list)))){
        return NULL;        
    }
    plist->head = get_sentinel(plist);
    return plist;
}

void add_node_at_front(struct node* pnode,struct list* plist){
    pnode->next = plist->head;
    plist->head = pnode;
}

void add_node_at_back(struct node* pnode,struct list* plist){
    struct node *current, *sentinel = get_sentinel(plist);

    // search for last element
    current = plist->head;
    while (current->next != sentinel)
        current = current->next;

    // insert node
    pnode->next = sentinel;
    current->next = pnode;
}

void remove_node(struct list* plist, int value){
    struct node **prevnext, *sentinel = get_sentinel(plist);
    prevnext = &plist->head; // ptr to next pointer of previous node
    while (*prevnext != sentinel) {
        struct node *current = *prevnext;
        if (current->value == value) {
            *prevnext = current->next; // remove current from list
            free(current); // and free it
            break; // we're done!
        }
        prevnext = &current->next;
    }
}

void print_list(struct list* plist){
    struct node *current, *sentinel = get_sentinel(plist);
    for (current = plist->head; current != sentinel; current = current->next)
        print_node(current);
}

一些評論:

  • 我認為當刪除頭節點並且列表大於3個元素時,remove函數沒有正確調整循環列表指針。 由於列表是循環的,您必須將列表中的最后一個節點指向新頭。
  • 您可以通過創建“find_node”函數來稍微縮短刪除功能。 然而,由於列表是循環的,因此仍然存在刪除頭節點的邊緣情況,其將比非循環列表中更復雜。
  • 代碼“美”在旁觀者的眼中。 隨着代碼的推移,你的代碼很容易閱讀和理解,這些代碼在野外勝過很多代碼。

我使用以下內容創建一個動態循環單鏈表。 所需要的只是尺寸。

Node* createCircularLList(int size)
{
    Node *it; // to iterate through the LList
    Node *head;

    // Create the head /1st Node of the list
    head = it = (Node*)malloc(sizeof(Node));
    head->id = 1;

    // Create the remaining Nodes (from 2 to size)
    int i;
    for (i = 2; i <= size; ++i) {
        it->next = (Node*)malloc(sizeof(Node)); // create next Node
        it = it->next;                          // point to it
        it->id = i;                             // assign its value / id
        if (i == 2)
            head->next = it; // head Node points to the 2nd Node
    }
    // close the llist by having the last Node point to the head Node
    it->next = head;

    return head;    // return pointer to start of the list
}

我定義Node ADT如下:

typedef struct Node {
    int id;
    struct Node *next;
} Node;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM