线性表中数据结构在存储器中的位置是任意的,逻辑上相邻的数据元素在物理位置上不一定相邻
n个结点由指针链组成一个链表,链表是顺序存取的
- 单链表:每个结点只有一个指针域
- 双链表:每个结点由两个指针域
- (单/双)循环链表:链表结点首尾相接
缺点:
时间:存取效率不高,必须采用顺序存取,即存取 数据元素时只能按链表的顺序进行访问
空间:存储密度小
带头结点的单链表
单链表由表头唯一确定,因此单链表可以用头指针的名字命名。若头指针名是L,则把链表成为表L
单链表的存储结构
结点由数据域和指针域组成,用结构体表示
typedef struct Lnode{
ElemType data;
struct Lnode *next;
}Lnode,*LinkList;
定义链表L:LinkList L;
定义结点指针p:LNode *p <=> LinkList p;
单链表的初始化
构造一个空表
步骤:
- 生成新结点作为头结点,用头指针L指向头结点
- 将头指针的指针域置空
Status InitList L(LinkList &L){
L = new Lnode; //或L = (LinkList) malloc (sizeof (LNode));
L->next = NULL;
return OK;
}
补充算法
判断链表是否为空:链表中无元素,称为空链表(头指针和头结点仍然在)
算法思路:判断头结点指针域是否为空
int ListEmpty(LinkList L){
if(L->next) return 0;
else return 1;
}
单链表的销毁:链表销毁后不存在
算法思路:从头指针开始,依次释放所有结点
Status DestuoyList_L(LinkList &L){ //销毁单链表L
Lnode *p; //或LinkList p;
while(L) {
p = L;
L = L->next;
delete p;
}
return OK;
}
清空链表:链表仍然存在,但链表中无元素,成为空链表(头指针和头结点仍然在)
算法思路:依次释放所有结点,并将头结点指针域设置为空
Status ClearList(LinkLIst &L){ //将L重置为空表
Lnode *p,*q; // 或LinkList p,q;
p = L->next;
while(p){ //没到表尾
q = p->next;
delete p;
p = q;
}
L->next = NULL; //头结点指针域为空
return OK;
}
求单链表的表长
算法思路:从首元结点开始,依次计数所有结点
int ListLength_L(LinkList L){ //返回L中数据元素个数
LinkList p;
p = L->next; //p指向第一个结点
i = 0;
while(p){ //遍历单链表,统计结点数
i++;
p = p->next;
}
return i;
}
取值:取单链表中第i个元素的内容
算法思路:从链表的表头出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止,因此,链表不是随机存取结构
算法步骤:
- 从第一个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p = L->next
- j做计数器,累计当前扫描过的结点数,j初值为1
- 当p指向扫描到的下一结点时,计数器j+1
- 当j=i时,p所指的结点就是要找的第i个结点
Status GetElem_L(LinkList L,int i,ElemType &e){
//获取线性表L中的某个数据元素的内容,通过变量e返回
p = L->next;
j = 1; //初始化
while(p && j < i){ //向后扫描,直到p指向第i个元素或p为空
p = p->next;
++j;
}
if(!p || j > i) return ERROR; //第i个元素不存在
e = p->data; //取第i个元素
return OK;
} //GetElem_L
查找
按值查找:根据指定数据获取该数据所在的位置(该数据的地址)
算法步骤:
- 从第一个结点起,依次和e比较
- 如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址
- 如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”
Lnode *LocateElem_L(LinkList L,Elemtype e){
//在线性表L中查找值为e的数据元素
//找到,则返回L中值为e的数据元素的地址,查找失败则返回NULL
p = L->next;
while(p && p->data != e) p = p->next;
return p;
}
按值查找:根据指定数据获取该数据所在的位置序号(是第几个数据元素)
//在线性表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinkList L,Elemtype e){
//返回L中值为e的数据元素的位置序号,查找失败返回0
p = L->next;
j = 1;
while(p && p->data != e) {
p = p->next;
j++;
}
if(p) return j;
else return 0;
}
删除
算法步骤:
- 首先找到ai-1的存储位置p,保存要删除的a的值
- 令p->next指向ai+1(p->next = p->next->next)
- 释放结点ai的空间
//将线性表L中第i个数据元素删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
p = L;
j = 0;
while(p->next && j < i-1){
p = p->next;
++j;
//寻找第i个结点,并令p指向其前驱
}
if(!(p->next) || j > i-1) return ERROR; //删除位置不合理
q = p->next; //临时保存被删结点的地址以备释放
p->next = q->next; //改变删除结点前驱结点的指针域
e = q->data; //保存删除结点的数据域
delete q; //释放删除结点的空间
return OK;
} // ListDelete_L
单链表的查找、插入、删除算法时间效率分析
查找
因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为O(n)
插入和删除
因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1)
但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱节点,所耗时间复杂度为O(n)
单链表的建立
头插法:元素插入在链表头部
从一个空表开始,重复读入数据
生成新结点,将读入数据存放到新结点的数据域中
从最后一个结点开始,依次将各结点插入到链表的前端
void CreateList_H(LinkList &L,int n){
L = new LNode;
L->next = NULL; //先建立一个带头结点的单链表;
for(i = n;i < 0;--i){
p = new LNode; //生成新结点p = (LNode*)malloc(sizeof(LNode));
cin>>p->data; //输入元素值scanf(&p->data);
p->next = L->next;
L->next = p;
}
} //CreatList_H
时间复杂度为O(n)
尾插法:元素插入在链表尾部
从一个空表L开始,将新结点逐个插入到链表尾部,尾指针r指向链表的尾结点
初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点
//正位序输入n个元素的值,建立代表头结点的单链表L
void CreateList_R(LinkList &L,int n){
L = new LNode;
L->next = NULL;
r = L; //尾指针r指向头结点
for(i = 0;i < n;i++){
p = new LNode;
cin>>p->data; //生成新结点,输入元素值
p->next = NULL;
r->next = p; //插入到表尾
r = p; //r指向新的尾结点
}
}//CreateList_R
时间复杂度为O(n)