目录
一、什么是单链表:
每个结点只含有一个指针域来存放其直接后继元素的地址;
1.1如何定义单链表:
1.2 C语言代码实现(通过结构体类型实现链表定义):
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
typedef int ElemType; //自定义链表的数据元素为整数(也可以定义数据元素为结构体类型,根据实际需求定义,这里为了方便就定义整数类型)
typedef struct LNode
{
ElemType data; //存放结点的数据元素
struct LNode *next; //指向下一个结点的指针
}LNode,*LinkList;
//LNode 为结构体别名,LinkList为结构体类型指针的别名
带头结点的单链表;
二、单链表的创建和销毁:
2.1 代码实现(通过函数实现功能):
//初始化链表LL,返回值; 失败返回NULL,成功返回头结点的地址。
LNode* InitList1()
{
LNode *head = (LNode *)malloc(sizeof(LNode));
if(head==NULL)return NULL; //失败原因可能是系统内存不足,一般不会失败;
head->next=NULL; //将头结点next指针域置空
return head;
}
//销毁链表LL(释放全部结点)
//注意:在主函数中调用该函数销毁链表后,需要将链表指针置空,防止野指针;
void DestroyList1(LinkList LL)
{
if(LL==NULL)return ; //判断LL链表是否存在;不存在返回空,无需销毁
LinkList tmp=LL; //定义链表指针指向头结点
//从头结点开始循环释放每一个结点的内存空间
while(LL!=NULL) //判断需要释放的结点是否为空
{
tmp=tmp->next;
free(LL); //释放结点
LL=tmp;
}
return ;
}
//清空链表(保留头结点,释放其余结点)
void ClearList(LinkList LL)
{
if(LL==NULL)return ; //判断LL链表是否存在;不存在返回空,无需清空
LinkList tmp1=LL->next; //从首节点开始删除结点
LinkList tmp2;
while(tmp1!=NULL)
{
tmp2=tmp1->next;
free(tmp1);
tmp1=tmp2;
}
LL->next=NULL; //将头结点next置空,防止野指针,此步骤不可以少;
return ;
}
三、单链表的插入和删除(按位序):
3.1 示例分析:
假定此时有一 链表LL 存在于内存中,其逻辑结构和位序位置为:
链表结构为: LL->A->B->C->D;
位序位置 | 0(头结点) | 1(首节点) | 2 | 3 | 4 | 5 | |||||||
逻辑结构 | LL | A | B | C | D | NULL |
假定现在需要向第3个位置插入元素H(即变成下表中的结构),那么如何实现呢?
newnode结点是我们申请的用于存放H元素的新结点;
位序位置 | 0(头结点) | 1(首节点) | 2 | 3 | 4 | 5 | 6 | ||||||
逻辑结构 | LL | A | B | newnode | C | D | NULL |
大致过程:newnode-->next=C; B-->next=newnode; C的地址我们不知道,但是我们知道B的后继结点为C,也就是B-->next=C;
所以用 newnode-->next=B->next; B-->next=newcode;
那么问题来了,B的位置我们也不知道?我们该如何获取到B的地址呢?
现在已经知道位序3是我们需要插入元素的位置,而B为其前一个位置,即第2个位置;
所以可以从头结点,开始遍历,直到找到第2个结点的位置即可;
那么假设现在需要在第 ii 个位置插入元素ee,相信大家应该知道需要寻找第 ii-1个位置了吧。
查找位置伪代码实现:
//伪代码
//链表为LL,待查找的位置ii
LinkList tmp=LL; //从头结点开始查找
int jj=0; //位序标志,头结点位序为0,首结点位序为1,以此类推
while(tmp!=NULL && jj<ii )
{
tmp->next;
jj++;
}
if(tmp!=NULL && jj ==ii ) tmp就是待查找位置的地址
可以将寻找位置封装成一个函数LocateNode:
// 获取链表中第ii个结点,成功返回结点的地址,失败返回空。
// 注意,ii可以取值为0,表示头结点。
//
//LL->a->b->c->d 假定获取c 头结点序号为0
LNode *LocateNode(LinkList LL, unsigned int ii)
{
if(LL==NULL){printf("链表不存在,获取失败。\n");return NULL;}
LinkList tmp=LL; //从头结点开始寻找
int jj=0; //头结点位序为0,如果tmp从首节点开始,其序号为1;
while(tmp!=NULL && jj<ii )
{
tmp=tmp->next; //取下一结点
jj++;
}
//地址不为空且位序为ii
if(jj==ii && tmp!=NULL)return tmp;
return NULL;
}
3.2 按位序插入元素代码实现:
// 在链表LL的第ii个位置插入元素ee,返回值:0-失败;1-成功。
int InsertList(LinkList LL, unsigned int ii, ElemType *ee)
{
if(LL==NULL ||ee==NULL ){ printf("链表不存在或者元素不存在,无法插入。\n"); return 0; }
if(ii<1){ printf("插入位置%d不合法,ii的值应大于1。\n",ii);return 0; }
// 0 1 2 3 4
// LL->NULL
// LL->a->NULL
// LL->a>b->NULL
// LL->a->b->c->d 在3个元素插入e 即在b后插入e 所以需要得到b的地址
/*
LinkList tmp=LL;
int jj=0; //位序标志,头结点位序为0,首结点位序为1,以此类推
while(tmp!=NULL && jj<ii-1 )
{
tmp->next;
jj++;
}
if(tmp==NULL || jj<ii-1){ printf("位置%d不合法,超过了表长",ii); return 0; }
*/
上述注释代码可用该函数替代:
LinkList tmp;
tmp=Locate(LL,ii-1);
if(tmp==NULL){ printf("位置%d不合法,超过了表长",ii); return 0; }
LNode* newcode=(LNode*)malloc(sizeof(LNode)); //获取一个新结点来存放元素ee
if(newcode==NULL)return 0; //系统内存不足,申请失败
memcpy(&(newcode->data),ee,sizeof(ElemType)); //将元素值复制到结点中
//插入到位置上
newcode->next=tmp->next;
tmp->next=newcode;
return 1;
}
3.3 打印链表全部元素:
// 打印链表中全部的元素。
void PrintList(LinkList LL)
{
if(LL==NULL){ printf("链表不存在.\n");return; }
LNode* tmp=LL->next; //头结点不存在元素,所以从首节点开始
while( tmp != NULL )
{
printf("%-3d",tmp->data); //打印输出元素,如果元素数据类型为结构体,此处代码需要更改
tmp=tmp->next;
}
printf("\n");
return;
}
3.4 在链头或者链尾插入元素:
// 在链表LL的头部插入元素ee,返回值:0-失败;1-成功。
int PushFront(LinkList LL, ElemType *ee)
{
if(LL==NULL || ee==NULL)
{printf("链表为不存在或者ee数据元素ee不存在。\n");return 0;}
if(InsertList(LL,1,ee)!=0)return 0; //调用位序插入函数即可
return 1;
}
//在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int PushBack(LinkList LL, ElemType *ee)
{
if(LL==NULL || ee==NULL){printf("链表为不存在或者ee数据元素ee不存在。\n");return 0; }
if(InsertList(LL,LengthList(LL)+1,ee)!=0)return 0;
return 1;
}
//链表长度
int LengthList(LinkList LL)
{
if(LL==NULL){printf("链表不存在。\n");return -1;}
LinkList tmp=LL; //从头结点开始,因为链表可能为空,即长度为0
int length=0;
while(tmp->next!=NULL)
{
tmp=tmp->next;
length++;
}
return length;
}
3.5 按位序删除元素代码实现:
实现方法类似于插入代码,假定要删除上述链表LL中的第三个结点,需要先找到第二个结点,然后将第二个结点的next指向待删除结点的next,然后将第三个结点释放即可。
// 删除链表LL中的第ii个结点,返回值:0-位置ii不合法;1-成功。
int DeleteNode(LinkList LL, unsigned int ii)
{
if(LL==NULL){printf("链表不存在。\n");return 0;}
if(ii<1){printf("位置%d不合法,ii应该大于1\n",ii);return 0;}
LinkList tmp;
tmp=LocateNode(LL,ii-1); //查询第ii-1个结点
if(tmp==NULL){printf("位置%d超过了链表长度。\n",ii); return 0; }
LinkList oldcode=tmp->next; //待删除结点的地址
tmp->next=oldcode->next; //将待删除结点的前一个结点指针 指向 待删除结点的后一个结点 a->b->c 删除b 即将a的next指向c
free(oldcode);oldcode=NULL;
return 1;
}
3.6 在链头或者链尾删除元素:
// 删除链表LL中第一个结点,返回值:0-位置不合法;1-成功。
int PopFront(LinkList LL)
{
if(LL==NULL){ printf("链表不存在。\n");return 0;}
if(DeleteNode(LL,1)!=0)return 0; //调用删除函数删除第一个元素即可
return 1;
}
// 删除链表LL中最后一个结点,返回值:0-位置不合法;1-成功。
int PopBack(LinkList LL)
{
if(LL==NULL){ printf("链表不存在。\n");return 0;}
if( DeleteNode(LL,LengthList(LL)) !=0)return 0; //调用删除函数删除最后一个元素
return 1;
}