线性表
一.线性表的类型定义
(1)图片导言
(2)图片总结
二. 顺序存储(顺序表)
(1).顺序表[数组实现]
A.静态分配
a.缺点
1.顺序表的长度不可以调,存储空间是静态
2.如果刚开始MaxSize取超大数,导致空间浪费
b.各种操作
b1.定义struct与MaxSize
#define MaxSize 10 //定义最大长度
typedef struct
{
int data[MaxSize]; //静态数组存放元素
int length; //顺序表当前长度
}SqList; //顺序表的类型定义
b2.初始化InitList(SqList &L)
//基本操作---初始化一个单链表
void InitList(SqList &L)
{
for(int i=0;i<MaxSize;i++ )//因为数组下标从0开始 ,让数组长度为MaxSize即可 [0~MaxSize-1]
L.data[i]=0; //将所有数据初始化默认值0
L.length=0; //顺序表长度从0开始
}
b3.插入操作 ListInsert(SqList &L,int i,int e) + O(n)
时间复杂度
1.最好O(1)
2.最坏O(n)
3.平均O(n)
bool ListInsert(SqList &L,int i,int e)
{
if(i<1||i>L.length+1)return false; //i属于[1~length+1]
if(L.length >= MaxSize)return false; //当前存储空间已满,无法插入
for(int j=L.length ;j>=i;j--) //因为要后移,a[length]没有存元素,所j=length
L.data[j]=L.data[j-1]; //eg:i=3,下标=2,即将a[2] a[3]....后移为a[3] a[4]....,即i,下标i-1, L.data[i]=L.data[i-1]终止
L.data[i-1]=e; //i位置放入e
L.length ++; //长度加1
return true;
}
b4.删除操作ListDelete(SqList &L,int i,int &e) + O(n)
时间复杂度
最好O(1)
最坏O(n)
平均O(n)
//删除操作
bool ListDelete(SqList &L,int i,int &e)
{
if(i<1||i>L.length)return false; //删除[1~length]中的
e=L.data[i-1]; //删除元素赋值给e
for(int j=i ;j<L.length;j++)
L.data[j-1]=L.data[j]; //data[i-1]=data[i]将后边元素前移,删除3,下标2,即a[3]a[4]前移
L.length --; //长度减1
return true;
}
//此时需要注意下 e=-1; ListDelete(L,1,e)
int main()
{
SqList L;
InitList(L) ;
int e=-1;
if(ListDelete(L,1,e)) { }
else { }
return 0;
}
b5(a4).按位查找操作get(SqList L,int i) + O(1)
int get(SqList L,int i)
{
//if(i<1||i>length) 根据实际而定,增强代码健壮性
return L.data [i-1];
}
b6(a5 ).按值查找操作LocateElem(SeqList L,int e)+ O(n)
B.动态分配
a.各种操作
a1.定义struct与MaxSize
#define InitSize 10 //默认最大长度
typedef struct
{
int *data; //指向第一个元素的地址
int MaxSize; //顺序表最大长度
int length; //顺序表当前长度
}SeqList;
a2.初始化InitList( SeqList &L)
//初始化顺序链表
void InitList( SeqList &L)
{
//使用malloc函数申请一片连续的空间
L.data = (int *)malloc(InitSize*sizeof(int));
L.length=0;
L.MaxSize=InitSize;
}
a3.增加动态数组长度IncreaseSize(SeqList &L,int len)
//增加动态数组长度
void IncreaseSize(SeqList &L,int len)
{
int * p=L.data; //p和L.data指向同一位置
L.data =(int *)malloc((L.MaxSize +len)*sizeof(int));//重新开辟L.data连续空间,长度=最大长度+增加长度
for(int i=0;i<L.length ;i++)
{
L.data[i]=p[i];//进行复制,因为L.data是新空间,p是L.data的原来空间,所以可以赋值
}
L.MaxSize=L.MaxSize +len;//更新最大长度
free(p);//p是中间变量,现在可以free掉
}
a4(b5).按位查找操作get(SeqList L,int i) + O(1)
int get(SeqList L,int i)
{
//if(i<1||i>length) 根据实际而定,增强代码健壮性
return L.data [i-1];
}
a5(b6).按值查找操作LocateElem(SeqList L,int e) + O(n)
注意:结构体
c语言不可==比较
c++可以重载运算符
时间复杂度
最好O(1)
最坏O(n)
平均O(n)
int LocateElem(SeqList L,int e)
{
for(int i=0;i<L.length;i++)
if(L.data[i]==e)return i+1;//成功返回i+1
return 0; //查找失败
}
a6.插入操作ListInsert(SqList &L,int i,int e) + O(n)
//代码注意,伪代码
bool ListInsert(SqList &L,int i,int e)
{
if(i<1||i>L.length+1)return false; //i属于[1~length+1]
if(L.length >= MaxSize) {/*具体操作申请空间*/}; //当前存储空间已满,申请空间
for(int j=L.length ;j>=i;j--) //因为要后移,a[length]没有存元素,所j=length
L.data[j]=L.data[j-1]; //eg:i=3,下标=2,即将a[2] a[3]....后移为a[3] a[4]....,即i,下标i-1, L.data[i]=L.data[i-1]终止
L.data[i-1]=e; //i位置放入e
L.length ++; //长度加1
return true;
}
a7.删除操作ListDelete(SqList &L,int i,int &e) + O(n)
//代码注意,伪代码[应该可用,试过了,没报错]
bool ListDelete(SeqList &L,int i,int &e)
{
if(i<1||i>L.length)return false; //删除[1~length]中的
e=L.data[i-1]; //删除元素赋值给e
for(int j=i ;j<L.length;j++)
L.data[j-1]=L.data[j]; //data[i-1]=data[i]将后边元素前移,删除3,下标2,即a[3]a[4]前移
L.length --; //长度减1
return true;
}
C.图片总结
c1==静态[b1定义,b2初始化]+动态[a1定义,a2初始化,a3增加动态数组]
c2==静态[b3插入,b4删除]
c3==静态[b5按位,b6按值]+动态[a4按位,a5按值]
三.链式存储(链表)
(1)单链表
A.两种单链表(是否带头结点)-----图片
d1.定义单链表typedef struct LNode
//定义
typedef struct LNode //定义单链表节点类型
{
ElemType data;//节点存放的数值
struct LNode* next;//指向下一个节点
}LNode ,*LinkList;
//原因
typedef struct LNode {ElemType data;struct LNode* next;} LNode;
typedef struct LNode LNode
typedef struct LNode* LinkList;
//区别
LNode * 强调一个节点
LinkList 强调链表
d2.初始化InitList(LinkList &L)+判断空Empty(LinkList L)======不带头结点
L—>NULL
//初始化
bool InitList(LinkList &L)
{
L=NULL;//防止脏数据
return true;
}
//判断链表是否为空
bool Empty(LinkList L)
{
if(L==NULL)return true;
else return false;
}
//或者
bool Empty(LinkList L)
{
return (L==NULL);
}
d3.初始化InitList(LinkList &L)+判断空Empty(LinkList L)========带头结点
L—>【data,next】 ; next=NULL
//初始化
bool InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode));//分配一个头节点
if(L==NULL) return false;//内存不足,分配失败
L->next =NULL; //头节点之后还没有节点,头节点不存储任何数据
return true;
}
//判断是否空
bool Empty(LinkList L)
{
if(L->next ==NULL)return true;
else return false;
}
B.插入操作------图片
e1.按位插入(带头结点)istInsert (Linklist &L,int i, ElemType e) + O(n)
bool ListInsert (Linklist &L,int i, ElemType e)
{
if(i<1)return false; //i取值>0
LNode* p;//指向当前扫描的结点
int j=0;//记录p指向的第几个结点
p=L; //c此时的L不知道指向谁
while(p!=NULL&&j<i-1)//循环找到第i-1个点
{
p=p->next ;
j++;
}
if(p==NULL)return false;
LNode* s=(LNode*)malloc(sizeof(LNode));
s->data =e;
s->next = p->next ;
p->next =s;
return true;
}
e2.按位插入(不带头结点)istInsert (Linklist &L,int i, ElemType e) + O(n)
bool ListInsert (Linklist &L,int i, ElemType e)
{
if(i<1)return false; //i取值>0
//区别是当i==1时=============================================
if(i==1)
{
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next =L;
L = s;
return true;
}
LNode* p;//指向当前扫描的结点
int j=1;//记录p指向的第几个结点 =========================
p=L; //c此时的L不知道指向谁
while(p!=NULL&&j<i-1)//循环找到第i-1个点
{
p=p->next ;
j++;
}
if(p==NULL)return false;
LNode* s=(LNode*)malloc(sizeof(LNode));
s->data =e;
s->next = p->next ;
p->next =s;
return true;
}
e3.指定结点的后插操作InsertNextNode (LNode *p,ElemType e) + O(1)
//在p结点后插入元素e
bool InsertNextNode (LNode *p,ElemType e)
{
if(p==NULL)return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==NULL) return false;//内存分配失败
s->data =e;
s->next =p->next ;
p->next =s;
return true;
}
e4.指定结点的前插操作 O(1)或O(n)
1.InsertPriorNode (LNode *p,ElemType e) ,O(1)
传入一个结点,只能向后操作,不能够找到前一个结点
2.InsertPriorNode (Linklist L,LNode * p, ElemType e)
传入L头指针,可以一直向后,O(n)
巧妙操作:方法1进行优化,把e,x互换
//O(1)
bool InsertPriorNode(LNode *p,ElemType e)
{
if(p==NULL)return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==NULL)return false;//内存分配失败
s->next =p->next ;
p->next =s;
//关键两步 ,将值互换即可
s->data =p->data ;
p->data =e;
return true;
}
C.删除操作------图片
f1.按位序删除(带头结点)ListDelete (Linklist &L,int i, ElemType &e) + O(n)
bool ListDelete (Linklist &L,int i, ElemType &e)
{
if(i<1)return false; //i取值>0
LNode* p;//指向当前扫描的结点
int j=0;//记录p指向的第几个结点
p=L; //c此时的L不知道指向谁
while(p!=NULL&&j<i-1)//循环找到第i-1个点
{
p=p->next ;
j++;
}
if(p==NULL)return false;//i值不合法
if(p->next==NULL)return false; //i-1个结点后无其他点
LNode* q;
q = p->next ; //令q指向被删除结点
e=q->data ; //用e返回值
p->next =q->next ;//将q断开
free(q); //释放空间
return true;
}
f2.指定结点的删除 DeleteNode (LNode *p) + O(1)
巧妙操作
//若p是最后一个结点,则会有bug,回扣分1~2
bool DeleteNode (LNode *p)
{
if(p==NULL)return false;
LNode *q = p->next;//令q指向p的后继节点
p->next =p->next ->data;//和后继节点交换数据域
p->next =q->next ;//将q断开
free(q); //释放空间
return true;
}
D.查找操作-------图片
g1.按位查找GetEiem(LinkList L,int i) + O(n)
//按位查找,返回第i个元素(带头结点)
LNode* GetEiem(LinkList L,int i)
{
if(i<1)return false; //i取值>0
LNode* p;//指向当前扫描的结点
int j=0;//记录p指向的第几个结点
p=L; //c此时的L不知道指向谁
while(p!=NULL&&j<i)//循环找到第i个点
{
p=p->next ;
j++;
}
}
g2.按值查找LocateEiem(LinkList L,elemtype i) + O(n)
LNode* LocateEiem(LinkList L,elemtype i)
{
LNode *p = L->next;
//从第一个结点开始查找数据域为e的结点
while(p!=NULL&&p->data !=e)
p=p->next;
return p; //返回该点指针或NULL
}
g3.求链表长度Length(LinkList L) + O(n)
//求链表长度 O(n)
int Length(LinkList L)
{
int len=0;
LNode* p=L;
while(p->next !=NULL)
{
p=p->next ;
len++;
}
return len;
}
E.建立单链表
h1.尾插法建立操作List_Taillnsert(LinkList &L) + O(n)
LinkList List_Taillnsert(LinkList &L)
{
int x;
L=(LinkList)malloc(sizeof(LNode));//建立头结点
LNode *s,*r=L; //r为表尾指针
scanf("%d",&x);
while(x!=-1) //数据-1输入结束
{
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s; //r指向表尾结点
scanf("%d",&x);
}
r->next=NULL; //尾结点指针置空
return L;
}
h2.头插法建立操作List_headInsert(LinkList &L) + O(n)
//逆向创建单链表
LinkList List_headInsert(LinkList &L)
{
LNode *s;
int x;
L=(LNode*)malloc(sizeof(LNode));//创建头结点
L->next=NULL;//初始为空链表
scanf("%d",&x);
while(x!=-1)
{
s=(LNode*)malloc(sizeof(LNode));//创建新结点
s->data=x;
s->next=L->next;
L->next=s; //将新节点插入表中,L为表头
scanf("%d",&x);
}
return L;
}
(2)双链表----------图片
A.定义typedef struct DNode
typedef struct DNode
{
ElemType data;
struct DNode *prior,*next;
}DNode ,*DLinkList;
B.初始化InitDLinkList (DLinkList &L)
NULL<-----【 prior | data | next 】------>NULL
bool InitDLinkList (DLinkList &L)
{
L=(DNode*)malloc(sizeof(DNode));
if(L==NULL)return false;//分配失败
L->prior =NULL;//头结点prior永远指向NULL
L->next =NULL;//头结点之后暂时没有其他结点
return true;
}
C.插入(后插操作)InsertNextDNode(DNode *p,DNode *s) + O(1)
//在p之后插入s
bool InsertNextDNode(DNode *p,DNode *s)
{
if(p==NULL||s==NULL)return false;//非法参数
s->next=p->next ;
if(p->next !=NULL)//p有后继结点
p->next ->prior=s;
s->prior =p;
p->next =s;
return true;
}
D.删除(后删)DeleteNextDNode(DNode *p) + O(1)
按位删除,前删都可转化为后删
//删除p结点的后继结点
bool DeleteNextDNode(DNode *p)
{
if(p==NULL)return false; //非法参数
DNode *q=p->next ; //找到p的后继节点q
if(q==NUll)return false ;//p没有后继结点
p->next =q->next ;
if(q->next !=NULL) //q不是最后一个结点
q->next ->prior=p;
free(q); //释放空间
return true;
}
E.销毁操作DestoryList(DLinkList &L)
void DestoryList(DLinkList &L)
{
while(L->next !=NULL)
DeleteNextDNode(L)
free(L);//释放头结点内存
L=NULL;//头指针指向NULL
}
F.遍历 + O(1)
1.后向遍历
while(p!=NULL)
{
//其他操作
p = p->next;
}
2.前向遍历
while(p!=NULL)
{
//其他操作
p = p->prior;
}
3.前向遍历(跳过头结点)
while(p->prior!=NULL)
{
//其他操作
p = p->prior;
}
(3)循环链表--------图片
A.循环单链表
1.循环单链表初始化+定义
//定义
typedef struct LNode
{
ElemType data;
struct LNode* next;
}LNode ,*LinkList;
//初始化
bool InitList(LinkList &L)
{
L=(LNode* )malloc(sizeof(LNode));
if(L==NULL)return false;//分配失败
L->next = L;//关键,头结点next指向头结点
return true;
}
2.循环单链表是否为空
bool Empty(LinkList L)
{
if(L->next ==L)return true;//条件L->next ==L
else return false;
}
3.p结点是否表尾结点
bool IsTail(LinkList L,LNode *p)
{
if(p->next ==L)return true;
else return false;
}
4.插入+删除(无代码,自己操作)
经常使用表头或表尾可用循环单链表
B.循环双链表
1.循环双链表初始化+定义
//定义
typedef struct DNode
{
ElemType data;
struct DNode *prior,*next;
}DNode ,*DLinkList;
//初始化
bool InitDLinkList (DLinkList &L)
{
L=(DNode*)malloc(sizeof(DNode));//分配头结点
if(L==NULL)return false;//分配失败
L->prior =L;//头结点的prior指向头结点
L->next =L; //头结点的next指向头结点
return true;
}
2.循环双链表是否为空
bool Empty(DLinkList L)
{
if(L->next == L)return true;
else return false;
}
3.p结点是否表尾结点
bool IsTail(DLinkList L,DNode *p)
{
if(p->next ==L)return true;
else return false;
}
4.插入操作InsertNextDNode(DNode *p,DNode *s) + O(1)【与双链表插入类似】
与双链表插入类似,但不需要特判:p的后继节点为NULL的情况
//P结点之后插入结点
bool InsertNextDNode(DNode *p,DNode *s)
{
s->next=p->next ;
//if(p->next!=NULL)不用判断
//循环链表p->next会指向头结点
p->next ->prior=s;
s->prior =p;
p->next =s;
}
5.删除操作 DeleteNextDNode(DNode *p) +O(1)【与双链表删除类似】
与双链表删除类似,但不需要特判:p的后继节点为NULL的情况
//删除p结点的后继结点
bool DeleteNextDNode(DNode *p)
{
DNode *q=p->next ; //找到p的后继节点q
p->next =q->next ;
q->next ->prior=p;
free(q); //释放空间
}
(4)静态链表(考差很少,代码实现也少)
1.定义
静态链表:用数组的方式实现的链表
方法1.高级见名思意
#define maxsize 10
typedef struct
{
int data;
int next;
}slinklist[maxsize];
int main()
{
//强调a是链表且最大长度为maxsize
slinklist a;
}
方法2.低级些,但效果相同
struct node
{
int data;
int next;
};
int main()
{
//slinklist定义“一个长度为10的node型a数组”
struct node a[maxsize];
}
验证:
2.初始化
将头结点a[0]的next设为-1,-1与null意义相同
3.查找 + O(n)
按位查找 O(n)
从头结点出发挨个往后遍历结点
4.插入
前提头结点的next=-1,空结点的next=-2
1.顺序扫描静态链表,找到一个空结点,存入元素(如何判断是否是空结点:找到next=-2的结点)
2.找到位序i-1的结点
3.新节点的next=i-1的next
4.修改i-1的next
5.删除
1.从头结点出发找到前驱结点
2.修改前驱节点的游标
3.被删除的结点next记作-2
6.优缺点
优点:增,删 操作不需要移动大量元素
缺点:不能随机存取,只能从头结点往后查找;容量固定不变
四.顺序表VS链表
(1)逻辑结构
都属于线性表,都属于线性结构。
(2)存储结构
顺序表:(顺序存储)
优点:支持随机存取,存储密度高
缺点:大片连续空间分配不便,改变容器不方便
链表:(链式存储)
优点:离散的小空间分配方便,改变容器方便
缺点:不可随机存取,存取密度低
(3)基本操作(创销,增删改查)
A.创(链表优)
B.销
C.增+删(链表优)
D.查(顺序表优)
(4)总结 + 简答技巧