About:链式线性表
链式线性表的数据存储方式不同于顺序链式表。由于它的数据元素是存储在任意的单元空间(可以连续,也可非连续),所以它的存,取数据操作就不必受“牵一发而动全身”的罪。我们将一个数据元素存储的空间称为数据域,其直接后继存储空间称为指针域,二者统称为一个结点。各结点就可连成具有**“一对一”关系的链表。因该结点只含有一个指针域,所以我们可称该链表为线性链表或单链表**。
单链表的初始化
在单链表中,我们用头指针指示链表中第一个结点的存储位置,由于最后一个结点已无后继,所以最后一个结点的指针为‘’空“(NULL)。在算法实现中,多数情况下我们都用一个头结点顶替第一个结点的位置,这样在后续的存,取操作中我们就无需改变头指针的指向了。(头结点为一个数据域无需存储信息的结点)
由此我们来初始化一下单链表:
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
void CreateList_L(LinkList *L,int n) //n为将要建立的结点数目
{
*L=(LinkList)malloc(sizeof(LNode));
(*L)->next=NULL;
int i; LinkList p;
for(i=n;i>0;--i)
{
p=(LinkList)malloc(sizeof(LNode));
scanf("%d",&p->data);
p->next=(*L)->next; //重点代码
(*L)->next=p;
}
}
这里我们采用的是逆向动态生成单链表,即从表尾到表头逐个初始化新的数据元素,为什么这样玩呢,后面章节会引用到。
获取指定位序下的元素
因单链表没有随机存取的特点,所以当我们想要得到具体位序下的元素时,我们只能从第一个结点出发,依次寻找到该位序下的元素。具体算法如下:
Status GetElem_L(LinkList L,int i,ElemType *e)
{
LNode *p=L->next; int j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR;
*e=p->data;
return OK;
}
单链表的插入和删除
单链表既然不用忍受**“牵一发而动全身”**的罪,那么它是如何进行插入和删除操作的呢,我们来看看吧。
插入操作
由上图可知,在插入结点时,我们需要进行以下两个关键性步骤:
- S->next=P->next
- P->next=S
注意这两个步骤不能互换顺序,否则会造成S指针紊乱。
由上两条特征,算法实现如下:
Status ListInsert_L(LinkList L,int i,ElemType e)
{
LNode *p=L,*s=NULL; int j=0;
while(p&&j<i-1)
{
p=p->next;
++j;
}
if(!p||j>i-1) return ERROR;
s=(LinkList)malloc(sizeof(LNode));
s->data=e;
s->next=p->next; p->next=s; //重点代码
return OK;
}
这里j=0时,P指向的是头结点。
删除操作
由上图可知,在删除结点时,我们只需跳过待删结点方可:
- P->next=P->next->next或P->next=Q->next
- free(Q)
由上两条特征,算法实现如下:
Status ListDelete_L(LinkList L,int i,ElemType *e)
{
LNode *p=L,*q=NULL; int j=0;
while(p->next&&j<i-1)
{
p=p->next;
++j;
}
if(!(p->next)||j>i-1) return ERROR;
q=p->next; p->next=q->next;
*e=q->data; free(q);
return OK;
}
以上代码,我们为保证算法的健壮性,也附增了许多条件,大家细品。
In the end:
这里,我们采用的是以结点的方式来描述单链表。其实,单链表也可用一维数组进行描述。此类链表我们称为静态链表。但静态链表的算法实现过于冗杂,且这种存储方式,后面的数据结构也会涉及到,这里就不详解了。