带头循环双向链表的实现

文章目录

带头循环双向链表的实现

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

头文件

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

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

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

test.c: 主函数

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

接口函数

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

    c 复制代码
    ListNode* ListInit()
    {
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
        newnode->next = newnode;
        newnode->prev = newnode;
        return newnode;
    }

    初始化函数的使用:

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

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

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

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

    c 复制代码
    //对于双向带头循环链表的尾插,我们需要改变的指针有:头结点的前指针、新结点的前和后指针、原链表尾结点的后指针。
    /*这里有个一问题:指针改变的顺序
        当我们没有将原链表的尾结点备份,我们想要改变尾结点的语句是: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;
    //由此发现,我们的代码对于链表没有元素的情况仍然适用。
  • 头插函数

    c 复制代码
    //相同地,我们头插函数也需要改变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;
    }
    //与尾插一样,适用于链表没有元素的情况
  • 尾删函数

    c 复制代码
    //尾删,要改变的指针是:原倒数第二个结点的后指针,头结点的前指针。同时释放空间
    //为了避免顺序不当带来的问题,我们备份尾结点
    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);
    这里我们将头结点释放了,肯定是不可以的,所以我们再加入一个断言在上述代码。
  • 头删函数

    c 复制代码
    //头删,要改变的指针是:头结点的下一个结点的前指针,头结点的后指针
    //备份首结点
    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掉头结点的情况。
  • 查找函数,情景是:我想寻找某个元素,查找并返回所在结点的地址

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

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

  • 插入函数

    C 复制代码
    //当我们利用查找函数找到我们的结点的地址时,我们就可以向插入函数传参
    //需要改变的指针有: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;
    }
  • 删除函数

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

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

完整代码

c 复制代码
//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);
c 复制代码
//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;
}
相关推荐
DdddJMs__1352 分钟前
C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
c语言·leetcode·题解
汤米粥5 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾8 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺14 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马剑威(威哥爱编程)39 分钟前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思40 分钟前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计