线性表的顺序存储表示
我们可以想象,线性表有两种物理存储结构:顺序存储结构和链式存储结构。线性表的顺序表示指的是用一组地址连续的存储单元一次存储线性表的数据元素,这种也称作线性表的顺序存储结构或者映像。
- 通常称这种顺序存储结构的线性表为顺序表(Sequential
List)。其特点是,逻辑上的相邻的的数据元素,其物理次序也是相同的。 - 物理上的存储方式事实上就是在内存中找到初始地址,然后通过占位的形式,把一定的内存空间给占了,然后把相同的数据类型的数据元素一次放在这块空地中。
假设每个元素占用l个存储单元,并把第一个存储单元的存储地址作为数据元素存储的起始位置。则线性表中第i+1个数据元素的存储地址LOC(ai+1)和第i个数据元素的存储位置LOC(ai)之间满足以下关系:
LOC(ai+1) = LOC(ai) + l
一般来说,线性表第i个数据元素ai 的存储位置是:
LOC(ai) = LOC (a1) + (i-1) *l
由上式可以看出:每一个数据元素的存储位置都与第一个数据元素的存储位置相差一个常数(i-1)*l,这个常数和数据元素在线性表中的位序成正比。所以只要确定了存储线性表的其实地址,线性表中任意数据元素都可以都可以随机存取,线性表的顺序存储结构是一种随机存取的存储结构。
线性表顺序存储的结构代码:
//-----------顺序表的存储结构---------
#define MAXSIZE 100 //顺序表可能达到的最大长度
typedef int ElemType;
typedef struct {
ELemType *elem; //存储空间的基地址
ElemType data[MAXSIZE]; //最大存储容量
int length; //当前长度
}SqList; //顺序表的结构类型是SqList
这里我们封装了一个结构,事实上就是对数组进行封装,增加了当前长度的变量。由此可以总结出顺序存储结构的三个属性:
- 存储空间的基地址,数组data它的存储位置就是线性存储空间的存储位置;
- 线性表的最大存储容量,数组的长度MAXSIZE;
- 线性表的当前长度,数组长度length;
顺序表中基本操作的实现
1、初始化得到一个顺序表:
Status InitList (SqList &L) {
L.elme = new ElemType[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的数组空间
if ( !L.elem )
exit(OVERFLOW); //存储失败退出
L.length = 0; //空表长度为0
return OK;
}
2、顺序表取值
Status GetElem (SqList L ,int i ,ElemType &e) {
if (i<1||i>L.length) return ERROR; //判断i的取值是否合法
e = L.elem[i-1];
return OK;
}
3、顺序表插入
Status ListInsert (SqList *L, int i ,ElemType e) {
if ((i<1)||i>L->length+1) {
//数组元素是从0开始的,判断i是否在区间内
return ERROR;
}
if (L->length==MAXSIZE){
//数组元素已满
return ERROR;
}
if (i<L->length) {
for(K=L->length-1;k>=i-1;k--) {
L->data[k+1] = L->data[k]; //从顺序表的最后一个元素开始分别向后挪一位
}
L->data[i-1] = e; //插入元素
L->length++; //数组长度+1
return OK;
}
}
4、顺序表的删除元素操作:
Statua ListDelete (SqList &L,int i ) {
if (i<1||i>L.length+1) {
//i值不合法
return ERROR;
}
for (k = i;k <=L.length-;k++) {
L.data[k-1] = L.data[k]; //从当前位置开始,后面所有元素通通向前挪一位
}
L.length--; //表长减1
return OK;
}
JAVA实现:
package array;
public class Array {
//定义整型数据data保存数据
public int data[];
//定义数组长度
private int n;
//定义中实际个数
private int count;
//构造方法,定义数组大小
public Array(int size){
this.data = new int[size];
this.n = size;
this.count=0;//一开始一个数都没有存所以为0
}
//根据索引,找到数据中的元素并返回
public int find(int i){
if (i<0 || i>=count) return -1;
return data[i];
}
//插入元素:头部插入,尾部插入
public boolean insert(int index, int value){
//数组中无元素
if (index == count && count == 0) {
data[index] = value;
++count;
return true;
}
// 数组空间已满
if (count == n) {
System.out.println("没有可插入的位置");
return false;
}
if (index < 0||index > count ) {
System.out.println("位置不合法");
return false;
}
for( int i = count; i > index; --i){
data[i] = data[i - 1];
}
data[index] = value;
++count;
return true;
}
//根据索引,删除数组中元素
public boolean delete(int index){
if (index<0 || index >=count) return false;
//从删除位置开始,将后面的元素向前移动一位
for (int i=index+1; i<count; ++i){
data[i-1] = data[i];
}
--count;
return true;
}
public void printAll() {
for (int i = 0; i < count; ++i) {
System.out.print(data[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
Array array = new Array(5);
array.printAll();
array.insert(0, 3);
array.insert(0, 4);
array.insert(1, 5);
array.insert(3, 9);
array.insert(3, 10);
//array.insert(3, 11);
array.printAll();
}
}
最后我们分析一下顺序表(数组)的插入和删除操作的时间复杂度:
- 最好情况:插入和删除的元素恰好在最后一位,时间复杂度为O(1);
- 最坏情况:插入和删除的元素恰好在最第一位,时间复杂度为O(n);
- 平均情况:中间值O((n-1)/2);
- 所以时间复杂度为O(n);
数组缺点:
- 若申请内存空间很大,比如100M,但若内存空间没有100M的连续空间时,则会申请失败,尽管内存可用空间超过100M。
- 大小固定,若存储空间不足,需进行扩容,一旦扩容就要进行数据复制,而这时非常费时的。如果代码对内存的使用非常苛刻,那数组就更适合。