(总结源自《大话数据结构》,初学数据结构推荐此书)
目录
线性表的链式存储结构
基础知识与概念
顺序存储结构比较简单,但它是有缺点的:插入和删除的时候需要移动大量元素,若在第一个元素之前的位置插入元素或删除第一个位置的元素,则所有元素都需要往后移动。
为什么呢? 因为顺序存储是紧挨着的,元素和元素之间没有空隙,容不下其他元素插足,而删除元素留下的空隙也需要被填补。为了解决这个问题,大神们引入了链式存储结构:每个元素不再紧挨直接后继元素,而是知道直接后继元素的位置。如何知道呢?指针!
指针是指向直接后继元素的位置的,而最后一个元素没有直接后继元素,则它的指针域为NULL或^。
头指针与头结点
头指针:链表中第一个结点的存储位置就叫头指针
头结点:头结点可不是第一个结点! 为了更加方便对链表的操作,会在单链表的第一个结点前附设一个结点,称为头结点,它可以不存储任何信息(也可以存储线性表长度等附加信息),头结点的指针域存储指向第一个结点的指针。
(《大话数据结构》书中图3-6-4与图3-6-6的头指针在头结点位置,是错的。头指针指向头结点(如果有头结点的话,图中则表示为黑色结点))
解释:头指针无论什么时候都不为空——链表为空时,头结点的指针域为空,头指针指向头结点,不为空。
/*线性表的单链表存储结构*/
typedef int ElemType;
typedef int Status;
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; /*定义LinkList*/
那么,若p是指向线性表第i个元素的指针,则p->data则可以表示a(i)的数据与,p->next则可以表示a(i)的指针域。
那么,p->next->data=a(i+1)的数据域,即
单链表的读取 O(n)
由于是链表,所以我们并不知道第i个元素的位置,只能从头开始找。
思路:声明一个指针指向第一个结点,遍历链表,若到链表最末端,仍未查到所需数据,则数据不存在,反之查找成功。
//查找链表第i个数据
Status GetElem(LinkList L ,int i,ElemType *e)
{
int j; //计数器
LinkList p; //定义一个结点
p=L->next;
j=1;
while(p && j<i) //遍历链表
{
p=p->next;
++j;
}
if( !p || j>1 ) //查找失败的情况
return ERROR;
*e=p->data;
return OK;
}
(上图来自极客时间的数据结构与算法之美专栏)
单链表的插入 O(n)
前面的读取相比顺序存储结构而言,并不能看出链表的优势,而链表的插入相比较顺序存储结构就会显得非常方便。
这是因为顺序存储结构在插入与删除操作时,需要移动插入与删除后的所有数据,而链表在插入操作中,只有两个结点(插结点S与被插前结点P)发生了联系与变化。(核心代码:s->next=p->next; p->next=s; 顺序千万不能错,可以自己推导一下错了会怎么样)
//在第i个数据前插入结点
Status ListInsert(LinkList *L,int i, ElemType e )
{
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)); //开辟一块空间给e数据
//将e赋予s结点,后进行插入操作
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
单链表的删除 O(n)
单链表的删除操作本质上就是绕过要删除的结点。A牵着B的手,B牵着C的手,A现在绕过B,直接牵起C的手,就是单链表的删除操作。(核心:p->next=p->next->next)
//删除第i个数据的结点
Status ListDelete (LinkList *L,int i)
{
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;
free(q); //释放第i个数据
return OK;
}
单链表的整表创建
创建单链表的思路:先创建一个空表,再创建结点续在空表后。续分前后,所以就有了两种方法:头插法和尾插法
头插法:在表头插入
//随机产生n个元素的值,建立代表头结点的单链线性表L(头插法)
void CreateListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof (Node));
(*L)->next=NULL; //L为空表
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;
int i;
srand( time (0) );
*L = (LinkList) malloc (sizeof(Node));
r=*L;
for (i=0; i<n;i++)
{
p=(Node *)malloc(sizeof(Node));
p->data=rand()%100+1;
r->next=p;
r=p;
}
r->next=NULL;
}
这里要分清r和L的区别,r是指向尾结点的变量(这也是最后r=p的原因),L是整个单链表。
单链表的整表删除
删除的思路:
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next;
while(p)
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL;
return OK;
}