线性表的顺序表示和实现

前文我们提到过线性表是逻辑结构,只说明了数据元素之间的相互关系,想要使用线性表,我们还需要在计算机上表示出这些数据元素以及元素之间的关系。而对于同一种逻辑结构,可以有多种存储结构来实现它。线性表作为一种基础的数据结构,可以用顺序存储和链式存储两种不同方式来实现,本节主要说明线性表的顺序表示。


1 线性表的顺序表示

1.1 顺序表的定义

线性表的顺序存储又称为顺序表, 它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理地址上也相邻。顺序表的特点是表中元素的逻辑顺序与其物理顺序相同。顺序表是一种存储结构,关注的是如何在计算机中存储数据元素及其关系。

1.2 顺序表的特点

假设线性表的每个元素需占用 lll 个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置。则线性表中第 i+1i+1i+1 个数据元素的存储位置 LOC(ai+1)LOC(a_i+1)LOC(ai​+1) 和第 iii 个数据元素的存储位置 LOC(ai)LOC(a_i)LOC(ai​) 之间满足下列关系:
LOC(ai+1)=LOC(ai)+lLOC(a_i+1) = LOC(a_i) +l LOC(ai​+1)=LOC(ai​)+l
一般来说,线性表的第 iii 个数据元素 aia_iai​ 的存储位置为:
LOC(ai)=LOC(a1)+(i−1)∗lLOC(a_i) = LOC(a_1) +(i-1)*l LOC(ai​)=LOC(a1​)+(i−1)∗l
其中 LOC(a1)LOC(a_1)LOC(a1​) 是线性表的第一个数据元素 a1a_1a1​ 的存储位置,也被称为线性表的起始位置或基地址。

观察上图我们可以发现,顺序表的每一个数据元素的存储位置都和线性表的起始位置相差一个和数据元素在线性表中的位序成正比的常数。由此,只要确定了存储线性表的起始位置,表中任一数据元素都可以随机存取(也称为直接访问),所以线性表的顺序存储结构是一种随机存取的存储结构。更广泛地说,顺序存储一般对应随机存取。

总结
顺序表有以下主要特点:

  • 随机存取,读取(或查找)操作时间复杂度为 O(1)O(1)O(1) 。
  • 存储密度高,每个节点只存储数据元素。
  • 逻辑上相邻的元素物理上也相邻,因此插入和删除操作需要移动大量元素。

2 顺序表的实现

由于高级程序语言中的数组类型也有随机存取的特性,因此通常用数组来描述数据结构中的顺序存储结构。

下文的默认实现语言为C++,因此一切未注明的语法都以C++为准。

2.1 定义

C++中的一维数组可以是静态分配的,也可以是动态分配的。详情见C++动态数组。

  • 静态分配时,由于数组的大小和空间已经事先决定了,一但空间占满,再加入新的数据就会产生溢出,导致程序崩溃。

    /*** 静态数组实现顺序表的类型定义 ***/
    #define  MaxSize 10typedef int elemType;        // 定义新的类型elemType
    typedef struct{elemType data[MaxSize]; // ElemType为指定元素类型int length;
    } SqList;
    
  • 动态分配就不存在这样的问题,一旦空间占满,就另外开辟一块更大的存储空间,用以替换原来的空间。但要注意的是,动态分配并不是链式存储,它同样属于顺序存储结构,依然是随机存取的方式。但是动态数组需要用户显式地释放内存。

    /*** 动态数组实现顺序表的类型定义 ***/
    #define InitSize 50         // 线性表存储空间的初始分配量
    #define Increment 10        // 线性表存储空间的分配增量typedef int elemType;        // 定义新的类型elemType
    typedef struct{         elemType *data;         // 存储空间基地址int capacity, length  // 当前分配的最大容量和数组长度
    } SqList
    

2.2 主要操作的实现

在顺序存储下,容易实现线性表的某些操作,如随机存取第 iii 个元素等,因此这里只讨论顺序表较复杂的插入、删除和按值查找操作。

因为静态数组的局限性较大,后文正文内皆以动态数组实现为例展示代码,静态数组实现将放于文末附录。

2.2.1 插入操作

在顺序表 LLL 的第 i(1≤i≤L.length+1)i (1 \le i \le L.\mathrm{length+1})i(1≤i≤L.length+1) 个位置插入新元素 eee。若 iii 的输入不合法,则返回 false,表示插入失败;否则,将第 iii 个元素及其后的所有元素依次往后移动一个位置,顺序表长度增加1,返回 true

/*** 动态数组顺序表实现插入操作 ***/
// 扩容操作
void increment(SqList &L){elemType *data = new elemType[L.capacity+Increment];   // 重新申请内存for (int i=0;i<L.capacity;i++){                        // 将原来的元素移动到新的顺序表中data[i] = L.data[i];}delete[] L.data;                                       // 释放内存L.data = data;L.capacity += Increment;printf("Finish Increment.\n");
}// 插入操作
bool listInsert(SqList &L, int i, elemType e){if (i<1 || i>L.length+1){               // 判断i的范围是否有效printf("Index is illegal!\n");return false;}   if (L.length >= L.capacity){            // 判断存储空间是否还有剩余,printf("Need Increment.\n");increment(L);                       // 不够则扩容}for (int j=L.length;j>=i;j--){          // 将位置i及之后的元素向后移动一位L.data[j] = L.data[j-1];}L.data[i-1] = e;L.length++;return true;
}

时间复杂度分析

对插入操作来说,插入元素只需要一步,而移动元素可能重复很多次,所以基本操作为移动元素。我们只需要关心每一次插入操作发生时,总共移动了多少个元素,而移动元素的个数取决于插入新元素的位置。

  • 最优情况:在表尾插入(即 i=n+1i = n+1i=n+1),元素不用后移,时间复杂度为 O(1)O(1)O(1)。
  • 最差情况:
    • 静态数组:在表头插入(即 i=1i = 1i=1),现存所有元素(共 nnn 个)都需后移,时间复杂度为 O(n)O(n)O(n)。
    • 动态数组:在表头插入且空间不够,需先执行扩容操作,移动表内现存的所有元素,共 nnn 次。然后再插入新的元素,将表内的旧元素全部后移一位,共 nnn 次。总共需要 2n2n2n 次移动,时间复杂度为 O(n)O(n)O(n)。
  • 平均情况:令 pip_ipi​ 为在第 iii 个位置上插入一个元素的概率,假设任意一个位置上发生插入元素的概率是相等的,那么 pi=1(n+1)p_i = \frac{1}{(n+1)}pi​=(n+1)1​。在长度为 nnn 的线性表中插入一个元素时,所需移动元素的平均次数为
    ∑i=1n+1pi(n−i+1)=n2\sum_{i=1}^{n+1} p_i(n-i+1) = \frac{n}{2} i=1∑n+1​pi​(n−i+1)=2n​
    因此,平均情况的时间复杂度为 O(n)O(n)O(n)。

2.2.2 删除操作

删除顺序表 LLL 中第 i(1≤i≤L.length+1)i(1 \le i \le L.\mathrm{length+1})i(1≤i≤L.length+1) 个位置的元素,用引用变量 eee 返回。若 iii 的值不合法,则返回 false ;否则,将被删除元素赋给引用变量 eee ,并将第 i=1i=1i=1 个元素及其后的所有元素依次往前移动一个位置,返回 true

/*** 动态数组顺序表实现删除操作 ***/
// 删除操作
bool listDelete(SqList &L, int i, elemType &e){if (i<1 || i>L.length){                    // 判断i的范围是否有效printf("Index is illegal!\n");return false;}e = L.data[i-1];                          // 将被删除的元素的值赋给efor (int j=i;j<L.length;j++){              // 将位置i以后的元素向前移动一位L.data[j-1] = L.data[j];}L.length--;return true;
}

时间复杂度分析

分析和插入操作类似,删除操作的基本操作也是对元素的移动。

  • 最优情况:在表尾删除(即 i=ni = ni=n),元素不用后移,时间复杂度为 O(1)O(1)O(1)。
  • 最差情况:在表头删除(即 i=1i = 1i=1),现存所有元素(共 nnn 个)都需前移,时间复杂度为 O(n)O(n)O(n)。
  • 平均情况:令 pip_ipi​ 为在第 iii 个位置上删除一个元素的概率,假设任意一个位置上发生插入元素的概率是相等的,那么 pi=1np_i = \frac{1}{n}pi​=n1​。在长度为 nnn 的线性表中插入一个元素时,所需移动元素的平均次数为
    ∑i=1npi(n−i)=n−12\sum_{i=1}^{n} p_i(n-i) = \frac{n-1}{2} i=1∑n​pi​(n−i)=2n−1​
    因此,平均情况的时间复杂度为 O(n)O(n)O(n)。

2.2.3 按值查找操作

在顺序表 LLL 中查找第一个元素值等于 eee 的元素,并返回其位序。

/*** 动态数组顺序表实现按位查找操作 ***/
// 按值查找操作
int locateElem(SqList L, elemType e){for (int i=0;i<L.length;i++){if (L.data[i] == e){return i+1;}}return 0;
}

时间复杂度分析
查找操作的基本操作是比较元素,因此我们只关注元素的比较次数。

  • 最优情况:查找元素就在表头(即 i=1i = 1i=1),只用比较一次,时间复杂度为 O(1)O(1)O(1)。
  • 最差情况:查找元素在表尾(即 i=ni = ni=n)或不存在时,需要比较表内的所有元素,共 nnn 次,时间复杂度为 O(n)O(n)O(n)。
  • 平均情况:令 pip_ipi​ 为查找元素出现在第 iii 个位置上的概率,假设任意一个位置上发生插入元素的概率是相等的,那么 pi=1np_i = \frac{1}{n}pi​=n1​。在长度为 nnn 的线性表中插入一个元素时,所需移动元素的平均次数为
    ∑i=1npi×i=∑i=1n1n×i=1nn(n+1)2=n+12\sum_{i=1}^{n} p_i \times i = \sum_{i=1}^{n}\frac{1}{n}\times i = \frac{1}{n} \frac{n(n+1)}{2} = \frac{n+1}{2} i=1∑n​pi​×i=i=1∑n​n1​×i=n1​2n(n+1)​=2n+1​
    因此,平均情况的时间复杂度为 O(n)O(n)O(n)。

参考资料:
【C++】细说C++中的数组之“静态”数组
【C++】细说C++中的数组之动态数组

相关章节

第一节 【绪论】数据结构的基本概念
第二节 【绪论】算法和算法评价
第三节 【线性表】线性表概述
第四节 【线性表】线性表的顺序表示和实现
第五节 【线性表】线性表的链式表示和实现
第六节 【线性表】双向链表、循环链表和静态链表
第七节 【栈和队列】栈
第八节 【栈和队列】栈的应用
第九节 【栈和队列】栈和递归
第十节 【栈和队列】队列


附录

静态数组实现顺序表的完整代码

/** File: SqList.h* -------------------------* Using struct to implement SqList*//********** 使用静态数组实现顺序表 **********/
#ifndef _STATIC_SEQUENTIAL_LIST_h_
#define _STATIC_SEQUENTIAL_LIST_h_#include <stdio.h>
#include <iostream>
using namespace std;/*** 静态数组顺序表的定义 ***/
#define MaxSize 10              // 默认静态数组最大容量为10typedef int elemType;           // 定义新的类型elemType
typedef struct {elemType data[MaxSize];int length;                 // 记录静态数组的有效长度
} SqList;/*** 基本操作的实现 ***/
// 初始化操作
void initList(SqList &L){L.length = 0;              // 将有效长度归零
}// 销毁操作
void destroyList(SqList &L){}// 判空操作
bool listEmpty(SqList L){return not L.length;
}// 求表长操作
int listLength(SqList L){return L.length;
}// 按位查找操作,如果存在返回1,否则返回0
int getElem(SqList L, int i, elemType &e){if (i<1 || i>L.length) {printf("Index is illegal!\n");return 0;} else {e = L.data[i-1];return 1;}
}// 按值查找操作,返回第一个符合查找要求的元素的位置,如果不存在则返回0
int locateElem(SqList L, elemType e){for (int i=0;i<L.length;i++){if (L.data[i] == e){return i+1;}}return 0;
}// 插入操作,在位值i处插入元素e
bool listInsert(SqList &L, int i, elemType e){if (i<1 || i>L.length+1){          // 判断i的范围是否有效printf("Index is illegal!\n");return false;}   if (L.length >= MaxSize){          // 判断存储空间是否还有剩余printf("Out of space!\n");return false;}for (int j=L.length;j>=i;j--){     // 将位置i及之后的元素向后移动一位L.data[j] = L.data[j-1];}L.data[i-1] = e;L.length++;return true;
}// 删除操作,删除第i个位置的元素,并用e返回删除元素的值
bool listDelete(SqList &L, int i, elemType &e){if (i<1 || i>L.length){            // 判断i的范围是否有效printf("Index is illegal!\n");return false;}e = L.data[i-1];                   // 将被删除的元素的值赋给efor (int j=i;j<L.length;j++){      // 将位置i以后的元素向前移动一位L.data[j-1] = L.data[j];}L.length--;return true;
}// 输出操作
void listPrint(SqList L){for (int i=0;i<L.length;i++){printf("%d ", L.data[i]);}printf("\n");
}#endif // _STATIC_SEQUENTIAL_LIST_h_

动态数组实现顺序表的完整代码

/** File: SqList.h* -------------------------* Using struct to implement SqList*//********** 使用动态数组实现顺序表 **********/
#ifndef _DYNAMIC_SEQUENTIAL_LIST_h_
#define _DYNAMIC_SEQUENTIAL_LIST_h_#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;/***** 动态数组顺序表的定义 *****/
#define InitSize 10         // 动态数组初始容量为10
#define Increment 10        // 线性表存储空间的分配增量typedef int elemType;       // 定义新的类型elemType
typedef struct {elemType *data;int capacity, length;   // 动态数组当前最大容量和有效长度
} SqList;/***** 基本操作的实现 *****/
// 初始化操作
void initList(SqList &L){L.data = new elemType[InitSize]; // 申请内存L.length = 0;L.capacity = InitSize;
}// 销毁操作
void destroyList(SqList &L){if (L.data != NULL){delete[] L.data;L.length = 0;L.capacity = 0;}
}// 判空操作
bool listEmpty(SqList L){return not L.length;
}// 求表长操作
int listLength(SqList L){return L.length;
}// 按位查找操作
int getElem(SqList L, int i, elemType &e){if (i<1 || i>L.length) {printf("Index is illegal!\n");return 0;} else {e = L.data[i-1];return 1;}
}// 按值查找操作
int locateElem(SqList L, elemType e){for (int i=0;i<L.length;i++){if (L.data[i] == e){return i+1;}}return 0;
}// 扩容操作
void increment(SqList &L){elemType *data = new elemType[L.capacity+Increment];   // 重新申请内存for (int i=0;i<L.capacity;i++){                        // 将原来的元素移动到新的顺序表中data[i] = L.data[i];}delete[] L.data;                                       // 释放内存L.data = data;L.capacity += Increment;printf("Finish Increment.\n");
}// 插入操作
bool listInsert(SqList &L, int i, elemType e){if (i<1 || i>L.length+1){               // 判断i的范围是否有效printf("Index is illegal!\n");return false;}   if (L.length >= L.capacity){            // 判断存储空间是否还有剩余,printf("Need Increment.\n");increment(L);                       // 不够则扩容}for (int j=L.length;j>=i;j--){          // 将位置i及之后的元素向后移动一位L.data[j] = L.data[j-1];}L.data[i-1] = e;L.length++;return true;
}// 删除操作
bool listDelete(SqList &L, int i, elemType &e){if (i<1 || i>L.length){                    // 判断i的范围是否有效printf("Index is illegal!\n");return false;}e = L.data[i-1];                          // 将被删除的元素的值赋给efor (int j=i;j<L.length;j++){             // 将位置i以后的元素向前移动一位L.data[j-1] = L.data[j];}L.length--;return true;
}// 输出操作
void listPrint(SqList L){for (int i=0;i<L.length;i++){printf("%d ", L.data[i]);}printf("\n");
}#endif // _DYNAMIC_SEQUENTIAL_LIST_h_

顺序表检测程序

/** File name: SqList.cpp* -----------------------* Using struct to implement SqList*/#include "SqList.h"int main(){SqList L;initList(L);int n, i, res;elemType e;char helpInfo[] = "*****************************\n" \"Sequential list check: \n"                     "\t1-Insert element\n" \"\t2-Delete element\n" \"\t3-Print\n" \"\t4-Empty check\n" \"\t5-Get Length\n" \"\t6-Search by value\n" \"\t7-Search by location\n" \"\t8-Initialization\n" \"\t9-Quit\n" \"*****************************\n";                  while (n!= 9){printf(helpInfo);scanf("%d", &n);switch(n){case 1:printf("Please enter the location: ");scanf("%d", &i);printf("Please enter the element: ");scanf("%d", &e);listInsert(L, i, e);break;case 2:printf("Please enter the location: ");scanf("%d", &i);listDelete(L, i, e);printf("Element in location %d with value %d has been deleted.\n", i, e);break;case 3:printf("List: ");listPrint(L);break;case 4:if (listEmpty(L)) {printf("This list is empty!\n");}else {printf("This list is not empty!\n");}break;case 5:printf("Length of list is: %d\n", listLength(L));break;case 6:printf("Please enter the target value: ");scanf("%d", &e);res = locateElem(L, e);if (res) {printf("The index of target is %d\n", res);} else {printf("We didn't find the value.\n");}break;case 7:printf("Please enter the target index: ");scanf("%d", &i);res = getElem(L, i, e);if (res) {printf("The value of target is %d\n", e);}break;case 8:initList(L);printf("List has been initialized.\n");break;}}destroyList(L);return 0;
}

四、【线性表】线性表的顺序表示和实现相关推荐

  1. 构建线性表的c语言代码,数据结构严蔚敏C语言版—线性表顺序存储结构(顺序表)C语言实现相关代码...

    1.运行环境 这里说明一下这里所有的C语言代码都是基于code::blocks 20.03编译运行的.当然一些其他集成开发环境应该也是可以的,个人不太喜欢功能太过强大的IDE,因为那同样意味着相关设置 ...

  2. 用Java描述数据结构之线性表的顺序存储(顺序表),ArrayList及其方法的介绍

    我们先来想一想什么是线性表? 线性表是最基本.最简单.也是最常用的一种数据结构.线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列. 线性表中数据元素之 ...

  3. 数据结构严蔚敏C语言版—线性表顺序存储结构(顺序表)C语言实现相关代码

    数据结构严蔚敏C语言版-线性表顺序存储结构(顺序表)C语言实现相关代码 1.运行环境 2.准备工作 1)项目构建 1>新建一个SeqList项目 2>新建两个文件Sources和Heade ...

  4. 线性表(一)——顺序表

    线性表(一) 线性表(linear list),也称有序表(ordered list),是线性结构的典型代表.数据元素之间仅具有单一的前驱和后继关系. 一.线性表的逻辑结构 1.线性表的定义 线性表简 ...

  5. 线性表:2.线性表的顺序存储结构--顺序表及C语言实现

    逻辑结构上呈线性分布的数据元素在实际的物理存储结构中也同样相互之间紧挨着,这种存储结构称为 线性表的顺序存储结构 . 也就是说,逻辑上具有线性关系的数据按照前后的次序全部存储在一整块连续的内存空间中, ...

  6. 线性表的顺序存储结构——顺序表

    什么是线性表? 线性表简称表,是n(n>=0)个具有相同类型的数据元素的有限序列,线性表中数据元素的个数称为线性表的长度,长度为0的表称为空表. 什么是顺序表? 线性表的顺序存储结构称为顺序表. ...

  7. 数据结构与算法——线性结构——线性表及其表示

    -"一,线性结构 1.顺序储存结构直接表示 多项式. 1).使用数组来表示多项式.(用数组下标来表示指数,值来表示系数) 可以表示成: 2).使用结构数组来表示.(把系数和指数看成一个二元组 ...

  8. 【数据结构】哈希表——线性探测法、链地址法、查找成功、查找不成功的平均长度

    一.哈希表 1.概念 哈希表(Hash Table)也叫散列表,是根据关键码值(Key Value)而直接进行访问的数据结构.它通过把关键码值映射到哈希表中的一个位置来访问记录,以加快查找的速度.这个 ...

  9. 【数据结构基础】线性数据结构——线性表概念 及 数组的封装(C和Java)

    前言 数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷. 也因如此,它作为博主大二上学期最重 ...

最新文章

  1. LeetCode简单题之距离顺序排列矩阵单元格
  2. angr学习笔记(6)(内存地址单元符号化)
  3. hadoop的datanode多磁盘空间处理
  4. 带旋treap概念及模板,带例题:普通平衡树
  5. UIAlertAction添加输入框
  6. 大数据之-Hadoop完全分布式_完全分布式模式下的集群配置---大数据之hadoop工作笔记0034
  7. inner/left/right/full join on
  8. 第1章 异常产生异常处理
  9. 深度学习图像分类(二):AlexNet
  10. android之textview属性介绍
  11. 互联网日报 | 滴滴正式入局货运市场;苏宁小店宣布开放加盟;钉钉推出新职业在线学习平台...
  12. 邮箱如何设置smtp服务器端口,如何改变你的SMTP端口来允许发送电子邮件
  13. VS各个版本的优缺点和发展过程(VC6.0——VS2015)
  14. DDD.实践思考随笔,看书不在所而在于自己有多少思考
  15. 查询历史使用过的命令并使用(history)
  16. java basic认证_HTTP Basic auth认证
  17. 宝兰德BES安装及Springboot项目打包部署及websocket解决方案
  18. 你不可不知的《哈利波特》秘密(三)
  19. 香港理工大学李青教授团队招收机器学习方向全奖博士/博后/RA
  20. windows 8 音乐(Xbox Music)详解

热门文章

  1. linux 内核参数 杨,Linux 内核参数
  2. android 之Dialog的多种Dialog对话框(以及反射在自定义对话框的运用)
  3. ubuntu 安装 spconv
  4. Python实现快速傅里叶变换(FFT)
  5. 灯光插件_Light Kit Pro 3灯光插件
  6. 定位系列论文阅读-RoNIN(二)-Robust Neural Inertial Navigation in the Wild: Benchmark, Evaluations
  7. pyechart 应用: graph
  8. keras 实战系列之Self-Attention详细解析
  9. 深究 ElasticSearch 查询的秘密
  10. Linux中date命令用法及大小比较