数据结构学习之路-第二章:线性单链表

【 声明:版权所有,转载请标明出处,请勿用于商业用途。  联系信箱:libin493073668@sina.com】


前言:

前面我们已经学习了顺序表的构建,我们知道顺序表就好像一个数组一样,在取出某个位置的时候,时间复杂度是O(1),但是在插入与删除的时候,却每次都需要对其他位置的数进行移位,这样就显得效率比较低下了,那么我们怎么在插入与删除的过程中来优化这个过程呢?这个时候我们就需要用到链式存储结构了。

顺序结构要求逻辑上相邻的两个元素在存储的物理上也是相邻的,但是链式存储结构则不然,它在存储上是自由的,因此在插入与删除的操作上它会比顺序结构更加方便,但是相对的,它也失去了顺序表随机存取的优势。

好了,现在就让我们进入链式存储结构的学习吧。


注:

本文仅代表博主本人的一些浅显的见解,欢迎大家评论学习,共同创造出一个良好的环境

对于一些问题,博主会尽量为大家解答,但是如果有疑问没有及时回答的,也希望其他热心人心帮忙解决,鄙人不胜感激。


线性单链表


1.存储方法

链接方式存储的线性表简称为链表(Linked List)。
链表的具体存储表示为:
① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)
② 链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))
注意:
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构
2.结点结构


对于单链表的一个结点,我们可以看到它有一个data域和一个next域。

data域--存放结点值的数据域

next域--存放结点的直接后继的地址(位置)的指针域(链域)

注意:
①链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。
②每个结点只有一个链域的链表称为单链表(Single Linked List)。

typedef struct LNode
{
    ElemType data;
    struct LNode* next;
}LNode,*LinkList;//LNode定义了链表中的单个节点,LinkList定义了整个链表


3.结点获取

在顺序表中,我们知道其逻辑上相邻的两个元素之间,在物理上的存储也是相邻的,因此每个元素的存储位置都可以通过起始位置计算得到。

但是在单链表中,任何两个元素的存储位置之间并没有固定的联系,那么我们怎么取得单链表中的元素呢?通过其结点结构我们可以知道,每个元素的存储位置都包含在其直接前驱结点的信息中。

假设a[i] = p->data

那么我们要取得a[i+1],就需要使用结点结构中的next域,p->next代表的就是下一个节点的信息

因此我们可以得到a[i+1] = p->next->data

所以,每次我们若要寻找单链表中的第i个结点,我们都必须从头结点出发。

下面我们来看如何实现

Status GetElem(LinkList L,int i,ElemType *e)
{
    //初始条件:L为带头结点的单链表的头指针。
    //操作结果:当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
    int j;                   //j为计数器
    LinkList p = L->next;    //p指向第一个结点
    while(p && j<i)          //顺指针向后查找,直到p指向第i个元素或p为空
    {
        p = p->next;
        j++;
    }
    if(!p || j>i) return ERROR;   //第i个元素不存在
    (*e) = p->data;               //得到第i个元素
    return OK;
}

4.插入与删除

开篇我们就说了,因为顺序表的插入与删除太过于繁琐,所以我们才来学习链表。

那么对于一个单链表,又是如何进行插入与删除的呢?


上图是一个节点插入的过程

现在要将x结点插入a,b结点之间,究竟该如何实现呢?

首先我们插入的过程,必须获得指向a的指针p,指向x的指针s

那么通过之前的学习,我们知道通过a来寻找b的方法,就是p->next,这就是指向b的指针

首先我们第一步要做的,是让s的next域指向b,也就是s->next = p->next,这个操作就连接了x与b两个结点

然后再把a的next域指向x,通过p->next = s,这样就链接了a与x结点

因此把s插入a,b结点的操作就是s->next = p->next;p->next = s;

那么现在问题来了,这两个操作能不能互换呢?

答案是不行的,

因为如果首先进行p->next = s,那么a的next域就改变了,p->next代表的就是x结点了,那么再进行s->next = p->next还能使得x的next指向b结点吗?答案是不行的,因为我们现在并没有一个指向b的指针,也就是说我们无法再寻得b的地址了

由此可见,对于一个单链表中,结点插入的顺序是很重要的

Status ListInsert(LinkList L,int i,ElemType e)
{
    //操作结果:在带头结点的单链线性表L中第i个位置之前插入元素e
    LinkList p = L,s;
    int j = 0;
    while(p&&j<i-1)        //寻找第i-1个结点
    {
        p = p->next;
        j++;
    }
    if(!p||j>i-1) return ERROR;                   //i小于1或者大于表长
    s = (LinkList)malloc(sizeof(struct LNode));  //生成新结点
    s->data = e;                                  //插入L中
    s->next = p->next;
    p->next = s;
    return OK;
}


接下来是删除结点,还是先看一张图


现在我们要做的是删除a,c结点之间的b结点

通过前面的插入,我们不难知道其方法就是直接让a的next指向c

那么具体实现就是p->next = p->next->next

这就是一个节点删除的过程

Status ListDelete(LinkList L,int i,ElemType *e)
{
    //操作结果:在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
    LinkList p;
    int j = 0;
    while(p->next&&j<i-1)      //寻找第i个结点,并令p指向其前趋
    {
        p=p->next;
        j++;
    }
    if(!(p->next)||j<i-1) return ERROR;  //删除位置不合理
    q = p->next;                         //删除并释放结点
    p->next = q->next;
    e = q->data;
    free(q);
    return OK;
}

5.其他操作

除了获取,插入,删除外,我们还要添加一些操作来完善整个单链表

通过顺序表的建立与学习,要写出下面这些操作也不是太难,理解了上面对于单链表结点的三个操作,下面的这些都只是将线性表的操作稍作修改便可

Status InitList(LinkList *L)
{
    //操作结果:构造一个空的线性表L
    *L = (LinkList)malloc(sizeof(struct LNode));   //产生头结点,并使L指向此头结点
    if(!L) exit(OVERFLOW);    //存储分配失败
    (*L)->next = NULL;        //指针域为空
    return OK;
}

Status DestroyList(LinkList *L)
{
    //初始条件:线性表L已存在。
    //操作结果:销毁线性表L
    LinkList q;
    while(*L)
    {
        q = (*L)->next;
        free(*L);
        (*L) = q;
    }
    return OK;
}

Status ClearList(LinkList L) //不改变L
{
    //初始条件:线性表L已存在。
    //操作结果:将L重置为空表
    LinkList p,q;
    p = L->next;       //p指向第一个结点
    while(p)           //没到表尾
    {
        q = p->next;
        free(p);
        p = q;
    }
    L->next = NULL;     //头结点指针域为空
    return OK;
}

Status ListEmpty(LinkList L)
{
    //初始条件:线性表L已存在。
    //操作结果:若L为空表,则返回TRUE,否则返回FALSE
    return (L->next==NULL);
}

int ListLength(LinkList L)
{
    //初始条件:线性表L已存在。
    //操作结果:返回L中数据元素个数
    int i = 0;
    LinkList p = L->next;
    while(p)
    {
        i++;
        p = p->next;
    }
    return i;
}

int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{
    // 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
    // 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回值为0
    int i = 0;
    LinkList p = L->next;
    while(p)
    {
        i++;
        if(compare(p->data,e))  //找到这样的数据元素
            return i;
        p->next;
    }
    return 0;
}

Status PriorElem(LinkList L,ElemType cur_e,ElemType *pre_e)
{
    //初始条件: 线性表L已存在
    //操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,返回OK;否则操作失败,pre_e无定义,返回INFEASIBLE
    LinkList p=L->next;
    while(p->next)
    {
        if(cur_e = p->next->data)
        {
            *pre_e =  p->data;
            return OK;
        }
    }
    return INFEASIBLE;
}

Status NextElem(LinkList L,ElemType cur_e,ElemType *next_e)
{
    // 初始条件:线性表L已存在
    // 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,返回OK;否则操作失败,next_e无定义,返回INFEASIBLE
    LinkList p = L->next;
    while(p->next)
    {
        if(p->data == cur_e)
        {
            *next_e = p->next->data;
            return OK;
        }
    }
    return INFEASIBLE;
}

Status ListTraverse(LinkList L,void(*vi)(ElemType))
{
    //初始条件:线性表L已存在
    //操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
    LinkList p = L->next;
    while(p)
    {
        (*vi)(p->data);
        p = p->next;
    }
    return OK;
}


6.有序链表归并

对于单链表,书本最后还介绍了两个知识,一个是逆位序输入n个元素的值来建立单链表

还有一个就是归并两个有序单链表

对于有序链表的归并,其原理与顺序表的归并也是一回事,只要我们能运用好对链表的相关操作,那么将顺序表的归并转化为单链表的归并也就不是什么难事了

void CreateList(LinkList *L,int n)
{
    //操作结果:逆位序(插在表头)输入n个元素的值,建立带表头结构的单链线性表L
    int i;
    LinkList p;
    *L = (LinkList)malloc(sizeof(struct LNode));
    (*L)->next = NULL;              //先建立一个带头结点的单链表
    printf("请输入%d个数据\n",n);
    for(i = n; i>0; i--)
    {
        p = (LinkList)malloc(sizeof(struct LNode));    //生成新结点
        scanf("%d",&p->data);                          //输入元素值
        p->next = (*L)->next;                          //插入到表头
        (*L)->next = p;
    }
}
void CreateList2(LinkList *L,int n)
{
    //正位序(插在表尾)输入n个元素的值,建立带表头结构的单链线性表
    int i;
    LinkList p,q;
    *L = (LinkList)malloc(sizeof(struct LNode));
    (*L)->next = NULL;
    q = *L;
    printf("请输入%d个数据\n",n);
    for(i = 1; i<=n; i++)
    {
        p = (LinkList)malloc(sizeof(struct LNode));   //生成头结点
        scanf("%d",&p->data);
        q->next = p;
        q = q->next;
    }
    p->next = NULL;
}

void MergeList(LinkList La,LinkList *Lb,LinkList *Lc)
{
    //初始条件:已知单链线性表La和Lb的元素按值非递减排列。
    //操作结果:归并La和Lb得到新的单链线性表Lc,Lc的元素也按值非递减排列
    LinkList pa = La->next,pb = (*Lb)->next,pc;
    *Lc = pc = La;       //用La的头结点作为Lc的头结点
    while(pa&&pb)
    {
        if(pa->data <= pb->data)
        {
            pc->next = pa;
            pc = pa;
            pa = pa->next;
        }
        else
        {
            pc->next = pb;
            pc = pb;
            pb = pb->next;
        }
    }
    pc->next = pa?pa:pb;   //插入剩余段
    free(Lb);              //释放Lb的头结点
    Lb = NULL;
}



7.具体测试

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
#include <ctype.h>
#include <malloc.h>
#include <limits.h>
#include <stdlib.h>
#include <io.h>
#include <process.h>
using namespace std;

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1

typedef int ElemType;
typedef int Status;

typedef struct LNode
{
    ElemType data;
    struct LNode* next;
} LNode,*LinkList; //LNode定义了链表中的单个节点,LinkList定义了整个链表

Status InitList(LinkList *L)
{
    //操作结果:构造一个空的线性表L
    *L = (LinkList)malloc(sizeof(struct LNode));   //产生头结点,并使L指向此头结点
    if(!(*L)) exit(OVERFLOW);    //存储分配失败
    (*L)->next = NULL;        //指针域为空
    return OK;
}

Status DestroyList(LinkList *L)
{
    //初始条件:线性表L已存在。
    //操作结果:销毁线性表L
    LinkList q;
    while(*L)
    {
        q = (*L)->next;
        free(*L);
        (*L) = q;
    }
    return OK;
}

Status ClearList(LinkList L) //不改变L
{
    //初始条件:线性表L已存在。
    //操作结果:将L重置为空表
    LinkList p,q;
    p = L->next;       //p指向第一个结点
    while(p)           //没到表尾
    {
        q = p->next;
        free(p);
        p = q;
    }
    L->next = NULL;     //头结点指针域为空
    return OK;
}

Status ListEmpty(LinkList L)
{
    //初始条件:线性表L已存在。
    //操作结果:若L为空表,则返回TRUE,否则返回FALSE
    return (L->next==NULL);
}

int ListLength(LinkList L)
{
    //初始条件:线性表L已存在。
    //操作结果:返回L中数据元素个数
    int i = 0;
    LinkList p = L->next;
    while(p)
    {
        i++;
        p = p->next;
    }
    return i;
}

int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{
    // 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
    // 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回值为0
    int i = 0;
    LinkList p = L->next;
    while(p)
    {
        i++;
        if(compare(p->data,e))  //找到这样的数据元素
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(LinkList L,ElemType cur_e,ElemType *pre_e)
{
    //初始条件: 线性表L已存在
    //操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,返回OK;否则操作失败,pre_e无定义,返回INFEASIBLE
    LinkList p=L->next;
    while(p->next)
    {
        if(cur_e == p->next->data)
        {
            *pre_e =  p->data;
            return OK;
        }
        p = p->next;
    }
    return INFEASIBLE;
}

Status NextElem(LinkList L,ElemType cur_e,ElemType *next_e)
{
    // 初始条件:线性表L已存在
    // 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,返回OK;否则操作失败,next_e无定义,返回INFEASIBLE
    LinkList p = L->next;
    while(p->next)
    {
        if(p->data == cur_e)
        {
            *next_e = p->next->data;
            return OK;
        }
        p = p->next;
    }
    return INFEASIBLE;
}

Status ListTraverse(LinkList L,void(*vi)(ElemType))
{
    //初始条件:线性表L已存在
    //操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
    LinkList p = L->next;
    while(p)
    {
        (*vi)(p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}

Status GetElem(LinkList L,int i,ElemType *e)
{
    //初始条件:L为带头结点的单链表的头指针。
    //操作结果:当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
    int j=1;                   //j为计数器
    LinkList p = L->next;    //p指向第一个结点
    while(p && j<i)          //顺指针向后查找,直到p指向第i个元素或p为空
    {
        p = p->next;
        j++;
    }
    if(!p || j>i) return ERROR;   //第i个元素不存在
    (*e) = p->data;               //得到第i个元素
    return OK;
}

Status ListInsert(LinkList L,int i,ElemType e)
{
    //操作结果:在带头结点的单链线性表L中第i个位置之前插入元素e
    LinkList p = L,s;
    int j = 0;
    while(p&&j<i-1)        //寻找第i-1个结点
    {
        p = p->next;
        j++;
    }
    if(!p||j>i-1) return ERROR;                   //i小于1或者大于表长
    s = (LinkList)malloc(sizeof(struct LNode));  //生成新结点
    s->data = e;                                  //插入L中
    s->next = p->next;
    p->next = s;
    return OK;
}

Status ListDelete(LinkList L,int i,ElemType *e)
{
    //操作结果:在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
    LinkList p=L,q;
    int j = 0;
    while(p->next&&j<i-1)      //寻找第i个结点,并令p指向其前趋
    {
        p=p->next;
        j++;
    }
    if(!(p->next)||j<i-1) return ERROR;  //删除位置不合理
    q = p->next;                         //删除并释放结点
    p->next = q->next;
    *e = q->data;
    free(q);
    return OK;
}

void CreateList(LinkList *L,int n)
{
    //操作结果:逆位序(插在表头)输入n个元素的值,建立带表头结构的单链线性表L
    int i;
    LinkList p;
    *L = (LinkList)malloc(sizeof(struct LNode));
    (*L)->next = NULL;              //先建立一个带头结点的单链表
    printf("请输入%d个数据\n",n);
    for(i = n; i>0; i--)
    {
        p = (LinkList)malloc(sizeof(struct LNode));    //生成新结点
        scanf("%d",&p->data);                          //输入元素值
        p->next = (*L)->next;                          //插入到表头
        (*L)->next = p;
    }
}
void CreateList2(LinkList *L,int n)
{
    //正位序(插在表尾)输入n个元素的值,建立带表头结构的单链线性表
    int i;
    LinkList p,q;
    *L = (LinkList)malloc(sizeof(struct LNode));
    (*L)->next = NULL;
    q = *L;
    printf("请输入%d个数据\n",n);
    for(i = 1; i<=n; i++)
    {
        p = (LinkList)malloc(sizeof(struct LNode));   //生成头结点
        scanf("%d",&p->data);
        q->next = p;
        q = q->next;
    }
    p->next = NULL;
}

void MergeList(LinkList La,LinkList *Lb,LinkList *Lc)
{
    //初始条件:已知单链线性表La和Lb的元素按值非递减排列。
    //操作结果:归并La和Lb得到新的单链线性表Lc,Lc的元素也按值非递减排列
    LinkList pa = La->next,pb = (*Lb)->next,pc;
    *Lc = pc = La;       //用La的头结点作为Lc的头结点
    while(pa&&pb)
    {
        if(pa->data <= pb->data)
        {
            pc->next = pa;
            pc = pa;
            pa = pa->next;
        }
        else
        {
            pc->next = pb;
            pc = pb;
            pb = pb->next;
        }
    }
    pc->next = pa?pa:pb;   //插入剩余段
    free(Lb);              //释放Lb的头结点
    Lb = NULL;
}

Status comp(ElemType c1,ElemType c2)
{
    //操作结果:数据元素判定函数
    return c1==c2;
}

void visit(ElemType c)
{
    printf("%d ",c);
}

int main()
{
    LinkList L;
    ElemType e,e0;
    Status i;
    int j,k;
    i=InitList(&L);
    for(j=1; j<=5; j++)
        i=ListInsert(L,1,j);
    printf("在L的表头依次插入1~5后:L=");
    ListTraverse(L,visit); //依次对元素调用visit(),输出元素的值
    i=ListEmpty(L);
    printf("L是否空:i=%d(1:是 0:否)\n",i);
    i=ClearList(L);
    printf("清空L后:L=");
    ListTraverse(L,visit);
    i=ListEmpty(L);
    printf("L是否空:i=%d(1:是 0:否)\n",i);
    for(j=1; j<=10; j++)
        ListInsert(L,j,j);
    printf("在L的表尾依次插入1~10后:L=");
    ListTraverse(L,visit);
    GetElem(L,5,&e);
    printf("第5个元素的值为:%d\n",e);
    for(j=0; j<=1; j++)
    {
        k=LocateElem(L,j,comp);
        if(k)
            printf("第%d个元素的值为%d\n",k,j);
        else
            printf("没有值为%d的元素\n",j);
    }
    for(j=1; j<=2; j++) //测试头两个数据
    {
        GetElem(L,j,&e0); //把第j个数据赋给e0
        i=PriorElem(L,e0,&e); //求e0的前驱
        if(i==INFEASIBLE)
            printf("元素%d无前驱\n",e0);
        else
            printf("元素%d的前驱为:%d\n",e0,e);
    }
    for(j=ListLength(L)-1; j<=ListLength(L); j++) //最后两个数据
    {
        GetElem(L,j,&e0); //把第j个数据赋给e0
        i=NextElem(L,e0,&e); //求e0的后继
        if(i==INFEASIBLE)
            printf("元素%d无后继\n",e0);
        else
            printf("元素%d的后继为:%d\n",e0,e);
    }
    k=ListLength(L); //k为表长
    for(j=k+1; j>=k; j--)
    {
        i=ListDelete(L,j,&e); //删除第j个数据
        if(i==ERROR)
            printf("删除第%d个数据失败\n",j);
        else
            printf("删除的元素为:%d\n",e);
    }
    printf("依次输出L的元素:");
    ListTraverse(L,visit);
    DestroyList(&L);
    printf("销毁L后:L=%u\n",L);
    puts("")
    //归并
    printf("===双表归并测试===")
    int n=5;
    LinkList La,Lb,Lc;
    printf("按非递减顺序, ");
    CreateList2(&La,n); /* 正位序输入n个元素的值 */
    printf("La="); /* 输出链表La的内容 */
    ListTraverse(La,visit);
    printf("按非递增顺序, ");
    CreateList(&Lb,n); /* 逆位序输入n个元素的值 */
    printf("Lb="); /* 输出链表Lb的内容 */
    ListTraverse(Lb,visit);
    MergeList(La,&Lb,&Lc); /* 按非递减顺序归并La和Lb,得到新表Lc */
    printf("Lc="); /* 输出链表Lc的内容 */
    ListTraverse(Lc,visit);

    return 0;
}


总结:

书本上关于线性表的单链表存储结构与操作差不多就着一些了,不知道对大家是否有用呢?

在这些已有的基础上,有兴趣的同学还可以尝试用所学的这些知识自己来为单链表添加一些有趣的操作,这也是一个学以致用的过程。

单链表就这样讲完了吗?

还没有的,下次我们再继续把剩下的静态单链表讲完,那么静态单链表与单链表又有什么不同呢?我们下回分解。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值