1.单链表
优点:不要求大片连续空间,改变容量方便。
缺点:不可随机存取,要耗费一定空间存放指针。
用代码定义一个链表
struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}
增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点。
struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));
typedef给数据类型重命名
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList; //将单链表名字定义为LNode,LinkList为一个指向struct LNode的指针
声明一个指向单链表第一个结点的指针:
LNode * L;或 LinkList L;这两种表示方法本质上是一样大,但是为了使代码的可读性更强,我们往往使用LNode *强调返回的是一个结点,LinkList强调这是一个单链表。
(1)不带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//初始化一个空的单链表
bool InitList(LinkList &L){
L = NULL; //空表,暂时还没有任何结点
return true;
}
void test(){
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
InitList(L);
}
(2)带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//初始化一个单链表(带头结点)
bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode)); //分配一个头结点
if(L==NULL) //内存不足分配失败
return false;
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
void test(){
LinkList L; //声明一个指向单链表的指针
InitList(L); //初始化一个空表
//后续代码....
}
(3)单链表的插入
按位序插入(带头结点)
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while(p!=NULL && j<i-1){
//循环找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
注意:这两行代码顺序不能写反
s->next = p->next;
p->next = s;
按位序插入(不带头结点)
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
return false;
if(i==1){
//插入第1个结点的操作与其他结点操作不同
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L=s; //头指针指向新结点
return true;
}
Lnode *p; //指针p指向当前扫描到的结点
int j=1; //当前p指向的是第几个结点
p=L; //p指向第一个结点(不是头结点)
while(p!=NULL && j<i-1){
//循环找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL)
return false; //i值不合法
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true; //插入成功
}
指定结点的后插操作
//后插操作:在p结点之后插入元素e
bool InsertNode (LNode *p,ElemType e){
if(p==NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s==NULL) //若内存不足可能分配失败
return false;
s->data = e; //用结点s保存数据元素e
s->next=p->next;
p->next=s; //将结点s连到p之后
return true;
}
指定结点的前插操作
方法一:
bool InsertPriorNode (LNode *p,ElemType e){
if(p==NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s==NULL) //内存分配失败
return false;
s->next = p->next;
p->next = s; //新结点s连到p之后
s->data = p->data; //将p中元素复制到s中
p->data = e; //p中元素覆盖为e
return true;
}
方法二:
bool InsertPriorNode(LNode *p, LNode *s){
if(p == NULL||s==NULL)
return false;
s->next = p->next;
p->next = s; //s连到p之后
ElemType temp = p->data; //交换数据域部分
p->data = s->data;
s->data = temp;
return true;
}
(4)单链表的删除
按位序删除(带头结点)
typedef struct LNode{
ELemType data;
struct LNode *next;
}LNode,*LinkList;
bool ListDelete(LinkList &L,int i,ELemType &e){
if(i<1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j=0;
p = L;
while(p!=NULL && j<i-1){
//循环找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL)
return false;
if(p->next == NULL) //第i-1个结点之后已无其他结点
return false;
LNode *q = p->next; //令q指向被删除结点
e = q->data; //e返回元素的值
p->next = q->next; //将*q结点从链中断开
free(q); //释放结点的内存空间
return true;
}
指定结点的删除
//删除指定结点p
bool DeleteNode(LNode *p){
if(p == NULL)
return false;
LNode *q = p->next; //令q指向*p的后继结点
p->data = p->next->data; //和后继结点交换数据域
p->next = q->next; //将*q结点从链中断开
free(p); //释放后继结点的存储空间
return true;
}
如果p是最后一个结点只能从表头开始依次寻找p的前驱。
单链表的局限性:无法逆向检索,有时候不太方便。
(5)单链表的查找
按位查找
//按位查找,返回第i个元素(带头结点)
LNode * GetElem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p = L;
while(p!=NULL && j<i){
p = p->next;
j++;
}
return p;
}
按值查找
//按值查找,找到数据域==e的结点
LNode * LocateElem(LinkList L,ElemType e){
LNode *p = L->next;
//从第一个结点开始查找数据域为e的结点
while(p != NULL && p->data != e)
p = p->next;
return p; //找到后返回该指针否则返回NULL
}
(6)求单链表的长度
//求表的长度
int length(LinkList L){
int len = 0; //统计表长
LNode *p = L;
while(p->next !=NULL){
p=p->next;
len++;
}
return len;
}
(7)单链表的建立
step1:初始化一个单链表
step2:每次取一个数据元素,插入到表尾/表头
①尾插法
先初始化单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
//初始化一个单链表(带头结点)
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode));
if(L==NULL) //内存不足,分配失败
return false;
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
void test(){
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
InitList(L);
}
设置变量length记录链表长度
使用while循环
while{
//每次取一个数据元素e;
ListInsert(L,length+1,e); //插到尾部;
length++;
}
尾插法建立单链表完整代码:
LinkList List_Tailnsert(LinkList &L){ //正向建立单链表
int x; //设ElemType为整型
L = (LinkList)malloc(sizeof(LNode)); //建立头结点
LNode *s,*r = L; //r为表尾指针
scanf("%d",&x); //输入结点的值
while(x!=9999){ //输入9999表示结束
s=(LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s; //r指向新的表尾结点
scanf("%d",&x);
}
r->next = NULL; //尾结点指针置空
return L;
}
②头插法
LinkList List_HeadInsert(LinkList &L){ //逆向建立链表
LNode *s;
int x;
L = (LinkList)malloc(sizeof(LNode)); //创建头结点
L->next = NULL; //初始为空链表
scanf("%d",&x); //输入结点的值
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode)); //创建新结点
s->data = x;
s->next = L->next;
L->next = s; //将新结点插入表中,L为头指针
scanf("%d",&x);
}
}
注意:头插法的重要应用:链表的逆置
2.双链表
(1)双链表既有后继指针也有前驱指针
typedef struct DNode{ //定义双链表结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱指针和后继指针
}DNode,*DLinkList;
(2)双链表的初始化
typedef struct DNode{
ElenType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
//初始化双链表
bool InitDLinkList(DLinkList &L){
L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if (L == NULL) //内存不足,分配失败
return false;
L->prior = NULL; //头结点的prior永远指向NULL
L->next = NULL; //头结点之后暂时还没有结点
}
void testDLinkList(){
//初始化双链表
DLinkList L;
InitDLinkList(L);
//后续代码
}
(3)双链表的插入
//在p结点之后插入s结点
bool InsertNextNode(DNode *p, DNode *s){
if(p == NULL || s == NULL) //非法参数
return false;
s->next = p->next; //将结点*s插入到结点*p之后
if (p->next != NULL) //如果p结点有后继结点
p->next->prior = s;
s->prior = p;
p->next = s;
}
(4)双链表的删除
//删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
if (p == NULL)
return false;
DNode *q = p->next; //找到p的后继结点q
if (q == NULL)
return false; //p没有后继
p->next = q->next;
if(q->next!=NULL)
q->next->prior=p;
free(q); //释放空结点
return true;
}
销毁一个双链表
void DestoryList(DLinkList &L){
//循环释放各个数据结点
while (L->next != NULL)
DeleteNextDNode(L);
free(L); //释放头结点
L = NULL; //头指针指向NULL
}
(5)双链表的遍历
①后向遍历
while(p!=NULL){
//对结点p做相应处理,如打印
p = p->next;
}
②前向遍历
while(p!=NULL){
//对结点p做相应处理
p = p->prior;
}
③前向遍历(跳过头结点)
while(p->prior != NULL){
//对结点p做相应处理
p=p->prior;
}
双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。
3.循环链表
(1)循环单链表
在初始化时头结点的next指针需要指向它自己。
typedef struct LNode{ //定义单链表结点类型
ElemType data;
struct LNode *next;
}LNode,*LinkList;
//初始化一个循环单链表
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode)); // 分配一个头结点
if(L == NULL) //内存不足,分配失败
return false;
L->next = L; //头结点next指向头结点
return true;
}
//判断循环单链表是否为空
bool Empty (LinkList L){
if(L->next == L)
return true;
else
return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
if (p->next == L)
return true;
else
return false;
}
(2)循环双链表
表头结点的prior指向表尾节点,表尾结点的next指向头结点。
循环双链表的初始化
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
//初始化空的循环双链表
bool InitDLinkList(DLinkList &L){
L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if(L == NULL) //内存不足,分配失败
return false;
L->prior = L; //头结点的prior指向头结点
L->next = L; //头结点的next指向头结点
return true;
}
void testDLinkList(){
//初始化循环双链表
DLinkList L;
InitDLinkList(L);
//...后续代码...
}
//判断循环双链表是否为空
bool Empty(DLinkList L) {
if (L->next == L)
return true;
else
return false;
}
//判断p结点是否为循环双链表的表尾结点
bool isTail (DLinkList L,DNode *p){
if (p->next == L)
return true;
else
return false;
}
双链表的插入
//在p结点之后插入s结点
bool InertNextDNode(ONode *p,DNode *s){
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
}
双链表的删除
//删除p的后继结点q
p->next = q->next;
q->next->prior = p;
free(q);
4.静态链表
(1)定义一个静态链表
定义一个结点
#define MaxSize 10 //静态链表的最大长度
struct Node{ //静态链表结构类型的定义
ElemType data; //存储数据元素
int next; //下一个元素的数组下标
};
定义多个连续存放的结点
void testLinkList(){
struct Node a[MaxSize];
//...后续代码
}
一个特殊的定义方法
#define MaxSize 10
typedef struct{
ElemType data;
int next;
}SLinkList[MaxSize];
在初始化的时候可以让next为某个特殊值,如-2,防止“脏数据”
(2)简述基本操作的实现
①初始化静态链表:
把a[0]的next设为-1
②查找某一个位序的结点:
从头结点出发挨个往后遍历结点
③插入位序为i的结点:
找到一个空的结点,存入数据元素;
从头结点出发找到位序为i-1的结点;
修改新结点的next为-1;
修改i-1号结点的next
注意:静态链表的容量固定不变