首页 > 其他分享 >C 语言实现抽象数据类型(ADT)之链表

C 语言实现抽象数据类型(ADT)之链表

时间:2023-12-07 17:12:27浏览次数:37  
标签:ADT const struct List 链表 抽象数据类型 pList 指针

C 语言实现抽象数据类型(ADT)之链表

1 什么是链表?(懂跳)

C 语言本身自带了很多基本数据类型,每种基本数据类型变量总是代表着某个数据,比如:我们通常用整型变量来计数,用浮点型变量来保存价格这样的数据……

int count;
double price;

而有时候我们需要表示的数据很复杂,比如我们想要保存一件商品的价格的同时,也能够保存这件商品的名称,这个时候结构体就派上用场了。

struct Book {
    char bookName[100];
    double bookPrice;
}

而像这样的多元数据,往往不止一个,我们通常的做法是使用结构体数组

struct Book books[100];

结构体数组的元素个数是有上限的,而且数组开辟的内存空间是连续的,这样内存利用率不高。在实际开发过程中,数据个数通常都是不确定的,都是动态变化的。

所以可以使用C 语言的动态内存分配在程序运行期间,动态的为每个这样的数据开辟空间。

(struct Book*)malloc(sizeof(struct Book));

但这样又有一个问题,就是每为一个数据开辟内存空间,就需要使用一个指针变量来保存它的地址,而如果使用数组来保存这些指针,虽说指针数组占用空间比较小,但显然又回到了最初的问题,就是数据的个数是有上限的。

而如果也使用动态内存分配的方式存储指针,那么存储这些指针的内存空间又需要新的指针“跟踪”


于是就有了链表

我们可以在每为一个数据开辟空间的时候,把这个数据的内存地址保存在上一个数据中,这样一来,只有第一个数据的指针需要另外保存,这个指针叫做头指针

struct Book {
    char bookName[100];
    double bookPrice;
    struct Book* next;
}
//每一个块数据中都存储着下一个数据的内存地址
//如果是最后一个,把其中“下一个”的指针赋为NULL即可
struct Book* head;	//只需要一个头指针定位整条链表的头即可

这样一来,通过动态内存分配为每一个数据分配的内存空间,都被巧妙的链接了起来,于是把这种数据结构称为链表。把其中的每一块内存空间又形象的称为一个节点

2 数据的封装

为了提高代码的复用性,我们在创建链表之前,应该对元数据进行封装。

我们可以将元数据的定义放在一个头文件中。

/*MyType.h*/
#pragma once

struct MyType {
    /*......*/
};

typedef struct MyType Item;

这样元数据就被封装成了Item类型。后面如果我们想要修改元数据类型,只需要需改此文件中的定义即可。

然后分别通过LinkedList.hLinkedList.c两个文件来实现链表

/*LinkedList.h*/
#pragma once
#include "MyType" //包含Item的定义
#include <stdbool.h>	//使用bool

//封装节点
typedef struct listNode {
    Item item;
    struct listNode* next;
}Node;

//抽象:链表类型,用于存储链表头指针和其他信息
typedef struct linkedList {
    Node* head;		//头指针,必须,不仅定位头节点,也定位整个链表
    Node* tail;		//尾指针:为了方便在链表末尾追加节点,不是必须
    bool isNull;	//存储一个链表是否为空的布尔值
    unsigned nodeCount;		//存储链表中的节点数
}List;

如此一来,就完成了从元数据链表的封装,封装不是必须的。

3 实现接口API

为什么要把链表封装成一种抽象数据类型(ADT)?在开发中,数据结构就像是,在造汽车中的轮胎一样,几乎是开发过程中必然会使用的工具,我们为了追求开发效率,必然不可能每次开发都去“造轮子”,所以把链表封装成一种抽象数据类型(ADT)可以大大简化后续的开发工作。

所以我们把对链表的所有操作都抽象得封装成函数,即把它当成一种数据类型,就如同C语言的内置类型一样。

每一种数据类型,都有属性相关操作这两个特征,比如int类型的属性就是表示一个整数,相关操作有加减乘除等。

链表属性就是表示多个内存空间不连续的相同类型的数据。和数组的概念类似(数组也是一种数据结构),只不过链表要比数据灵活,将其抽象封装的一个目的,就是能像使用数组那样简单而高效的使用链表这种数据结构。

链表的操作和数组有所不同,它更加复杂。它所有的操作都需要借助函数来完成。

  • 初始化链表
void initList(List* const pList);//const 表示这个函数不会改变plist的值
  • 判断一个链表是否为空
bool isListEmpty(const List* const pList);//const分别表示不会改变*pList和pList的值
  • 获取一个链表中元数据的数量(节点数)
unsigned itemsCountOfList(const List* const pList);
  • 在链表末尾添加一个元素
bool appendItemToList(List* const pList, const Item* const pItem);
  • 清空一个链表
void clearList(List* const pList);
  • 对链表中的每一项都执行某个函数(遍历链表)
void traverseList(const List* const pList, void(*pfun)(Item* pItem));

由于元数据的不同,所以不涉及任何对元数据的操作,这必须由实现元数据的人来写,或者说与元数据的定义写在一起。

等等……

关于链表的操作不止这些,这里只演示这些。

将这些接口函数的声明写在LinkedList.h中,然后在LinkedList.c中实现。这样做的好处是,隐藏具体实现,将接口对外公开;后续使用链表时,只需查看头文件即可一目了然。

/*LinkedList.c*/
#include "LinkedList.h"
#include <stdlib.h>
#include <stdbool.h>

void initList(List* const pList) {
    if (pList != NULL) {	//防止访问空指针
        pList->head = NULL;
        pList->tail = NULL;
        pList->isNull = true;
        pList->nodeCount = 0;
    }
}

bool isListEmpty(const List* const pList) {
    return pList->isNull;
}

unsigned itemsCountOfList(const List* const pList) {
    return pList->nodeCount;    
}

bool appendItemToList(List* const pList, const Item* const pNewItem) {
    if (pList != NULL) {
        Node* pNewNode = (Node*)malloc(sizeof(Node));        
        if (pNewNode != NULL) {
            pNewNode->item = *pNewItem;
            pNewNode->next = NULL;
            if (pList->head == NULL) {
                pList->head = pNewNode;
                pList->isNull = false;
            } else {
                pList->tail->next = pNewNode;
            }
            pList->tail = pNewNode;
            pList->nodeCount++;
            return true;
        }
    }
    return false;
}

void traverseList(const List* const pList, void(*pfun)(Item* item)) {
    Node* pNode = pList->head;
    while (pNode != NULL) {
        pfun(&pNode->item);
        pNode = pNode->next;
    }   
}

void clearList(List* const pList) {
    if (pList != NULL) {
        Node* pNode = pList->head;
        while (pList->head != NULL) {
            pList->head = pNode->next;
            free(pNode);
            pNode = pList->head;
        }
    }
}

标签:ADT,const,struct,List,链表,抽象数据类型,pList,指针
From: https://www.cnblogs.com/langmoyu/p/17883411.html

相关文章

  • H7-TOOL发布2.24固件,增加CMSIS-SVD解析,RTOS Trace链表,I2C/SPI从机,CANopen解析等,脱机烧
    H7-TOOL详细介绍(含操作手册):http://www.armbbs.cn/forum.php?mod=viewthread&tid=89934视频介绍:https://www.bilibili.com/video/BV1494y1j7mj【PC软件】V2.2.41.脱机烧录功能升级  -新增GD32C10x系列  -新增钜泉光电HT502x  -新增英飞凌TLE987x系列  -新......
  • 141. 环形链表
    给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识......
  • 第2章. 链表(LinkedList)
    链表链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的。单向链表一、单向链表的设计1.1、不带虚拟头结点publicclassLinkedList<E>{//链表的节点数量privateintsize;//链表的头结点privateNode<E>first;//静态成员内部类:s......
  • QuadTree 优化版
     QuadTree.h#pragmaonce#include<iostream>#include<string>#include<stdlib.h>#include<ctime>#include<vector>#include<algorithm>usingnamespacestd;#defineMAX_ELE_NUM300//每块区域的最大点数#defineQUADRANT_R......
  • [LeetCode Hot 100] LeetCode19. 删除链表的倒数第N个结点
    题目描述思路一:采用两次遍历第一遍遍历先获取链表的长度length第二次从dummy节点开始走length-n步然后将该节点指向下下个节点思路二:采用一次遍历设置虚拟节点dummyHead指向head设定双指针p和q,初始都指向虚拟节点dummyHead移动q,直到p与q之间相隔的元素个数为n(即q走......
  • [LeetCode Hot 100] LeetCode21. 合并两个有序链表
    题目描述思路:新建dummy去"穿针引线"新建一个dummy节点去"穿针引线"注意最后返回的是dummy.next方法一:/***Definitionforsingly-linkedlist.*publicclassListNode{*intval;*ListNodenext;*ListNode(){}*ListNode(intval){this......
  • 【实战技能】 单步运行源码分析,一期视频整明白FreeRTOS内核源码框架和运行机制,RTOS Tr
    从源码的角度来看,OS内核源码就是通过各种链表组装起来的,FreeRTOS就是下面几个链表组成的。FreeRTOS的调度,任务切换就是倒腾这几个链表。而其它的几款OS是一个链表就一撸到底了,FreeRTOS是搞了好几个。所以视频里面就重点介绍下这个,其它的支持的也做个拓展说明。搞清楚这几个链表也......
  • [LeetCode Hot 100] LeetCode234. 回文链表
    题目描述思路1:将值复制到数组中然后使用双指针计算链表的长度创建等长的数组将链表中的数依次放入数组中使用左右指针判断链表是否是回文链表时间复杂度:O(n)空间复杂度:O(n)思路2:快慢指针+反转链表用快慢指针,快指针走两步,慢指针走一步,快指针遇到终止位置时,慢指针就在......
  • [LeetCode Hot 100] LeetCode206. 反转链表
    题目描述思路:双指针算法方法一:/***Definitionforsingly-linkedlist.*publicclassListNode{*intval;*ListNodenext;*ListNode(){}*ListNode(intval){this.val=val;}*ListNode(intval,ListNodenext){this.val=v......
  • [LeetCode Hot 100] LeetCode141. 环形链表
    题目描述思路:快慢指针slow指针:每次移动一个节点fast指针:每次移动两个节点如果链表中存在环,fast指针最终会在某一时刻追上slow指针,这是由于移动速度快的fast指针会在某个时刻绕圈并追上速度慢的slow指针条件fast!=null&&fast.next!=null保证了在每一步迭代中,fast和......