详述单链表

单链表

单链表概念

1.链表的概念:链表其实是将一个个相同或者不同的数据用指针串联起来。

链表结构和火车相似。在淡季的时候,去外面旅游的人少,火车装载的车厢少。当节假日时,旅游的人数很多,每列火车就会多装载几节车厢。这些车厢是独立的,用不到的时候,可以拆卸,而不会影响剩余的车厢。如果你在车头,要到车尾,每个车厢之间通过某种方式建立联系,一个车厢一个车厢的走,才能到达车尾。在链表中,每节车厢都被称为“结点”(节点),每个结点中有我们要存储的数据,也有指向下一个结点的的指针。

2.与顺序表对比:顺序表每个数据之间的储存位置是连续的,而顺序表存储的位置不连。如果要对数据进行插入,顺序表还要挪动其它数据的位置,而链表在这方面就有很大的优势,它可以通过修改结点的指向来插入数据。

每个结点中的指针就是为了存储下一个结点的地址,通过这个就可以访问下一个结点。图中的数据可以是任意类型,任意个数据。每个结点都需要向堆区申请空间。

单链表的实现

单链表头文件包含

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int TYPEDATA;//定义数据类型
//创建一个节点
typedef struct NODE
{
    TYPEDATA data;//看作是数据
    struct SL* nextnode;//指向下一个结点的指针
}SL;

void PushFront(SL** pphead, TYPEDATA data);//头插
void DelData(SL** pphead);//头删
void AddBack(SL** pphead, TYPEDATA data);//尾插
void DelBack(SL** pphead);//尾删

void AddAny(SL** pphead, TYPEDATA data);//任意位置插入
void DelAny(SL** pphead, TYPEDATA data);//在任意位置删除

void ModifyData(SL** pphead, TYPEDATA data);//修改已经存在的数据

SL* Find(SL** pphead, TYPEDATA data);//查找
void PrintData(SL** pphead);//打印

单链表各功能实现

#include"SQL.h"
//头插
void PushFront(SL** pphead, TYPEDATA data)
{
    
    SL* newnode = (SL*)malloc(sizeof(SL));//先建立节点,
    if (newnode == NULL)                //
    {
        perror("malloc failed!");
        return 1;
    }
        newnode->data = data;
        newnode->nextnode = *pphead;   //然后将这个节点中的指针指向目前的头节点
        (*pphead) = newnode;        //然后将只向头指针的指针指向新建立的节点
}                                   //头指针—>节点1,新节点-->节点1,头指针-->新节点。就达成了:头指针—>新节点-->节点1。
//头删
void DelData(SL** pphead)
{
    assert(*pphead);//断言,如果链表中没有节点,就终止执行
    SL* newnode = *pphead;//建立指针指向头节点,
    *pphead = (*pphead)->nextnode;//然后将指向头节点的指针指向下一个节点
    free(newnode);//销毁头节点
    newnode = NULL;//防止newnode变成野指针
}
//尾插
void AddBack(SL** pphead, TYPEDATA data)
{
    if (*pphead == NULL)//如果没有结点,可以直接使用头插
    {
        PushFront(pphead, data);
    }
    else
    {
        
        SL* pretail = *pphead;
        while (pretail->nextnode)//使pretail指向的该结点的下一个指针刚好是空指针
        {
            pretail = pretail->nextnode;
        }
        SL* newnode = (SL*)malloc(sizeof(SL));
        if (newnode == NULL)                
        {
            perror("malloc failed!");
            return 1;
        }
        newnode->data = data;//使pretail所指的结点指向新创立的结点,然后新创立的结点指向空指针
        pretail->nextnode = newnode;
        newnode->nextnode = NULL;
    }
}
//尾删
void DelBack(SL** pphead)//尾删的思路是:创建两个指针,第一个指针指向第一个结点,第二个指针指向第二个结点,然后使第一个指针指向第二个结点,第二个指针指向第三第三个结点,直到第二个指针的下一个结点是NULL
{
    assert(*pphead);//如果没有结点,就终止程序
    SL* backnode = (*pphead)->nextnode;//
    SL* pre = *pphead;
    while (backnode&&backnode->nextnode)//这里的思路是:若backnode指向的下一个结点为空就退出,刚好backnode会指向NULL前一个指针。
    {
        backnode = backnode->nextnode;
        pre = pre->nextnode;
    }
    if (pre->nextnode==NULL)//这里出现了特殊情况:剩下最后一个结点时,不能要用这样的方法去消除最后一个结点
    {
        free(*pphead);
        *pphead = NULL;//使指向头结点的指针指向NULL
        return;
    }
    free(backnode);//循环出来后,backnode刚好指向最后一个结点
    backnode = NULL;
    pre->nextnode = NULL;//pre指向的是倒数第二个结点,然后使第二个结点中的指针指向NULL
}
//在某数据前添加结点
void AddAny(SL** pphead, TYPEDATA data)//在查找的数据的结点前添加数据
{
    if (*pphead == NULL)//如果没有结点,可以直接使用头插
    {
        PushFront(pphead, data);
        return;
    }
    if (Find(pphead, data)) //先看看有没有该数据,如果没有就退出
    {
        TYPEDATA n;
        printf("输入你要添加的数据\n");        scanf("%d", &n);
        SL* newnode = (SL*)malloc(sizeof(SL));//先建立节点
        if (newnode == NULL)
        {
            perror("molloc failed!");
            return;
        }
        newnode->data = n;
        SL* find = Find(pphead, data);//接收要查找的数据前一个结点的地址
        newnode->nextnode = find->nextnode;//先将新建立的结点指向含有目标数据的地址,再将该目标数据的前一个结点指向新建立的结点
        find->nextnode = newnode;//如果不熟悉结构指针传递,建议画图,
    }
    else
        return;
}
//删除已经存在的数据
void DelAny(SL** pphead, TYPEDATA data)
{
    assert(*pphead);//如果没有结点,就终止程序
    SL* pre = Find(pphead, data);
    if (pre)//判断数据是否存在
    {
        SL* back = pre->nextnode;
        pre->nextnode = back->nextnode;//将要删除的结点两边的结点连接起来
        free(back);
        back = NULL;
    }
    else
    {
        printf("要删除的数据不存在\n");
        return;
    }
        
}
//修改已经存在的数据
void ModifyData(SL** pphead, TYPEDATA data)
{
    SL* check = *pphead;
    while (check)
    {
        if (check->data == data)//让它循环到目标结点,然后对它进行修改
        {
            TYPEDATA n=0;
            printf("请输入你要修改后的数据\n");        scanf("%d", &n);
            check->data = n;
            return;
        }
        check = check->nextnode;
    }//如果循环退出,说明要修改的数据不存在
    printf("没有你要修改的数据\n");
}
//查找
SL* Find(SL** pphead,TYPEDATA data)//返回要找的数据前一个结点的地址
{
    assert(*pphead);//如果没有数据,就终止查找
    SL* des = (*pphead)->nextnode;
    SL* pre = *pphead;
    while (des)//des指针指向下一个结点的地址,如果是NULL,说明没找到
    {
        if (des->data == data)
        {
            return pre;
        }
        des = des->nextnode;
        pre = pre->nextnode;
    }
        printf("没找到\n");
        return NULL;
}
void PrintData(SL** pphead)//打印
{
    assert(*pphead);
    SL* newnode = *pphead;
    while (newnode)
    {
        printf("%d ", newnode->data);//打印的思路是:创建一个结点类型的指针每打印完一个,就指向下一个结点,直到指向NULL就退出
        newnode=newnode->nextnode;
    }
    printf("\n");
}

力扣经典算法题

1.反转链表

方法:

各位亲们,这画图太耗时间了,咱就不画了,先送思路和代码吧,还请谅解。

代码实现:

 typedef struct ListNode List;

struct ListNode* reverseList(struct ListNode* head) {

     List*mid=head;

     if(head==NULL)//如果没有结点,就返回头指针

     {

        return head;

     }

     List *front=head->next;

     List*back=NULL;

     while(mid)

     {

       //使中间的指针指向后面的指针

       mid->next=back;

       //然后使各指针向前移动,必须要从后面的指针开始移动,不然后面的其中一个指针会丢失结点地址,找不到下一个结点的位置

       back=mid;

       mid=front;

       if(front!=NULL)//如果front指向空指针,就不能对它解引用

       {

        front=front->next;

       }

     }

     return back;

}

2.移除链表元素

方法一:

 typedef struct ListNode LS;

struct ListNode* removeElements(struct ListNode* head, int val) {

    LS*front=head;

    LS*des=NULL;

    LS*back=NULL;

    LS*newhead=NULL;

    while(front)//这里front!=NULL

    {

        if(front->val==val)//这里总体思路是:如果front->val==val,那么就对该结点进行销毁

        {

            while(front&&front->val==val)//如果有几个相邻的结点front->val==val,那么就对这几个结点重复销毁

            {

            des=front;

            front=front->next;

            free(des);

            des=NULL;

            }//走出循环后,说明现在front->val!=val

        }

        //先让后面的指针先指向front,在让front走向下一个结点

         if(back==NULL)//这里的判断不能直接写back->next=front,如果所有的数据都是val,那么就不能对空指针解引用

        {

            back=front;

        }

        else

        {

        back->next=front;

        back=front;

        }

        if(newhead==NULL&&back!=NULL)//先将头结点指向空,然后再找出第一个符合条件的结点作为头结点赋newhead

        {

            newhead=back;

        }

        if(front)

        {

        front=front->next;

        }

    }

    return newhead;

}

方法二:

 typedef struct ListNode LS;

struct ListNode* removeElements(struct ListNode* head, int val) {

    LS*newhead=NULL;

    LS*tail=NULL;//

   

     LS*front1=head;//遍历原链表

    while(front1)

    {

        if(front1->val!=val)//判断front1所指向的结点的val是否符合题意,如果符合,就将该结点连接到新链表中

        {

            if(newhead==NULL)//刚进入循环,第一个符合条件的结点赋给头指针

            {

                newhead=tail=front1;//这里先将头指针

            }

            else

            {

                tail->next=front1;//先将该结点指向下一个结点,然后将指针指向下一个结点

                tail=tail->next;

            }

        }

        front1=front1->next;

    }

    if(tail)

    {

        tail->next=NULL;

    }

    return newhead;

}

总结

数据结构中,理解链表并不是很难。单链表应该要比顺序表好理解。当我们每次学习完一个知识点的时候,首先就要去做总结,然后通过刷题加深对知识的理解,然后再次总结。我们对知识的理解总是不断地加深,可能以前的观点到了现在就会有点不符合,这都是很正常的事。人与社会一样,每时每刻都在发生变化,进步还是倒退都取决于自己。磨练才能有一番成就!

评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值