什么是链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点都包括两部分:一是数据域,用来存储元素数值数据,另一个是存储直接后继结点地址的指针域,该指针一般称为next,用来指向下一个结点的位置。由于下一个结点也是链表类型,所以next的指针也要定义为链表类型。
链表概念及特点
头指针:是指向链表中第一个结点的指针
首元结点:是指链表中存储的第一个数据元素的结点
头结点:是在链表的首元结点之前附设的一个结点
在链表中设置头结点有何好处?
1、便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无须进行特殊处理
2、便于空表和非空表的统一处理
无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了。
头结点的数据域内装的是什么?
可以为空,也可以存放线性表的长度等附加信息,但此节点不能计入链表长度值
链表的特点:
1、结点在存储器中的位置是任意的,即逻辑相邻的数据元素在物理上不一定相邻
2、访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间是不同的
单链表的表示
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType data; //数据域
struct Lnode *next; //指针域
}Lnode,*LinkList; //LinkList为指向结构体Lnode的指针类型
单链表的基本操作
单链表的初始化
bool InitList_L(LinkList& L) {
L = new Lnode;
L->next = NULL;
return true;
}
单链表的销毁
bool DestoryedList_L(LinkList &L){
LinkList p; // Lnode *p;
While(p){
p = L;
L = L -> next;
delete p;
}
return true;
}
将单链表置为空表
bool clearList_L(LinkList &L){
Lnode *p,*q;
p = L->next; // 首元结点
While(p){ // 未到表尾
q = p->next;
delete p;
p = q; // p指针后移
} // 直到p,q指针指向均为空
L -> next = NULL; // 头结点指针域为空
return true;
}
求单链表的表长
从首元结点开始,依次统计所有节点
int ListLength_L(LinkList &L){
Lnode *p;
int i = 0;
P = L -> next;
while(p){
i++;
p = p->next;
}
return i;
}
取单链表中第i个元素的内容
从链表的头指针出发,顺着链域next逐个节点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构
bool getElem_L(LinkList &L,int i,ElemType &e){
Lnode *p;
p = L->next; //初始化
int j = 1;
while(i > j && p) { //向后扫描,直到p指向第i个元素或者p为空
j++;
p = p -> next;
}
if(!p || j > i){ //第i个元素不存在
return 0;
}
e = p -> data; //取第i个元素
return true;
}
查找
根据指定数据获取该数据所在的位置
1、从第一个结点开始,依次和e相比较
2、如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址
3、如果查找整个链表都没有找到其值与e相等的元素,则返回0或Null
查找返回地址
Lnode *LocateElem_L(LinkList &L,Elemtype e){
p = L -> next;
while(p && p -> data != e){
p = p -> next;
}
return p;
}
查找返回值
// 在线性表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinkList &L,Elemtype e){
p = L -> next;
int j = 1;
while(p && p -> data != e){
j++;
p = p -> next;
}
if(p){
return j;
}
return 0;
}
插入节点
1、首先找到ai-1的存储位置p
2、生成一个数据域为e的新结点s
3、插入新结点:
(1)新结点的指针域指向ai;
(2)结点ai-1的指针域指向新结点e
① S -> next = p -> next;
② P -> next = s;
注:①②顺序不可直接交换,否则会造成ai地址丢失
bool ListInsert_L(LinkList &L,int i,Elemtype e){
Lnode *p;
p = L;
int j = 0;
while(p && j < i - 1){
p = p -> next;
j++;
}
if(!p || j > i - 1){ // i大于表长+1或者小于1,插入位置非法
return false;
}
s = new Lnode; // 生成新结点s
s -> data = e; // 将结点s的数据域置为e
s -> next = p -> next; // 将s插入L中
p -> next = s;
return true;
}
删除结点
1、首先找到ai-1的存储位置p,保存要删除的ai的值
2、令p -> next 指向 ai + 1
3、释放结点ai的空间
bool destoryedList_L(LinkList &L, int i,Elemtype &e){
Lnode *p;
p = L;
int j = 0;
while(p && j < i - 1){ //寻找第i个结点并令p指向其前驱
p = p -> next;
j++;
}
if(!p || j > i - 1){
return false;
}
Lnode *q ;
q = p -> next; //临时保存被删结点的地址以备释放
p -> next = q->next; //改变删除结点前驱的指针域
e = q -> data; // 保存被删除结点的数据域
//p -> next = (p -> next)->next;
delete q; // 释放删除节点的空间
return true;
}
建立单链表
1.头插法/前插法:
1、从一个空表开始,重复读入数据
2、生成新结点,将读入数据存放到新结点的数据域中
3、从最后一个结点开始,依次将各结点插入到链表的前端
void CreateList_H(LinkList &L,int n){
L = new Lnode;
L -> next = NULL; //先建立一个带头结点的单链表
for(int i = n ;i > 0; i--){
p = new Lnode; //生成新结点
cin >> p -> data; // 输入元素值
p -> next = L -> next; // 插入到表头
L -> next = p;
}
}
2.尾插法/后插法:
void CreateList_L(LinkList &L,int n){
L = new Lnode;
L -> next = NULL;
r = L ; //尾指针r指向头结点
for(int i = 0;i < n;i++){
p = new Lnode;
cin >> p -> data;
p -> next = NULL;
r -> next = p; //插入到表尾
r = p; //r指向新的尾结点
}
}