线性表的特点是数据元素仅具有单一的前驱和后继关系,在一个线性表中数据元素必须是相同的。
一、顺序表
顺序存储是在内存中用地址连续的一块存储空间顺序存放线性表各元素。→顺序表是静态存储分配,需事先确定好容量;存取每一个元素时间相同,为O(1);知道首地址和每个数据元素占存储单元个数可以求任意数据元素的地址,实现随机存取。
辨析—顺序表与数组的区别:1.顺序表是在计算机内存中以数组的形式保存的线性表 2.数组是物理存储结构,除了用数组实现顺序表,同样可以用数组实现二叉树、队列等结构;顺序表是逻辑结构,强调数据在逻辑上是连续的一条直线,并且可以定义表中元素个数、表空间大小等变量 3.数组长度不可变,线性表长度是动态可变的
操作算法:
类模板、无参构造(创建空表length=0)、有参构造(创建长度为n元素数组a[]中元素 length=n;for(i=0;i<n;i++) data[i]=a[i];)、求长度(return length;)、按位查找(return data[pos-1];)、按值查找(将待查找的值item与顺序表中元素依次进行比较 for(i=0;i<length;i++) if(data[i]==item) return i+1; else return 0;)、
遍历(细节注意-打印元素间要空格比较好看)
插入:
template<class T,int Maxsize>
void Seqlist<T,Maxsize>::Insert(int i,T item)
{
if(length>=Maxsize)
{
cerr<<"上溢";
exit(1);
}//1.检查存储空间是否达到最大值,若是则停止插入
if(i<1||i>length+1)
{
cerr<<"插入位置非法";
exit(1);
}//2.检查插入位置是否合法,若不合法则停止插入
for(j=length-1;j>=i;j--)
{
data[j+1]=data[j];
}3.从最后一个元素向前直到第i个元素为止,将每个元素均后移一个存储单元,从而将第i个元素空出来。
data[i-1]=item;//4.将元素item写入到第i个元素,即下标为i-1的位置
length++;//5.顺序表长度加1
}
——分析:总共需要移动n-i+1个元素,平均移动数据元素的次数为n(n+1)/2,每个数据被移动的概率为1/(n+1),总的为n/2,时间复杂度为O(n))
删除:
template<class T,int Maxsize>
void Seqlist<T,Maxsize>::Delete(int i)
{
if(length=0)
{
cerr<<"下溢";
exit(1);
}//1.检查顺序表是否为空若是则抛出下溢异常
if(i<1||i>length)
{
cerr<<"删除位置非法";
exit(1);
}//2.检查删除位置是否合法
x=data[i-1];//3.将表中第i个元素取出
for(j=i;j<length;j++)
data[j-1]=data[j];//4.从第i个元素(下标为i,j记录下标位置)向后到最后一个元素,每个元素均前移一个存储单元
length--;//5.将线性表长度减一
return x;
}
——分析:总共需要移动n-i个元素,平均移动数据元素的次数为n(n-1)/2,每个数据被移动的概率为1/n,总的为(n-1)/2,时间复杂度为O(n)
例题1:从顺序表中删除具有给定值x的所有元素。
法一:遍历顺序表以找到x,删除并覆盖,然后循环操作
核心代码如下:
for(i=1;i<=length;i++)//i、j都是元素的序号,代表第几个元素
if(x==data[i-1])
{
for(j=i;j<length;j++)
data[j-1]=data[j];
length--;
}
}
法二:依次一边遍历,一边覆盖(也可以理解成遍历的时候把不是x的元素的挑出来排好)
int k=0;
for(i=1;i<=length;i++)
{
if(data[i-1]!=x)
{
data[k]=data[i-1];
k++;
}
}
法三:先遍历完并用计数器记录x的个数,每个x后面的元素向前移动它前面总共的x的数量
int count=0;
for(i=1;i<=length;i++)
{
if(x==data[i-1])
count++;
else
data[i-count-1]=data[i-1];
}
length=-count;
例题2:从顺序表中删除所有值重复元素,使所有值不同。
解:注意的是这个顺序表是排过序的,比如“11222334566”是有序的或者说值相邻重复,然后经过这个算法之后变成“123456”。
法一:把不同的挑出来,即一边遍历一边覆盖掉重复的
int i,j; //i存储第一个不相同的元素,j为工作指针
for(i=0,j=1;j<length;j++)
if(data[i]!=data[j]) //查找下一个与上个元素值不同的元素
data[++i]=data[j]; //找到后就将元素前移
length = i+1; //因为i是从0开始的
法二:引入k记重复个数
int i,int k=0;//k记录重复元素个数
for(i=1;i<=length;i++)
{
if(data[i-1]==data[i])
k++;
data[i-k]=data[i];
}
length=length-k;
二、链表
(1)单链表是线性表链接存储结构,其存储思想是以一组任意的(连续、不连续、零散分布)存储单元存放线性表的元素。
判断题:线性表的逻辑次序与存放次序总是一致。(×)单链表元素之间的逻辑次序用指针来实现,其逻辑次序和物理次序并不一致。
单链表及其实现:
结点(struct Node{ T data; Node<T>*next};)、类模板、无参构造(head=new Node<T>;head->next=NULL;)、有参构造(创建含有n个元素的a[],考虑引入指针rear作为后继采用尾插与输入顺序相同: head=new Node<T>; rear=head;for(i=0;i<n;i++){ s=new Node<T>;s->data=a[i];rear->next=s;rear=s;} rear->next=NULL;)、
插入
尾插:s->next=p->next;p->next=s;
头插:q=head;while(q->next!=p) q=q->next;s->next=q->next;q->next=s;
求长度(从第一个数据结点开始扫描并用计数器记录 num=0;p=head->next;while(p){p=p->next; num++;} return num;)、
查找第i个元素位置(只能从头开始访问,并用计数器记录 p=head;j=0;while(p&&j<i-1){p=p->next;j++;} 直到找到j=i-1位置) p为头结点j取0;p初始化指针j取1.
按位查找(p=head->next;j=1;while(p&&j<pos) {p=p->next;j++;} if(!p||j>pos){ cerr<<"查找位置非法";exit(1);} else return p->data;)
例题:
解:
按值查找(p=head->head;j=1; while(p&&p->data!=item){p=p->next;j++;} if(p) return j;else return 0;)
遍历(p=head->next;//初始化 while(p){cout<<p->data<<" "<<endl; p=p->next;})
删除(p=q->next;//找到p的前驱结点 x=q->data; p->next=q->next;delete q; return x;)
析构(在释放之前必须有前驱(紧密链接、抓住它),所以不断地先往后指 p=head;while(p){q=p;p=p->next;delete q;} head=NULL;)
逆置(难点、经典例题、考研热门)
template<class T>
void LinkList<T>::Invert()
{
p=head->next;
head->next=NULL;//1.创建新的链表,初始化为空表
while(p!=NULL)
{
q=p;
p=p->next;//2.遍历原链表的结点
q->next=head->next;
head->next=q;//3.采用头插法,能实现和输入顺序相反的排放
}
}
辨析:链表的操作中数据域不动,只动指针。有人选择把数据放到数组中在依次输入实现逆置,是错误的违背链表“数据不搬家”的初衷。
核心理解:把链表的第一个结点和第二个结点断开(带头结点就把头结点断开,不带头结点就把存放第二个数据的指针断开),把第二个结点开始作为独立的链表p,扫描p链表取第一个元素,并用头插法插入元素
时间复杂度T=O(n)
例题:
解:
插入排序
template<class T>
void LinkList<T>::Sort()
{
p=head->next;
head->next=NULL;//1.创建新的链表,初始化为空表
while(p != NULL)
{
r = p->next;
q=p;
while(q->next !=NULL && q->data > q->next->data)
q = q->next;
p->next = q->next;
q->next = p;
p = r;
}
}
核心理解:从逻辑上将链表分为两个链表,一个链表默认有序(开始时只含有一个有效节点),另一个为无序链表,每次循环从无序链表拿出一个元素将其插入有序链表中
(2)循环链表:将单链表中最后一个结点的指针域指向头结点(也是判断条件),可以实现从任意结点遍历链表,注意结点中只有一个指向后继结点的指针next,故找后继的时间复杂度为O(1),找前驱还是只能顺着各结点的next进行复杂度为O(n)
(3)双向链表:图
插入(s->prior=p; s->next=p->next; p->next->prior=s; p->next=s;)
删除(p->prior->next=p->next; p->next->prior=p->prior;)注意步骤不可颠倒,先抓住后继再删去!
三、顺序表和链表比较:
优缺点:
选择:
结合: