线性表就是元素排成一列的结构,能够理解为行军队伍,例如一字长蛇阵c++
队中的成员除了第一我的外,每一个人前面都有一我的;算法
队中的成员除了最后一我的外,每一个人后面都有一我的。数组
简单来讲就是一个排成一字的队伍。函数
线性表按顺序存储和链式储存两种,顺序存储须要一块连续的存储空间,即为顺序表;而链式存储对于空间则没有要求,使用指针实现,只要有空间就能储存,元素之间用指针指向下一个元素的地址。下面按照上图依次介绍线性表内容。指针
有一天李华出去游玩,到了晚上找了一个酒店住下,服务员告诉李华说,您的房间号是523,因而李华拿上房卡,上了五楼,找到23号房间,住下了。code
拿这个例子来讲什么是顺序表?简单来讲,酒店的房间,按号顺序排列,这些房间就构成了一个顺序表!线性表的顺序存储就是顺序表(后面会说到链表,即链式存储)。例如说学生上体育课时按学号一个挨一个有顺序的排列也是一个顺序表。blog
在计算机中,顺序表的存储须要一组连续的存储空间(注意是连续的存储空间,下图为顺序表的图示),若是要表示顺序表,用咱们学过的数组好像就能够,那么下面来写出数组表示顺序表的方法。其中第一个存储空间(头节点)通常不放数据,这样数据就和数组的下标对应上了,找起来更加方便,例如第一个数据,就是数组的array[1];头节点也不会空着浪费,通常能够存放数组的长度等信息。it
#define maxSize 50 //定义maxSize的值为50 typedef struct{ //定义顺序表的结构体,起名叫sqList int data[maxSize]; //用数组来分配一块连续的存储空间,int表示顺序表元素的类型 int length; //顺序表的长度 }sqList; //结构体类型名sqList
以上就是用数组定义的顺序表的结构体,接下来咱们就能够对顺序表进行操做(增删改查)!拿顺序表增长元素来讲,你能够往顺序表里放元素,可是存在一个问题,在用数组表示顺序表时,在定义数组时,数组的长度就定下来了且后续不可修改,那么若是顺序表存满了元素,我还要继续添加元素的话,就会溢出。如何解决这个问题呢?class
答案是对顺序表的空间采用动态分配,上面的用数组是静态分配的方法,问题很明显,那么采用动态分配的方法,就能解决掉顺序表满时继续添加元素致使添加溢出的问题。当存储空间不够时,动态的分配一块更大的空间来替换之前的存储空间,就不会有问题了。date
那么如何动态分配存储空间呢?c语言中给出了一个malloc()函数,这里用int型举例
int *pointer; //定义一个int指针pointer pointer = (int *)malloc(n * sizeof(int));//使用malloc函数分配n个int型的空间,并用pointer指向该空间
那么动态分配顺序表的结构体定义就以下所示
#define InitSize 100 //初始化表长度 typedef struct{ ElemType *data; //动态分配数组的指针 int MaxSize,length; //数组的最大容量和当前个数(即数组长度) }SeqList;
定义了顺序表以后,下面说一说顺序表的特色
一、根据顺序表的图示,可知顺序表能够随机访问,经过地址序号和元素序号就能够在O(1)的时间找到指定的元素
二、顺序表的存储密度高,每个节点只存储数据
三、元素之间彼此相邻,当插入和删除元素时须要移动大量的元素
元素的插入位置如图箭头所示均为合法操做,即插入位置 i 的取值为 0 <= i <= L.length;若头节点不存储元素,则第一个箭头的位置不能插入,i 的取值为 1 <= i <= L.length;
假设增长的元素插入位置在 y 和 h 之间,则 h 以后的元素须要所有后移一格,若是删除元素 h ,则 h 以后的元素须要所有前移一格,这个就是顺序表的缺点,即增长删除元素须要移动大量的元素;下面继续说插入元素的操做,元素插入位置合法操做成功,返回 1 ;操做失败,返回 0 ;
代码以下
int insertElem(SqList &L,int p,int e){ //插入目标表L,插入位置p,插入元素e 【注1】 int i; if(p<0 || p>L.length || L.length==maxSize){ //插入位置错误或者表长达到最大值, return 0; //此时插入不成功,返回0 } for(i=L.length-1;i>=p;--i){ //插入成功的操做 L.data[i+1] = L.data[i]; //从表尾开始依次把元素后移一个位置 【注2】 L.data[p] = e; //元素后移以后将待插入元素放入空出的位置, ++(L.length); //插入完成,表长度增长1 return 1; //返回1 } }
代码注释中有两个注,下面解释一下
【注1】在方法传入的参数中,传入的顺序表SqList &L;为何表L前加了一个&符号?这个&符号在c++中表示引用型,具体的用法就是在操做表的时候,若是操做后的表和操做前的表不同,那么传入的表前需加引用型,例如增长,删除,修改,表均发生了改变,这时就须要用引用型,而查询操做,不改变表,就不须要加&符。
**【注2】**在插入元素对插入位置后的元素后移时,须要从后往前操做,最后的元素后移一个位置,倒数第二个元素再移动位置,依次从后往前操做,不可从前日后操做,否则前一个元素会把后一个元素覆盖;而在删除操做中,删除的元素直接用后面的元素覆盖掉就能够完成删除操做了,这时须要从前日后前移一格,操做就完成了,删除后面讲。
还记得咱们以前说过的时间复杂度吗?他是衡量一个算法优劣的指标,如今咱们来分析一下该插入算法的时间复杂度。
最好状况:当在表尾插入元素时,元素不须要移动,找到位置插入就完事了,很简单,时间复杂度为O(1);
最坏状况:在表头插入,全部表元素(n个元素)均要后移一个位置,后移的代码语句要执行n次,时间复杂度为O(n);
那么在通常状况下,在表的任意位置插入,时间复杂度是多少呢?
从表头开始,在表头插入,需移动 n 个元素(n为表长);在第一个元素后插入,需移动 n-1 个元素;在第二个元素后插入,需移动 n-2 个元素;……;依次类推,在表尾插入,需移动n-n个元素,为0,即不须要移动元素;
那么在任意位置插入所需移动元素的平均次数即为该算法的时间复杂度
插入移动的总次数为 0+1+2+3+…+n = n(n+1)/2;
插入位置为n+1个
时间复杂度为计算结果为n/2,故而为O(n);
咱们在讲时间复杂度时说过,时间复杂度是关于问题规模的函数,上式的结果就是一个关于 n 的函数,时间复杂度取决于表长即 n 的大小,和其前面的系数二分之一没有关系,故而为O(n);
关于时间复杂度计算出的结果及其大小比较以下所示,了解便可
删除顺序表 L 的第 i 个元素,成功返回 true ,失败返回false;
其中i的取值为0 <= i <= L.length
bool ListDelete(SqList &L,int i){ //删除元素,表L要改变,故而使用引用型&符号 if(i<0 || i>L.length){ //判断i的范围是否合法,不合法返回false return false; } for(int j=i;j<L.length;j++){ //将第i个位置以后的元素前移 L.date[j-1] = L.data[j]; //同上文代码 } L.length--; //删除元素以后表长减一 return true; //操做成功返回true }
时间复杂度的计算同上
最好状况:删除表尾元素,无需移动元素,时间复杂度为O(1);
最坏状况:删除表头元素,除第一个元素外,其他元素均需前移,时间复杂度为O(n);
平均状况:删除第i个位置
移动次数为:0+1+2+3+…+(n-1)=n(n-1)/2;
移动的位置共有n个
故平均为(n-1)/2,仍为O(n);
以上是按位置删除元素,读者可自行写出按值删除元素的代码
查询顺序表L中第一个元素值为e的元素,并返回位序
int LocateElem(SqList L,ElemType e){ //查询不改变表结构,不须要引用型 for(int i=0;i<L.length;i++){ if(L.data[i] == e){ return i+1; //下标为i的元素,返回i+1 【注3】 }else return 0; } }
【注3】以前说过若是表头元素不存储,那么表的下标和位序是同样的,若是表头存储数据,那么下标为i,位序就为i+1;
时间复杂度的计算
最好状况:查找的元素就在表头,比较一次就成功,时间复杂度为O(1);
最坏状况:查找元素在表尾,比较次数即为表长n,时间复杂度为O(n);
平均状况:查找第i个位置
移动次数为:1+2+3+…+n=n(n+1)/2;
移动的位置共有n个
故平均为(n+1)/2,仍为O(n);