线性表( List )
零个或多个数据元素的有限序列,各个元素之间是有顺序的;
定义:
将线性表记为( a1,a2,……,ai-1,ai,ai+1,…… ),则ai-1领先ai,……
直接前驱元素:ai-1 为 ai 的直接前驱元素;
直接后继元素:ai+1为 ai 的直接后继元素;
线性表元素的个数 n ,定义为线性表的长度。
每个元素都有特定的位置,ai 是第 i 个元素, i 称为数据元素 ai 在线性表中的位序。
特点:
数据结构分为逻辑结构和物理结构,逻辑结构分为集合结构、线性结构、树形结构和图形结构四大类。物理结构分为顺序存储结构和链式存储结构。我在之前写的《数据结构和算法》中已经介绍过。
1. 线性表是一个序列。
2. 0个元素构成的线性表是空表。
3. 线性表中的第一个元素无前驱,最后一个元素无后继,其他元素有且只有一个前驱和后继。
4. 线性表是有长度的,其长度就是元素个数,且线性表的元素个数是有限的,也就是说,线性表的长度是有限的。
线性表基本操作:
InitList(*L):初始化操作,建立一个空的线性表L。
ListEmpty(L):判断线性表是否为空表,若线性表为空,返回true,否则返回false。
ClearList(*L):将线性表清空。 GetElem(L,i,*e):将线性表L中的第i个位置元素值返回给e。
LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
ListInsert(*L,i,e):在线性表L中第i个位置插入新元素e。
ListDelete(*L,i,*e):删除线性表L中第i个位置元素,并用e返回其值。
ListLength(L):返回线性表L的元素个数。
对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的。
对于实际问题中涉及的关于线性表的更复杂操作,完全可以用这些基本操作的组合来实现。
两种不同的线性表
数据结构分为逻辑结构和物理结构,逻辑结构分为集合结构、线性结构、树形结构和图形结构四大类。
物理结构分为顺序存储结构和链式存储结构。
线性表是线性结构的一种,那么线性表当然也有物理结构,也就是说,线性表有两种,分别是:
顺序结构的线性表(叫做顺序表)和链式结构的线性表(叫做链表)。
顺序存储结构的线性表:
顺序表是指顺序存储结构的线性表,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
顺序表表现在物理内存中,也就是物理上的存储方式,事实上就是在内存中找个初始地址,然后通过占位的形式,把一定的内存空间给占了,然后把相同数据类型的数据元素依次放在这块空地中。注意,这块物理内存的地址空间是连续的。
举个例子,比如C语言中的基本变量的存储就是连续的存储在内存中的,比如声明一个整数i,在64位系统中整数i在内存中占8字节,那么系统就会在内存中为这个整型变量分配一个长度为8个字节的连续的地址空间,然后把这个i的二进制形式从高地址向低地址存储,长度不足时候,最高位用0补齐。
顺序表优缺点
线性表的顺序存储结构,在存、读取数据时,不管是在哪个位置,时间复杂度都是O(1)。而在插入或者删除时,时间复杂度都是O(n)。
这也就是线性表的顺序存储结构比较适合存取数据,不适合经常插入和删除数据的应用。
优点:
1.无需为了表示表中元素之间的逻辑关系而增加额外的存储空间(相对于链式存储而言)。
2.可以快速的存取表中任意位置的元素。
缺点:
1.插入和删除操作需要移动大量的元素。
2.当线性表长度变化较大时,难以确定存储空间的容量。
3.容易造成存储空间的“碎片”(因为线性表的顺序存储结构申请的内存空间都以连续的,如果因为某些操作(比如删除操作)导致某个部分出现了一小块的不连续内存空间,因为这一小块内存空间太小不能够再次被利用/分配,那么就造成了内存浪费,也就是“碎片”)
链式存储结构的线性表
前面我们讲的线性表的顺序存储结构,它最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间。
那我们能不能针对这个缺陷或者说遗憾提出解决的方法呢?要解决这个问题,我们就得考虑一下导致这个问题的原因!
为什么当插入和删除时,就要移动大量的元素?
原因就在于相邻两元素的存储位置也具有邻居关系,它们在内存中的位置是紧挨着的,中间没有间隙,当然就无法快速插入和删除。
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。
也就是说,链式存储结构的线性表由一个(可以使零)或者多个结点(Node)组成。每个节点内部又分为数据域和指针域(链)。数据域存储了数据元素的信息。指针域存储了当前结点指向的直接后继的指针地址。
因为每个结点只包含一个指针域,所以叫做单链表。顾名思义,当然还有双链表。
可实现代码:
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#define MAXSIZE 20 // 存储空间分配量 20
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef bool Status ; // 定义Status 变量,其值是函数结果状态代码,OK等;
bool getflag = 0;
typedef int ElemType; // 定义每个元素均为整型变量
typedef struct
{
ElemType data[MAXSIZE]; // 数组存储数据元素( 连续空间 ),最大个数为 MAXSIZE;
int length; // 线性表当前长度;
} SqList;
ElemType GetElem(SqList *L, int i);
Status PrintList(SqList *L);
Status InsertList(SqList *L, int i, ElemType e);
Status InitList(SqList *L);
Status DeleteList(SqList *L, int i, ElemType *e);
Status AddList(SqList *L);
int main()
{
int n,position;
ElemType save[40],savenum = 0,data;
SqList StuNum = { {0}, 0 }, *StuNump;
StuNump = &StuNum;
InitList(StuNump);
while(1)
{
printf("Input The Number to Choose the Service:\n");
printf("\n1:\t View the Full Data;\n");
printf("\n2:\t Add the Data;\n");
printf("\n3:\t Delete the Data;\n");
printf("\n4:\t Scan for the Data;\n");
printf("\n5:\t Insert the Data;\n");
printf("\n0:\t Initiate the Data;\n\n\n");
scanf("%d",&n);
printf("\n\n");
if(n == 1)
{
PrintList(StuNump);
}
else if(n == 2)
{
if(AddList(StuNump))
{
printf("Data Insert successfully!\n");
printf("\n");
}
else
{
;
}
}
else if(n == 3)
{
printf("Please Input the position to be Deleted: \n");
scanf("%d",&position);
DeleteList(StuNump, position, &save[savenum]);
savenum++;
printf("\nData have been Deleted Successfully!\n");
printf("\n");
}
else if(n == 4)
{
printf("Please Input the position of the Data: \n");
scanf("%d",&position);
data = GetElem(StuNump,position);
if(getflag == 1)
{
printf("The Data you are looking for is %d .\n\n",data);
printf("\n");
getflag = 0; // 成功 get 后标志位清零,否则下一次查询可能出现错误;
}
else
{
printf("ERROR! Please Retry!\n");
}
}
else if(n == 5)
{
printf("\n Please Input the position and the data:\n");
scanf("%d%d",&position,&data);
if(InsertList(StuNump,position,data))
{
printf("The data is inserted Successfully!\n");
}
printf("\n");
}
else if(n == 0)
{
if(InitList(StuNump))
{
printf("The Data have been Initiated successfully!\n\n");
}
else
{
;
}
}
else
{
printf("ERROR!! Please Input the Right Number.\n");
continue;
}
printf("\n\n\n\n\n\n\n");
}
return 0;
}
ElemType GetElem(SqList *L, int i)
{
if( (L->length == 0) || ( i < 1) || ( i > L->length ))
{
getflag = 0;
return ERROR;
}
else
{
getflag = 1;
return L->data[ i-1 ]; // 第 i 个元素,实际上是存储在第 i-1 位;
}
}
Status PrintList(SqList *L)
{
if(L->length == 0)
{
printf("The Data is NULL!\n");
}
else
{
for(int i = 0; i < L->length ; i++)
{
printf("%d\t\t",L->data[i]);
}
}
printf("\n");
}
Status InsertList(SqList *L, int i, ElemType e)
{
int k;
if( L->length >= MAXSIZE)
{
printf("The Data is Full!\n");
return ERROR;
}
if(( (i < 1) || ( i > L->length ) ) && L->length != 0)
{
printf("Wrong Data.\n");
return ERROR;
}
if( i <= L->length ) // 此时 i 不在队尾!;
{
for( k = L->length-1; k >= i-1 ; k--)
{
L->data[k+1] = L->data[k]; // 第 i-1 位后面的所有数据向后移一位;( 从后向前 )
}
}
L->data[i-1] = e;
L->length++;
return OK;
}
Status InitList(SqList *L)
{
int i = 0;
for(i = (L->length)-1; i >= 0 ; i--)
{
L->data[i] = 0;
}
L->length = 0;
return OK;
}
Status DeleteList(SqList *L, int i, ElemType *e)
{
int k;
if( L->length == 0)
{
return ERROR;
}
if( i<1 || i > L->length )
{
return ERROR;
}
*e = L->data[i-1];
if( i < L->length)
{
for( k = i; k < L->length ; k++) // 当 i 不是队尾的元素的时候,从前向后覆盖数据;
{
L->data[k-1] = L->data[k];
}
}
L->length--;
return OK;
}
Status AddList(SqList *L)
{
int n,data;
printf("数据个数:\n");
scanf("%d",&n);
printf("Input the Data:\n");
for(int j = 0; j < n; j++)
{
scanf("%d",&data);
L->data[L->length] = data;
L->length++;
}
return OK;
}
内容解释:
1. #define MAXSIZE 20 ;
定义线性表的最大长度为 MAXSIZE;( 例子中为20 )
2. typedef bool Status ;
定义一个布尔型变量用于返回函数是否执行成功 Status;
3. typedef int ElemType;
定义表中的元素为某种变量,此例中为 int 型;
4.
typedef struct
{
ElemType data[MAXSIZE]; // 数组存储数据元素( 连续空间 ),最大个数为 MAXSIZE;
int length; // 线性表当前长度;
} SqList;
定义一个线性表 SqList,其中元素保存在数组中,即系统直接按顺序分配空间,但有最大长度 MAXSIZE 限制;
线性表内的当前元素个数用 length 来记录;( length 当然不可超过MAXSIZE );
5. main函数:
int main()
{
int n,position;
ElemType save[40],savenum = 0,data;
SqList StuNum = { {0}, 0 }, *StuNump;
StuNump = &StuNum;
InitList(StuNump);
while(1)
{
.......
}
}
1. 定义 n: 用于选择接下来的服务功能选项( 个人不喜欢 Debug 中查看变量,所以写了一个 DOS下的显示 );
2. 定义 position: 用于读取用户输入的寻找或者插入数据位置;
3. 定义数组 Save[] 和 savenum: 分别保存被用户删除的数据,以及被删除数据的个数,用于数据找回;
4. 定义元素数据类型 data: 用于处理临时的数据存入和读出;
5. 定义顺序存储表 StuNum 和 指针 StuNump: 用于处理学生的学号信息;
6. InitList() : 初始化原数据,并开始执行程序;
注:这里必须用指针去操作,否则不能够真正改变实参的值!
6. 函数:
一、 GetElem : 获取第 i 个位置的数据,如果正确返回数据,如果错误,返回ERROR;
ElemType GetElem(SqList *L, int i)
{
// 如果表的长度为0( 为空表 ) 或者 所寻找的数据不在数据域内 ( 第 1 个 —— 第 length 个 )
注: i 指的是第 i 个数据,但是在线性表中是存储在 i - 1 位! 所以不可能搜索到第 0 位的数据;
if( (L->length == 0) || ( i < 1) || ( i > L->length ))
{
getflag = 0;
return ERROR;
}
else
{
getflag = 1;
// 第 i 个数据在线性表中是存储在 i - 1 位;
return L->data[ i-1 ];
}
}
二、PrintList: 按顺序打印出表中的所有数据; ( 是我为了便于检查功能加入 )
Status PrintList(SqList *L)
{
// 如果是个空表,此时返回为空;
if(L->length == 0)
{
printf("The Data is NULL!\n");
}
else
{
// 否则打印数据即可;
for(int i = 0; i < L->length ; i++)
{
printf("%d\t\t",L->data[i]);
}
}
printf("\n");
}
三、InsertList:在线性表第 i 个位置插入数据元素 e;
注:此数据是为了修改原表中数据,所以数据表用指针类型,而数据 e 所用为基本元素类型;
Status InsertList(SqList *L, int i, ElemType e)
{
// 定义循环变量 k;
int k;
// 如果原表中数据已满,则无法再加入,需要动态分配空间或删除数据;
if( L->length >= MAXSIZE)
{
printf("The Data is Full!\n");
return ERROR;
}
// 如果插入位置不合法,则错误;
if(( (i < 1) || ( i > L->length ) ) && L->length != 0)
{
printf("Wrong Data.\n");
return ERROR;
}
// 如果插入数据不在队尾( 此时需要将插入点后的全部数据向后移动一位! ),移动数据;
// 判断插入点在队尾:
对于总共 length 个元素,如果 i != length + 1( 即把新的数据直接放在线性表的最后一个元素的后面 ),即为插入
// 插入方法:
从第 length 个元素开始( 放在length-1下标下 ),依次向后移动一位,直到第 i 个( 第 i 个元素也要动,以留空位);
if( i <= L->length ) // 此时 i 不在队尾!;
{
for( k = L->length-1; k >= i-1 ; k--)
{
L->data[k+1] = L->data[k]; // 第 i-1 位后面的所有数据向后移一位;( 从后向前 )
}
}
// 把数据放入第 i 位 ( 下标为 i -1 ),并且长度加 1;
L->data[i-1] = e;
L->length++;
return OK;
}
四、InitList:初始化线性表,清空数据并重置长度为 0;
Status InitList(SqList *L)
{
int i = 0;
for(i = (L->length)-1; i >= 0 ; i--)
{
L->data[i] = 0;
}
L->length = 0;
return OK;
}
五、DeleteList:删除第 i 位的数据,并把原数据压入回收站save[];
注:此时需要把表中的数据输出给实参 e,所以需要指针型变量( 与Insert操作不同 )!
Status DeleteList(SqList *L, int i, ElemType *e)
{
// 定义循环变量 k;
int k;
// 如果为空表或者删除位置非法,则返回ERROR;
if( L->length == 0)
{
return ERROR;
}
if( i<1 || i > L->length )
{
return ERROR;
}
// 将第 i 个数据( 下标为 i - 1 )返回给指针所指内容,即复制内容到回收站;
*e = L->data[i-1];
// 当所取的元素不是队尾的元素的时候,此时可将后位元素向前移动,覆盖所删元素即可;
if( i < L->length)
{
for( k = i; k < L->length ; k++) // 当 i 不是队尾的元素的时候,从前向后覆盖数据;
{
L->data[k-1] = L->data[k];
}
}
// 不要忘记长度减一;
L->length--;
return OK;
}
六、AddList:在线型表尾插入多个新的元素,用于数据输入;
Status AddList(SqList *L)
{
// 定义 n 记录新增数据个数,data 用于暂存输入数据;
int n,data;
printf("数据个数:\n");
scanf("%d",&n);
printf("Input the Data:\n");
for(int j = 0; j < n; j++)
{
scanf("%d",&data);
L->data[L->length] = data;
L->length++;
}
return OK;
}