这次需要实现的操作有
list.h
#ifndef __LIST_H__
#define __LIST_H__
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
typedef struct Node
{
DataType data;
struct Node* next;
}Node, *pNode, *pList;
void InitLinkList(pList* pplist); //二级指针 初始化
void PushFront(pList* pplist, DataType d); //头部插入
void Display(pList pl);
int GetLength(pList plist); //求一个无环链表长度
void PushBack(pList* pplist, DataType d); //尾部插入
pNode Find(pList pl, DataType d); //查询
void Show(pList pl); //逆序打印
void DelNotTail(pNode pos); //删除一个无头节点
void InsertFrontNode(pNode pos, DataType d); // 无头节点前插入
void JosephCycle(pList pl, int k); // 约瑟夫环
void ReverseList(pList* pplist);// 逆序
void BubbleSortList(pList plist); // 排序
pList Merge(pList *ppl1, pList *ppl2); //对两个有序链表进行合并 合并为一个有序链表
Node * FindMidNode(pList plist);// 查找一个中间数 要求只遍历一遍
void DelKNode(pList plist, int k); //删除倒数第K个元素
pNode CheckCircle(pList plist); //判断是否有环 并返回相遇点
int GetCircleLength(pNode meet); //求环的长度
pNode GetCircleEntry(pNode meet,pList plist); //求带环链表的链入点
void CheckCross(pList plist1, pList plist2); //分情况讨论 求两条链的链入点
int Check(Node* meet, pList plist); //判断meet是否在plist上
void print(pNode meet); //打印环上的所有数据
#endif
只实现部分稍微复杂的函数。
void DelNotTail(pNode pos); //删除一个无头节点
假设数据存储方式是这样的,如果要删除第2个节点,则可以将第二个节点和第三个节点的数值就行交换,然后让第二个字节指向第四个字节,并销毁第三个字节就可以了。
实现:
void DelNotTail(pNode pos)
{
Node *cur = pos;
Node *next = cur->next;
//值交换
int tmp = cur->data;
cur->data = next->data;
next->data = tmp;
//指向下一节点,销毁
cur->next = next->next;
free(next);
}
void InsertFrontNode(pNode pos, DataType d); // 无头节点前插入
同样是使用值交换,然后进行链接;
实现:
void InsertFrontNode(pNode pos, DataType d)
{
//值创建
Node*cur = NULL;
cur = (Node*)malloc(sizeof(Node));
if (cur == NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
cur->data = d;
cur->next = NULL;
//值交换
int tmp = pos->data;
pos->data = cur->data;
cur->data = tmp;
//链接
cur->next = pos->next;
pos->next = cur;
}
void ReverseList(pList* pplist);// 逆序
从前向后依次遍历,最终E的next指向D,结束逆序;
实现:
void ReverseList(pList* pplist) // 逆序
{
Node *head = *pplist; //维护头部
Node *cur = *pplist;
Node *tmp = *pplist;
tmp = tmp->next;
head->next = NULL; //先进行维护
while (tmp->next) //交换次序
{
cur = tmp;
tmp = tmp->next;
cur->next = head;
head = cur;
}
*pplist = head;
}
void JosephCycle(pList pl, int k) ; //约瑟夫环
一个环链表,从初始位置开始报数,每数到3,链接到下一个节点,并销毁数到3的节点。因为第一个人已经报数,所以计数器应从1开始;
实现:
void JosephCycle(pList pl, int k) //约瑟夫环
{
int count = 1;
while (pl != NULL)
{
pl = pl->next;
count++;
if (count == k)
{
printf("%d ", pl->data);
DelNotTail(pl); //删除当前节点
count = 1;
if (pl->data == pl->next->data) //当剩下最后一人的时候跳出
{
return ;
}
}
}
}
void BubbleSortList(pList plist); // 排序
排序可以当作冒泡排序,设定一个tail节点,指向NULL;每次遍历后tail向前走一次,当tail走到第一个节点的next时,停止比较;
实现:
void BubbleSortList(pList plist) //排序
{
assert(plist);
Node *head = plist;
Node *cur = plist;
Node *til = NULL;
while (til!=head->next)
{
while (cur->next != til)
{
if (cur->data > cur->next->data) //交换数据
{
int tmp = cur->data;
cur->data = cur->next->data;
cur->next->data = tmp;
}
cur = cur->next; //后移
}
til = cur; //til向前挪一位 每轮减少一次筛选
cur = head; //cur重新指向头部 重新开始比较
}
}
pList Merge(pList *ppl1, pList *ppl2); //对两个有序链表进行合并 合并为一个有序链表
创建一个head节点,让head节点先指向两个链表中第一个元素的最小值;
这时候需要一个新的new_head节点提前记录head最开始的位置;
随后每次进行一次比较,让head->next指向该最小值,该链表向后移一位,head也向后移一位直到其中一个为空,因为都是有序链表,让head->next指向另一个非空链表;
实现:
pList Merge(pList *ppl1,pList *ppl2) //对两个有序链表进行合并 合并为一个有序链表
{
assert(*ppl1);
assert(*ppl2);
Node *p1 = *ppl1;
Node *p2 = *ppl2;
Node *head = NULL;
Node *new_head =NULL;
if (ppl1 == NULL) //ppl1 如果l1为空 直接将head链接上l1
{
return p2;
}
else if (ppl2 == NULL)
{
return p1;
}
//先对head进行处理,让head指向第一个最小元素
else if (p1->data > p2->data)
{
head = p2;
p2 = p2->next;
}
else
{
head = p1;
p1 = p1->next;
}
new_head = head; //new_head用来保存一个起始位置
while ((p1 != NULL) && (p2 != NULL)) //跳出循环后必有一个为空 一个不为空
{
//比较之后链接到head上
if (p1->data > p2->data) //如果p1>p2 将p2插入到p1的后面
{
head->next = p2;
p2 = p2->next;
}
else //如果p1<p2,则将p1插入到p2的后面
{
head->next = p1;
p1 = p1->next;
}
head = head->next;
}
if (p1 == NULL)
{
head->next = p2;
return new_head;
}
else if (p2 == NULL)
{
head->next = p1;
return new_head;
}
}
Node * FindMidNode(pList plist);// 查找一个中间数 要求只遍历一遍
用快慢指针的方法,快指针每次走两步,慢指针每次走一步,当快指针为NULL时,慢指针指向中间数的地址
实现:
Node * FindMidNode(pList plist) // 查找一个中间数 要求只遍历一遍
{
assert(plist);
//快慢指针
Node *fast = plist; //快指针每次走2步
Node *slow = plist; //慢指针每次走1步
while((fast != NULL)&&(fast->next != NULL))
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
void DelKNode(pList plist, int k); //删除倒数第K个元素
同样使用快慢指针的方法,让快指针先走k步,当快指针为NULL时,慢指针指向倒数第K个元素;
实现:
void DelKNode(pList plist,int k) //删除倒数第K个元素
{
void DelKNode(pList plist,int k) //删除倒数第K个元素
{
//快慢指针
if (plist == NULL)
{
return;
}
Node * fast = plist;
Node *slow = plist;
Node *del = plist;
int count = k;
while (count >1 && fast != NULL) //快指针先走k步
{
fast = fast->next;
count--;
}
if (count>1||fast==NULL) //越界
{
printf("无法删除\n");
return;
}
while (fast->next!=NULL) //移动
{
fast = fast->next;
slow = slow->next;
}
if (count <= 1 && slow->next != NULL)
{
slow->data = slow->next->data;
del = slow->next;
slow->next = slow->next->next;
}
free(del);
}
pNode CheckCircle(pList plist); //判断是否有环 并返回相遇点
用快慢指针的方法,快指针每次走两步,慢指针每次走一步,当快指针为空则表示不带环,否则快指针必然和慢指针在环上相遇,返回相遇点;
实现:
pNode CheckCircle(pList plist) //判断是否有环 并返回相遇点
{
//快慢指针
Node *fast = plist;
Node *slow = plist;
while (fast!=NULL&&fast->next!=NULL)
{
fast = fast->next->next;
slow = slow->next;
if (fast==slow)
{
return slow; //相遇点
}
}
return NULL; //当fast为空时,不带环,返回NULL
}
int GetCircleLength(pNode meet); //求环的长度
让相遇点的位置一直向后偏移,当偏移的位置和相遇点的位置相同时,则表明已经走了一圈,这时返回长度;
实现:
int GetCircleLength(pNode meet) //求环的长度
{
Node *cur = meet;
Node *next = meet->next;
int count = 1; //next已经提前走了一步,所以从1开始
while (next != cur)
{
count++;
next = next->next;
}
return count;
}
pNode GetCircleEntry(pNode meet,pList plist); //求带环链表的链入点
设环长为L,链入点到相遇点的长度为y,起始位置到链入点的长度为x;
则当快节点(快节点走了m圈)和慢节点(慢节点走了n圈)都走到相遇点meet时,存在一个公式,2(x+y+nl)=x+y+ml;消元后,x+y=ml-nl;
设m-n为常数k,则x+y=kl >> x=kl-y;所以当一个节点从起始位置出发,另一个节点从相遇点出发,每次向后移一位,必定在链入点处相遇;
pNode GetCircleEntry(pNode meet, pList plist) //求带环链表的链入点
{
int len = GetCircleLength(meet);
Node *fast = plist; //fast从起始点开始出发
Node *cur = meet; //cur从相遇点出发
while (fast!=cur)
{
fast = fast->next;
cur = cur->next;
}
return cur;
}
void CheckCross(pList plist1, pList plist2); // 求两条链的接入点
要求两条链的接入点,首先要判断两条链的类型;
1.如果两条链都无环的话,对末尾的位置进行判断,如果位置相同,则开始求两条链的接入点,如果末尾位置不同,则表明没有接入点;
2.如果一条链有环,另一条无环,则必然没有接入点;
3如果两条链都有环,则对两条链的相遇点进行判断,若第一条链的相遇点在第二条链上,则表明两条链有链入点;否则是两条无接入点的有环链;
当两条链有接入点时,需要对链的类型进行判断,存在两种情况
分别求出两条链的接入点,如果接入点相同,则是第一种情况,如果接入点不同则是第二种情况;
第一种情况,可以将任意一条链的相遇点断开,然后求两条无环链的接入点;
第二种情况,整条环都是接入点,所以可以用任意一条链的接入点或相遇点打印环上的所有数字;
总共应该有以下6种情况
实现:
在这里面封装了三个新的函数:
int Check(Node* meet, pList plist); //判断相遇点meet是否在另一条链plist上
int GetLength(pList plist); //求一个无环链表长度
void print(pNode meet); //打印环上的所有数据 利用相遇点打印
void CheckCross(pList plist1,pList plist2) // 返回两条链表是否有接入点,有就输出该接入点 分情况讨论
{
Node *p1= CheckCircle(plist1); //p1为plist1的相遇点
Node *p2 = CheckCircle(plist2); //p2为plist2的相遇点
if (p1 == NULL && p2 == NULL) //如果p1,p2都为NULL,则表明是两条无环链
{
//1. 无环 -- 不相交 相交
Node *cur1 = plist1;
Node *cur2 = plist2;
while (cur1->next != NULL) //cur1,cur2分别指向末位置
{
cur1 = cur1->next;
}
while (cur2->next != NULL)
{
cur2 = cur2->next;
}
if (cur1 != cur2) //终点不同则表明无交点并退出
{
printf("两条无环链表无交点\n");
return;
}
else
{
printf("两条无环链表有交点\n");
}
cur1 = plist1; //重新指向头部
cur2 = plist2;
int len1 = GetLength(plist1); //求两条无环链的接入点
int len2 = GetLength(plist2);
if (len1 > len2) //让长的先走
{
int count1 = len1 - len2;
while (count1 > 1)
{
cur1 = cur1->next;
count1--;
}
}
else
{
int count2 = len2 - len1;
while (count2 > 0)
{
cur2 = cur2->next;
count2--;
}
}
while (cur1 != cur2)
{
cur1 = cur1->next;
cur2 = cur2->next;
}
printf("%d-->\n",cur2->data);
return;
//return cur2;
}
//2. 一个有环 一个无环 相交 不相交
if ((p1 == NULL && p2 != NULL)||(p2 == NULL && p1 != NULL))//如果一条是无环,一条有环,则必定无接入点
{
printf("一条有环链,一条无环链,无交点\n");
return;
}
else //先判断是否有两个不相交的有环链
{
Node*txt=CheckCircle(plist1);
int ret=Check(txt,plist2); //判断plist1的相遇点在不在plist2上,在则返回1
if (ret==1)
{
printf("两条相交的有环链\n");
Node *cur1 = plist1;
Node *cur2 = plist2;
Node *net1=CheckCircle(plist1); //p1的相遇点
Node *net2 = CheckCircle(plist2); // p2的相遇点
//如果链入点相同,则第一种情况,否则第二种情况
Node * zcc = GetCircleEntry(net1, plist1); //plist1的接入点
Node * xcc = GetCircleEntry(net2, plist2); //plist2的接入点
if (zcc == xcc) //接入点相同 则是第一种情况
{
net1->next = NULL; //断开环上的任意一个相遇点
int len1 = GetLength(cur1);
int len2 = GetLength(cur2);
if (len1 > len2) //让长的先走
{
int count1 = len1 - len2;
while (count1 > 1)
{
cur1 = cur1->next;
count1--;
}
}
else
{
int count2 = len2 - len1;
while (count2 > 0)
{
cur2 = cur2->next;
count2--;
}
}
while (cur1 != cur2) //当cur1==cur2时,就是接入点的位置
{
cur1 = cur1->next;
cur2 = cur2->next;
}
printf("%d-->\n",cur2->data);
//return cur2;
}
else if (zcc != xcc) //链入点不相同 则接入点是整条环 所以需要打印任意一个环上的所有数据
{
print(net1);
}
}
else
{
printf("两条不相交的有环链\n");
return;
}
}
//两个都有环 不相交 相交--》相遇点在环上 --》 相遇点不在环上
}