C中循环单链表的优雅实现?

Posted

技术标签:

【中文标题】C中循环单链表的优雅实现?【英文标题】:Elegant implementation of circular singly-linked list in C? 【发布时间】:2011-05-13 06:25:11 【问题描述】:

遍历经典数据结构并停在链表上。刚刚实现了一个循环单链表,但我的印象是这个列表可以用更优雅的方式表达,特别是 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;
        

    

【问题讨论】:

【参考方案1】:

看一下Linux内核源码中的循环链表:http://lxr.linux.no/linux+v2.6.36/include/linux/list.h

它的美妙之处在于您没有一个特殊的结构来让您的数据适合列表,您只需要将struct list_head * 包含在您想要作为列表的结构中。用于访问列表中项目的宏将处理偏移量计算,以从 struct list_head 指针获取数据。

内核中使用的链表更详细的解释可以在 kernelnewbies.org/FAQ/LinkedLists 找到(抱歉,我没有足够的业力来发布两个超链接)。

编辑:嗯,列表是一个双链表,而不是像你这样的单链表,但你可以采用这个概念并创建你自己的单链表。

【讨论】:

可移植且无处不在的 sys/queue.h 提供了类似的接口,但具有更大的灵活性,因为它具有尾队列、单链表、双链表和循环链表。它通过相同的偏移宏机制编译成非常紧凑的代码,没有开销。【参考方案2】:

当您将列表头视为列表的元素(所谓的“哨兵”)时,列表处理(尤其是循环列表)会变得更容易。很多特殊情况就消失了。您可以为哨兵使用虚拟节点,但如果下一个指针在结构中的第一个,您甚至不需要这样做。另一个大技巧是在修改列表时保留指向前一个节点的下一个指针的指针(以便以后修改它)。把它们放在一起,你会得到这个:

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】:

几个cmets:

我认为当您删除头节点并且列表大于 3 个元素时,remove 函数无法正确调整循环列表指针。由于列表是循环的,您必须将列表中的最后一个节点指向新的头。 您可以通过创建“find_node”函数来稍微缩短删除函数。但是,由于列表是循环的,因此仍然存在删除头节点的边缘情况,这将比非循环列表更复杂。 代码“美”在旁观者的眼中。随着代码的运行,您的代码很容易阅读和理解,这胜过大量代码。

【讨论】:

发现不错,只是修复了 remove_node 功能【参考方案4】:

我使用以下内容来创建一个动态循环单链表。它只需要大小。

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;

【讨论】:

以上是关于C中循环单链表的优雅实现?的主要内容,如果未能解决你的问题,请参考以下文章

线性表5线性表的链式实现:循环单链表

C/C++数据结构:动图演示 单链表的结构介绍与实现

C/C++数据结构:动图演示 单链表的结构介绍与实现

C/C++数据结构:动图演示 单链表的结构介绍与实现

数据结构C语言版 —— 链表增删改查实现(单链表+循环双向链表)

求单链表的长度