链式存储:用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表简称线性链表。
存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是可以零散分布在内存的任意位置上的。
结点的逻辑顺序和物理顺序不一定相同。
为了正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其直接后继结点的地址,称为指针(pointer),这两部分组成了链表中的结点结构。
链表是通过每个结点的指针域将线性表的n个结点按其逻辑次序链接在一起的。
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素称为存储映像,称为结点(Node)。
n个结点链接成一个链表,即为线性表(a1,a2,a3,…,an)的链式存储结构。
因为此链表的每个结点中只包含了一个指针域,所以叫做单链表。
我们把链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为空(NULL)。
头指针:
头结点:
typedef struct Lnode{
ElemType data; /*数据域,保存结点的值*/
struct Lnode *next; /*指针域*/
}Lnode, *LinkList; /*结点的类型*/
结点的实现:
结点是通过动态分配和释放来实现的,即需要时分配,不需要时释放。
动态分配:
p = (LNode* )malloc(sizeof(LNode));
函数malloc分配了一个类型为LNode的结点变量的空间,并将其首地址放入指针变量p中。
动态释放:free(p);
系统回收由指针变量p所指向的内存区。p必须是最近一次调用malloc函数时的返回值。
建立单链表
设结点的数据类型是整型,32767作为结束标志。
动态地建立单链表的方法有如下两种:
头插入法,尾插入法
(1)头插入法建表
从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止,即每次插入的结点都作为链表的第一个结点。
算法描述:
LNode *create_LinkList(void)
/*头插入法创建单链表,链表的头结点head作为返回值*/
{
int data;
LNode *head,*p;
head = (LNode *)malloc(sizeof(LNode));
head->next=NULL;/*创建链表的表头结点head*/
while(1){
scanf("%d",&data);
if (data==32767) break;
p = (LNode *)malloc(sizeof(LNode));
p->data=data;/*数据域赋值*/
p->next = head>next;
head->next=p;
/*钩链,新创建的结点总是作为第一个结点*/
}return (head);
}
(2)尾插入法建表
头插入建立链表虽然简单,但生成的链表中结点的次序和输入的顺序相反
若希望二者次序一致,采用尾插入法建表,该方法是将新结点插入到当前链表的表尾,使其成为当前链表的尾节点。
每次将新结点加在插到链表的表尾;
设置一个尾指针last,总是指向表中最后一个结点,新结点插在它的后面;
尾指针last初始时为指向表头结点地址。
算法描述:
LNode *create_LinkList(void){
/*尾插入法创建单链表,链表的头结点head作为返回值*/
LNode *head,*last,*q;
head = p = (LNode *)malloc(sizeof(LNode));
p->next=NULL;/*创建单链表的表头结点head*/
while(1){
scanf("%d",&data);
if (data==32767) break;
q = (LNode *)malloc(sizeof(LNode));
q->data = data;/*数据域赋值*/
q->next = last->next;
last->next = q;
last = q;
/*钩链,新创建的结点总是作为最后一个结点*/
} return (head);
}
单链表的查找
(1)按序号查找,取单链表中的第i个元素。
从链表的头结点出发,沿链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。
设单链表的长度为n,要查找表中第i个结点,仅当1<=i<=n时,i的值是合法的。
算法描述:
ElemType Get_Elem(LNode *L,int i){
int j; LNode *p;
p=L->next;j=1;/*使p指向第一个结点*/
while (p!=NULL&&j<i){
p = p->next;j++ }/*移动指针p,j计数*/
if (j!=i)
return (-32767);
else
return(p->data);/*p为NULL表示i太大;j>i表示i为0*/
}
移动指针p的频率:
i<1时:0次;i ∈ [1,n] : i - 1 次; i>n : n 次。
所以:时间复杂度为O(n)
(2)按值查找
按值查找是在链表中,查找是否有结点值等于给定值key的结点
若有,则返回首次找到的值为key的结点的存储位置;
否则返回NULL。查找时从开始结点出发,沿链表逐个将结点的值和给定值key作比较。
算法描述:
LNode *Locate_Node(LNode *L,int key)
/*在以L为头结点的单链表中查找值为key的第一个结点*/
{ LNode *p = L->next;
while (p!=NULL&&p->data!=key)
p = p->next;
if (p->data == key) return p;
else{
printf("所要查找的结点不存在!\n");
return (NULL);
}
算法的执行与形参key有关,平均时间复杂度为O(n)。
单链表的插入
插入运算是将值为e的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。因此,必须首先找到ai-1所在的结点p,然后生成一个数据域为e的新结点q,q结点作为p的直接后继结点。
(1)在第一个结点前插入q:
q- >next = head- >next , head- >next = q;
(2)在链表中间p后插入q:
q - next = p - >next , p - > next = q;
(3)在链表末尾插入q:
设尾指针为p,
p - > next = q , q - >next = null , p = q;
p - > next =q, q - > next = p - >next , p = q;
算法描述:
void Insert_LNode(LNode *L,int i,ElemType e)
/*在L为头结点的单链表的第i个位置插入值为e结点*/
{
int j = 0;LNode *p,*q;
p = L -> next;
while(p!=NULL&&j<i-1){
p = p ->next;j++; }
if (j!=i-1)
printf("i太大或i为0!!\n");
else{
q = (LNode *)malloc(sizeof(LNode));
q ->data = e; q ->next = p ->next;
p ->next = q;
}
}
设链表的长度为n,合法的插入位置是1<=i<=n。算法的时间主要耗费移动指针p上,故时间复杂度为O(n)。
4.单链表的删除
(1)按序号删除
删除单链表中的第i个结点。
为了删除第i个结点ai,必须找到结点的存储地址。该存储地址是在其直接前趋结点ai-1的next域中,因此,必须首先找到ai-1的存储位置p,然后令p->next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间,将其归还给“存储池”。
设单链表长度为n,则删去第i个结点仅当1<=i<=n时是合法的。则当i=n+1时,虽然被删除结点不存在,但其前趋结点却存在,是终端结点。故判断条件之一是p->next != NULL。时间复杂度为O(n)。
算法描述:
void Delete_LinkList(LNode *L,int i)
/*删除以L为头结点的单链表中的第i个结点*/
{
int j=1; LNode *p, *q;
p = L;q = L->next;
while (p->next!=NULL&&j<i)
{
p=q;q=q->next;j++;
}
if (j!=i) printf("i太大或i为0!!\n");
else{
p ->next = q ->next; free(q);
}
}
(2)按值删除
删除单链表中值为key的第一个结点。
与按值查找相类似,首先要查找值为key的结点是否存在:
若存在,则删除;
否则返回NULL。
算法描述:
void Delete_Link(LNode *L,int key)
/*删除以L为头结点的单链表中值为key的第一个结点*/
{
LNode *p = L, *q = L ->next;
while (q!=NULL&& q ->data!=key){
p=q; q = q ->next;
}
if (q ->data==key){
p ->next = q->next;free(q);
}
else printf("所要删除的结点不存在!!\n");
}
算法的执行与形参key有关,平均时间复杂度为O(n)。
链表上实现插入和删除运算,无需移动结点,仅需修改指针。
解决了顺序表的插入或删除操作需要移动大量元素的问题。
例题1:
删除单链表中值为key的所有结点。
基本思想:从单链表的第一个结点开始,对每个结点进行检查,若结点的值为key,则删除之,然后检查下一个结点,直到所有的结点都检查。
算法描述:
void Delete_LinkList_Node(LNode *L,int key)
/*删除以L为头结点的单链表中值为key的第一个结点*/
{
LNode *p=L,*P = L->next;
while (q!=NULL){
if (q->next==key){
p->next=q->next; free(q);
q = p->next; }
else{
p = q;q = q->next;}
}
}
例题2:
删除单链表中所有值重复的结点,使得所有结点的值都不相同。
基本思想:从单链表的第一个结点开始,对每个结点进行检查,检查链表中该结点的所有后继节点,只要有值和该结点的值相同,则删除之;然后检查下一个结点,直到所有的结点都检查。
void Delete_Node_value(LNode *L)
/*删除以L为头结点的单链表中所有值相同的结点*/
{
LNode *p=L->next, *q,*ptr;
while (p!=NULL)/*检查链表中所有的结点*/
{
*q = p,*ptr = p->next;
/*检查结点p的所有后继结点ptr*/
while (ptr!=NULL){
if (ptr ->data==p->data){
q->next=ptr->next;
free(ptr); ptr=q->next; }
else {q = ptr;ptr=ptr->next;}
}
p = p->next;
}
}