链式存储概要: 不光要存储数据元素信息,还要存储他后继元素的存储地址(类似指针)。
一些定义:
数据域:存储数据元素信息的域。
指针域:存储直接后继位置的域,指针域中存储的信息称为指针或链。
数据域和指针域组成数据元素成为存储映像,也叫结点Node。
n个结点链接成一个链表,就是线性表的链式存储结构。
此链表中每个结点只包含一个指针域,所以叫做单链表。
头指针和头结点的异同
头指针:
1.头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
2.头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字)。
3.无论链表是否为空,头指针不为空。
头指针时链表的必要元素
头结点:
1.头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)。
2.有了头结点,对在第一元素结点前插入结点和删除第一结点起操作与其他结点的操作就统一了。
3.头结点不一定是链表的必要元素。
4.头结点的数据域一般不存储数据。
单链表
链表第一个结点的存储位置叫头指针,最后一个指针为空,NULL。
1.获取链表第i个数据的算法:
1.声明一个结点p指向链表的第一个结点,初始化j从1开始。
2.当j < 1时,就遍历链表,让p的指针向后移动,不断指向下一结点,j + 1.
3.若到链表末尾p为空,则说明第i个元素不存在。
4.否则查找成功,返回结点p的数据。
typedef struct Node
{
ElemType data;//定义数据data为ElemType型,此处不需要纠结Elemtype到底是什么类型,记住就行。
struct Node* next;//定义结构指针next
}Node;
typedef struct Node*LinkList;
//LinkList为结构指针型,所以在后文中可以定义指针p,并使其在后文中代替结构体struct Node出现,
//所以在后文中LinkList L代表定义了一个指针L,L指向结构体Node
//而LinkList *L代表结构体Node,就好比如下代码
int *p,a = 8;
p = &a;
//则*p = 8在这里的*p就相当于上文的*L, 因为L是指向结构体Node的指针
//所以*L就代表结构体Node,通过操作运算符 . 来对结构中的成员进行操作;
Status GetElem( LinkList L, int i , ElemType *e)
//此处不会对原链表做出改变,所以用L
//讲解:单链表需要头指针才能对后面的元素进行访问,
//所以当我们不需要对链表做出改变时,
//只需要一个指向头指针的指针即可,在这里也就是L。
//因为我们要获取一个元素,所以需要用到指针*e,
//return只能返回一个值,并且其需要返回代码执行状态(OK或者ERROR)
//单链表不像顺序表,可从中间开始查找
//链表若向查找某个数据,只能从第一个元素依次找下去
{
int j;
LinkList p; //定义指针P,
while( p && j < i) //当指针p不为空或者查找位置j
{
p = p-> next; //p指向下一结点;
++j;
}
if( !p ||j > i )
//当指针变为空指针或者查找位置越过第i个时
{
return ERROR;
}
*e = p->data;//通过指针*e返回data数据
return OK;
2.单链表的插入
算法思路:
1.声明一结点p指向链表头结点,初始化j 从1开始。
2.当就j < 1 ,就遍历链表,让P的指针向后移动,不断指向下一结点,j累加1;
3.若到链表末尾p为空,在系统中生成一个空结点s;4.将数据元素e赋值给s->data;
5.单链表插入的两条标准语句s->next = p->next; p->next = s;
Status ListInsert( LinkList *L; int i , ElemType e) //在i处插入元素e
//
//
{
int j;
LinkList p, s;
p = *L;
j = 1;
while( p && j < i) //当
{
p = p->next;
j++;
}
if( !p || j > i) //当第 i 个元素不存在时
{
return ERROR;
}
s = (LinkList)malloc(sizeof(Node));
s->data = e;//将要插入的数据e存入s中
s->next = p->next;
//此时p—>next指向的是未插入元素时的第 i 个数据的数据库,
//这串代码将第 i 位的数据库和s的指针域相连
p->next = s;
//把原本处于第i-1的结点的指针指向s的数据库。
return OK;
}
3.单链表的删除
算法思路:
1.声明结点p指向链表第一个结点,初始化j = 1;
2.当j < 1时,就遍历链表,让p的指针向后移动,不段指向下一个节点,j 累加 1;
3.若链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,将欲删除结点p->next = q->next;
5.将q结点中的数据赋值给e,作为返回;
6.释放结点q
Stasus ListDelete(LinkList *L, int i, ElemType e)
{
int j = 1;
LinkList p q;
p = * L;
j = 1 ;
while(p->next && j < i)
{
p = p->next;
++j;
}
if(!(p->next) || j > i)//当第 i 个元素不存在时
{
return ERROR;
}
q = p->next;
p->next = q->next;//将第i-1个数据的指针指向第i+1个数据的数据库
*e = q->data;//记录被删除的数据
free(q);
return OK;
}
3.单链表的整表创建(两种方法)
算法思路:
1.声明一个结点p和计数器变量i;
2.初始化一空链表L;
3.让 L 的头结点的指针指向NULL,即创立一个带头结点的单链表;
4.循环时间后继节点的赋值和插入
①头插法:
算法思路:
1.先让新结点的next指向头结点。
2.然后让表头的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;//生成数据
p->next = (*L)->next;//将新建立的结点的指针指向上衣结点的数据域
(*L)->next = p;//将新建立的结点变成头结点
//头插法实际上是将(*L)->不断变成头指针的过程
}
}
②尾插法
void CreateListTail(LinkList *L, int n)
{
LinkList p. r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;//初始化r指向L的头结点
for(i = 0; i < n;; i++)
{
p = (Node*)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
//r所在的结点的指针域和下一结点的数据域相连
r = p;
//把r移到下一结点上
}
r->next = NULL;//链表创建完毕,将最后一个结点指向空
}
单链表的整表删除
算法思路:
1.声明结点p和q。
2.将第一个结点赋值给p,下一个结点赋值给q。
3.循环执行释放p和将q赋值给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;
}
free释放时会将结点的数据域和指针域全部释放,如若不用q记录下一结点,而用p = p->next其实是找不到的,因为链表不像顺序表是一个挨着一个的,链表中个结点可能处于任意位置,但因其中间有"链"而连接起来,free§后,相当于把p和p->next的链剪断,所以需用q记录下一结点,使p可以找到下一结点.
若线性表需要频繁查找,则应用顺序存储
若频繁插入删除,则用单链表结构.
当线性表不知道大小,最好用链式存储结构,防止溢出.如果知道大小,用顺序存储好点.