目录
链表的概念
单链表的实现
1 链表的概念
引例1 建立学生信息表
假设需要建立一个学生信息表,学生人数无法估计,而且学生人数经常发生变化,该如何实现?
单链表:线性表的链接存储结构。
存储思想:用一组任意的存储单元存放线性表的元素。
不连续、分散分布
单链表
*单链表存储特点:
1.逻辑次序和物理次序不一定相同。
2.元素之间的逻辑关系用指针表示。
举例:
(a1,a2,a3,a4)的存储示意图
由上图可知,单链表
单链表是由若干结点构成的;
单链表的结点只有一个指针域。
结点(node):由数据域和指针域构成
data:存储数据元素
next:存储指向后继结点的地址
单链表的结点结构:
typedef struct node
{
DataType data;
struct node *next;
}Node,*Link;
这样就可以动态分配内存:
p=(Link)malloc(sizeof(Node));
这里的意思就是:
Node st; 等价于 struct node st;
Link p; 等价于 struct node *p;
p=&st;
p = (Link)malloc(sizeof(Node));
等价于
p = (struct node*)malloc(sizeof(Node));
如何引用数据元素?
(*p).data = p->data
如果想访问指针域就使用:p->data的方式
什么是存储结构?
重点在数据元素之间的逻辑关系的表示,所以,将实际存储地址抽象。
因为链表的存储空间是不连续的
空表:
head==NULL
非空表:
空表和非空表不统一,缺点?
如何将空表与非空表统一?
头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便空表和非空表处理统一。
2 单链表的实现
(1)单链表的遍历操作
-操作接口:void displayNode(Link head);
void displayNode(Link head)
{
p = head->next;
while(p!=NULL)
{
printf("%d",p->data);
p=p->next;
}
}
p=head->next; //头结点指向下一个结点的地址
p=p->next; //使p指向下一个结点的地址
p++能否完成指针右移?
(2)求单链表的元素个数
-操作接口:int length(Link head);
count++
伪代码:
1.工作指针p初始化;累加器count初始化;
2.重复执行下述操作,直到p为空:
2.1 工作指针p后移
2.2 count++;
3.返回累加器count的值;
int length(Link head)
{
p = head->next;
count=0;
while(p!=NULL)
{
p=p->next;
count++;
}
return count;
}
(3)单链表的查找操作
int queryNode(Link head,DataType x)
{
p=head->next;
count = 0;
while(p!=NULL)
{
print (data);
return true;
}
p=p->next;
}
//如果循环结束了,说明没有找到
return false;
}
(4)单链表的插入操作
-操作接口:void insertNode(Link head,int i,DataType x);
如何实现结点a(i-1)、x和a(i)之间逻辑关系的变化?
算法描述:
让p移动到正确的位置;
node=(Link)malloc(sizeof(Node));
node->data=x;
node->next=p->next;
p->next=node;
①创建一个结点让node只写它:
node=(Link)malloc(sizeof(Node));
②把x的值赋给node结点的数据域
node->data=x;
③让node的指针域指向a(i )结点首地址
node->next=p->next;
④让a(i-1)的指针域指向node结点的首地址
p->next=node;
由于单链表带头结点,表头、表中、表尾三种情况的操作语句一致。
插入操作的算法描述如下总结:
1.工作指针p初始化;
2.查找第i-1个结点并使工作指针p指向该结点;
3.若查找不成功,则返回false;
否则,
3.1生成一个元素值为x的新结点s;
3.2将新结点s插入到结点p之后;
3.3返回true;
单链表的插入操作
bool insertNode(Link head,int i,DataType x)
{
p=head;
count = 0;
while(p!=NULL&&count<i-1)
{
p = p->next;
count++;
}
if(p==NULL)
return false;
bool insertNode(Link head,int i,DataType x)
{
p=head; //工作指针p指向头结点
count=0;
while(p!=NULL&&count<i-1) //查找第一个i-1个结点
{
p=p->next;
count++;
}
if(p==NULL)
return false; //没找到第i-1个结点
else
{
node=(Link)malloc(sizeof(Node));//申请一个结点node
node->data=x;
node->next = p->next; //结点node插入结点p之后
p->next = node;
return ture;
}
(5)创建一个单链表---头插法
操作接口:Link newList(DataType a[],int n);
头插法:将待插入结点插在头结点的后面。
初始化头结点
算法描述:
head=(Link)malloc(sizeof(Node));
head->next=NULL;
头插法
插入第一个元素结点:
算法描述:
node=(Link)malloc(sizeof(Node));
node->data=a[0];
node->next=head->next;
head->next=node;
Link newList(DataType a[],int n)
{
//创建头结点
head=(Link)malloc(sizeof(Node));
head->next=NULL;
//创建后续结点
for(i=0;i<n;i++)
{
node=(Link)malloc(sizeof(Node));
node->data=a[i];
node->next=head->next;
head->next=node;
}
return head;
}
尾插法:
操作接口:Link newList(DataType a[],int n);
尾插法:将待插入结点插在终端结点的后面。
插入第一个元素结点
算法描述:
node=(Link)malloc(sizeof(Node));
node->data=a[0];
rear->next=node;
rear=node;
注意点:rear->next=NULL
尾插法
Link newList(DataType a[],int n )
{
head=(Link)malloc(sizeof(Node)); //生成头结点
head->next=NULL;
rear=head; //尾指针初始化
for(i=0;i<n;i++)
{
node=(Link)malloc(sizeof(Node));
node->data=a[i];
rear->next=node;
rear=node;
}
rear->next=NULL;
return head;
}
(6)单链表结点的删除
操作接口:bool deleteNode(Link head,Datatype x);
如何删除p所指向的结点?
算法描述:
q->next=p->next;
free(p);
思考:
删除很简单,但如何能让指针移动到合适的位置,并且保证p指向要删除的结点,q则指向p的前驱结点?
但是,查找结点过程中,如何保证p,q指针一前一后?
q=p;
p=p->next;
思路:
在查找过程中,如果发现p所指向的结点data值不是要找的x,则p,q同时后移;一旦找到,则执行删除操作。
如果没有找到x的数据
思路:
在查找过程中,如果若一直没有找到data域为x的结点,则进入如上状态。
此时:退出循环,返回false。
删除时注意分析边界情况——要删除的表为空表
空表的状态是怎么样的?
head->next=NULL;
head=NULL;
思路:
在查找过程中,如果发现待删除的表是空表(左边两种情况都是空表),则提前返回false。
算法描述:
if(head==NULL||head->next==NULL)
{
//空表
return false;
}
伪代码:
1.判断是否是空表,如果是空表返回false;
2.工作指针p,q初始化;
3.若p指针不为空,则继续下列循环:
3.1如果找到data域等于x的结点,则:
3.1.1摘链,将结点p的从链表上摘下;
3.1.2释放被删结点;
3.1.3提前返回true,代表删除成功。
否则:
3.2.1q移动到 p所在的位置
3.2.2p移动到下一个结点
4.循环结束了,说明没有找到和x相等的结点,则返回false。
bool deleteNode(Link head,DataType x)
{
if(head==NULL || head->next==NULL)
{
return false;
}
p=head->next;
q=head;
while(p!=NULL)
{
if(p->data==x)
{
q->next = p->next;
free(p);
return true;
}
else
{
q=p;
p=p->next;
}
}
return false;
}
(7)单链表的释放
-操作接口:void clearLink(Link head);
-将单链表中所有结点的存储空间释放。
注意:保证链表未处理的部分不断开
算法描述:
q=head;
head=head->next;
free(p);
提问:这段代码循环执行,循环退出的条件是什么?
循环链表的实现
循环链表
从单链表中某结点p出发如何找到其前驱?
将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表,简称循环链表。
3循环链表的实现
循环链表
循环链表的插入
算法描述:
node=(Link)malloc(sizeof(Node));
node->data=x;
node->next=p->next;
p->next=node;
循环链表没有明显的尾端 ---> 如何避免死循环
循环退出条件:
p!=NULL----->p!=head
p->next!=NULL---->p->next!=head