c语言数据结构之单链表

99 阅读4分钟

基本定义

单链表中每个结点存放一个数据元素,并用一个指针表示结点间的逻辑关系,即每个结点的指针域存放后继结点的地址(线性表中每个元素有唯一的后继元素)。因此单链表的一个存储结点包含两个部分,结点的基本形式如下:

image.png

其中,data部分称为数据域,用于存储线性表的一个数据元素,也就是说在单链表中一个结点存放一个数据元素;next部分称为指针域或链域,用于指向后继元素对应的结点。

单链表中尾结点之后不再有任何结点,那么它的 next 域设置为什么值呢?有以下两种方式:

  • 将尾结点的next域用一个特殊值 NULL ,这样的单链表为非循环单链表。通常所说的单链表都是指这种类型的单链表
  • 将尾结点的 next 域指向头结点,这样可以通过尾结点移动到头结点,从而构成一个查找环,将这样的单链表称为循环单链表

假设数据元素的类型为 ElementType ,单链表的结点类型声明如下:

typedef struct node{
    ElementType data;   //数据域
    struct node *next;  //指针域
}SLinkNode;     //单链表结点类型的申明

基本运算

先讨论线性表基本运算在单链表上的实现。

在带头结点的单链表中,头结点的data域通常不存储任何信息,头结点的 next 域指向第一个数据结点,即存放第一个数据结点的地址。通过头结点的指针L(称为头指针)来标识整个单链表。头结点指针称为头指针 ,第一个数据结点称为首结点,首结点指针称为首指针(对于不带头结点的单链表,一般是通过首指针标识单链表)。最后一个结点称为尾结点,尾结点指针称为尾指针。

image-20220609155519310

1,初始化线性表运算算法

创建一个空的单链表,它只有一个头结点,由L指向它。该结点的next域为空,data 域未设定任何值。算法如下:

void InitList(SLinkNode *&L){ //L 为引用型参数
    L = (SLinkNode *) malloc(sizeof(SLinkNode));    //创建头结点L
    L->next=NULL;   //头结点next 置为空表示空单链表
}

2,销毁线性表运算算法

一个单链表 L 中的所有结点空间都是通过 malloc 函数分配的(即程序员自己手工分配的),在不再需要 L 时系统不会自动释放这些结点空间,程序员必须通过调用 free函数 释放所有结点的空间(即程序员自己手工分配的空间需要程序员自己手工释放)。算法如下:

void DestroyList(SLinkNode *&L){
    SLinkNode *pre=L,*p=pre->next;
    while(p!=NULL){
        free(pre);  //释放pre结点空间
        pre=p;
        p=p->next;  //pre、p同步后移
    }
    free(pre);  //释放pre 指向的尾结点空间
}

移动次数与单链表L 中数据结点的个数有关,故其时间复杂度为 O(n)。

image-20220609161134397

3,求线性表长度运算

不同于顺序表,在单链表中没有直接保存长度信息,需要通过扫描方式求长度。对应算法如下:

int GetLength(SLinkNode *L){
    int i=0;
    SLinkNode *p=L->next;   //p指向头结点,i置为0
    while(p!=NULL){
        i++;
        p=p->next;  //p移到下一个结点,i++
    }
    return i;   //p 为空是,i即是数据结点的个数
}

移动次数与单链表L 中数据结点的个数有关,故其时间复杂度为 O(n)。

4,求线性表中第i个元素运算

这个直接从头开始扫描遍历直到第i个就行了,下面直接看代码:

int GetElem(SLinkNode *L,int i,ElemType &e){
    int j=0;
    SLinkNode *p=L; //p指向头结点,计数器j置为0
    if(i<=0)
        return 0;
    while(p!=NULL&&j<i){
        j++;
        p=p->next;
    }
    if(p==NULL)
        return 0;
    else{
        e=p->data;
        return 1;   //找到后返回1
    }
}

在这里我们是否想到了在顺序表中进行该算法时,直接按序索取就行了,表明顺序表具有随机存取特性。由此可见,单链表不具有随机存取的特性,其时间复杂度为 O(n)。

5,按值查找运算算法

顾名思义,就是在单链表L中从首结点开始查找第一个值域与e相等的结点,若存在这样的结点,则返回其逻辑序号,否则返回0。对应算法如下:

int Locate(SLinkNode *L,ElemType e){
    SLinkNode *p=L->next;
    int j=1;    //p指向首结点,j置为其序号1
    while(p!=NULL&&p->data!=e){
        p=p->next;
        j++;
    }
    if(p==NULL)
        return 0;
    else
        return j;   //找到后返回其序号
}