程序猿必修课之数据结构(四)线性表2

原文出自:http://www.jianshu.com/p/94fc4be7d61e

你还在为开发中频繁切换环境打包而烦恼吗?快来试试 Environment Switcher 吧!使用它可以在app运行时一键切换环境,而且还支持其他贴心小功能,有了它妈妈再也不用担心频繁环境切换了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

上一章:程序猿必修课之数据结构(三)线性表1

上篇我们复习的线性表的顺序存储结构,它的最大缺点就是:插入和删除是需要移动大量元素,造成时间的浪费。

导致这个问题的原因是,相邻两个元素的存储位置也具有邻居关系,也就是说它们在内存中是挨着的,中间没有空隙,当然就无法快速插入,而删除后,当中就会留出空隙,自然需要弥补。链式存储就是为了解决这个问题而产生的。

线性表链式存储结构定义

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些元素可以存在内存未被占用的任意位置。

在顺序结构中,每个数据元素只需要存储数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。

为了表示每个元素 Ai 与其直接后继元素 A(i+1) 之间的逻辑关系,对数据元素 Ai 来说,除了存储其本身的信息外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息叫做指针或链。这两部分信息组成数据元素 Ai 的存储映像,称为结点(Node)。

n 个结点链接成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。

链表中第一个结点的存储位置叫做头指针。

为了方便对链表进行操作,会在单链表的第一个结点前附加一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针。

头指针与头结点的异同

头指针:

  • 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
  • 头指针具有标识作用,所以常用头指针冠以链表的名字。
  • 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。

头结点:

  • 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可存放链表的长度)。
  • 有了头结点,对在第一个元素结点前插入结点或删除第一个结点,其操作与其它结点的操作就统一了。
  • 头结点不一定是链表必须元素。

线性表链式存储结构代码描述

typedef struct Node {
    ElemType data;
    struct Node *next;
} Node, *LinkList;

单链表的读取

在线性表的顺序储存结构中,我们要计算任意一个元素的存储位置是很容易的。但是在单链表中,由于第 i 个元素到底在哪,一开始不知道,必须从头开始找。

获取单链表第 i 个数据的算法步骤

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点, j 累加 1;
  3. 若到链表末尾 p 为空,则说明第 i 个元素不存在;
  4. 否则查找成功,返回结点 p 的数据。

代码如下:

ElemType GetElem (LinkList L, int i) {
    int j;
    LinkList p;
    p = L->next;
    j = 1;
    while (NULL != p && j < i) {
        p = p->next;
        ++j;
    }
    if (!p || j > i)
        return Error;
    return p->data;
}

由于这个算法的时间复杂度取决于 i 的位置,当 i = 1 时,则不需遍历,而当 i = n 时则遍历 n - 1次才可以,因此最坏情况的复杂度为O(n)。

单链表的插入

单链表第 i 个位置插入结点的算法步骤:

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点, j 累加 1;
  3. 若到链表末尾 p 为空,则说明第 i 个位置不存在;
  4. 否则查找成功,创建一个空结点 s;
  5. 将数据元素赋值给 s->data;
  6. 单链表的插入标准语句 s->next = p->next; p->next = s;
  7. 返回成功。

代码如下:

bool ListInsert (LinkList *L, int i, ElemType e) {
    int j;
    LinkList p, s;
    p = *L;
    j = 1;
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    if (!p || j > i)
        return false;
    s = (LinkList)malloc(sizeof(Node));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

单链表删除第 i 个结点的算法步骤:

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点, j 累加 1;
  3. 若到链表末尾 p 为空,则说明第 i 个结点不存在;
  4. 否则查找成功,将要删除的结点 p->next 赋值给 q;
  5. 单链表的删除标准语句 p->next = q->next;
  6. 将 q 结点的数据赋值给 e,作为返回;
  7. 释放 q 结点;
  8. 返回成功。

代码如下:

bool ListDelete (LinkList *L, int i, ElemType *e) {
    LinkedList p, q;
    int j;
    p = *L;
    j = 1;
    while (p->next && j < i) {
        p = p->next;
        j++;
    }
    if (!(p->next) || j > i)
        return false;
    q = p->next;
    p->next = q->next;
    *e = q->data;
    free(q);
    return true;
}

对于插入或删除数据越频繁的操作,单链表的效率比顺序存储结构要高。

单链表的创建

我们已经知道,顺序存储结构的创建,其实就是一个数组的初始化,即声明一个类型和大小的数组并赋值的过程。而单链表和顺序存储结构不一样,它不像顺序存储结构这么集中,可以很散,是一种动态结构。对于每个链表来说,它所占用空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。

所以创建单链表的过程就是一个动态生成链表的过程,即从“空表”的初始状态,依次建立各元素结点,并逐个插入链表。

创建单链表整表的步骤(头插法):

  1. 声明一个结点 p 和计数器变量 i;
  2. 初始化一个空链表 L;
  3. 让 L 的头结点的指针指向 NULL,即建立一个带头结点的单链表;
  4. 循环:
    1. 生成一个新结点赋值给 p;
    2. 将值赋值给 p的数据域 p->data;
    3. 将 p 插入到头结构与前一新结点之间。

代码如下:

void createList(LinkList *L, int n) {
    LinkList p;
    int i;
    /* 初始化随机数种子 */
    srand (time(0));
    *L = (LinkList)malloc(sizeof(Node));
    /* 创建一个带头结点的单链表 */
    (*L)->next = NULL;
    for (i = 0; i < n; i++) {
        p = (LinkList)malloc(sizeof(Node));
        /* 随机生成100以内的数字 */
        p->data = rand() % 100 + 1;
        p->next = (*L)->next;
        (*L)->next = p;
    }
}

尾插法:

void createList(LinkList *L, int n) {
    LinkList p, r;
    int i;
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    /* 指向尾结点的结点 */
    r = *L;
    for (i = 0; i < n; i++) {
        p = (Node *)malloc(sizeof(Node));
        p->data = rand() % 100 + 1;
        /* 将表尾结点的指针指向新结点 */
        r->next = p;
        /* 将当前的新结点定义为尾结点 */
        r = p;
    }
    r->next = NULL;
}

单链表的整表删除

单链表整表删除的步骤:

  1. 声明一个结点 p 和 q;
  2. 将第一个结点赋值给 p;
  3. 循环:
    1. 将下一个结点赋值给 q;
    2. 释放 p;
    3. 将 q 赋值给 p。

代码如下:

bool clearList(LinkList *L) {
    LinkList p, q;
    /* p指向第一个结点 */
    p = (*L)->next;
    while(p) {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;
    return true;
}

单链表结构与顺序存储结构优缺点

比较内容 顺序存储 单链表
存储分配方式 用一段连续的存储单元依次存储线性表的数据元素 采用链式存储结构,用一组任意的存储单元存放线性表的元素
查找的时间性能 O(1) O(n)
插入删除的时间性能 O(n) O(1)
空间性能 需要预先分配存储空间,分大了浪费,分小了易发生上溢 不需要分配存储空间,只要有就可以分配,元素个数不受限制

总结

  • 若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。
  • 当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题。

总之,线性表的顺序存储结构和单链表结构各有优缺点,不能简单的说哪个好,哪个不好,需要根据实际情况综合平衡采用哪种数据结构更能满足和达到需求。

下一章:程序猿必修课之数据结构(五)线性表3

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

推荐阅读更多精彩内容

  • 本文内容取自于小甲鱼的数据结构与算法。http://www.jianshu.com/p/230e6fde9c75 ...
    阿阿阿阿毛阅读 2,773评论 0 7
  • 1.线性表的定义 线性表:零个或多个数据元素的有限序列序列:也就是说元素之间是有顺序的,若元素存在多个,则第一个元...
    e40c669177be阅读 1,965评论 6 15
  • 在上一篇文章中我们简单说了数据结构的概念和数据结构与算法的一些关系,这一篇文章的内容是关于线性表的东西,主要有线性...
    硅谷小虾米阅读 1,221评论 1 3
  • 大学的时候不好好学习,老师在讲台上讲课,自己在以为老师看不到的座位看小说,现在用到了老师讲的知识,只能自己看书查资...
    和珏猫阅读 1,387评论 1 3
  • 前言 什么是线性表?线性表的两大存储结构是什么?各种存储结构是如何实现存取、插入删除等操作的?本篇主要解答了这几个...
    JonyFang阅读 1,508评论 4 17