线性表的深入了解概念篇
关于线性表.顺序表.链表
线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。
顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构。线性表采用顺序存储的方式存储就称之为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,链表比较方便插入和删除操作。
线性表的抽象数据类型
Data
线性表的数据对象集合为(a1.a2….an),每个元素的类型均为DateType。其中,除第一个元素a1外,每一个元素有且只有一个前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素的关系式一对一的关系。
Operation
List: 线性表
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返回其值。
ListLenght(L): 返回线性表L的元素个数。
对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的,对于市级问题中涉及的关于线性表的更复杂的操作,完全可以用这些基本操作的组合来实现。
线性表的不同储存结构
线性表有两种物理储存结构,顺序储存结构和链式储存结构。
线性表的顺序存储结构:
指的是用一段地址连续的存储单元依次存储线性表的数据元素。就是在内存中找了块地儿,通过占位的形式,把一定内存空间结占了, 然后把相同数据类型的数据元素依次存放在这块空地中。既然线性表的每个数据元素的类型都相同,所以可以用C语言的一维数组来实现顺序存储结构, 即把第一个数据元素存到数组下标为0的位置中,接着把线性表相邻的元素存储在数组中相邻的位置。
来看线性表的顺序存储的结构代码
#define MAXSIZE 20 //存储空间初始分配量
typedef iint ElemType; //ElemType类型根据实际情况而定,这里假设为int
typedef struct
{
ElenType data[MAXSIZE]; //数据存储数据元素,最大值为MAXIZE
int length; //线性表当前长度
}SqList;
线性表的顺序储存结构是存在缺点的,最大的缺点就是在进行插入和删除的时候需要移动大量数据,这时我们就需要一种新的储存结构,链式储存结构。
线性表的链式储存结构:
链表,类似它的名字,表中的每个节点都保存有指向下一个节点的指针,所有节点串成一条链。长度不固定,可以任意增删。存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素(根据单链表还是双链表有所不同)。 链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址。因此,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
#include "stdlib.h"
#include "stdio.h"
#define NULL 0
#define LEN sizeof(struct student)
struct student
{
int num; //学号
float score; //分数,其他信息可以继续在下面增加字段
struct student *next; //指向下一节点的指针
};
int n; //节点总数
/*
==========================
功能:创建n个节点的链表
返回:指向链表表头的指针
==========================
*/
struct student *Create()
{
struct student *head; //头节点
struct student *p1 = NULL; //p1保存创建的新节点的地址
struct student *p2 = NULL; //p2保存原链表最后一个节点的地址
n = 0; //创建前链表的节点总数为0:空链表
p1 = (struct student *) malloc (LEN); //开辟一个新节点
p2 = p1; //如果节点开辟成功,则p2先把它的指针保存下来以备后用
if(p1==NULL) //节点开辟不成功
{
printf ("\nCann't create it, try it again in a moment!\n");
return NULL;
}
else //节点开辟成功
{
head = NULL; //开始head指向NULL
printf ("Please input %d node -- num,score: ", n + 1);
scanf ("%d %f", &(p1->num), &(p1->score)); //录入数据
}
while(p1->num != 0) //只要学号不为0,就继续录入下一个节点
{
n += 1; //节点总数增加1个
if(n == 1) //如果节点总数是1,则head指向刚创建的节点p1
{
head = p1;
p2->next = NULL; //此时的p2就是p1,也就是p1->next指向NULL。
}
else
{
p2->next = p1; //指向上次下面刚刚开辟的新节点
}
p2 = p1; //把p1的地址给p2保留,然后p1产生新的节点
p1 = (struct student *) malloc (LEN);
printf ("Please input %d node -- num,score: ", n + 1);
scanf ("%d %f", &(p1->num), &(p1->score));
}
p2->next = NULL; //此句就是根据单向链表的最后一个节点要指向NULL
free(p1); //p1->num为0的时候跳出了while循环,并且释放p1
p1 = NULL; //特别不要忘记把释放的变量清空置为NULL,否则就变成"野指针",即地址不确定的指针
return head; //返回创建链表的头指针
}
关于静态链表,循环链表,双向链表
1.关于静态链表和动态链表
静态存储特点:在程序运行的过程中不用考虑追加内存的分配问题。
动态存储特点:可动态分配内存,有效利用内存资源,使程序具有可扩展性。
2.循环链表
所谓循环,就是到尾结点,没有空指针,尾结点反而指向了头结点,成环。那么从环中的任意一个结点都能达到表里其他结点。
3.双向链表
在单链表的每个结点里再增加一个指向其直接前驱的指针 ,这样链表中就形成了有两个方向不同的链,故称为双向链表。
1 typedef struct node{
2 int data;
3 struct node *prior; //向前指向的指针
4 struct node *next; //向后指向的指针
5 } node, *doubleLinklist;