线性表的链式存储结构的图电视用一组任意的存储单元存储线性表的数组元素,这组存储线性表单元可以连续,也可以不连续。这意味着这些数据元素可以存在内存中未被占用的任意位置。
在顺序存储结构中,每个数据元素只需要存数据元素信息就可以了。在链式结构中, 除了要存数据元素信息外,还要存储它的后继元素的存储地址。
我们把存储数据元素信息的域叫做数据域;
把存储直接后继地址的域叫做指针域;指针域里存储的信息称作指针或链。
这两部分信息组成数据元素ai的存储映像,成为结点 , Node 。
n个结点链结成一个链表,即为线性表(a1,a2,,,,,an)的链式存储结构。
因为此链表的每个结点只包含一个指针域,所以叫做单链表。
单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起的。
头指针:链表中第一个结点的存储位置。因此整个链表的存取就必循从头指针开始进行。
头结点:为了更加方便对链表进行操作,会在单链表的第一个结点前附设一个结点。头节点的数据与可以不存储任何信息,也可以存储如线性表的长度等附加信息;头结点的指针域存储指向第一个结点的指针。
- 线性表的单链表存储结构
//线性表的单链表存储结构
typedef struct Node
{
ElemType data;
struct Node* next;
}Node; //Node相当于是这个结构模板的别名
typedef struct Node* LinkList;//定义LinkList
- 单链表的读取
思路:声明一个结点p指向链表的第一个结点 , 初始化j 从1开始;当j<i时,就遍历链表,让p的指针向后移,不断指向下一结点,j累加1;当到链表末尾p为空,则说明第i个元素不在;否则查找成功,返回结点p的数据。
Status GetElem(LinkList L, int i, ElemType* e)
{
int j;
LinkList p;//声明一个结点p
p = L->next;
j = 1;
while (p && j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR;
*e = p->data;
return OK;
}
- 单链表的插入
思路:要将第i个数据插入结点的;先声明一个结点p指向链表的第一个结点,初始化j从1开始;当j<i时,就遍历链表,让p的指针不断先后移动指向下一结点;若到链表末尾p为空,则说明第i个元素不存在;否则查找成功,在系统中生成一个空结点s;将数据元素e赋值给s->data ;单链表的插入标准语句:s->next=p->next ; p->next=s; 返回成功 。
Status ListInsetrt(LinkList *L, int i, ElemType e)//L难道是指向指针的指针??
{
int j;
LinkList p, s;
p =*L;
j = 1;
while (p && j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR;
s= (LinkList)malloc(sizeof(Node));//生成新结点 malloc()函数的头文件是malloc.h
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
- 单链表的删除
要实现将结点ai删除的单链表操作,其实就是将它的前驱结点ai-1的指针绕过,指向它的后继结点ai+1即可。(如上图)
思路应该是:
声明一节点p指向链表的第一个结点,初始化j从1开始;当j<i时,就遍历链表 , 让p的指针后移;若到链表末尾p为空,说明第i个元素不存在;否则查找成功,并将要删除的结点p->next赋值给q; 单链表的删除标准语句 p->next=q->next;将q结点中的数据赋值给e ,作为返回; 最后释放q结点
Status ListDelete(LinkList* L, int i, ElemType* e)
{
int j;
LinkList p, q;
p = *L;//让p指向链表的第一个结点
j = 1;
while (p->next && j < i)
{
p = p->next;//让p不断后移,指向下一个结点
++j;
}
if (!(p->next) || j > i)
return ERROR;
q = p->next;//将要删除的结点赋值给q
p->next = q->next;
*e = q->data;//将q结点的数据赋值给e
free(q);//系统回收此结点 ,释放内存
return OK;
}
- 单链表的整表创建1----头插法
单链表是一种动态结构,对于整个链表来说,他所占用空间的大小和位置是不需要预先分配划定浩,可以根据实际情况和需求即使生成。
因此创建单链表的过程就是一个动态生成单链表的过程。即从空表的初始状态起,依次建立各元素结点,并逐个插入链表。
思路:
声明一个结点p和计数器i ; 初始化一空链表L ; 让L 的头结点的指针指向NULL ,(建立一个带头结点的单链表);然后生成一新结点就赋给p,随机生成一数字赋给p->data; 然后将p 插入到头结点和前一新结点之间。
void CreatListHead(LinkList* L, int n)
{
LinkList p;
int i;
srand(time_t(0));//初始随机化种子 头文件stdlib.h
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;//建立一个带头结点的单链表
for (i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node));//生成新结点
p->data = rand() % 100 + 1;//随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p;//插入到表头
}
}
事实上,这种始终让新结点在第一的位置,我们把这种称位头插法。
除此之外,我们当然也可以把新结点放在最后,叫做尾插法。
- 单链表的整表创建2----尾插法
//随机产生n个元素的值,建立带头结点的单链线性表L
void CreatListTail(LinkList* L, int n)
{
LinkList p, r;
int i;
srand(time_t(0));//初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));//为整个线性表
r = *L;//r为指向表尾的结点
for (i = 0; i < n; i++)
{
p = (Node*)malloc(sizeof(Node));//生成新结点
p->data = rand() % 100 + 1;//随机产生100以内的数字
r->next = p;//将当前表尾结点的指针指向新结点
r = p;//将当前的新结点定义为表尾结点
}
r->next = NULL;//表示当前链表结束
}
这段代码中,有两处不易理解
r->next = p;
r = p;
第一条语句的意思:将表尾的终端节点r的指针指向新结点p
第二条语句:因为此时r已经不是终端节点了,现在的终端节点是p ,所以现在需要将p结点的这个最后的结点赋值给r ,如此,r又是表尾的尾结点了。
- 单链表的整表删除
当不需要这个链表时,我们需要销毁它,也就是在内存中将它释放掉。
思路:声明一个结点p和q ;将第一个结点赋值给p ; 然后将下一个结点赋值给q;然后释放p ,再将q赋值给p,用循环实现此操作。
Status ClearList(LinkList* L)
{
LinkList p, q;
p = (*L)->next;//p指向第一个结点
while (p)//
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;//将头结点的指针域置空
return OK;
}
以上就是线性表的链式存储结构的基本操作。
那线性表的顺序存储结构和链式存储结构到底有何差异呢?选择的时候该如何抉择用哪个?
- 若线性表需要频繁查找,比较少插入删除----顺序存储 ;若需要频繁插入删除----单链表
- 当线性表元素个数变化较大或不知道有多大时----单链表 ; 如果事先知道线性表的大致长度----顺序存储
事实上,顺序存储和链式存储都各有优缺点,并不能说哪个更好,应综合平衡采用哪种数据结构。