数据结构 - 单链表

442 阅读5分钟

一. 链表的定义

链表是一种物理存储结构上非连续的线性存储结构, 其每个元素都是一个节点对象, 每个节点由指针域数据域组成, 各个节点之间通过指针连接, 从当前节点通过指针可以访问到下一个节点. 由于当前节点的指针记录了下一个节点的内存地址, 因此无需保证内存地址的连续性, 从而可以将各个节点分散存储在内存各处.

二. 链表的分类

链表可以以三种基准分类.

1. 单向或者双向

image.png

2. 带头或者不带头 (哨兵位)

image.png

3. 循环或者非循环

image.png

组合起来就有8种链表结构.

三. 单链表各种接口的实现

image.png

typedef int SLTDataType;

typedef struct SListNode
{
    SLTDataType data;    // 数据域
    struct SListNode* next;    // 指针域
}SLTNode;

1. 打印

我们使用 for 循环来打印链表, 每次打印完当前节点的 data 后, 通过 next 指针前往下一个节点. 当走到链表尾部 (即 curr 指针指向空节点), 跳出循环, 打印结束.

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

void SLTPrint(SLTNode* phead)
{
    SLTNode* curr = phead;
    while (curr != NULL) {
        printf("%d->", curr->data);    // 打印当前节点的 data
        curr = curr->next;    // 前往下一个节点
    }
    printf("NULL\n");
}

2. 动态申请一个节点

使用 malloc 函数在堆区上动态开辟一个链表节点, data 赋值为 x , next 指针置空.

image.png

SLTNode* BuySLTNode(SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL) {
        perror("malloc fail");
        return NULL;
    }

    newnode->data = x;
    newnode->next = NULL;

    return newnode;
}    

3. 尾插

尾插, 即在链表的尾节点后插入一个新节点.

要进行尾插, 我们需要找到链表的尾节点, 将新节点的地址赋给尾节点的 next 指针.

image.png

image.png

image.png

image.png

image.png

image.png

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);

    SLTNode* newnode = BuySLTNode(x);

    // 链表为空
    if (*pphead == NULL) {
        *pphead = newnode;
    } else {    // 链表不为空
        // 找尾节点
        SLTNode* tail = *pphead;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        tail->next = newnode;
    }
}

4. 头插

头插, 即在链表的头节点前插入一个新节点.

Tip: 由以下两张示意图可以看出, 无论链表是否为空, 以下代码都适用.

image.png

image.png

image.png

image.png

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);

    SLTNode* newnode = BuySLTNode(x);

    newnode->next = *pphead;
    *pphead = newnode;
}

5. 尾删

尾删, 即删除链表的尾节点.

image.png

image.png

image.png

image.png

image.png

image.png

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);    // 判断链表是否为空表, 若为空表, 则无法对其进行尾删操作

    // 链表只有一个节点
    if ((*pphead)->next == NULL) {
        free(*pphead);
        *pphead = NULL;
    } else {    // 链表有多个节点
        // 找尾节点的前一个节点
        SLTNode* tailPrev = *pphead;
        while (tailPrev->next->next != NULL) {
            tailPrev = tailPrev->next;
        }

        free(tailPrev->next);
        tailPrev->next = NULL;
    }
}

6. 头删

头删, 删除链表的头节点.

image.png

image.png

image.png

image.png

image.png

void SLTPopFront(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);    // 判断链表是否为空表, 若为空表, 则无法对其进行头删操作

    SLTNode* first = *pphead;
    *pphead = first->next;

    free(first);
}

7. 查找

查找到存放数据 x 的节点的地址, 若查找不到, 返回 NULL .

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
    SLTNode* curr = phead;
    while (curr != NULL) {
        if (curr->data == x) {
            return curr;
        }
        curr = curr->next;
    }

    return NULL;
}

8. pos 之前插入

在内存地址为 pos 的节点前插入一个新节点.

void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    assert(pphead);
    assert(pos);    // 该函数不能用来尾插, 所以 pos 不能为空

    if (pos == *pphead) {
        SLTPushFront(pphead, x);
    } else {    // 找到 pos 的前一个位置
        SLTNode* prev = *pphead;
        while (prev->next != pos) {
            prev = prev->next;
        }

        SLTNode* newnode = BuySLTNode(x);
        prev->next = newnode;
        newnode->next = pos;
    }
}

9. pos 位置删除

删除内存地址为 pos 的节点.

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    assert(pphead);
    assert(pos);    // 不能删除空节点

    if (pos == *pphead) {
        SLTPopFront(pphead);
    } else {    // 找到 pos 的前一个位置
        SLTNode* prev = *pphead;
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = pos->next;

        free(pos);	
    }
}

10. pos 之后插入

在内存地址为 pos 的节点后插入一个新节点.

image.png

image.png

image.png

image.png

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
    assert(pos);

    SLTNode* newnode = BuySLTNode(x);

    newnode->next = pos->next; 
    pos->next = newnode; 
}

11. pos 之后删除

删除内存地址为 pos 的节点的后一个节点.

image.png

image.png

image.png

image.png

image.png

void SLTEraseAfter(SLTNode* pos)
{
    assert(pos);
    assert(pos->next);

    SLTNode* del = pos->next;
    pos->next = del->next;

    free(del);	
}

12. 销毁

从头节点开始, 一一释放链表中的每一个节点, 最后通过 pphead 将 phead 置空, 完成对链表的销毁.

void SLTDestroy(SLTNode** pphead)
{
    assert(pphead);

    SLTNode* curr = *pphead;
    while (curr != NULL) {
        SLTNode* temp = curr->next;
        free(curr);
        curr = temp;
    }
    *pphead = NULL;
}

四. 单链表的源码

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;

typedef struct SListNode
{
    SLTDataType data;    // 数据域
    struct SListNode* next;    // 指针域
}SLTNode;

// 打印
void SLTPrint(SLTNode* phead);

// 动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x);

// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

// 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

// 尾删
void SLTPopBack(SLTNode** pphead);

// 头删
void SLTPopFront(SLTNode** pphead);

// 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

// pos之前插入
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);

// pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);

// pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

// pos之后删除
void SLTEraseAfter(SLTNode* pos);

// 销毁
void SLTDestroy(SLTNode** pphead);

SList.c

#include "SList.h"

void SLTPrint(SLTNode* phead)
{
    SLTNode* curr = phead;
    while (curr != NULL) {
        printf("%d->", curr->data);    // 打印当前节点的 data
        curr = curr->next;    // 前往下一个节点
    }
    printf("NULL\n");
}

SLTNode* BuySLTNode(SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL) {
        perror("malloc fail");
        return NULL;
    }

    newnode->data = x;
    newnode->next = NULL;

    return newnode;
}   

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);

    SLTNode* newnode = BuySLTNode(x);

    // 链表为空
    if (*pphead == NULL) {
        *pphead = newnode;
    } else {    // 链表不为空
        // 找尾节点
        SLTNode* tail = *pphead;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        tail->next = newnode;
    }
}

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);

    SLTNode* newnode = BuySLTNode(x);

    newnode->next = *pphead;
    *pphead = newnode;
}

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);    // 判断链表是否为空表, 若为空表, 则无法对其进行尾删操作

    // 链表只有一个节点
    if ((*pphead)->next == NULL) {
        free(*pphead);
        *pphead = NULL;
    } else {    // 链表有多个节点
        // 找尾节点的前一个节点
        SLTNode* tailPrev = *pphead;
        while (tailPrev->next->next != NULL) {
            tailPrev = tailPrev->next;
        }

        free(tailPrev->next);
        tailPrev->next = NULL;
    }
}

void SLTPopFront(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);    // 判断链表是否为空表, 若为空表, 则无法对其进行头删操作

    SLTNode* first = *pphead;
    *pphead = first->next;

    free(first);
}

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
    SLTNode* curr = phead;
    while (curr != NULL) {
        if (curr->data == x) {
            return curr;
        }
        curr = curr->next;
    }

    return NULL;
}

void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    assert(pphead);
    assert(pos);    // 该函数不能用来尾插, 所以 pos 不能为空

    if (pos == *pphead) {
        SLTPushFront(pphead, x);
    } else {    // 找到 pos 的前一个位置
        SLTNode* prev = *pphead;
        while (prev->next != pos) {
            prev = prev->next;
        }

        SLTNode* newnode = BuySLTNode(x);
        prev->next = newnode;
        newnode->next = pos;
    }
}

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    assert(pphead);
    assert(pos);    // 不能删除空节点

    if (pos == *pphead) {
        SLTPopFront(pphead);
    } else {    // 找到 pos 的前一个位置
        SLTNode* prev = *pphead;
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = pos->next;

        free(pos);	
    }
}

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
    assert(pos);

    SLTNode* newnode = BuySLTNode(x);

    newnode->next = pos->next; 
    pos->next = newnode; 
}

void SLTEraseAfter(SLTNode* pos)
{
    assert(pos);
    assert(pos->next);

    SLTNode* del = pos->next;
    pos->next = del->next;

    free(del);	
}

void SLTDestroy(SLTNode** pphead)
{
    assert(pphead);

    SLTNode* curr = *pphead;
    while (curr != NULL) {
        SLTNode* temp = curr->next;
        free(curr);
        curr = temp;
    }
    *pphead = NULL;
}