1、单向链表的原地反转
逻辑:
-
使用两个指针prev 和 pCur,pCur是需要反转的结点,prev为需要反转的结点的前驱结点
-
让prev的指针域指向需要反转结点的下一个结点:prev->next = pCur->next
-
将pCur反转到prev前:pCur->next = head->next
-
将头指针的next指向反转后的结点:head->next = pCur
-
pCur指向下次需要反转的结点:pCur = prev->next
算法实现:单向链表的反转.c
#include <stdio.h>
#include <stdlib.h>
#define TRUE 0
#define FALSE -1
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
LinkList list_init()
{
LNode *t;
t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
t->next = NULL; //头结点的指针域为空
LinkList head; //定义一个头指针
head = t; //头指针指向头结点
return head;
}
/*
* @brief 头插法插入一个结点
* @param 需要插入的链表的头指针
* @param 需要插入的数据
* @return 成功返回TRUE, 失败返回FALSE
* */
int head_insert(LinkList head, ElemType data)
{
if (NULL == head)
return FALSE;
//创建一个新的结点p
LNode *p = (LNode *)malloc(sizeof(LNode));
p->data = data;
//将新的结点p的next指向头节点的下一个结点(head->next)
p->next = head->next;
//头节点的next指向新的结点p
head->next = p;
return TRUE;
}
/*
* @brief 输出链表中的所有结点
* @param head 链表的头指针
* @return 成功返回TRUE,失败返回FALSE
* */
int print_list(LinkList head)
{
if (NULL == head)
return FALSE;
//使用一个临时指针对链表进行遍历
LNode *t;
t = head->next;
while (t != NULL)
{
printf("%d ", t->data);
t = t->next;
}
printf("\n");
return TRUE;
}
LinkList reverse(LinkList head)
{
if (NULL == head || NULL == head->next)
return head;
LNode *prev, *pCur;
//prev 指向第一个数据结点
prev = head->next;
//pCur指向第二个数据结点
pCur = prev->next;
if (NULL == pCur) //如果只有一个数据结点,不需要反转
return head;
while (pCur != NULL)
{
prev->next = pCur->next;
pCur->next = head->next;
head->next = pCur;
pCur = prev->next;
}
return head;
}
int main()
{
LinkList head;
head = list_init();
int i;
for (i = 0; i < 5; i++)
head_insert(head, 100+i);
print_list(head);
head = reverse(head);
print_list(head);
return 0;
}
2、查找链表的中间结点
要求:只能遍历一次链表
思路:快慢指针
-
定义两个指针slow、fast
-
slow每次遍历一个结点
-
fast每次遍历两个结点
算法实现:get_mid.c
#include <stdio.h>
#include <stdlib.h>
#define TRUE 0
#define FALSE -1
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
LinkList list_init()
{
LNode *t;
t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
t->next = NULL; //头结点的指针域为空
LinkList head; //定义一个头指针
head = t; //头指针指向头结点
return head;
}
/*
* @brief 头插法插入一个结点
* @param 需要插入的链表的头指针
* @param 需要插入的数据
* @return 成功返回TRUE, 失败返回FALSE
* */
int head_insert(LinkList head, ElemType data)
{
if (NULL == head)
return FALSE;
//创建一个新的结点p
LNode *p = (LNode *)malloc(sizeof(LNode));
p->data = data;
//将新的结点p的next指向头节点的下一个结点(head->next)
p->next = head->next;
//头节点的next指向新的结点p
head->next = p;
return TRUE;
}
/*
* @brief 输出链表中的所有结点
* @param head 链表的头指针
* @return 成功返回TRUE,失败返回FALSE
* */
int print_list(LinkList head)
{
if (NULL == head)
return FALSE;
//使用一个临时指针对链表进行遍历
LNode *t;
t = head->next;
while (t != NULL)
{
printf("%d ", t->data);
t = t->next;
}
printf("\n");
return TRUE;
}
LNode *get_mid(LinkList head)
{
LNode *slow, *fast;
slow = head->next;
fast = head->next;
while (fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
int main()
{
LinkList head;
head = list_init();
int i;
for (i = 0; i < 6; i++)
head_insert(head, 100+i);
print_list(head);
LNode *item;
item = get_mid(head);
printf("%d\n", item->data);
return 0;
}
3、判断链表是否有环
思路:
-
使用快慢指针
-
如果存在环,最终在某个时间点快慢指针会相遇
算法实现:find_loop.c
#include <stdio.h>
#include <stdlib.h>
#define TRUE 0
#define FALSE -1
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
LinkList list_init()
{
LNode *t;
t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
t->next = NULL; //头结点的指针域为空
LinkList head; //定义一个头指针
head = t; //头指针指向头结点
return head;
}
/*
* @brief 头插法插入一个结点
* @param 需要插入的链表的头指针
* @param 需要插入的数据
* @return 成功返回TRUE, 失败返回FALSE
* */
int head_insert(LinkList head, ElemType data)
{
if (NULL == head)
return FALSE;
//创建一个新的结点p
LNode *p = (LNode *)malloc(sizeof(LNode));
p->data = data;
//将新的结点p的next指向头节点的下一个结点(head->next)
p->next = head->next;
//头节点的next指向新的结点p
head->next = p;
return TRUE;
}
/*
* @brief 输出链表中的所有结点
* @param head 链表的头指针
* @return 成功返回TRUE,失败返回FALSE
* */
int print_list(LinkList head)
{
if (NULL == head)
return FALSE;
//使用一个临时指针对链表进行遍历
LNode *t;
t = head->next;
while (t != NULL)
{
printf("%d ", t->data);
t = t->next;
}
printf("\n");
return TRUE;
}
LNode *find_loop(LinkList head)
{
LNode *fast, *slow ;
slow = fast = head->next ;
while (slow != NULL && fast fast -> next != NULL)
{
slow = slow -> next ;
fast = fast -> next -> next ;
if (slow == fast)
return slow;
}
return NULL;
}
int main()
{
LinkList head;
head = list_init();
int i;
for (i = 0; i < 6; i++)
head_insert(head, 100+i);
print_list(head);
//人为在链表上添加一个环
//使用一个指针记住链表上的第三个结点
LNode *flag = head->next->next->next;
//找到最后一个结点
LNode *tail = head->next;
while (tail->next != NULL)
tail = tail->next;
LNode *p = (LNode *)malloc(sizeof(LNode));
p->data = 200;
tail->next = p;
p->next = flag;
LNode *loop = find_loop(head);
if (loop != NULL)
printf("%d\n", loop->data);
else
printf("no loop\n");
return 0;
}
4、查找带环链表中环的入口
通过判断一个单向链表是否有环我们知道,如果有环的存在,假设环的长度为r,那么当快指针fast和慢指针slow相遇的时候,假如fast指针已经在环上走了n圈,假如slow走了s步,那么fast就走了2s步,又因为fast走过的步数 = s + n*r,则有下面的等式:
2*s = s + n * r ; (1)
=> s = n*r; (2)
如果假设整个链表的长度是L,入口和相遇点的距离是x(如上图所示),起点到入口点的距离是a(如上图所示),则有:
a + x = s = n * r; (3) 由(2)推出
a + x = (n - 1) * r + r = (n - 1) * r + (L - a) (4) 由环的长度 = 链表总长度 - 起点到入口点的距离求出
a = (n - 1) * r + (L -a -x) (5)
集合式子(5)以及上图我们可以看出,从链表起点开始到入口点的距离a,与从slow和fast的相遇点(如图)到入口点的距离相等。
因此我们就可以分别用一个指针(ptr1, prt2),同时从head与slow和fast的相遇点出发,每一次操作走一步,直到ptr1 == ptr2,此时的位置也就是入口点!
算法实现:find_entrance.c
#include <stdio.h>
#include <stdlib.h>
#define TRUE 0
#define FALSE -1
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
LinkList list_init()
{
LNode *t;
t = (LNode *)malloc(sizeof(LNode)); //创建一个头结点
t->next = NULL; //头结点的指针域为空
LinkList head; //定义一个头指针
head = t; //头指针指向头结点
return head;
}
/*
* @brief 头插法插入一个结点
* @param 需要插入的链表的头指针
* @param 需要插入的数据
* @return 成功返回TRUE, 失败返回FALSE
* */
int head_insert(LinkList head, ElemType data)
{
if (NULL == head)
return FALSE;
//创建一个新的结点p
LNode *p = (LNode *)malloc(sizeof(LNode));
p->data = data;
//将新的结点p的next指向头节点的下一个结点(head->next)
p->next = head->next;
//头节点的next指向新的结点p
head->next = p;
return TRUE;
}
/*
* @brief 输出链表中的所有结点
* @param head 链表的头指针
* @return 成功返回TRUE,失败返回FALSE
* */
int print_list(LinkList head)
{
if (NULL == head)
return FALSE;
//使用一个临时指针对链表进行遍历
LNode *t;
t = head->next;
while (t != NULL)
{
printf("%d ", t->data);
t = t->next;
}
printf("\n");
return TRUE;
}
LNode *find_entrance(LinkList head)
{
LNode *fast, *slow ;
slow = fast = head->next ;
while (slow != NULL && fast -> next != NULL)
{
slow = slow -> next ;
fast = fast -> next -> next ;
if (slow == fast)
break;
}
if (slow == NULL || fast -> next == NULL)
return NULL ; //没有环,返回NULL值
LNode * ptr1 = head->next ; //链表开始点
LNode * ptr2 = slow ; //相遇点
while (ptr1 != ptr2)
{
ptr1 = ptr1 -> next ;
ptr2 = ptr2 -> next ;
}
return ptr1 ; //找到入口点
}
int main()
{
LinkList head;
head = list_init();
int i;
for (i = 0; i < 6; i++)
head_insert(head, 100+i);
print_list(head);
//人为在链表上添加一个环
//使用一个指针记住链表上的第三个结点
LNode *flag = head->next->next->next;
//找到最后一个结点
LNode *tail = head->next;
while (tail->next != NULL)
tail = tail->next;
LNode *p = (LNode *)malloc(sizeof(LNode));
p->data = 200;
tail->next = p;
p->next = flag;
LNode *loop = find_entrance(head);
if (loop != NULL)
printf("%d\n", loop->data);
else
printf("no loop\n");
return 0;
}
5、约瑟夫环
问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。
问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。
解题思路:
-
构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
-
使用计数器count,记录当前报数的值;
-
遍历链表,每循环一次,count++;
-
判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
算法实现:JosephRing.c
#include <stdio.h>
#include <stdlib.h>
#define TRUE 0
#define FALSE -1
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域,指向当前结点的直接后继(下一个结点)
}LNode, *LinkList; //LinkList的类型为 LNode *
/*
* @brief 输出链表中的所有结点
* @param head 链表的头指针
* @return 成功返回TRUE,失败返回FALSE
* */
int print_list(LinkList head)
{
if (NULL == head)
return FALSE;
//使用一个临时指针对链表进行遍历
LNode *t;
t = head;
int i = 0;
for (i = 0; i < 41; i++)
{
printf("%d ", t->data);
t = t->next;
}
printf("\n");
return TRUE;
}
void JosephRing(int num)
{
//使用不带头结点的方式创建一个约瑟夫环
int i;
LNode *head; //第一个结点/头指针
head = (LNode *)malloc(sizeof(LNode));
LNode *tail = head;
for (i = 1; i < 41; i++)
{
tail->data = i;
tail->next = (LNode *)malloc(sizeof(LNode));
tail = tail->next;
}
tail->data = i;
tail->next = head;
print_list(head);
LNode *t = head;
LNode *pre = NULL;
int cnt = 0;
while (t != t->next)
{
cnt++;
if (cnt == num)
{
printf("%d: out\n", t->data);
pre->next = t->next;
free(t);
t = pre->next;
cnt = 0;
}
else
{
pre = t;
t = t->next;
}
}
printf("%d :left\n", t->data);
}
int main()
{
JosephRing(3);
return 0;
}