链表
typedef int ELEM_TYPE;
//单链表有效节点的的结构体设计
typedef struct Node
{
//数据域
ELEM_TYPE data;
//指针域
struct Node* next;
}Node, * PNode;
1.单链表
链表由一系列不再内存中相连的组织构成,每个节点均包含有数据元素和指向下一个节点的指针,即next指针,最后一个节点的next指向NULL,如图
1.单链表的初始化
把接收到的节点head的指针域置空,这是头节点
void Init_List(struct Node* head)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
head->next = NULL;
}
2.插入数据
//头插法
bool Insert_head(struct Node* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//1.malloc新节点
Node* pnewnode = (Node*)malloc(sizeof(Node));
//判断申请是否成功
if(NULL == pnewnode) exit(EXIT_FAILURE);
pnewnode->data = val;/给这个新节点赋值,即要插入的值
//2.插入
pnewnode->next = head->next;
head->next = pnewnode;
return true;
}
//尾插法
bool Insert_tail(struct Node* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//1.malloc新节点
Node* pnewnode = (Node*)malloc(sizeof(Node));
//判断申请是否成功
if(NULL == pnewnode) exit(EXIT_FAILURE);
pnewnode->data = val;
//2.找到合适的插入位置
Node* p head;
while(p->next != NULL)
p = p->next;
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
//按位置插
//插入到第pos个元素的后面
bool Insert_pos(struct Node* head,ELEM_TYPE val,int pos)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//1.malloc新节点
Node* pnewnode = (Node*)malloc(sizeof(Node));
//判断申请是否成功
if(NULL == pnewnode) exit(EXIT_FAILURE);
pnewnode->data = val;
//2.找到合适的位置
Node* p = head;
for(int i = 0 ; i < pos ; i++)//这里没有=
{
p = p->next;
}
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
3.删除数据
//头删
bool Del_head(struct Node* head)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//判断当前链表是否为空
if(Is_Empty(head)) return false;
//直接跨越待删除节点 然后释放内存
Node* p = head->next;
head->next = p->next;
free(p);
p = NULL;//避免悬空指针的出现
return true;
}
//尾删
bool Del_tail(struct Node* head)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//判空
if(Is_Empty(head)) return false;
//申请临时指针p,让其指向待删除节点的前驱
Node* p = head;
while(p->next->next != NULL)
{
p = p->next;
}
//申请临时指针q,让其指向待删除节点
Node* q = p->next;
//跨越指向+释放内存
p->next = q->next;
free(q);
q = NULL;
return true;
}
//按位置删
bool Del_pos(struct Node* head,int pos)
{
//这里的pos类似于数组下标,第一个元素的pos = 0
assert(NULL != head);
assert(pos >= 0 && pos < Get_Length(head));
if(NULL == head) exit(EXIT_FAILURE);
//申请两个临时指针p和q,让其分别指向待删除节点的前驱和待删除节点
Node* p = head;
Node* q = head;
for(int i = 0 ; i < pos ; i++)
p = p->next;
q = p->next;
//跨越指向+释放内存
p->next = q->next;
free(q) ;
q = NULL;
return true;
}
//按值删(删除val第一次出现的节点)
bool Del_val(struct Node* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//判空
if(Is_Empty(head)) return false;
//调用Search函数,查找当前值val是否存在,并用临时指针q接收
Node* q = Search(head,val);
if(q == NULL) return false;
//申请临时指针p,指向q的前驱
Node* p head;
while(p->next != q)
p = p->next;
//跨越指向+释放
p->next = q->next;
free(q);
q = NULL;
return true;
}
//按值删(删除所有val值)
bool Del_all_val(struct Node* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
if(Is_Empty(head)) return false;
//初始化双指针
Node* prev = head;//前驱指针
Node* cur = head->next;//当前指针
//遍历链表
while(cur != NULL)
{
if(cur->data == val)
{
prev->next = cur->next;
free(cur);
cur = prev->next;
}
else
{
prev = cur;
cur = cur->next;
}
}
return true;
}
4.查找
struct Node* Search(struct Node* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
//判空
if(Is_Empty(head)); return NULL;
//遍历链表
for(Node* p = head->next; p != NULL; p = p->next)
{
if(p->data == val) return p;//找到了,停止循环
}
//没找到
return NULL;
}
5.销毁
void Destroy(struct Node* head)
{
//方法一:只要不是空链表,则一直头删
/*
while(!Is_Empty(head)) Del_head(head);
*/
//方法二:申请辅助节点p,q,一直跨越节点释放
assert(NULL != head);
//申请指针p,让其保存头节点的下一个节点
Node* p = head->next;
Node* q;
while(p != NULL)
{
//1.q指向p的下一个节点
q = p->next;
//2.释放p指向的节点
free(p);
//3.让p指向q指向的节点
p = q;
}
//全部销毁完毕后,将头节点的指针域置空
head->next = NULL;
}
6.判空和获取有效长度
//判空
bool Is_Empty(struct Node* head)
{
return (head->next == NULL);
}
//获取有效值长度
int Get_Length(struct Node* head)
{
int count = 0;
for(Node* p = head->next; p != NULL; p = p->next)
{
count++;
}
return count;
}
2.循环链表
在单链表的基础上,让尾节点的next域指向头节点
//循环链表结构体设计
typedef int ELEM_TYPE;
typedef struct CNode
{
ELEM_TYPE data;
struct CNode* next;
}CNode, * PCNode;
1.初始化
void Init_CList(CNode* head)
{
assert(NULL != head);
if(NULL == head) exit(EXIT_FAILURE);
head->next = head;//尾节点的next域指向头节点
}
2.插入
//头插法
bool Insert_head(CNode* head,ELEM_TYPE val)
{
assert(NULL != plist);
if (NULL == plist)
exit(EXIT_FAILURE);
//1.申请新节点
CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
if(NULL == pnewnode) exit(EXIT_FAILURE);
pnewnode->data = val;
//2.插入
pnewnode->next = head->next;//让尾指向头
head->next = pnewnode;
return true;
}
//尾插法
bool Insert_tail(CNode* head,ELEM_TYPE val)
{
assert(NULL != plist);
if (NULL == plist)
exit(EXIT_FAILURE);
//1.申请新节点
CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
if(pnewnode == NULL) exit(EXIT_FAILURE);
pnewnode->data = val;
//2.找到合适的插入位置
CNode* p =head;
while(p->next != head)//这里不再是p->next != NULL
{
p = p->next;
}
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
//按位置插入
bool Insert_pos(CNode* head,ELEM_TYPE val,int pos)
{
assert(NULL != plist);
if (NULL == plist)
exit(EXIT_FAILURE);
//1.申请新节点
CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
if(pnewnode == NULL) exit(1);//等同于exit(EXIT_FAILURE)
pnewnode->data = val;
//2.找到合适位置
CNode* p = head;
for(int i = 0 ; i < pos ; i++)
{
p = p->next;
}
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
}
3.删除
//头删
bool Del_head(CNode* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(1);
//判空
if(Is_Empty(head)) return false;
//跨越待删除节点
CNode* p = head->next;
head->next = p->next;
free(p);
p = NULL;
return true;
}
//尾删
bool Del_tail(CNode* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(1);
//判空
if(Is_Empty(head)) return false;
//1.申请临时指针p,让其指向待删除节点前驱
CNode* p = head;
while(p->next->next != head)
p = p->next;
//2.申请临时指针q,指向待删除节点
CNode* q = p->next;
//3.跨越指向+释放内存
p->next = q->next;
free(q);
q = NULL;
return true;
}
//按位置删
bool Del_pos(CNode* head,ELEM_TYPE,int pos)
{
//这里的pos类似于数组下标,第一个元素的pos = 0
assert(NULL != plist);
assert(pos >= 0 && pos < Get_Length(plist));
if (NULL == plist)
exit(EXIT_FAILURE);
//1.申请两个临时指针p和q,让其分别指向待删除节点的前驱和待删除节点
CNode* p = head;
CNode* q = head;
for(int i = 0; i < pos; i++)
p = p->next;
q = p->next;
//2.跨越待删除节点+释放内存
p->next = q->next;
free(q);
q = NULL;
return true;
}
//按值删(删除第一次出现val的节点)
bool Del_val(CNode* head,ELEM_TYPE val)
{
assert(NULL != plist);
if (NULL == plist)
exit(EXIT_FAILURE);
//判空
if(Is_Empty(head)) return false;
//1.调用Search函数,查找当前值val是否存在,用临时指针q接收
CNode* q = Search(head,val);
if(q == NULL) return false;
//2.申请临时指针p,让其指向q的前驱
CNode* p = head;
while(p->next != q)
p = p->next;
//3.跨越指向+释放内存
p->next = q->next;
free(q);
q = NULL;
return true;
}
//按值删(删除所有val值节点)
bool Del_all_val(CNode* head,ELEM_TYPE val)
{
assert(NULL != plist);
if (NULL == plist)
exit(EXIT_FAILURE);
//判空
if(Is_Empty(head)) return false;
CNode* p = head;
CNode* q = head;
//遍历链表
//指针p和q分别指向val的前驱和val(如果有),方便跨越指向待删除节点
while(p->next != head)
{
q = p->next;
if(q->data == val)//找到了,跨越待删除节点并释放
{
p->next = q->next;
free(q);
q = NULL;
}
else p = q;//没找到,p,q都往前走一步
}
return true;
}
4.查找
CNode* Search(CNode* head, ELEM_TYPE val)
{
for (CNode* p = head->next; p != head; p = p->next)
{
if (p->data == val)
return p;
}
return NULL;
}
5.销毁
void Destroy(CNode* head)
{
assert(NULL != head);
//1.申请临时指针p,让其保存头节点的指针域,也就是第一个有效节点
CNode* p = head->next;
//2.申请临时指针q
CNode* q;
//3.遍历节点
while(p != head)
{
//1.q指向p的下一个节点
q = p->next;
//2.释放p
free(p);
//3.让p指向q
p = q;
}
//4.节点全部销毁完毕,别忘了把辅助节点的指针域置空
head->next = NULL;
}
6.判空+获取有效值长度
//判空
bool Is_Empty(CNode* head)
{
return head == head->next;
}
//获取有效值长度
int Get_Length(CNode* head)
{
int count = 0;
for (CNode* p = head->next; p != h; p = p->next)
{
count++;
}
return count;
}
3.双向链表
在单链表的基础上,多加上一个prior域,让其指向自己的前驱,这样,每个节点既能指向后继(next)节点,又可以指向前驱(prior)节点
typedef int ELEM_TYPE;
//双向链表的结构体设计
typedef struct DNode
{
ELEM_TYPE data;
DNode* next;
DNode* prior;
}DNode, * PDNode;
1.初始化
void Init_Double_List(DNode* head)
{
assert(head != NULL);
if(NULL == head) exit(1);
head->next = NULL;
head->prior = NULL;
}
2.插入
1.头插法
void Insert_head(DNode* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(1);
//申请新节点
DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
if(NULL == pnewnode) exit(1);
pnewnode->data = val;
//插入
//先修改申请节点的两个指针域,再修改插入位置的右边节点指针域,最后修改插入位置的左边节点的指针域
pnewnode->next = head->next; // 1
pnewnode->prior = head; //2
//空链表不存在节点,所有不需要将其前驱指向pnewnode
if(!Is_Empty(head))
pnewnode->next->prior = pnewnode;//3
head->next = pnewnode;//4
return true;
}
2.尾插法
bool Insert_tail(PDNode head,ELEM_TYPE val)
{
//任何情况下都是修改三个指针域,也即待插入节点的两个域和上一个节点的next域
assert(NULL != head);
if(NULL == head) exit(1);
//1.申请新节点
DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
if(NULL == pnewnode) exit(1);
pnewnode->data = val;
//2.找插入位置
DNode* p = head;
while(p->next != NULL)
p = p->next;
//3.插入
pnewnode->next = p->next;
pnewnode->prior = p;
p->next = pnewnode;
return true;
}
3.按位置插
bool Insert_pos(DNode* head,ELEM_TYPE val,int pos)
{
assert(NULL != head);
assert(pos >= 0 && pos <= Get_Length(head));
if(NULL == head) exit(1);
//对pos判断,如果pos=0,则调用头插函数
// 如果pos=length,则调用尾插函数
if(pos == 0) return Insert_head(head,val);
if(pos == Get_Length(head)) return Insert_tail(head,val);
//1.申请新节点
DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
if(NULL == pnewnode) exit(1);
pnewnode->data = val;
//2.找插入位置
DNode* p = head;
for(int i = 0; i < pos; i++)
p = p->next;
//3.插入
pnewnode->next = p->next;//1
pnewnode->prior = p;//2
pnewnode->next->prior = pnewnode;//3
p->next = pnewnode;//4
}
3.删除
1.头删
bool Del_head(DNode* head)
{
//普通情况:只需要修改两个指针域
//特殊情况:只有一个节点,此时只需要修改一个指针域
assert(NULL != head);
if(NULL == head) exit(1);
//判空
if(Is_Empty(head)) return false;
//1.申请新节点
DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
if(NULL == pnewnode) exit(1);
//2.申请临时指针q,让其分别指向待删除节点
DNode* q = head->next;
//3.跨越指向+释放内存
head->next = q->next;
//如果链表节点大于1,那么还需要将后面节点指向头节点以达到双向链表
if(q->next != NULL)
q->next->prior = head;
free(q);
q = NULL;
return true;
}
2.尾删
尾删只需要修改一个指针域,也就是倒数第二个节点的next域
bool Del_tail(DNode* head)
{
assert(head != NULL);
if(NULL == head) exit(1);
//判空
if(Is_Empty(head)) return false;
//申请新节点
DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
//2.申请两个临时变量p和q,让其分别指向待删除节点的前驱和待删除节点
DNode* q = head;
while(q->next != NULL)
q = q->next;
DNode* p = q->pripr;
//3.跨越指向+释放内存
p->next = q->next;
free(q);
q = NULL;
return true;
}
3.按位置删
bool Del_Pos(DNode* head,int pos)
{
//这里的pos类似于数组下标,第一个元素的pos = 0
assert(NULL != head);
assert(pos >= 0 && pos < Get_Length(head));//有这一步就不需要再判空了
if(NULL == head) exit(1);
//申请新节点
DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
if(NULL == pnewnode) exit(1);
//提前处理头尾节点
if(pos == 0) return Del_head(head);
if(Pos == Get_Length(head) - 1) return Del_tail(head);
//2.申请两个临时变量p和q,让其分别指向待删除节点的前驱和待删除节点
DNode* p = head;
for(int i = 0; i < pos; i++)
p = p->next;
DNode* q = p->next;
//跨越指向+释放内存
p->next = q->next;
q->next->prior = p;
free(q);
q = NULL;
return true;
}
4.按值删
//删除val第一次出现的节点
bool Del_val(DNode* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(1);
//判空
if(Is_Empty(head)) return false;
DNode* q = Search(head,val);//查找有没有这个值
if(q == NULL) return false;
if(head->next == q)
return Del_head(head);
DNode* p = q->prior;//待删除节点的前驱
p->next = q->next;//跨越指向
if(q->next != NULL) //实现双向链表 如果为空则不用
q->next->prior = p;
free(q);
q = NULL;
return true;
}
//删除所有val值节点
bool Del_val_all(DNode* head, ELEM_TYPE val)
{
//0.assert
assert(NULL != head);
if (NULL == head)
exit(EXIT_FAILURE);
//0.5 判空
if (Is_Empty(head))
return false;
DNode* p = head->next;
while (p != NULL)
{
//提前保存下一个地址,cur可能会被释放
DNode* q = p->next;
if (p->data == val)
{
p->prior->next = p->next;
if (p->next != NULL)
p->next->prior = p->prior;
free(p);
}
//下一个节点
p = q;
}
return true;
}
5.判空与获取有效长度
//判空
bool Is_Empty(DNode* head)
{
return head->next == NULL;
}
//获取有效长度
int Get_Length(DNode* head)
{
DNode* p = head->next;
int count = 0;
while(p != NULL)
{
count++;
p = p->next;
}
}
6.销毁
void Destroy(DNode* head)
{
assert(NULL != head);
//创建临时指针p,让其保存头节点的指针域
DNode* p = head->next;
//创建临时指针q,先不赋值
DNode* q ;
while(p != NULL)
{
//1.q指向p的下一个节点
q = p->next;
//2.释放p所指向的节点
free(p);
//3.让p指向q所在的节点
p = q;
}
//最后将头节点的指针域置空
head->next = NULL;
}
7.查找
DNode* Search(DNode* head,ELEM_TYPE val)
{
assert(NULL != head);
if(NULL == head) exit(1);
//判空
if(Is_Empty(head)) return NULL;
for(DNode* p = head->next; p != NULL; p = p->next)
{
if(p->data == val)
return p;
}
return NULL;
}
4.链表相关题目
1.逆置链表
本质上还是头插法,假设头插1,3,5,2,4,遍历链表读取数据顺序为4,2,5,3,1,所以链表逆置可以理解为将原链表节点重新头插给头节点
void ReverseLink(Node* head)
{
//保存有效链表
Node* p = head->next;
if(p == NULL) return ;
//头节点断开,开始重新头插
head->next = NULL;
while(p != NULL)
{
Node* q =p->next;
//p指向的节点进行头插
p->next = head->next;
head->next = p;
p = q;
}
}
2.求单链表的倒数第k个节点
定义两个指针pre和p都指向头节点,先让p走k步,然后pre和p同步向前走,直到p指向空,则此时pre指向的节点恰好是倒数第k个节点
也就是让pre走n-k步,p总共走n步。
p = k + (n - k) , pre = n - k;,即倒数第k个节点
Node* Get_LastK_Node(Node* head,int k)
{
Node* p = head;
Node* pre = head;
for(int i = 0; i < k; i++)
{
p = p->next;
if(p == NULL) return NULL;//长度不够
}
while(p != NULL)
{
pre = pre->next;
p = p->next;
}
return pre;
}
3.有序单链表合并
定义两个指针p,q分别指向两个链表的第一个有效节点
定义last指向已连接的末尾节点
void MergeLink(Node* head1,Node* head2)
{
Node* p = head1->next;
Node* q = head2->next;
Node* last = head1;
head2->next = NULL;//用不到head2了
while(p != NULL && q != NULL)
{
//选择较小的节点
if(p->data < q->data)
{
last->next = p;//重新指向
p = p->next;//更新p
last = last->next;//last始终指向末尾
}
else
{
last->next = q;
q = q->next;
last = last->next;
}
}
//当一个节点为空之后,直接将last指向下一个非空指针,即接上非空的链表
if(q != NULL) last->next = q;
else last->next = p;
}
4.判断单链表有没有环,如果有,返回入环点
Node* Get_Entry_loop_point(Node* head)
{
//先判断有没有环
Node* fast = head;
Node* slow = head;
while(fast != NULL && fast->next != NULL)
{
//先各自走一步
slow = slow->next;
fast = fast->next->next;
if(slow == fast)//k
{
//2.根据公式X = (n-1)(Y+Z)+Z
Node* p = head;
Node* q = fast;
while(p != q)
{
p = p->next;
q = q->next;
}
return p;
}
}
return NULL;//没有环
}
&& q != NULL)
{
//选择较小的节点
if(p->data < q->data)
{
last->next = p;//重新指向
p = p->next;//更新p
last = last->next;//last始终指向末尾
}
else
{
last->next = q;
q = q->next;
last = last->next;
}
}
//当一个节点为空之后,直接将last指向下一个非空指针,即接上非空的链表
if(q != NULL) last->next = q;
else last->next = p;
}
#### 4.判断单链表有没有环,如果有,返回入环点
```c
Node* Get_Entry_loop_point(Node* head)
{
//先判断有没有环
Node* fast = head;
Node* slow = head;
while(fast != NULL && fast->next != NULL)
{
//先各自走一步
slow = slow->next;
fast = fast->next->next;
if(slow == fast)//k
{
//2.根据公式X = (n-1)(Y+Z)+Z
Node* p = head;
Node* q = fast;
while(p != q)
{
p = p->next;
q = q->next;
}
return p;
}
}
return NULL;//没有环
}