线性表(二)——单链表的表示和实现

在上篇文章中我们分析讨论了线性表的顺序存储结构顺序表的原理和实现,这篇文章将分析讨论线性表另外一种存储结构链式存储结构中的单链表的实现原理。

线性表-概述
线性表(一)——顺序表
线性表(二)——单链表的表示和实现
线性表(三)——双向链表的表示和实现

1、线性表的链式存储结构

链式存储结构存储线性表数据元素的方法是把存储有数据元素的结点用指针域构成链。指针是指向物理存储单元地址的变量,我们把一个有数据元素和一个或若干个指针域组成的结构体称为一个结点。其中数据域用来存放数据元素,指针域用来构造数据元素之间的关联关系。链式存储结构的特点就是数据元素之间的逻辑关系表现在结点的链接关系上。链式存储结构的线性表称为链表。根据指针域的不同和结点构造链的方法不同,链表主要分为单链表、单循环链表和双向循环链表三种

2、单链表

单链表中构成链表的结点只有一个指向直接后继结点的指针域

2.1 单链表的表示方法

根据上面的论述可以知道单链表中每一个结点的结构如下图所示:

可以定义单链表结点的结构如下:

typedef struct Node{
    DataType   data;//存放数据
    struct Node *next; //存放指向下一个元素结点的指针
} SLNode;

其中data用来存放数据元素,next用来存放指向下一个结点的指针。
单链表有带头结点和不带头结点结构两种。我们把指向单链表的指针称作头指针头指针所指的不存放数据元素的第一个结点称作头结点。通常情况下单链表构造成带头结点的单链表,这里对不带头结点的单链表不做讨论。
如下图所示是一个带头结点的单链表结构图。


head表示头指针,头结点的数据域部分通常图上阴影,以表示该结点为头结点。符号“^”表示空指针。空指针用来标识链表的结束。空指针在算法描述中用NULL表示。在顺序存储结构中,采用的是数组的方式来存储数据元素的,用户向系统申请的是一块地址连续的内存空间,所以在任意两个逻辑上相邻的数据元素在物理上也是相邻的。而链式存储结构中,初始化时为空链,每当有新的数据元素需要存储时,用户才向系统动态申请所需要的存储空间插入链中,而这些在不同时间申请的内存空间很可能是地址不连续的,所以在链式存储结构中任意两个逻辑上相邻的数据元素在物理上不一定相邻,数据元素的逻辑次序是通过链中的指针链接来实现的。

2.1 单链表的操作实现

1、单链表初始化 ListInitiate(SLNode **head)

单链表中的每一个结点是在需要的时候才向系统申请的,这称作为动态内存空间申请。动态申请的内存空间,当不再需要时,必须要由申请者自己释放。

void ListInitiate(SLNode **head){
    *head = (SLNode *)malloc(sizeof(SLNode));//申请头结点
    (*head)->next = NULL;
}

在初始化操作前,头指针参数head没有具体的地址值,在初始化操作时,头指针参数head才得到了具体的地址值,而这个地址值要返回给调用函数,所以此时头指针参数head要设计成指针的指针类型。

2、获取当前元素个数 ListLength(SLNode *head)
int  ListLength(SLNode *head){
    SLNode *p = head;
    int size = 0;
    while (p->next != NULL){//当结点的下一个指针区域数据为NULL时,表示表结构的结束
        p = p->next;
        size++;
    }
    return size;
}
  1. 在循环前指针变量p指向头结点,计数变量size赋值0。
  2. 循环结束的条件为p->next != NULL,在循环中,每次让指针p指向它的值指向后继结点,让计数变量size加1。
  3. 函数返回计数变量size。
3、插入数据元素 ListInsert(SLNode *head,int i,DataType x)

在带头结点的单链表的第i(0\leq i\leq size)个位置前插入数据元素x的结点,插入成功返回1,插入失败返回0。

int ListInsert(SLNode *head,int i,DataType x){
    SLNode *p = head;
    int j = -1;
    /*找到插入位置的前一个结点位置i-1*/
    while (p->next != NULL && j < i-1){
        p = p->next;
        j++;
    }
    if(j != i-1){
        printf("插入的位置参数错误");
        return 0;
    }
    SLNode *q = (SLNode *)malloc(sizeof(SLNode));//生成一个新的结点
    q->data = x;//将x的值赋值给新的结点

    q->next = p->next;//新的结点的指针指向I结点
    p->next = q;//修改p的指针域指向新的结点
    return 1;
}
  1. 首先在单链表中寻找到第i-1个结点并由指针p指示。
  2. 动态申请一个结点存储空间并由指针q指示,并把数据元素x赋值给新结点的数据元素域q->data = x;
  3. 修改新结点的指针域指向a_i结点, q->next = p->next;
  4. 修改a_{i-1}结点的指针域指向新结点q,p->next = q;

插入的操作如下图所示:


4、删除数据元素 ListDelete(SLNode *head,int i,DataType *x)

删除带头结点的单链表的第i(0\leq i\leq size)个结点,被删除的结点的数据域值由x带回,删除成功返回1,删除失败返回0。

int ListDelete(SLNode *head,int i,DataType *x){
    SLNode *p = head;
    int j = -1;
    while (p->next != NULL && p->next->next != NULL && j < i-1){
        p = p->next;
        j++;
    }
    if(j != i -1){
        printf("插入的位置参数错误");
        return 0;
    }
    SLNode *s = p->next;
    *x = s->data;
    p->next = p->next->next;
    free(s);
    return 1;
}
  1. 在单链表中找到第i-1个结点并由指针p指示。
  2. 让指针s指向a_i结点:SLNode s = p->next,把结点a_i的数据元素值赋值给x:x = s->data。
  3. a_i结点脱链:p->next = p->next->next 并且释放a_i结点的存储空间:free(s)。

删除数据元素的操作如下图所示:


5、取数据元素 ListGet(SLNode *head,int i,DataType *x)

获取带头结点单链表的第i(0\leq i\leq size)个位置结点的数据域的值,由x带回,获取成功返回1,获取失败返回0。

int ListGet(SLNode *head,int i,DataType *x){
    SLNode *p = head;
    int j = -1;
    while (p->next != NULL && j < i){
        p = p->next;
        j++;
    }

    if(j != i){
        printf("取元素的位置参数错误");
        return 0;
    }

    *x = p->data;
    return  1;
}
6、销毁单链表 ListDestory(SLNode **head)

因为单链表的结点空间是程序动态申请的,而系统只负责自动回收程序中静态分配的内存空间,所以和顺序表相比,单链表需要增加一个销毁单链表的操作,用来在调用程序退出前释放动态申请的内存空间。

void ListDestory(SLNode **head){
    SLNode *p ,*p1;
    p = *head;
    while (p->next != NULL){
        p1 = p;
        p = p->next;
        free(p1);
    }
    *head = NULL;
}

3、单链表操作的时间复杂度

单链表的插入和删除操作的时间复杂度分析方法和顺序表的插入和删除操作的时间复杂度分析方法类同。差别是单链表的插入和删除操作不需要移动数据元素,只需要比较数据元素。因此当在单链表的任何位置上插入数据元素的概率相等时,在单链表中插入一个数据元素,比较数据元素的平均次数为:
E_{is} = \sum\limits_{i=0}^n P_{i}(n-i) = \frac{1}{n+1} \sum\limits_{i=0}^n (n-i) = \frac{n}{2}
删除单链表中的一个数据元素,比较数据元素的平均次数为:
E_{dl} = \sum\limits_{i=0}^{n-1} q_{i}(n-i) = \frac{1}{n} \sum\limits_{i=0}^{n-1} (n-i) = \frac{n-1}{2}
由上面的分析可知:

1. 单链表中插入和删除一个数据元素的平均时间复杂度为O(n)。
2. 求单链表数据元素个数操作和销毁单链表操作的时间复杂度为O(n)。
3. 单链表中取数据元素操作和数据元素个数n无关,其时间复杂度为O(1)。
4. 和顺序表相比,单链表的主要优点在于不需要预先确定数据元素的最大个数;主要缺点是每个结点中要有一个指针域,因此空间单元利用效率不高,而且单链表操作的算法比较复杂。

4、循环单链表

循环单链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针域不再是结束标记,而是指向整个链表的第一个结点,从而使链表形成一个环。和单链表一样循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作较为方便。实际应用中带头结点的循环单链表更为常见。
如下图所示是带头结点的循环单链表结构。


单链表的特点是从链表头到链表尾比较方便,但无法从链表尾到链表头,而循环单链表的长处是从链表尾到链表头比较方便。当要处理的数据元素序列具有环形结构特点时,适于采用循环单链表。
带头结点的循环单链表的操作实现和带头结点的单链表的操作实现方法类同,差别有如下两点:

  1. 在初始化函数中把语句(head)->next = NULL修改为(head)->next = *head。
  2. 在其他函数中循环判断条件p->next != NULL和p->next->next != NULL中的NULL改为头指针head。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容