数据结构开发(5):线性表的链式存储结构

0.目录

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

2.单链表的具体实现

3.顺序表和单链表的对比分析

4.小结

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

顺序存储结构线性表的最大问题是:

  • 插入和删除需要移动大量的元素!如何解决?

链式存储的定义:

  • 为了表示每个数据元素与其直接后继元素之间的逻辑关系;数据元素除了存储本身的信息外,还需要存储其直接后继的信息。

链式存储逻辑结构:

  • 基于链式存储结构的线性表中,每个结点都包含数据域和指针域

    1. 数据域:存储数据元素本身
    2. 指针域:存储相邻结点的地址

专业术语的统一:

  • 顺序表

    1. 基于顺序存储结构的线性表
  • 链表
    1. 基于链式存储机构的线性表

      1. 单链表:每个结点只包含直接后继的地址信息
      2. 循环链表:单链表中的最后一个结点的直接后继为第一个结点
      3. 双向链表:单链表中的结点包含直接前驱和后继的地址信息

链表中的基本概念:

  • 头结点

    1. 链表中的辅助结点,包含指向第一个数据元素的指针
  • 数据结点
    1. 链表中代表数据元素的结点,表现形式为:( 数据元素,地址 )
  • 尾结点
    1. 链表中的最后一个数据结点,包含的地址信息为空

单链表中的结点定义:

单链表中的内部结构:

头结点在单链表中的意义是:辅助数据元素的定位,方便插入和删除操作;因此,头结点不存储实际的数据元素。

在目标位置处插入数据元素:

  1. 从头结点开始,通过current指针定位到目标位置
  2. 从堆空间申请新的Node结点
  3. 执行操作:
    1. node->value = e;
    2. node->next = current->next;
    3. current->next = node;

在目标位置处删除数据元素:

  1. 从头结点开始,通过current指针定位到目标位置
  2. 使用toDel指针指向需要删除的结点
  3. 执行操作:
    1. toDel = current->next;
    2. current->next = toDel->next;
    3. delete toDel;

2.单链表的具体实现

本节目标:

  • 完成链式存储结构线性表的实现

LinkList 设计要点:

  1. 类模板,通过头结点访问后继结点
  2. 定义内部结点类型Node,用于描述数据域和指针域
  3. 实现线性表的关键操作( 增,删,查,等 )

LinkList的定义:

链表的实现 LinkList.h:

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"

namespace StLib
{

template <typename T>
class LinkList : public List<T>
{
protected:
    struct Node : public Object
    {
        T value;
        Node* next;
    };

    mutable Node m_header;
    int m_length;
public:
    LinkList()
    {
        m_header.next = NULL;
        m_length = 0;
    }

    bool insert(const T& e)
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T& e)
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if( ret )
        {
            Node* node = new Node();

            if( node != NULL )
            {
                Node* current = &m_header;

                for(int p=0; p<i; p++)
                {
                    current = current->next;
                }

                node->value = e;
                node->next = current->next;
                current->next = node;

                m_length++;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
            }
        }

        return ret;
    }

    bool remove(int i)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            Node* current = &m_header;

            for(int p=0; p<i; p++)
            {
                current = current->next;
            }

            Node* toDel = current->next;

            current->next = toDel->next;

            delete toDel;

            m_length--;
        }

        return ret;
    }

    bool set(int i, const T& e)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            Node* current = &m_header;

            for(int p=0; p<i; p++)
            {
                current = current->next;
            }

            current->next->value = e;
        }

        return ret;
    }

    T get(int i) const
    {
        T ret;

        if( get(i, ret) )
        {
            return ret;
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;
    }

    bool get(int i, T& e) const
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            Node* current = &m_header;

            for(int p=0; p<i; p++)
            {
                current = current->next;
            }

            e = current->next->value;
        }

        return ret;
    }

    int length() const
    {
        return m_length;
    }

    void clear()
    {
        while ( m_header.next )
        {
            Node* toDel = m_header.next;

            m_header.next = toDel->next;

            delete toDel;
        }

        m_length = 0;
    }

    ~LinkList()
    {
        clear();
    }
};

}

#endif // LINKLIST_H

main.cpp测试

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace StLib;

int main()
{
    LinkList<int> list;

    for(int i=0; i<5; i++)
    {
        list.insert(0, i);
        list.set(0, i*i);
    }

    for(int i=0; i<list.length(); i++)
    {
        cout << list.get(i) << endl;
    }
    cout << endl;

    list.remove(2);

    for(int i=0; i<list.length(); i++)
    {
        cout << list.get(i) << endl;
    }
    cout << endl;

    list.clear();

    for(int i=0; i<list.length(); i++)
    {
        cout << list.get(i) << endl;
    }

    return 0;
}

运行结果为:

16
9
4
1
0

16
9
1
0

问题:

  • 头结点是否存在隐患?
  • 实现代码是否需要优化?

头结点的隐患:

代码优化:

  • insert,remove,get,set等操作都涉及元素定位。

代码优化(LinkList.h):

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"

namespace StLib
{

template <typename T>
class LinkList : public List<T>
{
protected:
    struct Node : public Object
    {
        T value;
        Node* next;
    };

    mutable struct : public Object
    {
        char reserved[sizeof(T)];
        Node* next;
    } m_header;

    int m_length;

    Node* position(int i) const
    {
        Node* ret = reinterpret_cast<Node*>(&m_header);

        for(int p=0; p<i; p++)
        {
            ret = ret->next;
        }

        return ret;
    }
public:
    LinkList()
    {
        m_header.next = NULL;
        m_length = 0;
    }

    bool insert(const T& e)
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T& e)
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if( ret )
        {
            Node* node = new Node();

            if( node != NULL )
            {
                Node* current = position(i);

                node->value = e;
                node->next = current->next;
                current->next = node;

                m_length++;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
            }
        }

        return ret;
    }

    bool remove(int i)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            Node* current = position(i);
            Node* toDel = current->next;

            current->next = toDel->next;

            delete toDel;

            m_length--;
        }

        return ret;
    }

    bool set(int i, const T& e)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            position(i)->next->value = e;
        }

        return ret;
    }

    T get(int i) const
    {
        T ret;

        if( get(i, ret) )
        {
            return ret;
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;
    }

    bool get(int i, T& e) const
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            e = position(i)->next->value;
        }

        return ret;
    }

    int length() const
    {
        return m_length;
    }

    void clear()
    {
        while ( m_header.next )
        {
            Node* toDel = m_header.next;

            m_header.next = toDel->next;

            delete toDel;
        }

        m_length = 0;
    }

    ~LinkList()
    {
        clear();
    }
};

}

#endif // LINKLIST_H

3.顺序表和单链表的对比分析

问题

  • 如何判断某个数据元素是否存在于线性表中?

遗失的操作——find:

  • 可以为线性表( List )增加一个查找操作
  • int find(const T& e) const;
    1. 参数:

      1. 待查找的数据元素
    2. 返回值:
      1. =0:数据元素在线性表中第一次出现的位置

      2. -1:数据元素不存在

数据元素查找示例:

实现查找find函数:

在List.h中加入

virtual int find(const T& e) const = 0;

在SeqList.h中加入

    int find(const T& e) const
    {
        int ret = -1;

        for(int i=0; i<m_length; i++)
        {
            if( m_array[i] == e )
            {
                ret = i;
                break;
            }
        }

        return ret;
    }

在LinkList.h中加入

    int find(const T& e) const
    {
        int ret = -1;
        int i = 0;
        Node* node = m_header.next;

        while ( node )
        {
            if( node->value == e )
            {
                ret = i;
                break;
            }
            else
            {
                node = node->next;
                i++;
            }
        }

        return ret;
    }

但是若用类对象来进行测试,会有严重的bug:

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace StLib;

class Test
{
    int i;
public:
    Test(int v = 0)
    {
        i = v;
    }
};

int main()
{
    Test t1;
    Test t2;
    Test t3;
    LinkList<Test> list;

    return 0;
}

编译错误信息:

error C2678: 二进制“==”: 没有找到接受“Test”类型的左操作数的运算符(或没有可接受的转换)

于是应该在顶层父类Object中实现重载比较操作符

Object.h

    bool operator == (const Object& obj);
    bool operator != (const Object& obj);

Object.cpp

bool Object::operator == (const Object& obj)
{
    return (this == &obj);
}

bool Object::operator != (const Object& obj)
{
    return (this != &obj);
}

main.cpp再测试

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace StLib;

class Test : public Object
{
    int i;
public:
    Test(int v = 0)
    {
        i = v;
    }

    bool operator == (const Test& t)
    {
        return (i == t.i);
    }
};

int main()
{
    Test t1(1);
    Test t2(2);
    Test t3(3);
    LinkList<Test> list;

    list.insert(t1);
    list.insert(t2);
    list.insert(t3);

    cout << list.find(t2) << endl;

    return 0;
}

运行结果为:

1

时间复杂度对比分析:

有趣的问题:

  • 顺序表的整体时间复杂度比单链表要低,那么单链表还有使用价值吗?

效率的深度分析:

  • 实际工程开发中,时间复杂度只是效率的一个参考指标

    1. 对于内置基础类型,顺序表和单链表的下效率不相上
    2. 对于自定义类类型,顺序表在效率上低于单链表
  • 插入和删除
    1. 顺序表:涉及大量数据对象的复制操作
    2. 单链表:只涉及指针操作,效率与数据对象无关
  • 数据访问
    1. 顺序表:随机访问,可直接定位数据对象
    2. 单链表:顺序访问,必须从头访问数据对象,无法直接定位

工程开发中的选择:

  • 顺序表

    1. 数据元素的类型相对简单,不涉及深拷贝
    2. 数据元素相对稳定,访问操作远多于插入和删除操作
  • 单链表
    1. 数据元素的类型相对复杂,复制操作相对耗时
    2. 数据元素不稳定,需要经常插入和删除,访问操作较少

4.小结

  • 链表中的数据元素在物理内存中无相邻关系
  • 链表中的结点都包含数据域和指针域
  • 头结点用于辅助数据元素的定位,方便插入和删除操作
  • 插入和删除操作需要保证链表的完整性
  • 通过类模板实现链表,包含头结点成员和长度成员
  • 定义结点类型,并通过堆中的结点对象构成链式存储
  • 为了避免构造错误的隐患,头结点类型需要重定义
  • 代码优化是编码完成后必不可少的环节
  • 线性表中元素的查找依赖于相等比较操作符( == )
  • 顺序表适用于访问需求量较大的场合( 随机访问 )
  • 单链表适用于数据元素频繁插入删除的场合( 顺序访问 )
  • 当数据类型相对简单时,顺序表和单链表的效率不相上下

原文地址:https://www.cnblogs.com/PyLearn/p/10116981.html

时间: 2024-08-27 18:52:36

数据结构开发(5):线性表的链式存储结构的相关文章

数据结构--线性表的链式存储结构

一 线性表的链式存储结构 A.链式存储的定义为了表示每个数据元素与直接后继元素之间的逻辑关系:数据元素除了存储本身的信息外,还需要存储其直接后继的信息图示B链式存储逻辑结构基于链式存储结构的线性表中,每个结点都包含数据域和指针域1.数据域:存储数据元素本身2.指针域:存储相邻结点的地址图示C链表中的基本概念1.头结点--链表中的辅助结点,包含指向第一个数据元素的指针(方便插入和删除)2.数据结点--链表中代表数据元素的结点,表现形式为:(数据元素,地址)3.尾节点--链表中的最后一个数据结点,包

数据结构之线性表(链式存储结构)

线性表的实现分顺序存储结构和链式存储结构 上一节我们主要介绍了顺序存储结构,在最后我们还分别总结了顺序存储结构的优缺点, 对于顺序结构的缺点,我们有没有什么好的解决方法呢? 我们今天要介绍的线性表的链式存储结构就可以很好的解决顺序结构的缺点,一起来看. 顺序结构最大的缺点就是在进行插入和删除操作的时候,如果插入位置不理想,那么我们需要移动大量的元素,那产生这一问题的原因是什么呢? 仔细分析后,我们可以发现在顺序存储结构中,他们相邻的元素的存储位置也是相邻的,我们在申请内存的的时候,是一次性申请一

【数据结构】-线性表的链式存储结构

引言:由于线性表的顺序存储结构在插入和删除时需要大量移动数据元素,从而引入线性表的链式存储结构. 线性表的链式存储结构:用一组任意的存储单元(可以连续也可以不连续)存储线性表的数据元素. 为了表示数据元素ai和其直接后继ai+1之间的逻辑关系,对ai来说,除了存储其本身的数据信息外,还需要存储其直接后继的存储位置.这两部分信息组成数据元素ai的存储映像(结点).它包含两个域:其中存储数据元素信息的域称为数据域:存储直接后继存储位置的域称为指针域. n个结点链接成一个链表,称为线性链表,由于此链表

线性表的链式存储结构

1 n个结点链结成一个链表,即为线性表的链式存储结构,由于每一个结点只包含一个指针域,因此称为单链表. 链表中第一个结点的存储位置成为头指针,那么整个链表的存取就必须是从头指针开始了. 有时候会在单链表的第一个结点前附设一个结点,称为头结点. 头指针与头结点的区别: 头指针: (1)头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针. (2)头指针具有标识作用,所以常用头指针冠以链表的名字. (3)无论链表是否为空,头指针都不为空.头指针是链表的必要元素. 头结点: (1)

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

1.问题引入 开发数组类模板的原因在于:在创建基于顺序存储结构的线性表时,发现这样的线性表可能被误用,因为重载了数组访问操作符,使用时跟数组类似,但是线性表和数组有很大的区别,所以激发了新的需求:开发数组类替换C++原生数组类,因为原生数组类也存在着很大缺陷,使用不方便. 基于顺序存储结构的线性表的另一个缺点:插入或删除元素时,涉及到大量数据元素的移动,对于效率的影响非常大 一个新的需求:在插入或删除元素时不需要大量移动数据元素的一种数据结构,即基于链式存储结构的线性表 2.链式结构的定义 为了

03.线性表(二)链式存储结构.单链表1

链式存储结构.单链表1 1.基本概念 为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置) (1)数据域:存储线性表数据元素数据信息的域称为数据域: (2)指针域:把存储直接后继位置(下一个数据元素的地址)的域称为指针域,指针域中存储的信息为指针或链: (3)结点(Node):由数据域和指针域两部分信息组成数据元素ai的存储映像,称为结点. (4)头指针:把链表中第一个结点的存储

04.线性表(三)链式存储结构.单链表2

链式存储结构.单链表2 顺序存储结构的创建实质是一个数组的初始化,存储空间连续且其大小和类型已经固定:单链表存储空间不连续,是一种动态结构且它所占用空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成. 一.单链表的整表创建 创建单链表的过程就是一个动态生成链表的过程,即从"空表"的初始化起,依次建立各元素结点,并逐个插入链表. 1.算法思路 (1)声明一个结点p和计数器变量i; (2)初始化一空链表L (3)让链表L的头结点的指针指向NULL,即建立一个带头

05.线性表(四)链式存储结构.静态链表

链式存储结构.静态链表   一.静态链表 1.静态链表存储结构 单链表是通过指针实现的,但是我们也可以通过数组来代替指针描述单链表,即静态链表.如何实现静态链表?构造数组的元素由两个数据域组成:data和cur,即数组的每个下标都对应一个data和一个cur. 数据域data:用来存放数据元素,即要处理的数据: 游标cur:存放该元素的后继在数组中的下标,相当于单链表中的next指针: 为了方便插入数据,我们通常会把数组建立得大一些,以便有一些空闲空间而不致于出现溢出情况. 线性表的静态链表存储

第21课 线性表的链式存储结构

1. 链式存储的特点 (1)为了表示每个数据元素与其直接后继元素之间的逻辑关系: (2)数据元素除了存储本身的信息外,还需要存储其直接后继的信息. (3)避免了顺序存储结构线性表在插入和删除元素时需要移动大量元素的问题. 2. 链式存储逻辑结构 (1)数据域:存储数据元素本身 (2)指针域:存储相邻结点地址 3. 链表中的基本概念 (1)头结点:链表中的辅助结点,包含指向第一个数据元素的指针 (2)数据结点:链表中代表数据元素的结点,表现形式为:(数据元素,地址) (3)尾结点:链表中的最后一个