目录
(有相关的知识点还会继续更新,比较偏基础,以及很全了,这些先去搞懂)
(学习从一点一滴,坚持从每时每刻)
一 线性表的链式存储
链表定义:
采用链式存储结构的线性表称为链表 。 现在我们从两个角度来讨论链表:
1.从实现角度看,链表可分为动态链表和静态链表;
2.从链接方式的角度看,链表可分为单链表、循环链表和双链表。
☺单链表
结点(Node)为了正确地表示结点间的逻辑关系,必须在存储线性表的每个数据元素值的同时,存储指示其后继结点的地址(或位置)信息,这两部分信息组成的存储映象叫做结点(Node)。
单链表:链表中的每个结点只有一个指针域,我们将这种链表称为单链表。
单链表包括两个域:数据域用来存储结点的值;指针域用来存储数据元素的直接后继的地址(或位置)。
头指针 :指向链表头结点的指针。
单链表的示例图:
带头结点的单链表示意图
有时为了操作的方便,还可以在单链表的第一个结点之前附设一个头结点。
单链表的存储结构描述
typedef struct Node / * 结点类型定义 * /
{ ElemType data;
struct Node * next;
}Node, *LinkList;/* LinkList为结构指针类型*/
☺单链表上的基本运算:
建立单链表
头插法建表:
算法描述:从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直至读入结束标志为止。
Linklist CreateFromHead()
{ LinkList L; Node *s; int flag=1;
/* 设置一个标志,初值为1,当输入“$”时,flag为0,建表结束*/
L=(Linklist)malloc(sizeof(Node));/*为头结点分配存储空间*/
L->next=NULL;
while(flag)
{ c=getchar();
if(c!=’$’) /*为读入的字符分配存储空间*/
{ s=(Node*)malloc(sizeof(Node)); s->data=c;
s->next=L->next; L->next=s; }
else flag=0;
}
}
尾插法建表
尾插法建表算法
Linklist CreateFromTail() /*将新增的字符追加到链表的末尾*/
{ LinkList L; Node *r, *s; int flag =1;
L=(Node * )malloc(sizeof(Node));/*为头结点分配存储空间*/
L->next=NULL; r=L; /*r指针始终动态指向链表的当前表尾*/
while(flag)/*标志,初值为1。输入“$”时flag为0,建表结束*/
{ c=getchar();
if(c!=’$’)
{ s=(Node*)malloc(sizeof(Node)); s->data=c;
r->next=s; r=s }
else { flag=0; r->next=NULL; }
}
}
单链表查找
按序号查找
算法描述:设带头结点的单链表的长度为n,要查找表中第i个结点,则需要从单链表的头指针L出发,从头结点(L->next)开始顺着链域扫描,用指针p指向当前扫描到的结点,初值指向头结点(pL->next),用j做记数器,累计当前扫描过的结点数(初值为0),当j = i 时,指针p所指的结点就是要找的第i个结点 。算法实现,算法演示。
按序号查找算法实现
/ * 在带头结点的单链表L中查找第i个结点,若找到(1≤i≤n),则返回该结点的存储位置; 否则返回NULL * /
Node * Get(LinkList L, int i)
{ Node *p;
p=L;j=0; / * 从头结点开始扫描 * /
while ((p->next!=NULL)&&(j<i)
{ p=p->next; j++; / * 扫描下一结点 * /
} / * 已扫描结点计数器 * /
if(i= =j)return p; / * 找到了第i个结点 * /
else return NULL; / * 找不到,i≤0或i>n * /
}
按值查找
算法描述:按值查找是指在单链表中查找是否有结点值等于e的结点,若有的话,则返回首次找到的其值为e的结点的存储位置,否则返回NULL。查找过程从单链表的头指针指向的头结点出发,顺着链逐个将结点的值和给定值e作比较。算法实现,算法演示。
/ * 在带头结点的单链表L中查找其结点值等于key的结点,若找到则返回该结点的位置p,否则返回NULL * /
Node *Locate( LinkList L,ElemType key)
{ Node *p;
p=L->next; / * 从表中第一个结点比较 * /
while (p!=NULL)
if (p->data!=key)
p=p->next;
else break; / * 找到结点key,退出循环 * /
return p;
}
单链表插入操作
算法描述:要在带头结点的单链表L中第i个数据元素之前插入一个数据元素e,需要首先在单链表中找到第i-1个结点并由指针pre指示,然后申请一个新的结点并由指针s指示,其数据域的值为e,并修改第i-1个结点的指针使其指向s,然后使s结点的指针域指向第i个结点。
单链表插入操作算法实现
void InsList(LinkList L,int i,ElemType e)
{ /*在带头结点的单链表L中第i个结点之前插入值为e的新结点。 */
Node *pre,*s; pre=L; int k=0;
while(pre!=NULL&&k<i-1)
/*先找到第i-1个数据元素的存储位置,使指针Pre指向它*/
{ pre=pre->next; k=k+1; }
if(k!=i-1)
{ printf(“插入位置不合理!”); return; }
s=(Node*)malloc(sizeof(Node)); /*为e申请一个新的结点*/
s->data=e; /*将待插入结点的值e赋给s的数据域*/
s->next=pre->next; pre->next=s;
}
单链表删除
算法描述:欲在带头结点的单链表L中删除第i个结点,则首先要通过计数方式找到第i-1个结点并使p指向第i-1个结点,而后删除第i个结点并释放结点空间。
单链表删除算法实现
void DelList(LinkList L,int i,ElemType *e)
/*在带头结点的单链表L中删除第i个元素,并保存其值到变量*e中*/
{
Node *p,*r; p=L; int k =0;
while(p->next!=NULL&&k<i-1) /*寻找被删除结点i的前驱结点*/
{ p=p->next; k=k+1; }
if(k!=i-1) /* 即while循环是因为p->next=NULL而跳出的*/
{ printf(“删除结点的位置i不合理!”); return ERROR; }
r=p->next; p->next=p->next->next /*删除结点r*/
free(r); /*释放被删除的结点所占的内存空间*/
}
求单链表的长度
算法描述:可以采用“数”结点的方法来求出单链表的长度,用指针p依次指向各个结点,从第一个元素开始“数”,一直“数”到最后一个结点(p->next=NULL)。
int ListLength(LinkList L) /*L为带头结点的单链表*/
{ Node *p; p=L->next;
j=0; /*用来存放单链表的长度*/
while(p!=NULL)
{ p=p->next; j ++; }
return j;
}
求两个有序单链表的合并
有两个单链表LA和LB,其元素均为非递减有序排列,编写一个算法,将它们合并成一个单链表LC,要求LC也是非递减有序排列。要求:利用新表LC利用现有的表LA和LB中的元素结点空间,而不需要额外申请结点空间。例如LA=(2, 2, 3), LB=(1, 3, 3, 4), 则LC=(1, 2, 2, 3, 3, 3, 4)。
【算法描述】要求利用现有的表LA和LB中的结点空间来建立新表LC,可通过更改结点的next域来重建新的元素之间的线性关系,为保证新表仍然递增有序,可以利用尾插入法建立单链表的方法,只是新建表中的结点不用malloc,而只需要从表LA和LB中选择合适的点插入到新表LC中即可。
LinkList MergeLinkList(LinkList LA, LinkList LB)
/*将递增有序的单链表LA和LB合并成一个递增有序的单链表LC*/
{ Node *pa,*pb;
LinkList LC;
/*将LC初始置空表。pa和pb分别指向两个单链表LA和LB中的第一个结点,r初值为LC*/
pa=LA->next;
pb=LB->next;
LC=LA;
LC->next=NULL;r=LC; /*初始化操作*/
/*当两个表中均未处理完时,比较选择将较小值结点插入到新表LC中。*/
while(pa!=NULL&&pb!=NULL)
{if(pa->data<=pb->data)
{r->next=pa;r=pa;pa=pa->next;}
else
{r->next=pb;r=pb;pb=pb->next;}
}
if(pa) /*若表LA未完,将表LA中后续元素链到新表LC表尾*/
r->next=pa;
else /*否则将表LB中后续元素链到新表LC表尾*/
r->next=pb;
free(LB);
return(LC);
} /* MergeLinkList */
二 循环链表
循环链表(Circular Linked List) 是一个首尾相接的链表。
特点:将单链表最后一个结点的指针域由NULL改为指向头结点或线性表中的第一个结点,就得到了单链形式的循环链表,并称为循环单链表。在循环单链表中,表中所有结点被链在一个环上。带头结点循环单链表示意图。
已知:有两个带头结点的循环单链表LA、LB,编写一个算法,将两个循环单链表合并为一个循环单链表,其头指针为LA。
算法思想:先找到两个链表的尾,并分别由指针p、q指向它们,然后将第一个链表的尾与第二个表的第一个结点链接起来,并修改第二个表的尾Q,使它的链域指向第一个表的头结点。
LinkList merge_1(LinkList LA,LinkList LB)
/*此算法将两个链表的首尾连接起来*/
{ Node *p, *q; p=LA; q=LB;
while (p->next!=LA) p=p->next; /*找到表LA的表尾*/
while (q->next!=LB) q=q->next; /*找到表LB的表尾*/
q->next=LA;/*修改表LB 的尾指针,使之指向表LA 的头结点*/
p->next=LB->next;/*修改表LA的尾指针,使之指向表LB 中的第一个结点*/
free(LB);
return(LA);
}
三 双向链表
双向链表:在单链表的每个结点里再增加一个指向其前趋的指针域prior。这样形成的链表中就有两条方向不同的链,我们称之为双 ( 向) 链表 (Double Linked List)。
双向链表的结构定义:
typedef struct Dnode
{ ElemType data;
struct DNode *prior,*next;
} DNode, * DoubleList;
双链表的结点结构:
双向循环链表示意图
双向链表的前插操作
算法描述:欲在双向链表第i个结点之前插入一个的新的结点,则指针的变化情况如图所示。
双向链表的前插操作算法实现
void DlinkIns(DoubleList L,int i,ElemType e)
{ DNode *s,*p;
… /*首先检查待插入的位置i是否合法*/
… /*若位置i合法,则让指针p指向它*/
s=(DNode*)malloc(sizeof(DNode));
if (s)
{ s->data=e;
s->prior=p->prior; p->prior->next=s;
s->next=p; p->prior=s; return TRUE;
} else return FALSE;
} 算法演示连接。
双向链表的删除操作
算法描述:欲删除双向链表中的第i个结点,则指针的变化情况如图所示。
双向链表的删除操作算法实现:
int DlinkDel(DoubleList L,int i,ElemType *e)
{ DNode *p;
… /*首先检查待插入的位置i是否合法*/
… /*若位置i合法,则让指针p指向它*/
*e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return TRUE;
}
四 静态链表
基本概念:
游标实现链表的方法:定义一个较大的结构数组作为备用结点空间(即存储池)。当申请结点时,每个结点应含有两个域:data域和next域。data域存放结点的数据信息,next域为游标指示器,指示后继结点在结构数组中的相对位置(即数组下标)。数组的第0个分量可以设计成表的头结点,头结点的next域指示了表中第一个结点的位置,为0表示静态单链表的结束。我们把这种用游标指示器实现的单链表叫做静态单链表(Static Linked List)。静态链表的结构定义,静态链表的插入和删除操作示例。
基本操作:
初始化、分配结点与结点回收、前插操作、删除。
静态链表的结构定义
#define Maxsize= 链表可能达到的最大长度
typedef struct
{
ElemType data;
int cursor;
}Component, StaticList[Maxsize];
静态链表的插入和删除操作示例
静态链表初始化:
算法描述:初始化操作是指将这个静态单链表初始化为一个备用静态单链表。设space为静态单链表的名字,av为备用单链表的头指针,其算法如下:
void initial(StaticList space,int *av)
{ int k;
space[0]->cursor=0;/*设置静态单链表的头指针指向位置0*/
for(k=0;k<Maxsize-1;k++)
space[k]->cursor=k+1; /*连链*/
space[Maxsize-1] . cursor =0; /*标记链尾*/
*av=1; /*设置备用链表头指针初值*/
}
静态链表分配结点与结点回收
分配结点
int getnode(int *av)/*从备用链表摘下一个结点空间,分配给待插入静态链表中的元素*/
{ int i=*av;
*av=space[*av].cur;
return i;
}
结点回收
void freenode(int *av,int k) /*将下标为 k的空闲结点插入到备用链表*/
{ space[k].cursor=*av; *av=k; }
五 顺序表和链表的比较
1.基于空间的考虑
2.基于时间的考虑
3.基于语言的考虑
六 总结
主要知识点
1、线性表的特征:
线性表中每个数据元素有且仅有一个直接前驱和一个直接后继,第一个结点无前驱,最后一个结点无后继。
2、线性表存储方式:
线性表顺序存储(顺序表):采用静态分配方式,借助于C语言的数组类型,申请一组连续的地址空间,依次存放表中元素,其逻辑次序隐含在存储顺序之中
线性表链式存储(链表):采用动态分配方式,借助于C语言的指针类型,动态申请与动态释放地址空间,故链表中的各结点的物理存储可以是不连续的。当表长度变化时仅需适当变化指针的联接,适合于表中元素个数动态变化。
3、单链表的操作特点:
⑴顺链操作技术
⑵指针保留技术
4、链表处理中的相关技术
(有相关的知识点还会继续更新,比较偏基础,以及很全了,这些先去搞懂)