3.6链式存储结构
解决顺序存储结构在插入与删除时需要移动大量元素。
定义:
为了表示每个数据元素与其直接后继元素之间的关系,除了存储其本身的信息外,还需存储一个指示其直接后继的信息。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。
这两部分信组成数据元素的存储映像,称为结点。
另外,会在单链表的第一个结点之前附设一个结点,称为头结点。头结点不存储信息,可以存储如线性表的长度等附加信息,指针域存储指向第一个结点的指针。
头指针与头结点的异同
“头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。”
初读至此,开始心生疑惑,这里的头指针怎么一会说指向的是第一个结点,一会儿又是指向头结点嘞?
参考下文中图3-6-6以及图3-6-7,我发现原来自己的把头指针与头结点的概念混淆了,认为头指针即为头结点的指针域。
3.7 单链表的读取
算法思路
- 声明一个指针p指向链表的第一个结点,初始化j从1开始。
- 当j<i时,将指针向后移动,不断指向下一结点,并对j进行累加。
- 若链表末尾p为空时,则说明第i个结点不存在。
- 否则,返回结点p的数据。
注意
由于这里并不能确定循环次数,所以无法使用for循环,主要核心思想“工作指针后移”。
3.8 单链表的插入与删除
插入算法思路
- 声明一个指针p指向链表的第一个结点,初始化j从1开始。
- 当j<i时,将指针向后移动,不断指向下一结点,并对j进行累加。
- 若链表末尾p为空时,则说明第i个结点不存在。
- 否则查找成功,在系统中生成一个空结点s。
- 将数据元素赋值给s->data;
- 单链表的插入标准语句s->next=p->next;p->next=s;
- 返回成功。
注:【这里的步骤中的1-3与单链表的读取相同,就是通过不断后移指针,找到对应i位置】
删除算法思路
- 声明一个指针p指向链表的第一个结点,初始化j从1开始。
- 当j<i时,将指针向后移动,不断指向下一结点,并对j进行累加。
- 若链表末尾p为空时,则说明第i个结点不存在。
- 否则查找成功,将欲删除的结点p->next赋值给q;
- 单链表的删除标准语句p->next=q->next;
- 将q结点中的数据赋值给e,作为返回;
- 释放q结点
- 返回成功
静态链表、循环链表、双向链表
- 用数组表示的链表叫做静态链表
- 将单链表中终端结点由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表
- 双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针。
单链表代码实现
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int status;
//结点由数据域和指针域,指针域存放下一结点的地址
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
status InitList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node));
if (!(*L))
return ERROR;//存储分配失败
(*L)->next = NULL;//指针域为空
return OK;
}
/*初始条件:顺序线性表L已存在,并且i的位置也符合要求
操作结果:用e返回L中第i个数据元素的值*/
status GetElement(LinkList L, int i, ElemType *e)
{
LinkList p;
int j;
p = L->next;
j = 1;
while (p&&j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
{
return ERROR;
}
*e = p->data;
}
//插入
status ListInsert(LinkList *L, int i, ElemType e)
{
LinkList p, s;
int j;
p = (*L)->next;
j = 1;
while (p&&j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR;
//进行到这里即线性表中存在第i个位置可以用来插入
s = (LinkList)malloc(sizeof(Node));
s->data = e;
//
s->next = p->next;
p->next = s;
return OK;
}
//删除,并返回对应位置的元素
status ListDelete(LinkList *L, int i, ElemType *e)
{
LinkList p, q;
int j;
p->next = (*L);
j = 1;
while (p&&j < i)
{
p = p->next;
}
if (!p || j > i)
return ERROR;
//找到对应位置
q = p->next;
p->next = q->next;
*e = q->data;
free(q);//让系统回收此结点,释放内存
return OK;
}
/*单链表的整表创建*/
/*头插法:插入位置在头结点与next位置之间,其方法与普通插入相同*/
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0));
*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;
}
}
/*尾插法*/
void CreateListTail(LinkList *L, int n)
{
LinkList p, r;//这里的r指向尾结点
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));//生成头结点
r = *L;
for (i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;//生成随机数
r->next = p;
r = p;//将r的后继结点设为p,然后将当前的新结点定义为表尾终端结点
}
}
status ClearList(LinkList *L)
{
LinkList p, q;
p = (*L)->next;
while (p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}