带头结点的单链表
特点:逻辑相邻,物理不相邻
头结点:只在其指针域存放下一结点的地址,用于标记链表的起始(第一个有效数据节点的地址),不存数据。
头结点的空间:栈区或全局区
有效数据的空间:堆区
声明单链表结点类型
typedef struct LNode
{
int data;//数据域
struct LNode* next;//指针域
}LNode,* Linklist
操作
链表初始化
//链表初始化
void InitLinkList(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
plist->next = NULL;
}
为了实现以下插入,删除等一系列操作,先封装以下功能:
获取节点个数
//获取结点个数
int ListLength(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return 0 ;
}
int length = 0;
Linklist p = plist->next;
while (p)
{
length++;
p = p->next;
}
return length;
}
根据位置找结点
//根据位置找结点
Linklist GetNode(Linklist plist,int pos)
{
assert(plist != NULL);
if (plist == NULL)
{
return NULL;
}
if(pos < 0) return NULL;
Linklist p = plist;
while(pos && p != NULL)
{
p = p->next;
pos--;
}
return p;
}
申请节点
static Linklist _ApplyNode(int val,Linklist next)
{
Linklist s = (Linklist)malloc(sizeof(LNode));
assert(s != NULL);
if (s == NULL)
{
printf("Insert Fail : Apply Space Error\n");
return NULL;
}
s->data = val;
s->next = next;
return s;
}
插入
void Insert(Linklist plist, int val, int pos)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
//判断pos位置是否合法
if(pos < 0 || pos >ListLength(plist))
{
printf("Insert error : Pos is Error\n");
return ;
}
Linklist p = GetNode(plist,pos);
p->next = _ApplyNode(val,p->next);
}
头插
//头插
void Insert_Head(Linklist plist, int val)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
plist->next = _ApplyNode(val,plist->next);
}
尾插
//尾插
void Insert_Tail(Linklist plist, int val)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Insert(plist,val,ListLength(plist));
}
删除
1.先找到要删除节点的前一个节点,
2.再通过这个节点访问要删除的结点
3.让删除节点的前一个节点的next指向删除节点的next域
4.释放空间
//删除
void Delete(Linklist plist,int pos)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
if(pos < 0 || pos > ListLength(plist))
{
printf("Delete Fail : Pos is Error\n");
return ;
}
Linklist p = GetNode(plist,pos-1);//要删除节点的前一个节点
Linklist q = p->next;//要删除的节点pos位置
p->next = q->next;
free(q);
}
头删
//头删
void Delete_Head(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Linklist q = plist ->next;
if(q != NULL )
{
plist->next = q->next;
}
free(q);
}
尾删
//尾插
void Delete_Tail(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Delete(plist,ListLength(plist));
}
判空
//判空
int ListEmpty(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
printf("Not have Head\n");//不是一个单链表
return 0 ;
}
if(plist->next == NULL)//空
{
return 1;
}
return 0;
}
置空
//置空
void Clear(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
while(!ListEmpty(plist))
{
Delete_Head(plist);
}
}
显示
//显示
void Show(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Linklist p = plist->next;
while(p)
{
printf("%d ——> ",p->data);
p = p ->next;
}
printf("NULL\n");
}
逆置
void Reverse_list(Linklist plist)
{
if(plist == NULL || plist->next == NULL)
{
return;
}
Linklist p= NULL;//记录当前要处理的节点位置
Linklist q = plist->next;//当前要处理结点的后一个结点
Linklist s = q->next;
while(1)
{
q->next = p;
p = q;
q = s;
if(q == NULL)
{
break;
}
s = s->next;
}
plist->next = p;
}
判断两个单链表是否相交
若相交返回第一个相交的结点地址,若不相交返回空
算法: 必须通过结点的地址进行判断,不能通过data域进行判断
- 先通过比较两个结点的长度差值,让长的链表先走这个差值,因为对于两个相交的链表来说,相交后的部分应该有同一个尾结点,在相交前应该从同一逻辑位置开始遍历
- 两个链表的指针同时往后走,每走一步判断两个结点是否相等
Linklist IsIntersect(Linklist p1,Linklist p2)
{
if(ListEmpty(p1) || ListEmpty(p2))
{
return NULL;
}
if(p1 == NULL || p2 == NULL)
{
return NULL;
}
int len1 = ListLength(p1);
int len2 = ListLength(p2);
if(len1 > len2)
{
for(int i = 0;i < len1 - len2;i++)
{
p1 = p1->next;
}
}
else
{
for(int i = 0;i < len2 - len1;i++)
{
p2 = p2->next;
}
}
while(p1 != NULL)
{
if(p1 == p2)
return p1;
p1 = p1->next;
p2 = p2->next;
}
return NULL;
}
找到倒数第k个结点
时间复杂度尽可能低地找
算法: 用两个指针来实现,先让一个指针向后走k个位置,然后两个指针同步往后走,当之前的指针为空时,后面的指针就是倒数第k个位置
Linklist R_GetNode(Linklist plist,int k)
{
if (plist == NULL || plist->next == NULL || k <= 0)
{
return NULL;
}
Linklist p = plist;
Linklist q = plist;
if(k <= 0) return NULL;
for(int i = 0;i < k && p != NULL;i++)
{
p = p->next;
}
//k值大于链表长度
if(p == NULL)
{
return NULL;
}
while(p != NULL)
{
p = p->next;
q = q->next;
}
return q;
}
删除p结点(p是指向链表中的某一个不为尾结点的结点)o(1)
算法: 删除一个节点不是删除结点本身,而是要删除这个节点存储的值
int DeleteNodeofP(Linklist plist,Linklist p)
{
if(plist == NULL || p == NULL)
{
return 0;
}
if(p->next == NULL)//p指向尾结点
{
Delete_Tail(plist);//O(n)
return 1;
}
Linklist q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
return 1;
}
判断单链表是否有环
//判断单链表是否有环
bool IsCircle(Linklist plist)
{
if(plist->next == NULL || plist == NULL)
{
return false;
}
Linklist fast = plist;//快指针
Linklist slow = plist;//慢指针
while(fast != NULL && fast->next != NULL)//fast->next != NULL保证快指针一次走两步两个节点能够成功
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next == NULL)//链表长度为奇数fast->next为空,偶数时fast为空
return false;
else
return true;
}
找到有环单链表入环的第一个节点
//找到有环单链表入环的第一个节点
Linklist InToCircle(Linklist plist)
{
Linklist fast = plist;//快指针
Linklist slow = plist;//慢指针
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next == NULL)//链表长度为奇数fast->next为空,偶数时fast为空
return NULL;
//公式分析:
/*
设起点到入环点的距离为x,入环点到相遇点的距离为y,相遇点再到入环点的距离为k
则快指针走过x+y+(k+y)n(n为圈数)
慢指针走过x+y
所以有2*(x+y)= x+y+(k+y)n
——》x=(k+y)(n-1) + k
*/
slow = plist;
while(slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
头文件
#pragma once
/*typedef struct LNode
{
union
{
int data;//存储数据节点
int length;//头结点记录链表中节点的个数
};
struct LNode* next;
}LNode;*/
//对于单链表节点类型的声明
typedef struct LNode
{
int data;
struct LNode* next;
}LNode;
typedef struct LNode* Linklist;
//链表初始化
void InitLinkList(Linklist plist);
//插入
void Insert(Linklist plist, int val, int pos);
//获取结点个数
int ListLength(Linklist plist);
//根据位置找结点
Linklist GetNode(Linklist plist,int pos);
//头插
void Insert_Head(Linklist plist, int val);
//尾插
void Insert_Tail(Linklist plist, int val);
//删除
void Delete(Linklist plist,int pos);
//头删
void Delete_Head(Linklist plist);
//尾插
void Delete_Tail(Linklist plist);
//判空
int ListEmpty(Linklist plist);
//置空
void Clear(Linklist plist);
//显示
void Show(Linklist plist);
//判断两个单链表是否相交
Linklist IsIntersect(Linklist p1,Linklist p2);
//找到倒数第k个结点
Linklist R_GetNode(Linklist plist,int k);
//删除p结点(p是指向链表中的某一个不为尾结点的结点)o(1)
int DeleteNodeofP(Linklist plist,Linklist p);
//判断单链表是否有环
bool IsCircle(Linklist plist);
//找到有环单链表入环的第一个节点
Linklist InTo(Linklist plist);
//单链表逆置
void Reverse_list(Linklist plist);
测试用例
#include <stdio.h>
#include "singlelist.h"
//LNode head; //全局定义的头结点
int main()
{
LNode head; //栈区定义的头结点
InitLinkList(&head);
Insert_Head(&head,10);
Show(&head);
Insert_Tail(&head,20);
Show(&head);
Delete_Head(&head);
Show(&head);
Clear(&head);
Show(&head);
}
功能实现
#include "singlelist.h"
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
static Linklist _ApplyNode(int val,Linklist next)
{
Linklist s = (Linklist)malloc(sizeof(LNode));
assert(s != NULL);
if (s == NULL)
{
printf("Insert Fail : Apply Spave Error\n");
return NULL;
}
s->data = val;
s->next = next;
return s;
}
//链表初始化
void InitLinkList(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
plist->next = NULL;
}
//获取结点个数
int ListLength(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return 0 ;
}
int length = 0;
Linklist p = plist->next;
while (p)
{
length++;
p = p->next;
}
return length;
}
//根据位置找结点
Linklist GetNode(Linklist plist,int pos)
{
assert(plist != NULL);
if (plist == NULL)
{
return NULL;
}
if(pos < 0) return NULL;
Linklist p = plist;
while(pos && p != NULL)
{
p = p->next;
pos--;
}
return p;
}
//插入
void Insert(Linklist plist, int val, int pos)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
//判断pos位置是否合法
if(pos < 0 || pos >ListLength(plist))
{
printf("Insert error : Pos is Error\n");
return ;
}
Linklist p = GetNode(plist,pos);
p->next = _ApplyNode(val,p->next);
}
//头插
void Insert_Head(Linklist plist, int val)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
plist->next = _ApplyNode(val,plist->next);
}
//尾插
void Insert_Tail(Linklist plist, int val)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Insert(plist,val,ListLength(plist));
}
//删除
void Delete(Linklist plist,int pos)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
if(pos < 0 || pos > ListLength(plist))
{
printf("Delete Fail : Pos is Error\n");
return ;
}
Linklist p = GetNode(plist,pos-1);//要删除节点的前一个节点
Linklist q = p->next;//要删除的节点,pos位置
p->next = q->next;
free(q);
}
//头删
void Delete_Head(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Linklist q = plist ->next;
if(q != NULL )
{
plist->next = q->next;
}
free(q);
}
//尾插
void Delete_Tail(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Delete(plist,ListLength(plist));
}
//判空
int ListEmpty(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
printf("Not have Head\n");//不是一个单链表
return 0 ;
}
if(plist->next == NULL)//空
{
return 1;
}
return 0;
}
//置空
void Clear(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
while(!ListEmpty(plist))
{
Delete_Head(plist);
}
}
//显示
void Show(Linklist plist)
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
Linklist p = plist->next;
while(p)
{
printf("%d ——> ",p->data);
p = p ->next;
}
printf("NULL\n");
}
//判断两个单链表是否相交,若相交则返回第一个相交的结点,若没有相交,则返回空
Linklist IsIntersect(Linklist p1,Linklist p2)
{
if(ListEmpty(p1) || ListEmpty(p2))
{
return NULL;
}
if(p1 == NULL || p2 == NULL)
{
return NULL;
}
int len1 = ListLength(p1);
int len2 = ListLength(p2);
if(len1 > len2)
{
for(int i = 0;i < len1 - len2;i++)
{
p1 = p1->next;
}
}
else
{
for(int i = 0;i < len2 - len1;i++)
{
p2 = p2->next;
}
}
while(p1 != NULL)
{
if(p1 == p2)
return p1;
p1 = p1->next;
p2 = p2->next;
}
return NULL;
}
//找到倒数第k个结点
Linklist R_GetNode(Linklist plist,int k)
{
if (plist == NULL || plist->next == NULL || k <= 0)
{
return NULL;
}
Linklist p = plist;
Linklist q = plist;
if(k <= 0) return NULL;
for(int i = 0;i < k && p != NULL;i++)
{
p = p->next;
}
//k值大于链表长度
if(p == NULL)
{
return NULL;
}
while(p != NULL)
{
p = p->next;
q = q->next;
}
return q;
}
//删除p结点(p是指向链表中的某一个不为尾结点的结点)o(1)
int DeleteNodeofP(Linklist plist,Linklist p)
{
if(plist == NULL || p == NULL)
{
return 0;
}
if(p->next == NULL)//p指向尾结点
{
Delete_Tail(plist);
return 1;
}
Linklist q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
return 1;
}
//判断单链表是否有环
bool IsCircle(Linklist plist)
{
Linklist fast = plist;//快指针
Linklist slow = plist;//慢指针
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next == NULL)//链表长度为奇数fast->next为空,偶数时fast为空
return false;
else
return true;
}
//找到有环单链表入环的第一个节点
Linklist InToCircle(Linklist plist)
{
Linklist fast = plist;//快指针
Linklist slow = plist;//慢指针
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next == NULL)//链表长度为奇数fast->next为空,偶数时fast为空
return NULL;
//画图分析可知slow和fast第一次相遇的位置距离入环点的距离等于头结点到入环点的距离
slow = plist;
while(slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
//单链表逆置
void Reverse_list(Linklist plist)
{
Linklist p= plist->next;//记录当前要处理的节点位置
Linklist q ;//当前要处理结点的后一个结点
//由于第一个元素没有真正意义上的前驱,故在循环前要初始化头结点的指针域为NULL,用来表示它没有前驱,刚好合适
plist ->next = NULL;
while(p)
{
q = p->next;//q记录下p的后继结点
p->next = plist->next;//使p的前驱变后继
plist->next = p;//plist->next指向已处理的结点
p = q;//p指向下一个待处理的结点
}
}