数据结构(4)链表概念+单链表实现

1. 链表的概念及优缺点

1.1 链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,

数组元素的逻辑顺序是通过链表中的指针链接次序实现的。

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1. 单向、双向

2. 带头、不带头

3. 循环、非循环

1. 无头单向非循环链表:(下面实现的就是这种结构)

结构简单 ,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构

如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表: 结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,

都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。


1.2 顺序表和链表的优缺点

为什么顺序表和链表各自存在?

因为他们各有优点,他们是互补的,并不是有他没我,有我没他的

顺序表的优点:

① 支持随机访问,有些算法需要结构随机访问,比如二分查找和优化的快排等。

② 数据是按顺序存放的,空间利用率高。

③ 通过下标直接访问,存取速度高效。


顺序表的缺点:

① 中间/头部的插入删除,需要挪动,挪动数据时也是存在消耗的,时间复杂度为O(N)

② 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

③ 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,

我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。


链表的优点:

① 按需申请空间,不用就释放空间( 链表可以更合理地使用空间)。

② 头部或者中间位置的插入和删除,不需要挪动数据。

③ 不存在空间浪费。

链表的缺陷:

① 每一个数据,都要存一个指针去链接后面的数据节点。

② 不支持随机访问(用下标直接访问第 i 个),必须得走 O(N) 。


单链表的缺陷还是很多的,单纯单链表的增删查找的意义不大,

但是很多OJ题考察的都是单链表。单链表多用于更复杂的数据结构的子结构,

如哈希桶,邻接表等。所以真正存储数据还是得靠双向链表。


2. 链表的实现(完整代码)

还是和上一篇顺序表的实现一样,主要实现增删查改功能,可以一个一个函数看

Slist.h

cpp 复制代码
#pragma once
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
 
typedef int SLNDataType;
 
typedef struct SlistNode          //single单一的  node节点
{
    SLNDataType data;
    struct SlistNode* next;
}SLNode;
 
void SListPrint(SLNode* phead);
void SListPushBack(SLNode** pphead, SLNDataType x);
void SListPushFront(SLNode** pphead, SLNDataType x);
SLNode* CreateNewNode(SLNDataType x);
void SListPopBack(SLNode** pphead);
void SListPopFront(SLNode** pphead);
SLNode* SListFind(SLNode* phead, SLNDataType x);
void SListInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
void SListInsertAfter(SLNode* pos, SLNDataType x);
void SListEarse(SLNode** pphead, SLNode* pos);
void SListEarseAfter(SLNode* pos);
void SListDestroy(SLNode** pphead);

SList.c

cpp 复制代码
#include"SList.h"
 
void SListPrint(SLNode* phead)
{
    SLNode* cur = phead;
    while (cur != NULL)
    {
        printf("%d->", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}
 
void SListPushBack(SLNode** pphead, SLNDataType x)
{
    SLNode* NewNode = CreateNewNode(x);
 
    if (*pphead == NULL)
    {
        *pphead = NewNode;
    }
    else
    {
        SLNode* tail = *pphead;//tail 尾巴
        while (tail->next != NULL)//找到尾节点
        {
            tail = tail->next;
        }
        tail->next = NewNode;
    }
}
 
SLNode* CreateNewNode(SLNDataType x)
{
    SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
    if (NewNode == NULL)
    {
        printf("malloc failed!\n");
        exit(-1);
    }
    NewNode->data = x;
    NewNode->next = NULL;
    return NewNode;
}
 
void SListPushFront(SLNode** pphead, SLNDataType x)
{
    SLNode* NewNode = CreateNewNode(x);
 
    NewNode->next = *pphead;
    *pphead = NewNode;
}
 
void SListPopBack(SLNode** pphead)
{
    assert(*pphead != NULL);
    if ((*pphead)->next == NULL)//如果只有一个节点
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLNode* tail = *pphead;
        while (tail->next->next)
        {
            tail = tail->next;
        }
        free(tail->next);
        tail->next = NULL;
    }
}
 
void SListPopFront(SLNode** pphead)
{
    assert(*pphead != NULL);
 
    SLNode* begin = *pphead;
    *pphead = (*pphead)->next;
    free(begin);
    begin = NULL;
}
 
SLNode* SListFind(SLNode* phead, SLNDataType x)
{
    SLNode* cur = phead;
    while (cur != NULL)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }
    return NULL;
}
 
void SListInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
    SLNode* NewNode = CreateNewNode(x);
    if (*pphead == pos)
    {
        NewNode->next = *pphead;
        *pphead = NewNode;
    }
    else
    {
        SLNode* posPrev = *pphead;
        while (posPrev->next != pos)
        {
            posPrev = posPrev->next;
        }
        posPrev->next = NewNode;
        NewNode->next = pos;
    }
}
 
void SListInsertAfter(SLNode* pos, SLNDataType x)
{
    assert(pos != NULL);
    SLNode* NewNode = CreateNewNode(x);
    NewNode->next = pos->next;
    pos->next = NewNode;
    //和前面对比,单链表的缺陷是要找pos的前一个要从头往后找
}
 
void SListEarse(SLNode** pphead, SLNode* pos)
{
    assert(*pphead != NULL);
    assert(pos != NULL);
    if (*pphead == pos)//如果是头删
    {
        *pphead = pos->next;
        free(pos);
        pos = NULL;
    }
    else
    {
        SLNode* posPrev = *pphead;
        while (posPrev->next != pos)
        {
            posPrev = posPrev->next;
        }
        posPrev->next = pos->next;
        free(pos);
        pos = NULL;//可以不写,形参不会改变实参,这个野指针会被栈帧销毁,但是个好习惯emm
    }
}
 
void SListEarseAfter(SLNode* pos)
{
    //1 2 3 4
    assert(pos != NULL);
    assert(pos->next != NULL);
    SLNode* posAfter= pos->next;
    pos->next = posAfter->next;
    free(posAfter);
    posAfter = NULL;//可以不写,形参不会改变实参,这个野指针会被栈帧销毁
}
 
void SListDestroy(SLNode** pphead)
{
    assert(pphead);
    SLNode* cur = *pphead;
    while (cur)
    {
        SLNode* next = cur->next;
        free(cur);
        cur = next;
    }
    *pphead = NULL;
}

Test.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
 
#include"SList.h"
 
void TestSList1()//测试尾插头插
{
    SLNode* pList = NULL;
    SListPushBack(&pList, 1);
    SListPushBack(&pList, 2);
    SListPushBack(&pList, 3);
    SListPushBack(&pList, 4);
 
    SListPrint(pList);
 
    SListPushFront(&pList, 1);
    SListPushFront(&pList, 2);
    SListPushFront(&pList, 3);
    SListPushFront(&pList, 4);
 
    SListPrint(pList);
}
 
void TestSList2()//测试尾删头删
{
    SLNode* pList = NULL;
    SListPushBack(&pList, 1);
    SListPushBack(&pList, 2);
    SListPushBack(&pList, 3);
    SListPushBack(&pList, 4);
    SListPrint(pList);
 
    SListPopBack(&pList);
    SListPrint(pList);
 
    SListPopFront(&pList);
    SListPrint(pList);
 
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPrint(pList);
}
 
void TestSList3()//测试查找函数
{
    SLNode* pList = NULL;
    SListPushFront(&pList, 1);
    SListPushFront(&pList, 2);
    SListPushFront(&pList, 3);
    SListPushFront(&pList, 2);
    SListPushFront(&pList, 4);
    SListPushFront(&pList, 2);
    SListPushFront(&pList, 2);
    SListPushFront(&pList, 4);
    SListPrint(pList);
 
    SLNode* pos = SListFind(pList, 2);
    for (int i = 0;pos != NULL;i++)
    {
        printf("第%d个pos节点:%p->%d\n", i, pos, pos->data);
        pos->data = 9;//查找过程中把2改成9  返回SLNode*的好处
        pos = SListFind(pos->next, 2);
    }
    SListPrint(pList);
}
 
void TestSList4()//测试指定位置的前面插入
{
    SLNode* pList = NULL;
    SListPushBack(&pList, 1);
    SListPushBack(&pList, 2);
    SListPushBack(&pList, 3);
    SListPushBack(&pList, 4);
 
    SListPrint(pList);
 
    SLNode* pos = SListFind(pList, 2);
    SListInsert(&pList, pos, 60);
 
    SListPrint(pList);
 
    pos = SListFind(pList, 1);
    SListInsert(&pList, pos, 70);
    SListPrint(pList);
 
    SListInsert(&pList, NULL, 80);
    SListPrint(pList);
}
 
void TestSList5()//测试指定位置的后面插入
{
    SLNode* pList = NULL;
    SListPushBack(&pList, 1);
    SListPushBack(&pList, 2);
    SListPushBack(&pList, 3);
    SListPushBack(&pList, 4);
    SListPrint(pList);
 
    SLNode* pos = SListFind(pList, 3);
    SListInsertAfter(pos, 10);
    SListPrint(pList);
 
    pos = SListFind(pList, 4);
    SListInsertAfter(pos, 20);
    SListPrint(pList);
 
    pos = SListFind(pList, 1);
    SListInsertAfter(pos, 30);
    SListPrint(pList);
}
 
void TestSList6()//测试指定位置的删除
{
    SLNode* pList = NULL;
    SListPushBack(&pList, 1);
    SListPushBack(&pList, 2);
    SListPushBack(&pList, 3);
    SListPushBack(&pList, 4);
    SListPrint(pList);
 
    SLNode* pos = SListFind(pList, 3);
    SListEarse(&pList, pos);
    SListPrint(pList);
 
    pos = SListFind(pList, 1);
    SListEarse(&pList, pos);
    SListPrint(pList);
 
    pos = SListFind(pList, 2);
    SListEarse(&pList, pos);
    SListPrint(pList);
 
    pos = SListFind(pList, 4);
    SListEarse(&pList, pos);
    SListPrint(pList);
}
void TestSList7()//测试指定位置之后的删除和整个链表销毁
{
    SLNode* pList = NULL;
    SListPushBack(&pList, 1);
    SListPushBack(&pList, 2);
    SListPushBack(&pList, 3);
    SListPushBack(&pList, 4);
    SListPrint(pList);
    //SListDestroy(&pList);
    //SListPrint(pList);
 
    SLNode* pos = SListFind(pList, 3);
    SListEarseAfter(pos);
    SListPrint(pList);
 
    pos = SListFind(pList, 1);
    SListEarseAfter(pos);
    SListPrint(pList);
 
    pos = SListFind(pList, 1);
    SListEarseAfter(pos);
    SListPrint(pList);
 
    SListDestroy(&pList);
    SListPrint(pList);
}
 
int main()
{
    //TestSList1();//测试尾插头插
    //TestSList2();//测试尾删头删
    //TestSList3();//测试查找函数
    //TestSList4();//测试指定位置的前面插入
    //TestSList5();//测试指定位置的后面插入
    //TestSList6();//测试指定位置的删除
    TestSList7();//测试指定位置之后的删除和整个链表销毁
    return 0;
}

本篇完。(附下篇对应OJ链接)

下一篇是链表有关的力扣OJ题,穿越回来贴个链接:

相关推荐
代码栈上的思考2 小时前
二叉树的层序遍历:4道例题讲解
算法·宽度优先·队列在宽度优先搜索中的应用
杰瑞不懂代码2 小时前
【公式推导】AMP算法比BP算法强在哪(二)
python·算法·机器学习·概率论
野蛮人6号2 小时前
力扣热题100道之45跳跃游戏2
算法·leetcode·游戏
唐僧洗头爱飘柔95272 小时前
【区块链技术(05)】区块链核心技术:哈希算法再区块链中的应用;区块哈希与默克尔树;公开密钥算法、编码和解码算法(BASE58、BASE64)
算法·区块链·哈希算法·base64·默克尔树·区块哈希·公私钥算法
此生只爱蛋2 小时前
【Redis】浅谈数据结构和内部编码和单线程架构
数据结构·数据库·redis
山峰哥2 小时前
现代 C++ 的炼金术:铸就高性能与高可维护性的工程实践
java·开发语言·前端·数据结构·c++
不能只会打代码2 小时前
力扣--3578. 统计极差最大为 K 的分割方式数(Java实现,代码注释及题目分析讲解)
算法·leetcode·动态规划·滑动窗口
小尧嵌入式2 小时前
QT软件开发知识流程及秒表计时器开发
开发语言·c++·qt·算法
SoleMotive.2 小时前
Mysql底层的数据结构,为什么用B+树,如果在内存中,B树和B+树查询效率怎么样
数据结构·b树·mysql