数据结构之单链表(C语言代码及注释)

一、单链表的特点

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

优点:元素的存储单元是任意的,可连续也可不连续;不需要限定长度。
缺点:其查找时间复杂度为O(n);存放元素时需要另外开辟一个指针域的空间。

二、代码实现

头文件:SList.h

#ifndef SLIST_H_INCLUDED
#define SLIST_H_INCLUDED

#include <stdio.h>      //C语言标准库函数:用于输入和输出的函数、类型和宏。声明文件指针的FILE。常用的类型是size_t和fpos_t。
#include <malloc.h>     //是动态存储分配函数头文件,当对内存区进行操作时,调用相关函数。
#include <assert.h>     //assert函数像if判断语句中一样,然后如果该值为真则正常运行,否则报错,并调用abort(),产生异常中断,exit出来。

#define ElemType int    //定义数据元素类型为int
typedef struct Node     //定义节点
{
    ElemType data;      //数据类型
    struct Node *next;  //Node类型的next指针
}Node,*PNode;           //定义了结点的类型Node和指针类型PNode

typedef struct List     //定义出单链表的管理方式
{
    PNode first;    //指向头部
    PNode last;     //指向尾部
    size_t size;    //结点个数
}List;      //定义单链表的类型List

void InitList(List *list);                  //初始化链表
void push_back(List *list, ElemType x);     //声明尾插函数
void push_front(List *list, ElemType x);    //声明头插函数
void show_list(List *list);                 //声明显示函数
void pop_back(List *list);                  //声明尾删函数
void pop_front(List *list);                 //声明头删函数
void insert_val(List *list, ElemType x);    //声明按值插入函数
Node* find(List *list,ElemType key);        //声明查找函数(返回节点类型)
int length(List *list);                     //声明长度函数
void delete_val(List *list,ElemType key);   //声明按值删除函数
void sort(List *list);                      //声明排序函数
void reverse(List *list);                   //声明逆置函数
void clear(List *list);                     //声明清除函数
void destory(List *list);                   //声明摧毁函数

#endif // SLIST_H_INCLUDED

实现函数文件:

#include "SList.h"

void InitList(List *list)           //初始化函数的实现
{
    list->first = list->last = (Node*)malloc(sizeof(Node));  //申请了一个结点,将头和尾都指向该节点
    assert(list->first != NULL);    //检验结点是否申请成功
    list->first->next = NULL;       //这里必须要附为空,不然在头插时会乱码
    list->size = 0;                 //此时元素个数为0
}

void push_back(List *list, ElemType x)      //尾插函数的实现:从单链表的尾部插入数据
{
    Node *s = (Node *)malloc(sizeof(Node)); //申请一个新的结点,令s指向该结点
    assert(s != NULL);
    s->data = x;               //将要插入的值放人该结点的data中
    s->next = NULL;            //将它的next指针赋空(若不赋空,则是随机值,程序报错)

    list->last->next = s;
    list->last = s;            //将新结点链接进入list的尾部
    list->size++;              //元素个数加1
}

void push_front(List *list, ElemType x)      //头插函数的实现:从单链表的头结点的下一位开始插入节点
{
    Node *s = (Node *)malloc(sizeof(Node));
    assert(s!= NULL);
    s->data = x;
    s->next = NULL;               //添加新结点,并放入元素

    s->next = list->first->next;  //这里与尾插不同:在将新结点插入时,必须先让新结点的next指针先指向链表中
    list->first->next = s;        //再让头指针指向新结点
    list->size++;                 //元素个数加1
}

void show_list(List *list)        //显示链表函数的实现
{
    Node *p = list->first->next;  //创建一个Node类型的指针指向头结点
    while(p != NULL)              //由于尾结点为空,所以可以在遍历完链表后退出循环
    {
        printf("%d-->",p->data);
        p = p->next;
    }
    printf("Nul.\n");
}

void pop_back(List *list)      //尾删方法
{
    if(list->size == 0)        //如果链表长度为0,直接返回
    {
        return;
    }
    Node *p = list->first;
    while(p->next != list->last)   //去寻找尾结点的前驱结点
    {
        p=p->next;             //p最终指向尾结点的前驱结点
    }
    free(list->last);          //释放尾结点
    list->last = p;            //将尾指针指向p(原尾结点的前驱结点)
    list->last->next = NULL;
    list->size--;
}

void pop_front(List *list)     //头删方法的实现
{
    if(list->size == 0)        //如果链表长度为0,直接返回
    {
        return;
    }
    Node *p = list->first->next;
    list->first->next = p->next;    //头删比尾删简单,但应注意链表中仅有一个元素的情况
    free(p);
    if(list->size == 1)             //如果链表仅有一个元素,删除后,其尾首指针都应指向头结点
    {
        list->last = list->first;
    }
    list->size--;
}

void insert_val(List *list,ElemType x)     //按值插入函数的实现:前提是在链表有序的条件下按值插入
{
    Node *s = (Node *)malloc(sizeof(Node));        //创建一个新结点
    assert(s!= NULL);
    s->data = x;        //这里已经将要插入的值放入新结点中
    s->next = NULL;

    Node *p = list->first;
    while(p->next!=NULL && p->next->data<x)  //创建了新指针p去寻找s结点该插入的位置
    {
        p = p->next;
    }
    if(p->next == NULL)   //在尾部插入的情况
    {
        list->last = s;
    }
    s->next = p->next;
    p->next = s;
    list->size++;
}

Node* find(List *list,ElemType x)   //查询函数的实现:函数类型为指针类型,说明返回的是一个指针
{
    Node *p = list->first->next;
    while(p!=NULL && p->data!=x)    //遍历有没有等于x值的结点
    {
        p = p->next;
    }
    return p;
}

int length(List *list)    //链表长度函数的实现
{
    return list->size;
}

void delete_val(List *list,ElemType key)    //按值删除函数的实现
{
    if(list->size == 0)         //为空直接返回
    {
        return;
    }
    Node *p = find(list,key);   //查找该元素是否存在,存在则p指向该结点
    if(p == NULL)
    {
        printf("要删除的数据不存在。");
        return;
    }
    if(p == list->last)        //如果该值在链表尾部,则用尾删法
    {
        pop_back(list);
    }
    else                       //若在中间
    {
        Node *q = p->next;     //用q指向要删除结点的下一结点
        p->data = q->data;     //将下一结点的值赋给当前要删除结点的data
        p->next = q->next;     //将p所指的结点next跨过下一结点
        free(q);               //释放q结点
        list->size--;
    }
}

void sort(List *list)    //排序函数的实现:将链表从第一个结点后拆分成两个链表,再不断从后一链表中取值进来排序
{
    if(list->size==0 || list->size==1)
    {
        return;
    }
    Node *s = list->first->next;  //s指向第一个结点
    Node *q = s->next;            //q只想第二个结点

    list->last = s;
    list->last->next = NULL;  //从第一个结点后开始拆分(这里s指针就空出来了)

    while(q != NULL)
    {
        s = q;          //将s指针指向q所指链表的第一个结点
        q = q->next;    //q指向链表的下一元素

        Node *p = list->first;    //创建p指针去对新插入的结点排序
        while(p->next!=NULL && p->next->data<s->data)   //遍历链表寻找插入位置
        {
            p = p->next;
        }
        if(p->next == NULL)
        {
            list->last = s;
        }
        s->next = p->next;
        p->next = s;
    }
}

void reverse(List *list)   //逆置函数的实现:同理也是拆分成两个链表,不断进行头插达到逆置的效果
{
    if(list->size == 0 || list->size == 1)
    {
        return;
    }
    Node *p = list->first->next;
    Node *q = p->next;

    list->last = p;
    list->last->next = NULL;   //拆分成两个链表(排序函数相同)

    while(q != NULL)
    {
        p = q;          //p指向q的第一个结点
        q = p->next;    //q滑向下一结点

        p->next = list->first->next;
        list->first->next = p;     //头插结点(必须要让要插入的结点先链入表中)
    }
}

void clear(List *list)    //链表清除函数的实现
{
    if(list->size == 0)
    {
        return;
    }
    Node *p = list->first->next;  //p始终指向头结点的下一结点
    while(p != NULL)     //遍历元素,如果
    {
        list->first->next = p->next; //若到达最后一个元素,则其p->next为空则可退出循环
        free(p);         //释放p所指向的结点
        p = list->first->next;
    }
    list->last = list->first;
    list->size = 0;
}

void destory(List *list)  //摧毁链表函数的实现
{
    clear(list);          //先将链表清除
    free(list->first);    //再将头结点释放
    list->first = list->last = NULL;
    list->size = 0;
}

主函数:

#include "SList.h"

int main()
{
    List mylist;        //创建一个单链表
    InitList(&mylist);  //初始化单链表

    ElemType Item;      //定义一个int类型的数
    Node *p = NULL;     //定义一个Node类型的指针
    int select = 1;
    while(select)
    {
        printf("**********************************************\n");
        printf("*[1]  push_back             [2]  push_fornt  *\n");    //尾插   头插
        printf("*[3]  show_list             [4]  pop_back    *\n");    //显示   尾删
        printf("*[5]  pop_list              [6]  insert_val  *\n");    //头删   按值插入
        printf("*[7]  find                  [8]  length      *\n");    //查找   链长
        printf("*[9]  delete_val            [10] sort        *\n");    //按数值删   排序
        printf("*[11] resver                [12] clear       *\n");    //逆置   清除
        printf("*[13] destroy               [0] qiut_system  *\n");     //摧毁   退出
        printf("**********************************************\n");
        printf("请选择:>");
        scanf("%d",&select);
        if(select == 0)
        {
            break;
        }
        switch (select)   //根据select值选择不同功能
        {
        case 1: //尾插
            printf("请输入要插入的数据(-1结束):>");
            while(scanf("%d",&Item),Item != -1)
            {
                push_back(&mylist,Item);
            }
            break;
        case 2: //头插
            printf("请输入要插入的数据(-1结束):>");
            while(scanf("%d",&Item),Item != -1)
            {
                push_front(&mylist,Item);
            }
            break;
        case 3: //显示
            show_list(&mylist);
            break;
        case 4: //尾删
            pop_back(&mylist);
            break;
        case 5: //头删
            pop_front(&mylist);
            break;
        case 6: //按值插入
			printf("请输入要插入的数据:>");
			scanf("%d",&Item);
			insert_val(&mylist,Item);
			break;
        case 7: //查询
            printf("请输入要查找的数据:>");
			scanf("%d",&Item);
			p = find(&mylist,Item);
			if(p == NULL)
            {
                printf("要查找的数据不存在。\n");
            }
            else
            {
                printf("查找成功,数据存在。\n");
            }
            break;
        case 8: //链表长度
            Item = length(&mylist);
            printf("链表当前长度为:>%d\n",Item);
            break;
        case 9: //按值删除
            printf("请输入要删除的值:>");
            scanf("%d",&Item);
            delete_val(&mylist,Item);
            break;
        case 10: //排序
            sort(&mylist);
            break;
        case 11: //逆置
            reverse(&mylist);
            break;
        case 12: //清除
            clear(&mylist);
            printf("链表清除成功,请重新输入数据。\n");
            break;
        case 13: //摧毁
            destory(&mylist);
            printf("链表摧毁成功,已自动退出程序。\n");
            exit(0);
            break;
        case 0: //退出
            break;
        default:
            printf("输入命令错误,请重新输入。\n");
            break;
        }
    }
    destory(&mylist);
    return 0;
}

/**
* 本次编程所得:
* 1.要考虑边界条件和特殊情况(最容易出bug的地方)。
* 2.牵扯到链表的操作,很少直接对链表中的数据直接进行操作,而是对链表整体或结构进行操作。
*/

三、说明

编写代码的IDE是CodeBlock。
代码百度网盘链接:
链接:https://pan.baidu.com/s/1opVhpl5Bbh8GK-aJN-dqeQ
提取码:x0q9

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值