带头循环双向链表的实现

带头循环双向链表的实现

文章文章代码解析在代码块的注释中。

头文件

我们将代码分为三个文件实现,分别为:

List.h: 包含所有需要的头文件,定义以及接口函数的声明。

List.c: 保存所有接口函数的定义和实现

test.c: 主函数

//List.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDateType date;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

//初始化
ListNode* ListInit();
//申请空间
ListNode* BuyListNode(LTDataType x);
//打印
void ListPrint(ListNode* phead);
//尾插
void ListPushBack(ListNode* phead, LTDataType x);
//头插
void ListPushFront(ListNode* phead, LTDataType x);
//尾删
void ListPopBack(ListNode* phead);
//头删
void ListPopFront(ListNode* phead);
//查找
ListNode* ListFind(ListNode* phead, LTDataType x);
//插入
void ListInsert(ListNode* pos, LTDataType x);
//删除
void ListErase(ListNode* pos);
//释放
void ListDestory(ListNode* phead);

接口函数

  • 我们首先需要一个初始化函数,创建我们的头结点

    ListNode* ListInit()
    {
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
        newnode->next = newnode;
        newnode->prev = newnode;
        return newnode;
    }
    

    初始化函数的使用:

    我们在主函数中创建一个结构体的指针变量来接收初始化函数的返回值,这个结构体指针变量里存储的就是链表头结点的地址。

  • 考虑到插入新结点要不断申请空间,我们实现一个函数,这个函数负责:为新结点开辟空间,并将要插入的数据直接初始化给我们的结点的数据域。

    ListNode* BuyListNode(LTDataType x)
    {
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
        newnode->data = x;
        newnode->next = NULL;
        newnode->prev = NULL;
        return newnode;
    }
    
  • 打印函数

    void ListPrint(ListNode* phead)
    {
        ListNode* cur = phead;
        printf("head->");
        while(cur != phead)//注意循环条件,由于具有循环属性,结束条件不能利用NULL。
        {
            printf("%d->",cur->data);
            cur = cur->next;
        }
        printf("head\n");
    }
    
  • 尾插函数

    //对于双向带头循环链表的尾插,我们需要改变的指针有:头结点的前指针、新结点的前和后指针、原链表尾结点的后指针。
    /*这里有个一问题:指针改变的顺序
        当我们没有将原链表的尾结点备份,我们想要改变尾结点的语句是:phead->prev->next = newnode。
        如果我们先改变的指针是头结点的前驱(phead->prev = newnode),那么我们就无法在找寻到原链表尾结点,此时改变原尾结点达不到效果。
        当我们对原链表的尾结点备份时,指针改变的顺序就没有影响了。*/
        
        
    void ListPushBack(ListNode* phead, LTDataType x)
    {
        assert(phead);
        ListNode* newnode = BuyListNode(x);
        ListNode* tail = phead->prev;//备份尾结点
        tail->next = newnode;
        newnode->prev =tail;
        newnode->next = phead;
        phead->prev = newnode;
    }
    
    /*我们习惯考虑极端情况来保证代码的正确性。我们考虑一下,当我们的原链表只有头结点,没有其他任何结点时,代码实现结果满意吗*/
    //这时候我们将代码翻译:
    备份尾结点:tail = phead;
    phead->next = newnode;
    newnode->prev = phead;
    newnode->next = phead;
    phead->prev = newnode;
    //由此发现,我们的代码对于链表没有元素的情况仍然适用。
    
    
  • 头插函数

    //相同地,我们头插函数也需要改变4个指针:头结点的后指针、新结点的前和后指针、原链表首结点的前指针
    //仍然会有顺序问题,我们直接备份我们的首结点
    void ListPushFront(ListNode* phead, LTDataType x)
    {
        assert(phead);
        ListNode* newnode = BuyListNode(x);
        ListNode* prev = phead->next;//备份首结点
        prev->prev = newnode;
        newnode->next = prev;
        newnode->prev = phead;
        phead->next = newnode;
    }
    //与尾插一样,适用于链表没有元素的情况
    
  • 尾删函数

    //尾删,要改变的指针是:原倒数第二个结点的后指针,头结点的前指针。同时释放空间
    //为了避免顺序不当带来的问题,我们备份尾结点
    void ListPopBack(ListNode* phead)
    {
        assert(phead);
        assert(phead->next);
        ListNode* tail = phead->prev;
        tail->prev->next = phead;
        phead->prev = tail->prev;
        free(tail);
        tail = NULL;
    }
    //考虑极端情况,当链表没有元素:
    翻译语句:
    备份尾结点:tail = phead;
    free(phead);
    这里我们将头结点释放了,肯定是不可以的,所以我们再加入一个断言在上述代码。
    
  • 头删函数

    //头删,要改变的指针是:头结点的下一个结点的前指针,头结点的后指针
    //备份首结点
    void ListPopFront(ListNode* phead)
    {
        assert(phead && phead->next);
        ListNode* head = phead->next;
        head->next->prev = phead;
        phead->next = head->next;
        free(head);
        head = NULL;
    }
    //也存在尾删的极端问题,排除free掉头结点的情况。
    
  • 查找函数,情景是:我想寻找某个元素,查找并返回所在结点的地址

    ListNode* ListFind(ListNode* phead, LTDataType x)
    {
        ListNode* cur = phead->next;
        while(cur != phead)
        {
            if(x == cur->data)
            {
                return cur;
            }
            cur = cur->next;
        }
        return NULL;
    }
    

    一般伴随删除函数和插入函数使用。

  • 插入函数

    //当我们利用查找函数找到我们的结点的地址时,我们就可以向插入函数传参
    //需要改变的指针有:pos结点的前指针、新节点的前和后指针、pos的前一个结点的后指针
    //仍然存在顺序问题,我们需要利用pos->prev这一语句来找寻pos前一个结点,然后将它的后指针改变,再此之前,我们不能改变pos->prev的值
    //解决方案仍然是将pos前一个结点备份
    void ListInsert(ListNode* pos, LTDataType x)
    {
    	assert(pos);
    	ListNode* newnode = BuyListNode(x);
    	ListNode* prev = pos->prev;//备份
    	prev->next = newnode;
    	newnode->prev = prev;
    	newnode->next = pos;
    	pos->prev = newnode;
    }
    
  • 删除函数

    //删除函数也需要查找函数的返回值。
    void ListErase(ListNode* pos)
    {
    	assert(pos);
    	ListNode* tail = pos->next;/
    	ListNode* head = pos->prev;
    	head->next = tail;
    	tail->prev = head;
    	free(pos);
    	pos = NULL;
    }
    
  • 释放函数

    //释放时需要注意的是,我们遍历每个结点释放后无法找到下一个结点,这时候需要另一个指针来保存每次的下一个结点。
    void ListDestory(ListNode* phead)
    {
    	ListNode* cur = phead->next;
    	while (cur)
    	{
    		ListNode* tail = cur->next;
    		free(cur);
    		cur = tail;
    	}
    	free(phead);
    	phead = NULL;
    }
    

完整代码

//List.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;



ListNode* ListInit();
ListNode* BuyListNode(LTDataType x);
void ListPrint(ListNode* phead);

void ListPushBack(ListNode* phead, LTDataType x);
void ListPushFront(ListNode* phead, LTDataType x);
void ListPopBack(ListNode* phead);
void ListPopFront(ListNode* phead);

ListNode* ListFind(ListNode* phead, LTDataType x);
void ListInsert(ListNode* pos, LTDataType x);
void ListErase(ListNode* pos);
void ListDestory(ListNode* phead);
//List.c

# define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

ListNode* ListInit()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		return NULL;
	}
	else
	{
		newnode->next = newnode;
		newnode->prev = newnode;
	}
	return newnode;
}


ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}


void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	printf("head->");
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}


void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}


void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* prev = phead->next;
	prev->prev = newnode;
	newnode->next = prev;
	newnode->prev = phead;
	phead->next = newnode;
}


void ListPopBack(ListNode* phead)
{
	assert(phead && phead->next);
	ListNode* tail = phead->prev;
	tail->prev->next = phead;
	phead->prev = tail->prev;
	free(tail);
	tail = NULL;
}


void ListPopFront(ListNode* phead)
{
	assert(phead && phead->next);
	ListNode* head = phead->next;
	head->next->prev = phead;
	phead->next = head->next;
	free(head);
	head = NULL;
}


ListNode* ListFind(ListNode* phead, LTDataType x)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (x == cur->data)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyListNode(x);
	ListNode* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

}


void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* tail = pos->next;
	ListNode* head = pos->prev;
	head->next = tail;
	tail->prev = head;
	free(pos);
	pos = NULL;
}


void ListDestory(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur)
	{
		ListNode* tail = cur->next;
		free(cur);
		cur = tail;
	}
	free(phead);
	phead = NULL;
}
  • 32
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值