提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
-
一、什么是线性表?
-
二、存储方式
1.顺序存储
2.链式存储
3.其他链表
4.区别
5.选择
-
总结
前言
本文主要将线性表的两种存储方式及比较。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是线性表?
在讲数据结构概念时,主要就三个关键词:逻辑结构、物理结构、数据的运算。其中逻辑结构又包括线性结构和非线性结构,那么线性表顾名思义就是线性结构。线性表是多组具有相同特性的数据元素的有限集合。类似于常见的表格,每一行都是一个数据元素。所以,我们可以发现线性表的相邻元素间存在对应关系,除去第一个数据元素,每一个数据元素都有一个直接前驱;同样,除去最后一个元素,每一个数据元素都有一个直接后继。
二、存储方式
线性表有两种存储方式:顺序存储和链式存储。顺序存储?链式存储?区别?如何选择?
1.顺序存储
顺序存储,顾名思义就是按照顺序一个接一个,即逻辑上相邻的数据元素物理上也相邻。这里,我们就可以明白,当要对表中数据进行处理时,只需通过下标索引的方式进行处理(随机存取)。那么常见的增删查改又是如何通过代码实现的呢?
<1> 顺序表的定义
首先我们需要清楚顺序表应包含哪些内容?数据、表长和申请内存空间,因此定义时应包含数据和表长。若是基本数据类型数据直接定义即可,若是包含多种类型可通过结构体定义。代码如下:
#define Maxsize 100
// 定义数据类型
typedef struct{
char id[10];
char name[10];
int age;
float score;
}student;
//定义顺序表
typedef struct {
student data[Maxsize];
int length;
}SqList;
<2> 初始化
创建顺序表前一定也要先进行初始化将表长length设置为具体数!!!这里我个人认为是若不将length初始化的话,length可能会是一个随机值在后续通过下标索引查询修改等会出现错误结果。一般情况下我们选择设置为0或者-1。
SqList init(SqList &L) {
L.length = 0;
return L;
}
<3> 创建
将数据添入表中并让表长length加一。
//顺序表的创建
SqList create(SqList &L) {
student stu;
printf("请输入学号:");
scanf("%s", stu.id);
printf("请输入姓名:");
scanf("%s", stu.name);
printf("请输入年龄:");
scanf_s("%d", &stu.age);
printf("请输入成绩:");
scanf_s("%f", &stu.score);
L.data[L.length] = stu;
L.length++;
return L;
}
注意:引用型变量直接指向的是内存地址所以在用scanf负值时直接写变量名,而对于基本数据类型数据则需借用地址符号 。
<4> 查询
查询直接通过for循化语句将对应索引位置的数据输出即可。
//查询
void check(SqList L) {
int i;
printf("学号\t姓名\t年龄\t成绩\n");
for(i=0;i<L.length;i++)
{
printf("%s\t", L.data[i].id);
printf("%s\t", L.data[i].name);
printf("%d\t", L.data[i].age);
printf("%.2f\n", L.data[i].score);
}
}
<5> 修改
考虑到代码健壮性需对i值进行位置判断,再通过下标索引将新的数据赋值过去。
//修改指定位置数据
SqList update(SqList& L,int i) {
student stu;
if (i<0 || i>L.length - 1) {
printf("需修改的位置错误");
}
printf("\n请输入学号:");
scanf("%s", stu.id);
printf("请输入姓名:");
scanf("%s", stu.name);
printf("请输入年龄:");
scanf("%d", &stu.age);
printf("请输入成绩:");
scanf("%f", &stu.score);
L.data[i] = stu;
return L;
}
<6>插入
由于顺序表是按照顺序存储的所以对指定位置进行插入时可能会涉及到大量数据后移,个人建议处理此类情况时通过画图加深判断和理解。(此处难点在于索引位置的判断)
// 在指定位置插入数据
SqList insert(SqList &L,int i) {
if (i<0 || i> L.length+1) {
printf("需插入的位置错误");
}
student stu;
printf("\n请输入学号:");
scanf("%s", stu.id);
printf("请输入姓名:");
scanf("%s", stu.name);
printf("请输入年龄:");
scanf("%d", &stu.age);
printf("请输入成绩:");
scanf("%f", &stu.score);
int j;
for (j = L.length-1; j >= i-1; j--) {
L.data[j + 1] = L.data[j];
}
L.data[i-1] = stu;
L.length++;
return L;
}
<7>删除
删除数据,简言言之就是用后面的数据覆盖前面的数据。(处理方法与插入类似)
//删除指定位置数据
SqList delet(SqList& L, int i) {
if (i<0 || i>L.length - 1) {
printf("需删除的位置错误");
}
int j;
for (j = i; j < L.length; j++) {
L.data[j - 1] = L.data[j];
}
L.length--;
return L;
}
2.链式存储
链式存储分为单链表、循环单链表、双链表、循环双链表、静态链表五种。所谓链表就好比用一根绳将相关数据串在一块,不需要像顺序表那样逻辑相邻物理上也必须相邻,只需用跟绳子将逻辑上相邻的数据的存储地址连在一块即可,不需要物理结构也相邻。这样的话就体现出链表相对于顺序表更具有灵活性。而这里的绳就是大家熟知的指针。由于涉及到指针,链表的创建及使用相比于线性表略微复杂些。较为常用的是单链表,所以这里链式存储的代码以单链表为例,其他几种简单概述。
<1> 单链表的定义
首先需要清楚链表的是用一根线(指针)将一个个结点串接在一块,所以,链表中必须要有数据和指针。那么这里的指针应该是什么类型呢?思考一下,这里创建的指针作用是什么?指向下一个数据结点,而每一个结点都有数据域和指针,所以这里指针的类型应该与结点一样。
//链表定义
typedef struct LNode {
student data;
struct LNode* next;
}LNode,*LinkList;
<2> 单链表的创建
创建分两种:头插法和尾插法。
1.头插法:每次将数据插入表头,所以我们需定义一个指针L每次都指向头结点,创建一个新结点s并为其分配空间用于存放新数据,之后将s的next指针指向头结点的下一个结点(或者直接指向空:因为空表头结点的next指针为空),再将头结点的next指针指向s(这里的顺序不能颠倒).
1.尾插法:每次将数据插入表尾,与头插法相同,我们需定义一个指针L指向头结点,创建一个新结点s并为其分配空间用于存放新数据,之后将s的next指针指向L指向的结点的下一个结点(或者直接指向空),再将当前L指针的next指向s,L指针后移一位(即L=L->next).
//创建:头插法
LinkList create(LinkList &L){
student stu;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//创建中间结点
LNode* s = (LNode*)malloc(sizeof(LNode));
printf("请输入学号:");
scanf("%s", stu.id);
printf("请输入姓名:");
scanf("%s", stu.name);
printf("请输入年龄:");
scanf("%d", &stu.age);
printf("请输入成绩:");
scanf("%f", &stu.score);
s->data=stu;
s->next = L->next;
L->next=s;
return L;
}
<3> 查找指定位置结点
创建指针指向头结点,定义中间变量用于记录当前指针所指的位置。若该指针内容不为空并且当前指针所指的位置还未到达指定位置则将指针后移一位,继续前面查找操作。
LinkList get(LinkList L, int i) {
LinkList p = L;
int j = 0;
while (p && j < i) {
p = p->next;
j++;
}
if (!p) {
printf("不存在");
}
return p;
}
<4> 在指定位置插入数据
由于单链表是顺序存取,所以我们要先找到指定位置前一个结点p(借助上述查找方法),创建一个新结点s并为其分配空间用于存放新数据,之后将s的next指针指向p的next域指向的结点,再将p的next指针指向s(这里的顺序不能颠倒).
LinkList insert(LinkList& L, int i) {
student stu;
//找到指定位置的前一个位置
LNode* p = get(L, i - 1);
LNode* s = (LNode*)malloc(sizeof(LNode));
printf("请输入学号:");
scanf("%s", stu.id);
printf("请输入姓名:");
scanf("%s", stu.name);
printf("请输入年龄:");
scanf("%d", &stu.age);
printf("请输入成绩:");
scanf("%f", &stu.score);
s->data = stu;
s->next = p->next;
p->next = s;
return L;
}
<4> 删除指定位置结点
插入与删除原理相似,画图结合代码理解。
//删除指定位置结点
LinkList delet(LinkList& L, int i) {
//获取指定位置的前一个位置
LNode* p = get(L, i - 1);
LNode* q = p->next;
p->next = q->next;
//释放结点
free(q);
return L;
}
3.其他链表
<1> 双链表
与单链表不同的是双链表有两个指针pre和next分别指向前驱结点和后驱结点,所以双链表的操作相对于单链表来说更为复杂些。
<2>循环单链表
循环单链表与单链表不同的在于其最后一个结点next指针要指向头结点形成一个闭环。
<3>循环双链表
根据上述几种链表描述,不难看出循环双链表的最后一个结点的next指针要指向头结点,头结点的pre指针指向最后一个结点。
<4>静态链表
静态链表是借助数组实现链式存储结构,所以和顺序表一样要预先分配一片连续的内存空间用于存储,其next指针指的是结点的相对位置(也就是常说的数组下标)。
4.区别
顺序表:(1)逻辑上相邻物理上也相邻;(2)随机存取;(3)存储密度大; (4)创建前需预先分配一片连续的空间n,当空间不足申请m个空间时,需申请m+n个连续的地址空间,再将原先n个单元数据复制过去;(5)查找方便,插入/删除操作较为麻烦(除在最后位置插入/删除)
链表:(1)逻辑上相邻物理上不一定相邻;(2)顺序存取; (3)存储密度小; (4)只需在创建结点时申请空间,只要内存中有空间就可以分配,灵活度较高;(5)插入/删除操作方便,查找较为麻烦
5.选择
(1)从存储空间上来看,若线性表的存储规模难以估计时,建议采用链表存储;但链表的存储密度要比顺序表小
(2)基于运算考虑,若频繁的按序号查询访问或在最后插入和删除元素,优先考虑顺序表;反之考虑链表
(3)从操作上来讲,顺序表的操作相对于链表更简单,但链表的灵活度较高
总结
以上内容大概讲述了线性表顺序存储和链式存储的特点、比较及相关代码实现,都是基于带头结点进行的,对于不带头结点的相关操作可自行尝试。对于上述增删查改等操作一定要结合图来实现相关代码,加强理解。