版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/csuft20164442/article/details/86318844
线性表(List)
链式存储结构
链式存储结构: 用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在在内存中未被占用的任意位置。
-
把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成的数据元素称为存储映像,称为结点(Node)。
-
n个结点链接成一个链表,即为线性表(a1,a2,a3,…,an)的链式存储结构。
-
链表的每个结点中只包含一个指针域,所以叫做单链表。
-
链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为空(NULL)
单链表的结构代码:
typedef struct Node
{
ElemType data; //数据域
struct Node* Next; //指针域
} Node;
typedef struct Node * LinkList;
头指针与头结点的异同
-
头指针
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
- 头指针具有标识作用,所以常用指针冠以链表的名字(指针变量的名字)。
- 无论链表是否为空,头指针均不为空。
- 头指针是链表的必要元素。
-
头结点
- 头结点是一个为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)。
- 有了头结点,对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一了。
- 头结点不一定是链表的必须要素。
单链表图例:
空 链表图例:
单链表的读取
获得链表i个数据的算法思路
- 声明一个结点p指向链表第一个结点,初始化j从1开始;
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j+1;
- 若到链表末尾p为空,则说明第i个元素不存在;
- 否则查找成功,返回结点p的数据。
typedef int Status;
//Status 是函数的类型,其值是函数结果状态代码,如OK等
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果,在L中的查找第i个元素并用e返回
Status GetElem( SqList L,int 1,ElemType *e )
{
int j;
LinkList p;/*声明一指针p*/
p = L->next;/*让p指向链表L的第一个结点*/
j = 1;
while(p && j<i)/*当p不为空或者j还没有大于等于i时,循环继续*/
{
p= p->next;/*让p指向下一个结点*/
++j;
}
if(!p || j>i)
{
return ERROR;/*第i个元素不存在*/
}
*e=p->data;/*取出第i个结点的数据*/
return oK;
}
单链表的插入
只需让s->next 和p->next的指针做一点改变即可。
s->next = p->next;
p->next = s;
单链表插入结点的算法思路
- 声明一个指针p指向链表的第一个结点,初始化j从1开始;
- 当j< i 时,遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
- 若到链表末尾p为空,说明第i个元素不存在;
- 否则查找成功,在系统中生成一个空结点s;
- 将数据元素e赋值给s->data;
- 单链表的插入标准语句 s->next = p->next; p->next = s;
- 返回成功。
typedef int Status;
//Status 是函数的类型,其值是函数结果状态代码,如OK等
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果,在L中第i个位置之前插入新数据元素e,L的长度+1
Status ListInsert(SqList *L,int 1,ElemType *e )
{
int j;
LinkList p,s;
p = *L;
j = 1;
while(p && j<i) //遍历寻找第i个结点
{
p= p->next;/*让p指向下一个结点*/
++j;
}
if(!p || j>i)
{
return ERROR;/*第i个元素不存在*/
}
s=(LinkList)malloc(sizeof(Node)); //生成心结点,malloc:分配内存
s->data = e;
s->next = p->next;/*将p的后继结点赋值给s的后继*/
p->next = s;/*将s赋值给p的后继*/
L.length++;
return OK;
}
单链表的删除
实际上就是一步,p->next = p->next->next. 用p来取代p->next;
q=p->next;
p->next = q->next;
单链表第i个数据删除结点的算法思路:
- 声明一个指针p指向链表的第一个结点,初始化j从1开始;
- 当j< i 时,遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
- 若到链表末尾p为空,说明第i个元素不存在;
- 否则查找成功,将欲删除的结点p-next赋值给q;
- 单链表的删除标准语句 p->next = q->next;
- 将q结点中的数据赋值给e,作为返回;
- 释放q结点;
- 返回成功。
typedef int Status;
//Status 是函数的类型,其值是函数结果状态代码,如OK等
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果,在L中第i个位置之前插入新数据元素e,L的长度+1
Status ListInsert(SqList *L,int 1,ElemType *e )
{
int j;
LinkList p,q;
p = *L;
j = 1;
while(p->next && j<i) //遍历查抄第i个结点
{
p= p->next;
++j;
}
if(!(p->next) || j>i) //不在表内查找报错
{
return ERROR;
}
q = p->next;
p->next = q->next
*e = q->data;
free(q);
L.length--;
return OK;
}
单链表的整表创建
单链表是一种动态结构。 对于每个链表来说,它所占用的空间大小和位置是不需要预先分配的。 所以创建单链表的过程,就是从“空表”的初始状态,一次建立各元素结点,并插入链表中。
单链表的整表创建的思路如下
- 声明一结点p和计数器变量i;
- 初始化一空链表L;
- 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
- 循环实现后继点的赋值和插入。
头插法:
/*头插法建立单链表示例*/
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;
}
}
尾插法:
/*尾插法建立单链表示例*/
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;//p为结点,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->next = NULL;
}
单链表的整表删除
单链表的整表删除的算法思路如下:
- 声明结点p和q;
- 将第一个结点赋值给p,下一结点赋值给q;
- 循环执行释放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;
}
单链表和顺序存储结构优缺点
分配方式:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
时间性能:
- 查找
- 顺序存储结构O(1)
- 单链表O(n)
- 插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
- 单链表在计算出某位置的指针后,插入和删除时间仅为O(1)
空间性能:
- 顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易发生溢出。
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
综上所述:
- List item若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。
- 若需要频繁插入和删除时,宜采用单链表结构。