二.线性表

线性表

一.线性表的类型定义

(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)总结 + 简答技巧

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿斯卡码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值