C/C++单链表

               有余顺序表在不断地开辟空间和空间转移会有一定的损耗,因此链表应运而生,但是在这里我们只讲解单链表虽然单链表也有一定的缺陷但是可以为之后的学习进行初步的了解便于后续的学习在这里我们首先介绍单链表得构成 ,我们使用结构体来创造单链表基本原理如下:

typedef int SListType;
struct SList {
    SListType date;
    struct SList* next;
};

我们在结构体中让上一个结构体存储下一个结构体的地址,每一个部分称之为节点,在每一个节点中存储一个我们需要的实际值,并且存储下一个节点的地址这样我们可以通过使用下一个节点的地址访问下一个节点的内容,在这里我们有一个逻辑图这个逻辑图只是便于我们理解单链表的基本原理:

 

这种方式能够使我们更加理解链表的使用原理,我们还有第二张图它可以在逻辑图的基础上加深我们对链表的印象:

 

 基本原理可以简要概括为在上一个节点保存着下一个节点的地址最后一个节点保存的地址为空,这样可以判断是否是最后一个节点。

       对于单链表的基本原理就是如此那么我们接下来介绍单链表的使用我们先从最简单的遍历开始:

void SListPrint(SList* phead) {
    SList* temp = phead;
    while (temp != NULL) {
        cout << temp->date << " ";
        temp = temp->next;
    }
}

      在这里需要特别说明由于单链表是单向的因此无法像顺序表那样可以直接访问其中的任意一个元素因此我们只能一个一个的进行寻找我们想要寻求的目标(在插入,删除等中会用到)

      如图我们可以看出由于单链表的结束方式是节点中存储的地址是否为空,因此我们可以利用这个方法进行遍历操作,在图中  temp  就相当于 phead  在while循环中,由于单链表不管有没有节点都需要一个指针起到头作用如果链表中没有元素那么temp就为NULL;反之则指向第一个元素,在图中没打印一个节点数据后就将temp指向下一个节点 ,直至成为尾节点,由于尾节点的指向为空 此时temp的指向也为NULL,因此循环停止遍历结束,跳出遍历函数。

以上就是我们对单链表的简要理解和遍历方法,下面我们来介绍单链表的其他使用方法。

1,尾插法

void SListPush_back(SList** pphead, SListType x) {
    SList* NewNode = (SList*)malloc(sizeof(SList));
    if (NewNode == NULL) {
        cout << " NewNode is NULL , malloc fail" << endl;
        exit(-1);
    }
    NewNode->date = x;
    NewNode->next = NULL;
    if (*pphead == NULL) {
        *pphead = NewNode;
    }
    else {
        SList* tall = *pphead;
        while (tall->next != NULL) {
            tall = tall->next;
        }
        tall->next = NewNode;
    }
}

         在这里我们讲解一下大多数同学都容易迷惑的一个点在这个函数中我们为什么要使用二级指针?

         在上面我们说了,每一个单链表都需要一个开头(头结点)我们要插入一个节点是不是要改变这个头结点的指向,既然改变了指向我们就要传地址,但是如下:

SList* plist = NULL;
SListPush_back(plist, 10);

在这里有这样一个疑问我们传递的已经是地址了,但是此地址非彼地址这个地址仅仅只是指针本身的地址传入这个地址我们可以这样理解形参是对实参的临时拷贝实参的改变不对实参也就是plist进行任何影响,形参使用后会自动释放,因此如果是这种写法那么无论如何添加都不会对plist产生任何影响 ,因此正确的传递方式应为:

    SListPush_back(&plist, 10); 

只有这种传递方式形参才可以影响实参的内容,才可以对实参产生影响。

在插入的时候我们需要考虑两种情况:

     第一种情况当插入一个节点时它是第一个节点 。

     第二种情况插入节点时是第二个及以上节点。

SList* NewNode = (SList*)malloc(sizeof(SList));

NewNode->date = x;
NewNode->next = NULL;

           当然每次使用时都需要开辟一道空间,之后判断是否为第一个节点判断方式就是判断*pphead是否为空如果为空则说明此为插入的第一个节点。由于我们使用的是尾插法顾名思义就是在尾部插入的方法是作为尾节点的存在因此它的指向为NULL。

            之后当不断插入节点时我们只需让上一个节点指向我们插入的新节点NewNode这个插入就算完成了,但是由于我们无法直接找出尾节点因此我们需要借助循环逐个查找:

  SList* tall = *pphead;
        while (tall->next != NULL) {
            tall = tall->next;
        }
        tall->next = NewNode;

如图当tall->next的指向为NULL时该节点就位尾节点,此时我们只需要将尾节点的指向改为指向我们创造的新节点NewNode就大功告成了。

2,头插法

void SListPush_front(SList** pphead, SListType x) {
    SList* NewNode = (SList*)malloc(sizeof(SList));
    if (NewNode == NULL) {
        cout << " NewNode is NULL , malloc fail" << endl;
        exit(-1);
    }
    NewNode->date = x;
    NewNode->next = NULL;
    
    NewNode->next = *pphead;
    *pphead = NewNode;

           如图:头插法与尾插法的区别就是头插法不需要考虑是否是第一次插入,完成这一步关键在于;

     NewNode->next = *pphead;
    *pphead = NewNode;

         在这里我们只需将头结点指向NewNode即可无论是否是插入的第一个节点都不影响。

3,查找某项

SList* SListFind(SList* phead, SListType x) {
    SList* cur = phead;
    while (cur) {
        if (cur->date == x) {
            return cur;
        }
        else {
            cur = cur->next;
        }
    }
    return NULL;

       查找某项也是非常简单通过注逐一查找若找到则返回位置否则返回空,但是如果有多个相同的date则仅仅只返回一个,当然可以通过方法返回多个这就需要大家思考了。

4,尾删法

void SListPop_back(SList** pphead) {

     assert(*pphead!=NULL);
    SList* pren = NULL;
    SList* tail = *pphead;

    while (tail->next != NULL) {
        pren = tail;
        tail = tail->next;
    }
    free(tail);
    tail = NULL;
    pren->next = NULL;

}

 

       如果想要删除最后一个节点,按照原理只需要将尾节点的上一个节点指向NULl并且释放尾节点那么就算是删除成功了,那么如何实现这个方法呢?

      想要实现这个想法我们必须找到尾节点和尾节点的上一个节点但是由于单链表是单项得我们无法使用某一个节点获知上一个节点或者下一个节点的地址因此我们需要在找到尾节点的同时找到上一个节点进而完成删除操作,在这里我们用到了pren 和 tail这两个指针这样当tail->next空时,pren  指向的就是指向尾节点的节点这是改变指向释放尾节点则尾删操作就算是完成了。

5,头删法

void SListPop_Front(SList** pphead) {

    assert(*pphead!=NULL);
    if ((*pphead)->next==NULL) {
        free(*pphead);
        *pphead = NULL;
    }
    else {
        SList* temp = (*pphead)->next;
        *pphead = temp;
    }

     头删和尾删的原理基本一致都是改变指向,在头删和尾删当中我们都需要判断是否有节点,如果没有节点但仍进行操作就会产生错误因此使用assert断言来进行判断*pphead是否为NULL,若为空则程序停止,显示错误位置。

     如果*pphead指向为空也就表示为第一个节点因此只需要释放空间并将*pphead 为NULL则头删结束,否则则利用:

SList* temp = (*pphead)->next;
        *pphead = temp;

   通过改变*pphead的指向来删除第一个节点,也就是是*pphead指向下一个节点由于 解引用和->优先级相同所以使用*pphead时应带上括号。

6,改变某一节点的元素

void SListChange(SList** pphead, SList* pos, SListType x) {
    SList* cur = *pphead;
    while (cur->next==pos) {
        cur = cur->next;
    }
    cur->date = x;

}

      这一方法需要北河SListFind函数使用通过其找到节点位置进而改变其指向的date。

7, 在某一节点前插入一个节点

void SListInsert(SList** pphead, SList* pos, SListType x) {
    SList* NewNode = (SList*)malloc(sizeof(SList));
    NewNode->date = x;
    SList* cur = *pphead;
    if (*pphead == pos) {
        NewNode->next = *pphead;
        *pphead = NewNode;
    }
    else {
        while (cur->next != pos) {
            cur = cur->next;
        }
        cur->next = NewNode;
        NewNode->next = pos;
    }
}

         同理需要借助SListFind函数找到插入节点的位置,也需要判断是否为第一个节点,是的话方法同头插法如果不是那么改变指向就可以轻易完成操作,在某一位置前插入一个节点。

8,销毁链表

void SListDestroy(SList** pphead) {
    SList* cur = *pphead;
    while (cur) {
        free(cur);
        cur = cur->next;
    }
    free(cur);
    *pphead = NULL;
}

         销毁操作也是十分简单只需释放每一个空间即可。

下面我们展示单链表的较为完整的代码:

头文件Slist.h

#pragma once
#include<iostream>
#include<cstdlib>
#include<cassert>
using namespace std;
typedef int SListType;
struct SList {
    SListType date;
    struct SList* next;
};

void SListPrint(SList* phead);
void SListPush_back(SList** pphead, SListType x);
void SListPush_front(SList** pphead, SListType x);
void SListPop_back(SList** pphead);
void SListPop_Front(SList** pphead);
SList* SListFind(SList* phead, SListType x);
void SListChange(SList** pphead, SList* pos, SListType x);
void SListInsert(SList** pphead, SList* pos, SListType x);
void SListDestroy(SList** pphead);
void SListInsertAfter(SList** pphead, SList* pos, SListType x);

源文件Slist.cpp

 

#include"SList.h"

void SListPush_back(SList** pphead, SListType x) {
    SList* NewNode = (SList*)malloc(sizeof(SList));
    if (NewNode == NULL) {
        cout << " NewNode is NULL , malloc fail" << endl;
        exit(-1);
    }
    NewNode->date = x;
    NewNode->next = NULL;
    if (*pphead == NULL) {
        *pphead = NewNode;
    }
    else {
        SList* tall = *pphead;
        while (tall->next != NULL) {
            tall = tall->next;
        }
        tall->next = NewNode;
    }
}
void SListPrint(SList* phead) {
    SList* temp = phead;
    while (temp != NULL) {
        cout << temp->date << " ";
        temp = temp->next;
    }
}
void SListPush_front(SList** pphead, SListType x) {
    SList* NewNode = (SList*)malloc(sizeof(SList));
    if (NewNode == NULL) {
        cout << " NewNode is NULL , malloc fail" << endl;
        exit(-1);
    }
    NewNode->date = x;
    NewNode->next = NULL;
    
    NewNode->next = *pphead;
    *pphead = NewNode;
}
void SListPop_back(SList** pphead) {
    SList* pren = NULL;
    SList* tail = *pphead;

    while (tail->next != NULL) {
        pren = tail;
        tail = tail->next;
    }
    free(tail);
    tail = NULL;
    pren->next = NULL;

}
void SListPop_Front(SList** pphead) {

    assert(*pphead!=NULL);
    if ((*pphead)->next==NULL) {
        free(*pphead);
        *pphead = NULL;
    }
    else {
        SList* temp = (*pphead)->next;
        *pphead = temp;
    }
}
SList* SListFind(SList* phead, SListType x) {
    SList* cur = phead;
    while (cur) {
        if (cur->date == x) {
            return cur;
        }
        else {
            cur = cur->next;
        }
    }
    return NULL;
}
void SListInsert(SList** pphead, SList* pos, SListType x) {
    SList* NewNode = (SList*)malloc(sizeof(SList));
    NewNode->date = x;
    SList* cur = *pphead;
    if (*pphead == pos) {
        NewNode->next = *pphead;
        *pphead = NewNode;
    }
    else {
        while (cur->next != pos) {
            cur = cur->next;
        }
        cur->next = NewNode;
        NewNode->next = pos;
    }
}
void SListChange(SList** pphead, SList* pos, SListType x) {
    SList* cur = *pphead;
    while (cur->next==pos) {
        cur = cur->next;
    }
    cur->date = x;

}

void SListDestroy(SList** pphead) {
    SList* cur = *pphead;
    while (cur) {
        free(cur);
        cur = cur->next;
    }
    free(cur);
    *pphead = NULL;
}
void SListInsertAfter(SList** pphead, SList* pos, SListType x){
    SList* NewNode = (SList*)malloc(sizeof(SList));
    NewNode->date = x;
    NewNode->next = NULL;
    SList* cur = *pphead;
    while (cur->next != NULL) {
        cur = cur->next;
    }
    cur->next = NewNode;
}

源文件test.c

 

#include"SList.h"


void testSList() {
    SList* plist = NULL;
    SListPush_back(&plist, 10);
    SListPush_front(&plist, 100);
    SListPush_front(&plist, 100);
    SListPush_front(&plist, 100);
    SListPush_front(&plist, 100);
    SList* pos =SListFind(plist, 100);
    //if (pos != NULL) {
    //    SListInsert(&plist, pos, 1000);
    //    SListPrint(plist);
    // }
    SListChange(&plist, pos, 5);
    SListPrint(plist);

}
int main() {

    testSList();
    return 0;
}

对于单链表比较重要的知识点:传地址值需要传&plist否则则无法改变plist中的内容,还需注意是否为第一个节点以及尾节点的指向为NULL。 

以上就是单链表的大部分内容如有不足后续会进行补充。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃啊顶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值