单链表
链接存储方法
链接方式存储的线性表称为链表(Linked List)
链表的具体存储表示为:
1.用一组任意的存储单元来存放线性表的结点(这组存储单元可以是连续的也可以是不连续的)
2.链表中的逻辑次序和物理次序不一定相同。为了能够正确表示结点间的逻辑关系,在存储每个结点的同时,还必须存储指示其后继结点的地址信息或链
注:链式存储是最常用的存储方式之一,它不仅可以用来表示线性表,而且可用来表示各种非线性的数据结构。
链表的节点结构
data next
data域----存放结点值的数据域
next域----存放节点的直接后继的地址(位置)的指针域(链域)
注:每个结点只有一个链域的链表称为单链表(Single Linked List)
头指针head和终端节点指针域的表示
单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点(链表由头指针唯一确定,单链表可以用头指 针的名字命名)
终端结点无后继,故终端节点的指针域为空,即NULL。
单链表的图示法
单链表的类型描述
typedef char DataType;//假设结点的数据域类型为char型
typedef struct node {//结点类型定义
DataType data;//结点的数据域
struct node *next;//结点的指针域
} ListNode;
typedef ListNode *LinkList;
ListNode *p;
LinkList head;
注:LinkList和ListNode *是不同名字的同一个指针类型(命名的不同是为了概念上更明确)
LinkList类型的指针变量head表示他是单链表的头指针
ListNode * 类型的指针变量p表示它是指向某一结点的指针
指针变量p和结点变量*p的关系
指针变量p的值-------结点地址
结点变量*p的值-----结点内容
(*p).data的值-----p指针所指结点的data域的值
(*p).next的值-------*p后继结点的地址
*((*p).next) -------*p后继结点
单链表的运算
建立单链表
动态的建立单链表有如下两种方法:
1.头插法建表
算法思路:从一个空表开始,重复读入数据,生成新结点,将读入数据存放在新结点的数据域中,然后将新节点插入到当前链表的表头上,直到读入结束标志为止(该方法生成的链表结点次序与输入顺序相反)
具体算法实现:
LinkList CreatListF(void)
{//返回单链表的头指针
char ch;
LinkList head;//头指针
ListNode *s;//工作指针
head = NULL;//链表开始为空
ch =getchar;//读入第一个字符
while (ch !='\n') {
s= (ListNode *)malloc(sizeof(ListNode));//生成新节点
s->data =ch;//将读入的数据放入新节点的数据域中
s->next= head;
head = s;
ch = getchar();//读入下一个字符
}
return head;
}
2.尾插法建表
算法思路:从一个空表开始,重复读入数据,生成新节点,将读入数据存放在新节点的数据域中,然后将新结点插入到当前链表的表尾上,直到读入结束标志为止。
具体算法实现:
LinkList CreatListR(void)
{//返回单链表的头指针
char ch;
LinkList head;//头指针
ListNode *s ,*r;//工作指针,r为尾部指针
head = NULL;//链表开始为空
r = NULL;//尾指针初值为空
ch = getchar();//读入第一个字符
while(ch!='\n') {
s = (ListNode *) malloc (sizeof(ListNode));//生成新结点
s->data = ch;//将读入的数据放入新节点的数据域中
if (head == NULL)
head = s;//新结点插入空表
else
r ->next =s;
r = s;// 尾指针指向新表尾
ch =getchar();//读入下一个字符
}//endwhile
if(r! = NULL)
r->next = NULL;//对于非空表,将尾结点指针域置空head = s;
return head;
}
单链表的查找运算
1.按序号查找:在链表中,即使知道被访问结点的序号i,也不能象顺序表中那样直接按序号i访问结点,而只能从链表的头指针出发,顺链域next逐个节点往下搜索,直 至搜索 到第i个结点为止。因此,链表不是随机存取结构。
查找的思想方法:计数器j置为0后,扫描指针从链表的头结点开始顺着链扫描。当p扫描下一个结点时,计数器相应的加1,当j=i时,指针p所指的结点就是要找的第i个结点。而 当p指针指向null&& !=i 时表示找不到i结点。(头结点可看作第0个结点)
算法实现:
ListNode* GetNode(LinkList head,int i)
{
int j;
ListNode *p;
p = head; j=0;
while(p->next&&j<i) {
p = p->next;
j++;
}
if(i==j)
return p;
else return NULL;
}
算法分析:算法中,while语句终止的条件是搜索到表尾或找到j==i,所以时间复杂度为O(n)
2.按值查找
思想方法:从开始结点出发,顺着链逐个将结点的值和给定值key作比较,若有结点的值与key相等,则返回首次找到的其值为节点的存储位置,否则返回NULL。
算法实现:
ListNode* LocateNode (LinkList head,DataType key)
{
ListNode *p = head->next;
while(p&&p->data!=key)
p = p->next;
return p;
}
其时间复杂度为O(n)
插入算法
思想方法:插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。
具体步骤:
(1)找到ai-1存储位置p
(2)生成一个数据域为x的新结点*s
(3)令结点*p的指针域指向新结点
(4)新结点的指针域指向结点ai。
具体实现:
void InsertList(LinkList head,DataType x,int i)
{
ListNode *p;
p = GetNode(head,i-1);
if (p == NULL)
Error( " ");
s = (ListNode *)malloc (sizeof(ListNode));
s->data= x;s->next=p->next;p->next =s;
}
算法的时间主要耗费在查找操作GetNode上,故时间复杂度为O(n)
删除运算
思想方法:删除运算是将表的第i个结点删去。
具体步骤:(1)找到ai-1的存储位置p(因为在单链表中结点ai的存储地址是在其直接前趋结点ai-1的指针域next中)
(2)令p->next指向ai的直接后继结点(即把ai从链上摘下)
(3)释放结点ai的空间,将其归还给"存储池"。
具体算法实现:
void DeleteList(LinkList head,int i)
{//删除带头结点的单链表head上的第i个结点
ListNode *p,*r;
p = GetNode(head,i-1);//找到第i-1个结点
if (p == NULL || p->next == NULL)当i<1或i>n时,删除位置出错
Error(" ");//退出程序运行
r = p->next;//使r指向被删除的结点ai
p ->next =r->next;//将ai从链上摘下
free(r);//释放结点ai的空间给存储池
}
算法分析:算法的时间复杂度也是O(n)
链表上实现的插入和删除运算,无需移动结点,只需修改指针即可,所以对于改动比较频繁的数据,适合用链表存储,如果改动不频繁,可用顺序表(因链表需要额外的为指针开辟的存储空间,所以存储占用量大)