-
线性表:n个数据元素的有限序列,表中数据元素个数被称为线性表的长度,长度为0则是空表。
【抽象性】【(类型)相同性】【有限性】
-
任意相邻元素之间存在序偶关系,即前驱与后继。
顺序表【静态存储分配&动态存储分配】
- 线性表的顺序存储结构:用一段地址连续的存储单位依次存储线性表的数据元素。
- 计算任意一个元素的存储地址的时间是相等的,具该特点的存储结构为随机存取。
- 用一维数组实现顺序表->必须确定存放线性表的数组空间的长度。
构造顺序表
- 模板类定义 使用2个参数ver
template <typename T>
class SeqList{
private::
T data[100];
int length;
}
或者
private:
T *elem; //存储空间基址
int length;
- 构造函数
template <typename T>
SeqList<T>::SeqList(T a[],int n){
if(n>100)throw "参数非法";
for(int i=0;i<n;i++){
data[i]=a[i];
}
length=n;
}
或者
{
this->elem=(T *)malloc(sizeof(T)*n);
if(!this->elem)throw "参数非法";
this->length=n;
}
-
析构函数:
对于第一种:**静态存储分配,**顺序表变量退出作用域时自动释放该变量占用的内存空间,故无需销毁,析构函数为空
第二种:
free(this->elem); this->elem=nullptr; this->length=0;
-
判空函数
看length是否为0
-
遍历函数
template <typename T>
vpid SeqList<T>::PrintList(){
for(int i=0;i<length;i++){
cout<<data[i]<<" ";
}
cout<<endl;
}
或者
void SeqList<T>::printList(void (*print)(T &elem))
{
for(int i=0;i<this->length;i++)
print((this->elem)[i]);
cout<<endl;
}
- 查找函数
第一种按位置寻找看下标,按值寻找遍历;
第二种同理
- 插入函数
因为表定义长度为100,所以有可能有空余位置,有则把插入元素后的元素后移
template <typename T>
void SeqList<T>::Insert(int i,T x){
if(length==100)thorw"上溢";
if(i<1||i>length+1)throw"插入位置错误";
for(int j=length;j>=i;j--){
data[j]=data[j-1];
}
data[i-1]=x;
length++;
}
或者
{
if(this->length==100){
T *newelem=(T *)realloc(this->elem,(this->length+1)*sizeof(T));
if(!newelem)throws "Error allocating memory!"<<endl;
}
//像上面那样根据情况赋值
for(int j=this->length;j>=i;j--){
(this->elem)[j]=(this->elem)[j-1];
}
(this->elem)[i-1]=elem;
this->length+=1;
}
- 删除函数
第一种把插入反过来
//throws 1.length=0 2.i<1||i>length
elem=(this->elem)[i-1];
for(int j=i-1;j<this->length-1;j++){
(this->elem)[j]=(this->elem)[j+1];
}
this->length-=1;
单链表
- 为何设立头结点:无论单链表是否为空,头指针始终指向头结点,统一空表和非空表的处理。
构造单链表
- 结点定义
template <typename T>
struct Node{
T data;
Node<T> *next;
}
class LinkList{
private:
Node<T>*first;
}
-
构造函数
-
初始化——无参构造
template <typename T> LinkList<T>::LinkList(){ first7=new Node<T>; //生成头结点 irst->next=nullptr; //头结点 }
-
头插法:把申请的结点插在头结点后面
LinkList<T>::LinkList(T a[],int n){ first=new Node<T>; first->next=nullptr; for(int i=0;i<n;i++){ s=new Node<T>; s->data=a[i]; //把s插入到头结点后面 s->next=first->next; first->next=s; } }
-
尾插法:把申请的结点插在终端结点后面,故设立尾指针指向当前的终端结点
{ first=new Node<T>; Node<T>*r=first,*s=nullptr; for(int i=0;i<n;i++){ s=new Node<T>; s->data=a[i]; r->next=s; r=s; } r->next=nullptr; }
-
-
判空函数
判断单链表是否只有头结点==first->next是否为空
-
遍历函数
⭐工作指针p后移不能写成p++:存储单元可能不连续,p++不能一定保证p指向下一个结点。
template <typename T>
void LinkList<T>::PrintList(){
//工作指针p初始化
Node<T>*p=first->next;
while(p!=nullptr){
cout<<p->data<<"\t";
p=p->next;
}
cout<<endl;
}
-
求长函数
结合遍历函数。
-
寻找函数
按值寻找与按位均是遍历
//按位置: if(p==nullptr)throw "position wrong";
按位寻找时间复杂度O(n),所以为顺序存取结构
-
插入函数
时间花费在查找正确插入位置,故复杂度为O(n)
{
Node<T>*p=first->next;
int count=0;
while(p!=nullptr&&count<i-1){
p=p->next;
count+=1;
}
if(p==nullptr)throws "wrong position"
else{
s=new Node<T>;
s->data=x;
//把s插入到p后面
s->next=p->next;
p->next=s;
}
}
-
删除函数
删除第i个结点:前驱节点p存在且p不为终端结点
(int i) { T x; Node<T> *p=first,*q = nullptr; int count=0; //找第i-1个结点 if(p==nullptr||p->next==nullptr)throws "wrong position" //p不存在或p后继结点不存在 else{ q=p->next; x=q->data; p->next=q->next; delete q;//把q摘下来 rertun x;//返回q的值 } }
-
析构函数
由于动态申请,故需要释放单链表的空间
重复操作first=first->next;delete p;p=first;
双链表
可以快速确定单链表中任意结点的前驱结点
- 插入操作-修改四个指针
p后面插入s
s->prior=p;
s->next=p->next;
p->next->prior=s;
p->next=s;
- 删除操作-吞掉中间的结点
删除p
p->prior->next=p->next;
p->next->prior=p->prior;
循环链表-头尾相接
没有明显尾端,可能死循环:判断用作循环变量的工作指针是否等于头指针/尾指针/……(p!=first)