带头循环双向链表的实现

带头循环双向链表的实现

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

头文件

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

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

相关推荐

  1. 带头循环双向实现

    2024-04-01 00:00:02       7 阅读
  2. 带头双向循环实现及注释教学

    2024-04-01 00:00:02       5 阅读
  3. 数据结构基础(带头节点双向循环

    2024-04-01 00:00:02       28 阅读
  4. 带头双向循环基础

    2024-04-01 00:00:02       18 阅读

最近更新

  1. FFT快速傅里叶变换音频分析

    2024-04-01 00:00:02       0 阅读
  2. 基于单片机雨天自动关窗器的设计

    2024-04-01 00:00:02       0 阅读
  3. 基础矩阵和本质矩阵

    2024-04-01 00:00:02       0 阅读
  4. 水气表CJ/T188协议学习及实例

    2024-04-01 00:00:02       0 阅读
  5. 基于springboot的教学资源库源码数据库

    2024-04-01 00:00:02       0 阅读
  6. flink mysql数据表同步SQL CDC

    2024-04-01 00:00:02       0 阅读
  7. 【QT进阶】Qt http编程之json解析的简单介绍

    2024-04-01 00:00:02       0 阅读
  8. 从0开始深入理解Spring(1)--SpringApplication构造

    2024-04-01 00:00:02       0 阅读

热门阅读

  1. 《堕落的审判》吵架台词

    2024-04-01 00:00:02       5 阅读
  2. 算法设计-杨辉三角

    2024-04-01 00:00:02       6 阅读
  3. 记 SpringBoot 使用@RequestBody 接收不到参数

    2024-04-01 00:00:02       6 阅读
  4. of_get_named_gpio()函数解析

    2024-04-01 00:00:02       4 阅读