线性表 顺序表和链表入门和原理探究

线性表

线性表分为顺序表和链表

顺序表

逻辑上相邻的数据元素存储在相邻的物理存储单元中。
可以根据初始地址、单位长度和序列号而直接找到要查找的元素

顺序存储结构

#define maxsize 
typedef struct    //定义 (一个新的) 结构体 (数据类型) 
{
 	ElemType  elem[maxsize];  //这里的ElemType指的是int float等
 	 int length;		//
 }SeqList;    // 叫SeqList         
SeqList  L;或者//  创建一个 SeqList数据类型的变量L  (类似  int  L)
SeqList  *L   // 创建一个 SeqList数据类型的指针类型的变量L  (类似  int  *L)

顺序表的增删改查

查找操作
	1.按照序号查找  L.elem(i):
	结构体类型的变量  L 中的数据可以 “.”出来 使用  例如 L.elem[1]
	结构体类型的指针变量  *L 中的数据则用“->”取出使用  例如L->elem[1]
	2.按照内容查找
	思路:简单遍历
int 函数名( Seqlist L,ElemType x)
{
	int i=1;//初始化循环变量		  顺序表的下标是从1开始的这也是和数组的一个区别									
	while((i<=L.length)&&(L.elem[i]!=x))  // 循环和判断相等放在一起简化代码  而将判断是否相等放在后面可以防止越界
	i++;
	if(i<L.length)  return (i)
	else 			  return(-1)//未找到,返回一个一定不在表中的数据
} 		
插入操作
#define OK 1
#define  ERROR 0
int inslist(SeqList *L ,int i,ElemType x)//参数  *L:结构体变量的指针形式 i: 插入位置(第1、2、3...个)   x待插入的元素
{
	int k;
	if((i<1)||i>L->length+1)//  之所以有i>L->length +1 是为了保证顺序表以顺序存储,不会出现NULL间隔,这也是顺序表和数组的不同之处,当然仅从数据类型上看是可以的,但是这样就不符合顺序表这一数据结构的定义了
	{
	printf("插入位置i不合法")return (ERROR);//宏定义后,增加了代码的可读性 
	}
	if(L->length>=maxsize-1);//这里的maxsize-1 是因为其在内部的L->element[maxsize]依然是一个数组
	{
	printf("表已满")return (ERROR);//宏定义后,增加了代码的可读性 
	}
	for(k=L->length;k>=i;k--)
	{
	L->elem[k+1]=L->elem[k];			//循环移位,将第i位腾出了
	}
	L->elem[i]=x;  //终于可以插入了
	L->length++;//插入后,还要讲长度加一
	return(OK);//终于结束了
}
删除操作
int DelList(SeqList *L ,int i, ElemType *e)//只是删除的话,移位补上空位即可,而加入的第三个指针类型的参数是为了取出被删除的元素
{
	int k;
	if((i<)||i>->length)
	{
	printf("删除位置不合法")return(ERROR);
	}
	*e=L->elem[i];//取出被删除的元素      前期处理/手动滑稽
	for(k=i;k<=L->length-1;k++)//移位    覆盖
	{
		L->elem[k]=L-elem[k+1];
	}
	L->length--;//  长度减一  算是后期处理吧
	retuen (OK);
}

优缺点总结

优点:
1.无须为表示节点间的逻辑关系而增加额外的存储空间 (就是 逻辑结构简单 按顺序放就完了,不然为啥要叫顺序表😃 )
2.可以根据索引 随机方便的 存取任意一个元素
缺点:
1.插入删除不方便 😂 显而易见 每一次插入和删除都要都要把操作位 后面的元素都移动一次 ,简直够了
2.内部存储数据的实际上就是一个数组,所以当然和数组一样都是在编译时就为之分配了确定的内存大小 无法随意扩展 😂哎,静态存储 的老毛病了

链表 😅

采用链式存储结构的线性表被称为链表
  • 实现角度:1
    - 静态链表: 兼具链表和线性表的有点,采取静态存储,就是创建一个 自定义的结构体类型的数组 数组中每个元素都存储了两个数 data && next (当前数据,和下一关数据的数组下标)
    - 动态链表
    1😗* 一般而言考试是不会考的 **
  • 连接方式
    - 单链表
    - 双向链表
    - 循环链表

单链表

单链表的存储

typedef struct Node
{
		ElemType data ;
		struct Node  *next;
}LNode,*LinkList;

在这里讲一下的结构体的定义吧


一般而言,结构体最简单的定义方法是
struct   类型名{
成员列表;
成员列表;
....
};
然后就可以使用  *struct  类型名 变量名* 去定义一个自定义变量了;
还可以
struct   类型名{
成员列表;
成员列表;
....
}变量1,变量2;
最后 因为每一次 使用自定义类型去定义一个变量时都要加上一个struct 很麻烦
所以 为他取了一个别名
即 
typedef struct Node(自定义的数据类型)
{
		ElemType data ;
		struct Node  *next;
}LNode(等价于struct Node),*LinkList (等价于   *(struct  Node)) ;
这样就可以简化操作了。
单链表的插入

在这里插入图片描述

思路:

1.找到待插入的位置。动态链表采用的是动态分配的地址空间,不能根据索引直接获得待插入节点前后的地址,所以需要从头指针开始遍历链表,直至遍历到第i-1个节点。1
2.将第i-1个的节点的next的值赋给指向新的节点的next值 //这一步必须是在前面,23颠倒就找不到后面的节点了将i-1的节点的next值指向当前的新的节点,哎呦😂好像忘记些什么了。

我们还没有申请节点呢,哪来的待插入节点的地址值。/流汗
所以在第三步之前的某一个位置,我们需要先申请一个节点空间。
LNode *s;
s=(Node*)malloc(sizeof(Node));//应对考试的话,只记住要这样申请一下才可以使用就好了,下面的是我自己给出的一些扩展,可以了解一下。如果弄懂了的话,以后就不容易忘了。
这段语句中有一各malloc函数:这是一个申请节点空间的函数,它的参数是要申请的节点空间的大小
它的返回值是一个指向这个空间的指针
还有一个强转(Node *)将指向申请到的空间的指针强转为Node类型的指针,所以即使不阅读源码我们也可以猜到,当初C语言的设计者对于指针的设计使其同时可以存储两个信息,一个是指针所指向的空间,还有一个是空间的分配[^2]
[^2]:即申请到的空间是如何分块的,每一块有多大,举个例子: 比如指定一个数组类型的指针变量 
int *  a[10];   那么a就是这个指针变量,他其中存储的是这个数组的首地址,还有每个元素所占的内存大小。
void InsList(LinkList L, int i, ElemType e)	//参数接析:L  LinkList 类型的指针,存储有链表首地址,i 要插入的位置, 	 e 要插入的元素
{
 	LNode *pre,*s; 		//pre 用于上面的第一步,找到需要插入的位置之前的节点,s 即将申请的新节点
 	int k=0;
 	pre =L;
 	while(pre!=NULL&&k<i-1//循环定位
 	{
			pre=pre->next;
			  k=k+1;
	}
	if(k!=i-1)  //就是遍历完了没找到,判断是否是越界才跳出循环
	{
	printf("查入位置不合理");
	return ;是的话,就可以直接( ^_^ )/拜拜了
	}
	//终于定完位了,很快哈,我们可以开始 插入了
	s=(Node *)malloc(sizeof(Node));//申请节点
	s->data = e;	//  将数据加入到节点中,完善节点
	s-next=pre->next;//和后面的节点相连了哈,很快哈
	pre->next=s;//和前面的节点也相连了哈,没有二百多斤,但也很快哈
}
// 连接就两句
//定位,判断插入位置是否合理就用了一大半的篇幅
单链表的删除 (原理差不多,我略讲了哈)
1.定位  -->  遍历
2.判断待删除节点的合理性
3.将被删除的节点的后面的节点地址赋给被删除节点的前一个节点的next  ,被删除节点在这个链中去掉
4.释放这个节点空间

在这里插入图片描述

void DelList(LinkList  L,int i,ElemType *e)
{
LNode *pre,*s; 		//pre 用于上面的第一步,找到需要插入的位置之前的节点,s 即将申请的新节点
 	int k=0;
 	pre =L;
 	while(pre!=NULL&&k<i-1//循环定位
 	{
			pre=pre->next;
			  k=k+1;
	}
	if(k!=i-1)  //就是遍历完了没找到,判断是否是越界才跳出循环
	{
	printf("删除位置不合理");
	return ;是的话,就可以直接( ^_^ )/拜拜了
	}
	//终于定完位了,很快哈,我们可以开始 删除了
	s=pre->next;//保存被删除节点的位置,方便后面释放空间
	pre->next=pre->next->next;//删除  ,就这一步,哎!!!
	free(s);//释放空间
	return OK;
	
}
改,查就不展开说了
	1.	遍历到指定位置
	2.	查,改  就完了

循环链表

就是最后一个的next 不赋值为NULL了,而是赋值为头结点的地址。虽然只是很小的改变但是在以后的操作中会更方便,更适合某些情景

这是有尾指针的两个循环链表的合并
解释一下这里的RA->next=RB->next->next 第一个循环链表的尾指针连接到了第二个循环链表的第一个元素,其实是删除了第二个链表的头结点的,虽然第二条链表的头结点没有画出来,但是有一步free(RB->next)释放了第二条链表头结点的空间
在这里插入图片描述
如果没有尾指针则仍然需要遍历才能找到最后的节点。

双向链表

typedef struct DNode
{
ElemType data ;
struct DNode *prior,*next;//这个时候别名还没有取完,所以只能 struct DNode来定义了
}DNode,*DoubeList; 好嘛让我们恭喜别名取完。

就是增加了一个指向前一个节点的指针。考试应该不会考。我画几个简单的增删改查的原理图应该就足够了。
#### 增加
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201128164531592.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RoZWZseWJyaWQ=,size_16,color_FFFFFF,t_70)

#### 删除
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201128164833539.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RoZWZseWJyaWQ=,size_16,color_FFFFFF,t_70)
#### 改查 只是遍历之后取出或改变 节点中的数据而已

  1. i 是节点要插入的位置 ↩︎ ↩︎ ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值