数据结构之线性表和单链表(二)

线性表

定义

线性表是最基本、最简单、也是最常用的一种数据(线性)结构
线性表:具有像线一样性质的表。由n(n≥0)个数据元素(结点)a1,a2…,an组成的有限序列。
【注意】:
a.首先线性表是一个序列,即元素之间是有顺序的。
b.线性表是有限的,元素个数有限,数据元素的个数n定义为表的长度(n=0时为空表)
c.数据元素ai(1≤i≤n)只是个抽象符号,其具体含义在不同情况下可以不同
对于非空的线性表的特点如下:
1.集合中必存在唯一的一个“第一元素”。
2.集合中必存在唯一的一个 “最后元素” 。
3.除最后一个元素之外,均有唯一的后继(后件)。
4.除第一个元素之外,均有唯一的前驱(前件)。

存储结构

线性表主要由顺序表示或链式表示。在实际应用中,常以栈、队列、字符串等特殊形式使用。
附图两张:
这里写图片描述
这里写图片描述
顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存储结构或顺序映像。它以“物理位置相邻”来表示线性表中数据元素间的逻辑关系,可随机存取表中任一元素。
链式表示指的是用一组任意的存储单元存储线性表中的数据元素,称为线性表的链式存储结构。它的存储单元可以是连续的,也可以是不连续的。在表示数据元素之间的逻辑关系时,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置),这两部分信息组成数据元素的存储映像,称为结点。它包括两个域;存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称为指针或链。

线性表的顺序存储

在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构。
可用一维数组来实现顺序存储结构
如图所示:
线性表的顺序存储示意图
通常,假设线性表中所有的结点类型相同,则每结点占用的存储空间大小也是相同的。若表中每个节点占用c个存储单元,其中第一个单元的存储位置就是该结点的存储位置。设表中开始结点a1的存储结构(简称为基地址)是LOC(a1),那么结点ai的存储位置LOC(ai)可通过下列公式计算: LOC(ai)=LOC(a1)+(i-1)*c 1≤i≤n
【注意】:在顺序表中,每个结点ai的存储位置是该结点在表中的位置i的线性函数。只要知道基地址和每个结点的大小,就可在相同的时间里求出任一节点的存储位置,这是一种随机存取结构。

顺序存储结构的插入操作
插入数据时,实现过程如图所示:
顺序存储结构的插入操作示意图
插入算法思路如下:

  1. 若插入位置不合理,抛出异常
  2. 若线性表长度≥数组长度,抛出异常或动态增加容量
  3. 最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
  4. 将要插入的元素填入位置i处
  5. 表长+1

顺序存储结构的删除操作
删除数据时,实现过程如图所示:
顺序存储结构的删除操作示意图
删除算法思路如下:

  1. 若删除位置不合理,抛出异常
  2. 取出删除元素
  3. 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
  4. 表长-1

【说明】
插入和删除的时间复杂度分析:
线性表的顺序存储结构,在存、取数据时,时间复杂度为0(1),在插入、删除时,时间复杂度为0(n)
【总结】
线性表的顺序存储结构优缺点:
优点:

  • 无需为表中元素之间的逻辑关系而增加额外的存储空间(一开始便声明了长度)
  • 可以快速的存取表中任一位置的元素

缺点:

  • 插入和删除操作需要移动大量元素
  • 当线性表长度变化较大时,难以确定存储空间的容量
  • 造成存储空间的“碎片”

单链表

概念
存储方法:
链表是由许多相同数据类型的元素按照特定顺序排列而成的线性表,其特性是在计算机内存的位置是不连续与随机存储的。具体存储表示为:
1.用一组任意的存储单元来存放线性表的结点,这组存储单元既可以是连续的,也可以是不连续的。
2.链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))
【注意】
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。
结点结构:
为了表示每个数据元素ai与其直接后续元素ai+1之间的逻辑关系,对元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后续的信息。我们把存储元素信息的域称为数据域,把存储直接后续位置的域称为指针域。指针域中存储的信息叫做指针或域。这两部分信息组成数据元素ai的存储映像,称为结点。
n个结点链组成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
单链表通过每个结点的指针域将线性表的数据元素按其逻辑次序连接在一起,如下图:
结点存储示意图
(1).头指针head和终端结点tail指针域的表示
为了方便对链表的操作,在单链表的第一个结点钱附设一个结点,称为头结点。头结点的数据域可存储也可不存储信息,头结点的指针域存储指向第一个结点的指针。
头指针存储示意图
单链表中每个结点的存储地址是存放在其前驱结点的next域中,而开始结点无前驱,所以应设头指针head指向开始结点。
【注意】链表由头指针唯一确定,单链表可以用头指针的名字命名。
(2).单链表的一般图示法
单链表是通过指针把它的一串内存结点链接成一个链,因此结点在内存中的位置不必两两相邻。由于我们只注重结点间的逻辑顺序,不关心实际位置。所以可用箭头来表示链域中的指针,如图所示:
单链表示意图
若带有头结点的单链表,如图所示:
带头结点的单链表表示图

单链表的插入
单链表插入分为以下3种情况:

  1. 在链表的表头插入一个新节点(链表开始处)
  2. 在链表的结尾处插入一个新节点(链表结尾处)
  3. 在链表中间插入一个新节点(随机位置)

◆在链表的表头插入一个新节点(链表开始处)
(1).更新新节点next指针,使其指向当前的表头结点。
(2).更新表头指针的值,使其指向新节点。
插入结点过程,如图所示:
这里写图片描述

◆在链表的结尾处插入一个新节点(链表结尾处)
(1).新节点的next指针指向null
(2).最后一个结点的next指针指向新节点
如图所示:
这里写图片描述

◆在链表中间插入一个新节点(随机位置)
(1).若在位置ai增加一个新结点,则将指针定位于链表的ai-1处
(2).p点的next指针指向新结点
如图所示:
这里写图片描述
单链表的插入操作,主要代码如下:

Node InsertInlinkedList(Node headNode,Node nodeToInsert,int position){
//若链表为空,直接插入
   if(headNode==null)
       return nodeToInsert;
   int size=getListLength(headNode);  //获取链表长度
   if(position>size+1 || position<1){
        System.out.print("插入位置无效");
        return headNode;
   }
   if(position==1)   //在链表开头插入
   {
       nodeToInsert.setNext(headNode);
       return nodeToInsert;
   }else{  //在链表中间或末尾插入
       Node previousNode=headNode;
       int count=1;
       while(count<position-1){
           previousNode=previousNode.getNext();
           count++;
       }
       Node currentNode=previousNode.getNext();
       nodeToInsert.setNext(currentNode);
       previousNode.setNext(nodeToInsert);
   }
   return headNode;
}

单链表插入算法的时间复杂度为0(n),空间复杂度为0(1)。

单链表的删除
单链表删除分为以下3种情况:

  1. 删除链表的表头结点
  2. 删除链表的表尾结点
  3. 删除链表的中间结点

◇删除链表的第一个结点
修改头指针的值,使其指向下一个结点,并移除第一个结点
如图所示:
这里写图片描述
◇删除链表的最后一个结点
(1).遍历链表,在遍历时保存前驱结点的地址。遍历至链表表尾时,将有两个指针,分别是表尾结点的指针及前驱结点的指针
(2).将表尾的前驱结点的next指针更新为NULL
(3).移除表尾结点
如图所示:
这里写图片描述
◇删除链表的中间结点
(1).在遍历时保存p结点的地址。找到待删结点时,将p结点next指针的值更新为待删结点的next指针的值
(2).移除待删结点
如图所示:
这里写图片描述
单链表的删除操作,主要代码如下:

Node DeleteNodeFromlinkedList(Node headNode,int position){
       int size=getListLength(headNode);  //获取链表长度
       if(position>size || position<1) //判断删除位置是否合法
       {
          System.out.print("删除位置无效");
          return headNode;
       }
       if(position==1) //删除单向链表的表头结点
       {
          Node currentNode=headNode.getNext();
          headNode=nullreturn currentNode;
       } else{  //删除中间或表尾结点
           Node previousNode=headNode;
           int count=1;
           while(count<position-1){
              previousNode=previousNode.getNext();
              count++;
           }
           Node currentNode=previousNode.getNext();
           if(currentNode!=null)
               previousNode.setNext(currentNode.getNext());
           currentNode=null;
       }
       return headNode;
}

分析上文可发现:
单链表的删除和插入都是有两部分组成:

  • 遍历查找第i个元素
  • 插入和删除元素

结论

线性表和单链表的时间复杂度都是0(n)。如果不知道第i个元素的指针位置,单链表数据结构在插入和删除操作上,于线性表的顺序存储结构并无太大优势。但如果,希望从第i个位置上插入10个元素,对于顺序存储结构意味着,每一个插入都需要移动n-i个元素,每一次的时间复杂度都是0(n)。而单链表,只需在第一次找到第i个位置上的指针,此时时间复杂度为0(n),接下来只是简单的通过赋值移动指针,时间复杂度都是0(1)。显然,对于插入和删除数据越频繁的操作,单链表的效率优势越明显。
附图两张如下:
这里写图片描述
这里写图片描述

猜你喜欢

转载自blog.csdn.net/My_ben/article/details/82222111