数据结构-带头 双向 循环 链表函数接口实现


 
 

1. 双向、带头、循环链表介绍

在这里插入图片描述
带头双向循环链表:结构最复杂,但实现接口时比较简单,一般用在单独存储数据。实际使用的很多链表数据结构大多都是带头双向循环链表。
 

2. 双向链表结构体、常用函数接口

// 双向、带头、循环
typedef int LTDataType;
typedef struct ListNode
{   // 双向链表的一个结点
    struct ListNode* _prev;
    LTDataType _val;
    struct ListNode* _next;
}ListNode;

typedef struct List
{
    ListNode* _head; // 头指针 
}List;

   定义了两个结构体,第一个结构体是双向链表的结点(前驱指针数据域后继指针),第二个结构体定义了指向双向链表头指针
 
单链表的函数接口:

ListNode* BuyListNode(LTDataType x);// 建立新结点
void ListInit(List* plt);// 初始化
void ListDestroy(List* plt);// 销毁

void ListPushBack(List* plt, LTDataType x);// 尾插
void ListPopBack(List* plt);// 尾删
void ListPushFront(List* plt, LTDataType x);// 头插
void ListPopFront(List* plt);// 头删

ListNode* ListFind(List* plt, LTDataType x); // 查找结点x,返回指向结点x的指针
void ListInsert(ListNode* pos, LTDataType x);  // 在pos的前面插入结点x  
void ListErase(ListNode* pos);// 删除pos位置的结点
void ListRemove(List* plt, LTDataType x);// 删除结点x

int ListSize(List* plt);//双向链表的大小
int ListEmpty(List* plt);//判断双向链表是否为空
void ListPrint(List* plt);// 打印

 

3. 双向链表接口的实现

3.1 建立新结点

建立新结点时,先malloc一个新结点空间,再将给定值赋给新结点的数据域,最后把新结点的前后指针置空。

ListNode* BuyListNode(LTDataType x)// 建立新结点
{
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));  
    node->_val = x;
    node->_prev = NULL;
    node->_next = NULL;
    return node;
}

 

3.2 初始化

先调用BuyListNode函数申请一个头结点,因为是循环链表,所以所有指针都指向它自己。

void ListInit(List* plt)// 初始化
{
    assert(plt);
    ListNode* head = BuyListNode(-1);
   // plt->_head->_next = plt->_head;
   // 应该指向第一个结点,但是此时没有第一个结点和最后一个结点,  
   // 它指向它自己,所以简化一下,如下:
    head->_prev = head;
    head->_next = head;
    plt->_head = head;
}

 

3.3 销毁

定义一个指向第一个结点的指针,(第一个结点不是头结点),循环遍历,依次释放,并将指针指向下一个要释放的位置。循环结束后,头结点需要单独释放,并将头指针置空。

void ListDestroy(List* plt)// 销毁
{
    assert(plt);
    if (plt->_head == NULL)
    {
        return 0;
    }
    ListNode* cur = plt->_head->_next;//cur是指向第一个元素的指针  
    while (cur != plt->_head)  // 注意这个循环结束条件
    {
        ListNode* next = cur->_next;
        free(cur);
        cur = next;
    }
    free(plt->_head);//释放头结点
    plt->_head = NULL;//头结点指针置空
}

 

3.4 尾插

根据头结点的前驱指针找到最后一个结点,将新结点连接到最后一个结点之后,头结点之前,依次改变四个指针的指向。

void ListPushBack(List* plt, LTDataType x)// 尾插
{
    assert(plt);
    ListNode* head = plt->_head;
    ListNode* tail = head->_prev;
    ListNode* newnode = BuyListNode(x);
    
    tail->_next = newnode; // 将新结点连接到最后一个结点的后面  
    newnode->_prev = tail;
    
    head->_prev = newnode; // 将新结点连接到头结点前面
    newnode->_next = head;
}

 

3.5 尾删

定义两个指针,找到倒数第二个结点和最后一个结点,直接把倒数第二个结点和头结点连接,释放掉最后一个结点即可。

void ListPopBack(List* plt)// 尾删
{
    assert(plt);
    if (plt->_head == NULL || plt->_head->_next == plt->_head)  
    {
        return 0;
    } // 保证至少有一个结点
    
    ListNode* cur = plt->_head->_prev; // cur一定不为空  
    ListNode* prev = cur->_prev; // 找到倒数第二个结点
    
    prev->_next = plt->_head;
    plt->_head->_prev = prev;
    free(cur);
    cur = NULL;
}

 

3.6 头插

定义一个指向第一个结点的指针,将新结点和头结点和第一个结点连接起来,改变四个指针的指向。

void ListPushFront(List* plt, LTDataType x)// 头插
{
    assert(plt);
    ListNode* newnode = BuyListNode(x); // 申请一个新结点
    if (plt->_head->_next == plt->_head)
    {   // 链表为空,头插相当于尾插
        ListPushBack(&plt, x);
        return 0;
    } 
    
    ListNode* first = plt->_head->_next;  // 指向第一个结点  
    
    plt->_head->_next = newnode; // 头结点和新结点连接
    newnode->_prev = plt->_head;
    
    first->_prev = newnode; // 第一个结点和新结点连接
    newnode->_next = first;
}

 

3.7 头删

定义两个指针,分别指向第一个结点和第二个结点,再把头结点和第二个结点连接起来,释放掉第一个结点。

void ListPopFront(List* plt)// 头删
{
    assert(plt);
    ListNode* head = plt->_head;
    if (plt->_head->_next == plt->_head)
    {   // 只有头结点
        return 0;
    }
    
    ListNode* first = head->_next; // 指向第一个结点
    ListNode* second = first->_next; // 指向第二个结点  
    
    free(first); // 释放掉第一个结点
    head->_next = second;  // 连接
    second->_prev = head;
}

 

3.8 查找结点x

定义一个指针,从头结点开始依次查找,找到返回指向该值的指针。注意循环结束的条件。

ListNode* ListFind(List* plt, LTDataType x) // 查找结点x,返回指向结点x的指针  
{
    assert(plt);
    ListNode* cur = plt->_head->_next;
    while (cur != plt->_head) // 结束条件
    {
        if (cur->_val == x) 
        {
            return cur;
        }
        cur = cur->_next;
    }
    return NULL;
}

 

3.9 给定位置前插入结点

先申请一个新结点,定义一个指针找到pos前一个结点,连接pos前一个结点和新结点,最后连接pos和新结点。

void ListInsert(ListNode* pos, LTDataType x)  // 在pos的前面插入结点x  
{
    assert(pos);
    
    ListNode* newnode = BuyListNode(x); // 申请新结点
    ListNode* prev = pos->_prev; // pos前一个结点
    
    prev->_next = newnode; // 连接pos前一个结点和新结点
    newnode->_prev = prev;
    
    pos->_prev = newnode; // 连接pos和新结点
    newnode->_next = pos;
}

 

3.10 删除给定位置结点

定义两个指针,分别指向pos的前后结点,将前后两个结点连接起来,释放掉pos结点。

void ListErase(ListNode* pos) // 删除pos位置的结点  
{
    assert(pos);
    
    ListNode* prev = pos->_prev; // pos前驱
    ListNode* next = pos->_next; // pos后继
    
    prev->_next = next;  // 前驱连接后继
    next->_prev = prev;
    free(pos);
}

 

3.11 删除给定值结点

一般定义一个指针,遍历找到结点,调用ListErase函数直接删除;也可以用ListFind函数找到该结点,再调用ListErase函数直接删除。

void ListRemove(List* plt, LTDataType x)// 删除结点x 
{
    // 一般方法:
    //assert(plt);
    //ListNode* cur = plt->_head->_next;
    //while (cur != plt->_head)
    //{
    // if (cur->_val == x)
    // {
    //  ListErase(cur);
    //  return 0;
    // }
    //}
    
    // 更方便:
    assert(plt);
    ListNode* tmp = ListFind(plt, 1);
    if (tmp)
    {
        ListErase(tmp);
    }
}

 

3.12 返回双向链表大小

定义一个计数器,从第一个结点开始计数,注意循环结束条件(不算头结点)。

int ListSize(List* plt)//双向链表的大小
{
    assert(plt);
    
    ListNode* cur = plt->_head->_next; // 从第一个结点开始计算  
    int count = 0; 
    while (cur != plt->_head)
    {
        ++count;
        cur = cur->_next;
    }
    return count;
}

 

3.13 判断双向链表是否为空

一般判断头结点的指针是否指向它自己本身,如果是,链表就为空。
或者更直接就用ListSize函数判断链表大小即可。

int ListEmpty(List* plt)//判断双向链表是否为空,空为0,非空为-1  
{
    assert(plt);
    // 一般的判断条件:
    //if (plt->_head->_next == plt->_head)
    //{
    // return 0;
    //}
    //else return -1;

    // 更直接:
    return ListSize(plt) == 0 ? 0 : -1;
}

 

3.14 打印双向链表

打印的格式没有什么特殊要求,简洁明了就行。

void ListPrint(List* plt)// 打印
{
    assert(plt);
    if (plt->_head == NULL)
    {
        printf("NULL\n");
        return 0;
    }
    ListNode* cur = plt->_head->_next;
    printf("<==>head<==>");
    
    while (cur != plt->_head)
    {
        printf("%d<==>", cur->_val);
        cur = cur->_next;
    }
    printf("\n");
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值