链表
①非连续的内存单元–逻辑相邻元素的物理位置不一定相邻;
②结点构成:在数据之外,附加指针->指示逻辑关系、存储地址
单链表
为每个结点新增1个指针域,每个结点包括两个域:
①数据域:存放元素本身信息
②指针域:存放后继结点的存储位置;
指向链表中第一个结点的指针,称为这个链表的头指针。
最后一个元素的指针不指向任何结点,称为空指针,图示中用“^”表示,在算法中用“NULL”表示
带头结点的单链表
头结点:
– 可以不存信息,可以保存与整个表相关的信息,如表长;
– 空表的头结点指针域为空null;
– 在k0处插入、删除都不影响头指针的值;
– 辅助结点,不属于表的内容
– Link域指向表的第一个实际结点
单链表定义(C语言描述):
struct Node; //单链表结点类型
typedef struct Node * PNode; //结点指针类型(PNode是个指针,指向struct Node)
struct Node
{ DataType info; //数据域(info是DataType类型)
PNode link; //指针域,可指向一个结点,link是PNode类型
};
typedef struct Node * LinkList; //结点指针类型,C语言当中实际使用的是LinkList指针
LinkList llist; //llist为单链表的头指针
单链表运算的实现
- 创建空表
①声明头指针
②为头结点申请空间
③设置头结点的指针域
LinkList createNullList_link(void)
{ LinkList llist;
llist = (LinkList )malloc(sizeof(struct Node));
if(llist != NULL) //头结点空间分配成功
llist->link = NULL; // llist->link有意义,才能用
else printf(“out of space!\n”);
return llist; //返回头指针
}
- 判断是否为空表
检查头结点的指针域 llist->link 是否为空
int isNullList_link(LinkList llist)
{
return ( llist->link == NULL) ;
}
- 查找元素、求某元素的存储位置
PNode locate_link(LinkList llist, DataType x)
{ PNode p;
if(llist == NULL) return NULL;
p= llist->link; //p指向第1个结点
while( p !=NULL && p->info !=x)
p= p->link; //若未找到,游历指针右移
return (p);
}
- 在单链表中插入元素
在指针p所指结点之后,插入元素x
①申请新结点q,置其数据域为x;
②q挂到链表上:q的指针域指向p的后继;
③p的指针域指向q,原链自动断开;
int insertPost_link(LinkList llist, PNode p,DataType x)
{ PNode q = (PNode)malloc(sizeof(struct Node));
if (q == NULL) //若空间分配失败
{ printf(“Out of space!\n”); return(0);}
else
{ q->info=x; //设置新结点q
q->link = p->link; //q链入原链表
p->link = q; //完成新链表
}
}
在指针p所指结点之前,插入元素x
①找到p的前驱p1
②申请新结点q,置其数据域为x;
③q挂到原链表:q的指针域指向p;
④p1的指针域指向q,原链自动断开;
寻找指针p所指结点的前驱:
从第1个结点开始,检查各结点的指针域?=p
PNode locatePre_link(LinkList llist, PNode p)
{ if(llist==null) return null;
PNode p1= llist; //游历指针p1指向头结点
while( p1!=null&&p1->link!=p)
p1= p1->link;//若后继不是p, 则指针右移
return p1;
}
寻找值为x的结点的前驱:
从第1个结点开始,检查各结点的数据域?=x
在带头结点单链表中,第i+1个结点之前,(顺序表中下标为i的元素ki)之前,插入元素x
– 借助计数器 int j=0 寻找下标为i-1的元素
int insert_link(LinkList llist, int i, DataType x)
{ PNode p, q; //游历指针
p= llist; //p指向头结点
int j =0; //声明计数器
while(p!=NULL&& j<i) //当计数未达到, 指针右移
{ p= p->link; j=j+1;}
if (p==NULL || j!=i) //若未找到下标为i-1的元素
{ printf(“i =%d is illegal !\n”, i);
return 0; }//若成功找到下标为i-1的结点(p正指向它)
q=(PNode)malloc(sizeof(struct Node));
if (q==NULL)
{ printf(“Out of space!\n”); return 0; }
else//在p之后 插入新结点
{ q->info=x;
q->link = p->link ;
p->link = q; }
return 1;
}
- 从单链表中删除元素
从单链表中删除第1个值为x的结点
①遍历,查找x (地址为q),其前驱p;
②从链表中删除x
③释放x结点占用的内存空间
int deleteV_link(LinkList llist, DataType x)
{ PNode p=llist, q;
while( p->link !=NULL && p->link->info !=x)
p= p->link; //从头开始寻找x
if (p->link ==NULL) //判断是否找到x
{ printf(“x does not exist! \n”); return 0; }
else//找到x,且p指向x的前驱
{ q = p->link;
p->link = q->link; //删除q
free(q);
return 1; }
}
单链表代价分析
在单链表中查找第一个值为x的元素:
---- 从头结点开始,遍历、匹配
---- 时间复杂度为O(n)
在单链表中查找第i个元素:
---- 从头结点开始,借助计数器j, 遍历
---- 时间复杂度为O(n)
单链表分析与评价
优点:
插入或删除,只需修改指针,无需移动元素;
动态分配存储空间;
缺点:
存储密度比顺序表低;
不能随机访问第i个元素,需顺链查找;