单链表(含头结点)
一、单链表
- 1.1 关于单链表
单链表是一种采用链式存储的线性结构,其数据元素是由结点结构体组成,结点包括数据域和指针域。
相较于顺序表来说,单链表解脱了需要整片存储空间和存储空间大小固定的束缚;解决了插入、删除操作时对其他元素的操作问题。
相应的对于顺序表来说,单链表的整体存储空间大小需求更多;同时查询数据元素的方法也较单一。 - 2.1 单链表的结构体
单链表的结构体是由数据域和指针域组成的,数据域存放要保存的数据,指针域保存下一个结点的地址(下一个结点为空,保存NULL)。
typedef struct LNode
{
int data;
struct LNode *next;
}linklist_t, LNode_t;
二、单链表的创建(有头结点)
- 2.1 传参的方式创建单链表
void Create_Linklist(linklist_t **L){
//先判断传入的参数是否正确
if(NULL == L){
printf("Parameter error\n");
return;
}
//创建头结点
*L = (linklist_t *)malloc(sizeof(linklist_t);
if(NULL == *L){
printf("Space request error\n");
return;
}
(*L)->data = -1;
(*L)->next = NULL;
return;
}
- 2.2 指针函数的方式创建单链表
linklist *Create_Linklist(void){
linklist *L = (linklist *)malloc(sizeof(linklist));
if(NULL == L){
printf("Space request error\n");
return NULL;
}
L->data = -1;
L->next = NULL;
return L;
}
- 上述两种方法的区别
- 上述的两种方法仅仅是创建了带有头结点的单链表,想要得到想要的单链表还需要使用单链表的插入函数。
三、单链表的结点查找(按位置查找)
- 3.1 单链表中元素的个数
功能:统计单链表中元素个数
参数:单链表的头结点
返回值:成功返回单链表中元素个数;失败返回-1
//用于记录单链表中元素的个数
int Length_Linklist(linklist_t *L){
//判断参数是否正确
if(NULL == L){
printf("Parameter error\n");
return -1;
}
//num记录元素个数
int num = 0;
if(NULL == L->next){
return num;
}
LNode_t *Q = L->next;
num = 1;
while(NULL != Q->next){
Q = Q->next;
num++;
}
return num;
}
- 3.2 单链表的查找
功能:查找单链表的指定结点的前一个结点
参数:单链表的头指针、需要查找结点的位置参数
返回值:成功返回的为该结点的前一个结点的结点指针;失败返回NULL
//如果第一个有效结点算作第 0 位则pos不用改动,如果第一个有效结点算作第 1 位,则pos = pos - 1;
//返回的为该结点的前一个结点的结点指针,主要也是为了方便之后的插入和删除操作
LNode_t *PosFind_Linklist(linklist_t *L, int pos){
//判断参数是否正确
if(NULL == L){
printf("Parameter error\n");
return NULL;
}
//注意这里是 "pos > num",可能会返回的是尾结点(pos = num),\
这样写也主要是为了便于之后插入的方便,若只想查找元素数据可在该函数前加一个判断
int Length_Linklist(L);
if((pos < 0) || (pos > num)){
printf("Parameter error\n");
return NULL;
}
num = 0;
LNode_t *N = L;
while(num != pos){
num++;
N = N->next;
}
return N;
}
四、单链表的插入操作
- 4.1 头插法
int HeadInsert_Linklist(linklist_t *L, int value){
//判断参数是否正确
if(NULL == L){
printf("Parameter error\n");
return 0;
}
//创建结点
LNode_t *N = (LNode *)malloc(sizeof(LNode_t));
N->data = value;
N->next = NULL;
//插入到头结点之后
N->next = L->next;
L->next = N;
return 0;
}
- 4.2 尾插法
int TailInsert_Linklist(linklist_t *L, int value){
//判断参数是否正确
if(NULL == L){
printf("Parameter error\n");
return -1;
}
//创建结点
LNode_t *N = (LNode *)malloc(sizeof(LNode_t));
N->data = value;
N->next = NULL;
//创建尾指针
LNode_t *Q = L;
//遍历找到链尾
while(NULL != Q->next){
Q = Q->next;
}
//链尾元素之后插入新结点
Q->next = N;
Q = Q->next;
}
- 4.3 指定位置插入
功能:将新结点插入到指定位置
参数:单链表的头指针、新结点的位置、新结点数据域的值
返回值:成功返回0;失败返回-1
int PosInsert_Linklist(linklist_t *L, int pos, int value){
//如果第一个有效结点算作第 0 位则pos不用改动,如果第一个有效结点算作 1 位,则pos = pos - 1;
//寻找插入位置的前一个结点
LNode_t *Q = PosFind_Linklist(L, pos);
//创建结点
LNode_t *N = (LNode_t *)malloc(sizeof(LNode_t));
if(NULL == N){
printf("Space request error\n");
return -1;
}
N->data = value;
N->next = NULL;
//插入新结点
N->next = Q->next;
Q->next = N;
return 0;
}
如果要是一次性成型的话可以将链表的创建和插入写在一起,如果需要后续进行增删改建议还是分开写。然后设置 mode_flag 进行快速插入、删除或者单个插入、删除。
五、链表的删除操作
- 5.1 链表的头删法
int HeadDelete_Linklist(linklist_t *L){
//判断参数是否正确
if(NULL == L){
printf("Parameter error\n");
return -1;
}
if(0 == Length_Linklist(L)){ // Length_Linklist() 函数在上文“3.1 单链表中元素的个数”中
printf("linklist is empty\n");
return -1;
}
//头删
int value = 0;
LNode_t *N = L->next;
L->next = N->next;
N->next = NULL;
value = N->data;
free(N);
N = NULL;
return value;
}
- 5.2 链表的尾删法
int TailDelete_Linklist(linklist_t *L){
//判断参数是否正确
if(NULL == L){
printf("Parameter error\n");
return -1;
}
if(0 == Length_Linklist(L)){ /// Length_Linklist() 函数在上文“3.1 单链表中元素的个数”中
printf("linklist is empty\n");
return -1;
}
//每次都要遍历到尾部结点的前一个结点
int i = 0;
int num = Length_Linklist(L);
LNode_t *N = L;
while(i != num-1){
i++;
N = N->next;
}
//尾删
int value = 0;
value = N->next->data;
free(N->next);
N = NULL;
return value;
}
- 5.3 任意位置删除
int PosDelete_Linklist(linklist *L, int pos){
//如果第一个有效结点算作第 0 位则pos不用改动,如果第一个有效结点算作 1 位,则pos = pos - 1;
//判断参数是否正确
int num = Length_Linklist(L); // Length_Linklist() 函数在上文“3.1 单链表中元素的个数”中
if((pos < 0) || (pos >= num)){
printf("Parameter error\n");
return -1;
}
//遍历找到 pos 位置的前一个结点
LNode_t *N = PosFind_Linklist(L, pos);
//删除
int value = 0;
LNode_t *P = N->next;
value = P->data;
N->next = p->next;
P->next = NULL;
free(P);
P = NULL;
return value;
}
六、链表的逆置
归根结底就是将单链表头删/尾删的方式出单链表表,然后再头插/尾插的方式入另一个单链表。
当然一个单链表也是可以实现链表的逆置的,逆置的时候第1个结点要到表尾,可以设置一个指针一直指向这个结点,对该结点之后的结点进行删除然后头插,直到后续无结点逆置完成。
- 6.1 单一链表
int Reverse_Linklist(linklist_t *L){
if(NULL == L){
printf("Parameter error\n");
return -1;
}
if(NULL == L->next){
printf("linklist is empty\n");
return 0;
}
LNode_t *N = L->next;
LNode_t *Q = N;
while(NULL != N->next){
Q = N->next;
N->next = Q->next;
Q->next = L->next;
L->next = Q;
}
return 0;
}
- 6.2 两个链表
int Reverse_Linklist(linklist **L){
if(NULL == L || NULL == *L){
printf("Parameter error\n");
return -1;
}
if(NULL == (*L)->next){
printf("linklist is empty\n");
return 0;
}
LNode_t *N = L;
linklist *Q = NULL;
while(NULL != L->next){
N = L->next;
L->next = N->next;
N->next = Q->next;
Q->next = N;
}
(*L) = Q;
Q = NULL;
return 0;
}
七、链表的置空
简单来说就是将单链表除头结点之外的结点全部删除,其实现方式无非就是当链表不为空的时候就循环头删结点,直到链表为空。
int SetEmpty_Linklist(linklist_t *L){
if(NULL == L){
printf("Parameter error\n");
return -1;
}
if(NULL == L->next){
printf("Linklist is empty\n");
return 0;
}
while(NULL != L->next){
HeadDelete_Linklist(L);
}
return 0;
}
八、链表的销毁
有了上述的基础,离链表的销毁其实只差删除头结点和将头结点指针指向NULL。
int Free_Linklist(linklist_t **L){
if(NULL == *L){
printf("Parameter error\n");
return -1;
}
if(NULL != (*L)->next){
SetEmpty_Linklist(*L);
}
free(*L);
(*L) = NULL;
return 0;
}