1.前言
关于链表的考察和应用始终是公司考察的重点,因为链表可以衍生出更多具有特性的数据结构(链式栈、队列),那么我们在编写链表这类数据结构的时候一 定要设计好合理的结构体信息,结构体的设计越丰富合理,越有利于程序的编写。今天我们将采用带有控制信息的链表为例,为大家讲解各大公司面试题对链表的考 察。
面试题主要涉及一下部分:
1.链表初始化
2.链表的销毁
3.头部插入
4.尾部插入
5.头部删除
6.尾部删除
7.显示链表信息
8.升序排列链表
9.降序排列链表
10.得到链表的长度
11.合并两个已经排序的链表
12.合并两个已经排序的链表(递归方式)
13.得到链表中间节点的位置
14.得到链表的倒数第k个节点
15.反转链表
16.链表的拷贝
17.逆序打印链表
18.判断两个链表是否有交点
19.找到两个链表的第一个交点
20.在O(1)时间复杂度删除链表节点
21.判断链表是否有环
22.判断链表是否有环
23.找到链表环的入口(简便)
24.找到链表环的入口
链表面试题实现
关于上述所有的问题我们首先引入list.h文件,这个头文件中我们列举出了带有控制信息的链表节点的设计,分为控制信息和节点信息两部分,两者的定义如下所示:
点击(此处)折叠或打开
-
#define ZERO (0)
-
#define ONLY_ONE (1)
-
#define TWO (2)
-
#define FALSE (0)
-
#define TRUE (1)
-
-
//带有控制信息的单链表
-
typedef unsigned char Boolean;
-
-
//链表节点信息
-
typedef struct List_node
-
{
-
int data; //数据区域
-
struct List_node *next; //链域
-
}List_node;
-
-
//链表控制信息
-
typedef struct List
-
{
-
struct List_node *head; //链表头部节点
-
struct List_node *tail; //链表尾部节点
-
int count; //链表节点个数
- }List
点击(此处)折叠或打开
-
//链表操作接口
-
List *init_list(void) ; //链表初始化
-
void destroy_list(List **list) ; //链表的销毁
-
Boolean push_front(List *list, int value) ; //头部插入
-
Boolean push_back(List *list, int value) ; //尾部插入
-
Boolean pop_front(List *list) ; //头部删除
-
Boolean pop_back(List *list) ; //尾部删除
-
void show_list(List *list) ; //显示链表信息
-
void sort_list_ascend(List *list) ; //升序排列链表
-
void sort_list_descend(List *list) ; //降序排列链表
-
int get_list_count(List *list) ; //得到链表的长度
-
-
///
-
List *merge_two_lists(List *list1, List *list2) ; //合并两个已经排序的链表
-
List *merge_two_lists_recure(List *list1, List *list2); //同上(递归方式)
-
List_node *find_mid_node(List *list) ; //得到链表中间节点的位置
-
List_node *find_revise_node(List *list, int conut) ; //得到链表的倒数第k个节点
-
List *reverse_list(List *list) ; //反转链表
-
List *list_dup(List *list) ; //链表的拷贝
-
void reverse_print_list(List *list) ; //逆序打印链表
-
Boolean is_list_intersect(List *list1, List *list2) ; //判断两个链表是否有交点
-
List_node *get_first_common_node(List *list1, List *list2) ; //找到两个链表的第一个交点
-
void delete_one_node(List *list, List_node *node) ; //在O(1)时间复杂度删除链表节点
-
Boolean has_circle1(List *list) ; //判断链表是否有环
-
Boolean has_circle2(List *list, List_node **intersect) ; //判断链表是否有环
-
List_node *find_circle_first_node1(List *list) ; //找到链表环的入口(简便)
- List_node *find_circle_first_node2(List *list) ; //找到链表环的入口
1.链表初始化
点击(此处)折叠或打开
-
List *init_list(void) //链表初始化
-
{
-
List *list = NULL;
-
-
//链表控制信息申请
-
list = (List *)Malloc(sizeof(List));
-
bzero(list, sizeof(List));
-
-
return list;
- }
下面列出malloc包裹函数的具体实现:
点击(此处)折叠或打开
-
static void *Malloc(size_t size)
-
{
-
void *result = NULL;
-
-
result = malloc(size);
-
if(result == NULL){
-
fprintf(stderr, "the memory is full!\n");
-
exit(1);
-
}
-
return result;
- }
2.链表的销毁
在处理完链表创建,紧接着需要处理的应该是链表的销毁。作为数据结构的处理一定要“善始善终”,这样才不会造成内存的泄露。
链表的销毁我们传入的是链表控制信息的指针的指针,关于细节的问题我们如下图所示:
点击(此处)折叠或打开
-
void destroy_list(List **list) //链表的销毁
-
{
-
if(list == NULL || *list == NULL){
-
return ;
-
}
-
//1.释放链表本身节点
-
while((*list)->count){
-
pop_front(*list);
-
}
-
//2.释放链表的控制信息
-
free(*list);
-
*list = NULL;
- }
关于push_front(头部插入)、push_back(尾部插入)、pop_front(头部删除)、pop_back(尾部删除)四个接口 是作为一种底层容器的操作而存在的,这样对于线性容器来说,头和尾的四种增加和删除的操作都已经完备了,这在我们对于链表的封装,使其成为栈或者队列有着 重要的意义。
四种接口的实现如下所示:
3.头部插入
图3:头部插入
点击(此处)折叠或打开
-
Boolean push_front(List *list, int value) //头部插入
-
{
-
List_node *node = NULL;
-
-
if(list == NULL){
-
return FALSE;
-
}
-
node = buy_node();
-
node->data = value;
-
-
if(list->count == ZERO){ //链表没有元素
-
list->head = list->tail = node;
-
}else{
-
node->next = list->head;
-
list->head = node;
-
}
-
list->count++;
-
return TRUE;
- }
4.尾部插入
图4:尾部插入
点击(此处)折叠或打开
-
Boolean push_back(List *list, int value) //尾部插入
-
{
-
List_node *node = NULL;
-
-
if(list == NULL){
-
return FALSE;
-
}
-
node = buy_node();
-
node->data = value;
-
-
if(list->count == ZERO){ //链表没有元素
-
list->head = list->tail = node;
-
}else{
-
list->tail->next = node;
-
list->tail = node;
-
}
-
list->count++;
-
return TRUE;
- }
5.头部删除
点击(此处)折叠或打开
-
Boolean pop_front(List *list) //头部删除
-
{
-
List_node *p_node = NULL;
-
-
if(list == NULL || list->count == ZERO){ //链表不存在或者没有元素,直接退出
-
return FALSE;
-
}
-
-
p_node = list->head;
-
if(list->count == ONLY_ONE){ //如果链表只有一个节点
-
list->head = list->tail = NULL;
-
}else{
-
list->head = list->head->next;
-
}
-
free(p_node);
-
list->count--;
-
return TRUE;
- }
6.尾部删除
点击(此处)折叠或打开
-
Boolean pop_back(List *list) //尾部删除
-
{
-
List_node *p_node = NULL;
-
-
if(list == NULL || list->count == ZERO){
-
return FALSE;
-
}
-
-
p_node = list->head;
-
if(list->count == ONLY_ONE){ //只有一个元素
-
list->head = list->tail = NULL;
-
free(p_node);
-
}else{
-
while(p_node->next != list->tail){
-
p_node = p_node->next;
-
}
-
free(list->tail);
-
list->tail = p_node;
-
p_node->next = NULL;
-
}
-
list->count--;
-
return TRUE;
- }
7.显示链表信息
7.显示链表的信息是对链表遍历的过程,让一个链表节点指针从链表的头部信息遍历到链表的末尾,对每个节点中的数据区域进行输出则可以显示其节点信息。代码如下所示:
点击(此处)折叠或打开
-
void show_list(List *list) //显示链表信息
-
{
-
List_node *p_node = NULL;
-
-
if(list == NULL || list->count == ZERO){
-
return ;
-
}
-
-
//从头到尾对链表节点对数据信息进行输出
-
for(p_node = list->head; p_node; p_node = p_node->next){
-
printf("%d ", p_node->data);
-
}
-
printf("\n");
- }
8.升序排列链表
对链表的排序我们采用的方式和对数组的排序类似(插入排序),首先我们写出关于数组的排序,然后可以在此基础上模拟出对于链表的排序:
点击(此处)折叠或打开
-
//对于数组的排序(默认为升序排列)
-
int i = 0;
-
int j = 0;
-
-
for(i = 0; i < length - 1; ++i){
-
for(j = i + 1; j < length; ++j){
-
if(array[i] > array[j]){
-
swap(&array[i], &array[j], sizeof(array[i]));
-
}
-
}
- }
点击(此处)折叠或打开
-
#define data_size(p_node) (sizeof(List_node) - sizeof(List_node *))
-
-
//链表的升序排列
-
void sort_list_ascend(List_head head) //链表的升序排序
-
{
-
List_node *p_node = NULL;
-
List_node *q_node = NULL;
-
-
//如果链表为空,没有元素或者只有一个元素,不进行排序操作
-
if(head == NULL || head->next == NULL
-
|| head->next->next == NULL){
-
return ;
-
}
-
-
for(p_node = head->next; p_node != NULL; p_node = p_node->next){
-
for(q_node = p_node->next; q_node != NULL; q_node = q_node->next){
-
if(p_node->data > q_node->data){
-
//只交换数据域
-
swap(p_node, q_node, data_size(p_node));
-
}
-
}
-
}
- }
9.降序排列链表
降序的处理方式和升序相同,代码如下所示:
点击(此处)折叠或打开
-
void sort_list_descend(List_head head) //链表的升序排序
-
{
-
List_node *p_node = NULL;
-
List_node *q_node = NULL;
-
-
//如果链表为空,没有元素或者只有一个元素,不进行排序操作
-
if(head == NULL || head->next == NULL
-
|| head->next->next == NULL){
-
return ;
-
}
-
-
for(p_node = head->next; p_node != NULL; p_node = p_node->next){
-
for(q_node = p_node->next; q_node != NULL; q_node = q_node->next){
-
if(p_node->data < q_node->data){
-
//只交换数据域
-
swap(p_node, q_node, data_size(p_node));
-
}
-
}
-
}
- }
10.得到链表的长度
关于链表的长度问题,采用以前的方式我们需要将链表从头到尾进行遍历,如果链表的长度过于长的话,这样O(n)的时间复杂度是我们无法能够承受的。可是如今的链表可以通过控制信息中的count成员再O(1)的时间复杂度得到。
点击(此处)折叠或打开
-
int get_list_count(List *list) //得到链表的长度
-
{
-
if(list == NULL){
-
return -1;
-
}
-
return list->count;
- }
11.合并两个已经排序的链表
要对两个已经排序的链表进行合并(以升序为例),其主要思想如下:
两个链表A、B的第一个节点信息都是当前链表中值最小的一个,我们定义出两个链表节点指针p、q分别指向链表的第一个节点,然后对两个指针所指向的节点内
容进行”大小的比较“,如果p所指向的节点的值小于q所指向的节点的值。那么我们就以p所指向的节点的值构造出结果链表的当前节点,然后让p向后移动,反
之q亦然。如果p和q其中某个指针提前遍历到末尾,则把对方的剩余部分尾部追加到结果链表中。代码如下所示:
点击(此处)折叠或打开
-
List *merge_two_lists(List *list1, List *list2) //合并两个已经排序的链表
-
{
-
List *list3 = NULL;
-
List_node *list1_move = NULL;
-
List_node *list2_move = NULL;
-
-
if(list1 == NULL || list2 == NULL){
-
return list3;
-
}
-
-
list3 = init_list();
-
-
list1_move = list1->head;
-
list2_move = list2->head;
-
-
//进行两个链表的比较,把较小的元素尾插到list3中
-
while(list1_move != NULL && list2_move != NULL){
-
if(list1_move->data < list2_move->data){
-
//如果list1_move指向的值小于list2_move,则尾插list1_move的值
-
push_back(list3, list1_move->data);
-
list1_move = list1_move->next;
-
}else{
-
push_back(list3, list2_move->data);
-
list2_move = list2_move->next;
-
}
-
}
-
-
//当两个链表中有任意一个遍历完成,则把另外一个链表整体尾插到list3
-
while(list2_move != NULL){
-
push_back(list3, list2_move->data);
-
list2_move = list2_move->next;
-
}
-
-
while(list1_move != NULL){
-
push_back(list3, list1_move->data);
-
list1_move = list1_move->next;
-
}
-
-
return list3;
- }
12.合并两个已经排序的链表(递归方式)
点击(此处)折叠或打开
-
static List_node *copy_list(List_node *src_list) //通过链表节点拷贝
-
{
-
List_node *result = NULL;
-
List_node *p_node = NULL;
-
List_node *q_node = NULL;
-
-
if(src_list == NULL){
-
return result;
-
}
-
-
//如果原链表存在进行拷贝
-
result = buy_node();
-
p_node = q_node = result;
-
result->data = src_list->data;
-
src_list = src_list->next;
-
-
while(src_list != NULL){
-
//如钩src指向不为空,生成一个节点并赋值,然后判断src的next
-
p_node = buy_node();
-
p_node->data = src_list->data;
-
q_node->next = p_node;
-
q_node = p_node;
-
src_list = src_list->next;
-
}
-
-
return result;
-
}
-
-
-
static List_node *merge_lists(List_node *list1_first,
-
List_node *list2_first){
-
List_node *result = NULL;
-
-
if(list1_first == NULL){
-
//如果list1链表归并结束,把list2剩余部分添加到结果链表的末尾
-
return copy_list(list2_first);
-
}else if(list2_first == NULL){
-
return copy_list(list1_first);
-
}
-
-
if(list1_first->data < list2_first->data){
-
result = buy_node();
-
result->data = list1_first->data;
-
result->next = merge_lists(list1_first->next, list2_first);
-
}else{
-
result = buy_node();
-
result->data = list2_first->data;
-
result->next = merge_lists(list1_first, list2_first->next);
-
}
-
return result;
-
}
-
-
List *merge_two_lists_recure(List *list1, List *list2) //合并两个已经排序的链表(递归方式)
-
{
-
List *list3 = NULL;
-
-
if(list1 == NULL && list2 == NULL){
-
return list3;
-
}
-
-
if(list1 == NULL){
-
return list3 = list_dup(list2);
-
}else if(list2 == NULL){
-
return list3 = list_dup(list1);
-
}
-
-
//对结果链表进行初始化
-
list3 = init_list();
-
-
list3->head = merge_lists(list1->head, list2->head);
-
list3->count = list1->count + list2->count;
-
list3->tail = find_revise_node(list3, 1); //找到倒数第一个节点赋值给tail
-
// printf("count:%d\n", list3->count);
-
return list3;
- }
在上述的程序中我们不仅要处理好链表节点的合并,还要对链表的控制信息进行更新,关于控制信息中链表的末尾节点。我们只能采取比较笨的方法,使用函数 find_revise_node找到链表末尾节点的地址,把它赋值给控制信息的tail元素。关于如何寻找链表的倒数第n个节点,我们在下一个接口进行 了实现。
13.得到链表的倒数第k个节点
寻找倒数第k个节点的方法,如果没有链表控制信息,我们只能采用较为传统的方式:
给定两个链表节点指针p和q,让他们指向链表的第一个节点位置,让其中一个指针p首先移动k个节点,然后让另外一个指针q和p在不同的起始位置一起向后移动,当p指针到到末尾的时候,此时q指针指向链表的倒数第k个节点(代码自己实现)。
在我们已经具有链表控制信息的时候,可以得知链表的总数为count,如果我们想要找到链表的倒数第k个节点的话,所需要移动的步长move_count与其之间的关系如下所示:
move_count = count - k; (总数 - 倒数第k个)点击(此处)折叠或打开
- List_node *find_revise_node(List *list, int count) //得到链表的倒数第k个节点
- {
- List_node *result = NULL;
- int move_count = 0;
- if(list == NULL || list->count == ZERO
- || count <= ZERO || count > list->count){
- return result;
- }
- // 9 1 (9 - 1) (list->count - count)
- move_count = list->count - count;
- result = list->head;
- while(move_count--){
- result = result->next;
- }
- return result;
- }
14.得到链表中间节点的位置
得到链表中间位置的方式和得到链表倒数第k个节点的方式非常的类似,依然给它两个指向链表第一个节点的指针p和q,此时采用快慢结合的方式,让p指 针每次移动一个节点,让q指针每次移动两个节点,二者依次进行上述的步骤,当q指针移动到链表的末尾时,p指针所指向的位置为链表的中点。
我们拥有链表的控制信息,那么在查找的时候更加方便,到达中间节点所需要移动的步长是count / 2 (也可以用位运算count >> 1)。
代码如下图所示:
点击(此处)折叠或打开
-
List_node *find_mid_node(List *list) //得到链表中间节点的位置
-
{
-
List_node *mid = NULL;
-
int move_count = 0;
-
-
if(list == NULL || list->count == ZERO){
-
return mid;
-
}
-
-
mid = list->head;
-
-
//找到链表中间节点
-
move_count = list->count >> 1; // 9 1001 --> 0100 4
-
// 12 1100 --> 0110 6
-
while(move_count--){
-
mid = mid->next;
-
}
-
return mid;
- }
15.反转链表
链表的反转我们需要借助多个指针配合操作,并且关于反转的操作分为以下几个情况:
1.如果链表只有一个节点,则不需要进行反转。
2.如果链表拥有两个节点,那么反转的操作相对简单。
3.如果链表拥有三个或三个以上的节点,则需要使用迭代的方式对链表进行反转。
点击(此处)折叠或打开
-
List *reverse_list(List *list) //反转链表
-
{
-
List_node *p = NULL;
-
List_node *q = NULL;
-
List_node *m = NULL;
-
-
-
//如果链表没有节点或者只有一个节点
-
if(list == NULL || list->count <= ONLY_ONE){
-
return list;
-
}
-
-
//两个节点
-
if(list->count == TWO){
-
list->tail->next = list->head;
-
list->head->next = NULL;
-
}else{
-
//三个或三个以上
-
p = list->head;
-
q = p->next;
-
m = q->next;
-
-
p->next = NULL;
-
do{
-
q->next = p;
-
p = q;
-
q = m;
-
m = m->next;
-
}while(m != NULL);
-
q->next = p;
-
}
-
swap(&(list->head), &(list->tail), sizeof(List_node *));
-
return list;
- }
可以看到倒数第二行有一个swap(&(list->head), &(list->tail), sizeof(List_node *));的操作。该函数可以交换任意类型的两个变量(被交换的双方必须是类型相同的),也就是实现了通用编程的思想。
swap函数的实现原型如下所示:
点击(此处)折叠或打开
-
static void swap(void *a, void *b, int length)
-
{
-
void *temp = Malloc(length);
-
//使用内存拷贝的方式交换a、b两个指针所指向的空间
-
memcpy(temp, a, length);
-
memcpy(a, b, length);
-
memcpy(b, temp, length);
-
free(temp);
- }
16.链表的拷贝
对于链表的拷贝我们并没有只是让一个指针指向已经存在的链表,虽然这样也可以达到对该链表的操作,一旦该链表不存在,其中的一个或多个指针将会指向一个不可存在的地址。所以我们需要实现深拷贝,也就是说要有真实存在的另一个链表,而不是只拥有简单的地址。
点击(此处)折叠或打开
-
static List_node *copy_list(List_node *src_list) //通过链表节点拷贝
-
{
-
List_node *result = NULL;
-
List_node *p_node = NULL;
-
List_node *q_node = NULL;
-
-
if(src_list == NULL){
-
return result;
-
}
-
-
//如果原链表存在进行拷贝
-
result = buy_node();
-
p_node = q_node = result;
-
result->data = src_list->data;
-
src_list = src_list->next;
-
-
while(src_list != NULL){
-
//如钩src指向不为空,生成一个节点并赋值,然后判断src的next
-
p_node = buy_node();
-
p_node->data = src_list->data;
-
q_node->next = p_node;
-
q_node = p_node;
-
src_list = src_list->next;
-
}
-
-
return result;
- }
17.逆序打印链表
看到这个问题,很多同学的第一个反映就是刚才我们已经实现了链表的翻转,将翻转的链表进行输出就仿佛是对链表进行了逆序的打印。但这违反了一个基本 的原则,就是未经允许不可对原数据结构进行修改。所以我们要转换思路,需要采用递归的策略,如果理解了函数的栈帧,那最后一次调用的函数(链表的末尾)执 行打印操作的时候就是在输出最后一个节点的数据信息,紧接着函数的栈帧进行销毁,重复上述的操作,即可把链表的数据信息进行逆序的输出。
点击(此处)折叠或打开
-
static void re_print_list(List_node *node)
-
{
-
// 12 20 25 30 NULL
-
//
-
if(node == NULL){
-
return ;
-
}else{
-
re_print_list(node->next);
-
printf("%d ", node->data);
-
}
-
}
-
-
void reverse_print_list(List *list) //逆序打印链表
-
{
-
if(list == NULL || list->count == ZERO){
-
return ;
-
}
-
-
re_print_list(list->head);
-
printf("\n");
- }
18.判断两个链表是否有交点
判断两个链表是否具有交点,我们需要知晓一旦两个链表A、B具有交点的话,那么A、B链表的最后一个节点一定相交。基于上述的理论我们得到如下的代码:
点击(此处)折叠或打开
-
Boolean is_list_intersect(List *list1, List *list2) //判断两个链表是否有交点
-
{
-
if(list1 == NULL || list2 == NULL
-
|| list1->count == ZERO || list2->count == ZERO){
-
return FALSE;
-
}
-
return list1->tail == list2->tail;
- }
19.找到两个链表的第一个交点
一旦我们确定了两个链表具有交点,在这样一个条件下想要求出第一个交点也是比较轻松的事情。基本的思想如下图所示:
图 链表第一个交点
代码如下所示:
点击(此处)折叠或打开
-
List_node *get_first_common_node(List *list1, List *list2) //找到两个链表的第一个交点
-
{
-
//1.先计算两个链表的长度,求出差值dis,让较长链表先移动dis距离;
-
//2.两个链表从相同长度开始依次向后移动,每移动一次比较两个指针是
-
//否相等,如果相等则该地址为两个链表的第一个交点。
-
int list1_len = 0;
-
int list2_len = 0;
-
int dis = 0;
-
List_node *p_node = NULL;
-
List_node *q_node = NULL;
-
-
//1.
-
if(list1 == NULL || list2 == NULL
-
|| list1->count == ZERO || list2->count == ZERO
-
|| is_list_intersect(list1, list2) == FALSE){
-
return NULL;
-
}
-
list1_len = list1->count;
-
list2_len = list2->count;
-
-
p_node = list1->head;
-
q_node = list2->head;
-
-
if(list1_len > list2_len){ //链表1比链表2长
-
dis = list1_len - list2_len;
-
while(dis--){
-
p_node = p_node->next;
-
}
-
}else{
-
dis = list2_len - list1_len;
-
while(dis--){
-
q_node = q_node->next;
-
}
-
}
-
-
//2.
-
while(p_node != q_node){
-
p_node = p_node->next;
-
q_node = q_node->next;
-
}
-
-
return p_node;
- }
20.在O(1)时间复杂度删除链表节点
删除一个链表节点按照之前的说法必须要从头到尾进行遍历,找到被删除节点的前一个节点,然后让前一个节点的指向越过被删除节点,指向被删除节点的后一个节点。
可是现在题目的要求需要我们想到更加巧妙的方式,具体的过程如下图所示:
图 删除节点
可是这样的问题是常规现象,如果要删除的节点是最后一个节点,那么最后一个节点的next指向为不可访问的地址。那么对其操作将会引发段错误,此时需要调用链表操作删除尾部节点(pop_back)。
点击(此处)折叠或打开
-
void delete_one_node(List *list, List_node *node) //在O(1)时间复杂度删除链表节点
-
{
-
List_node *p_node = NULL;
-
-
if(list == NULL || node == NULL){
-
return ;
-
}
-
-
if(node->next != NULL){ //该结点不在末尾
-
p_node = node->next;
-
node->data = p_node->data;
-
node->next = p_node->next;
-
free(p_node);
-
}else{
-
pop_back(list);
-
}
- }
21.判断链表是否有环
首先我们列举出链表右环的几种情况:
如果要判断一个链表是否有环,我们采用以下策略:
1.如果链表有环,末尾指针的指向一定是指向链表其中的一个节点形成闭路,这是一个环状结构。那么我们可以定义两个指针,一个快,一个慢。快指针每次移动
两个步长,慢指针每次移动一个步长。这样的话如果链表没有环状结构,一定是快指针率先走到链表的末尾,如果链表拥有环状结构,那么快慢指针最终会在链表的
环中进行追击运动,然后两者会在环中的某一个节点相遇。代码如下所示:
点击(此处)折叠或打开
-
Boolean has_circle(List *list, List_node **instersect)
-
{
-
List_node *slow = NULL;
-
List_node *fast = NULL;
-
-
if(list == NULL || list->count < TWO){
-
return FALSE;
-
}
-
-
slow = list->head;
-
fast = list->head;
-
-
//1.fast每次向后移动两个节点,slow每次移动一个节点
-
//
-
while(fast != NULL && fast->next != NULL){
-
fast = fast->next->next;
-
slow = slow->next;
-
-
if(fast == slow){ //链表有环,并且该点在环内
-
*intersect = fast;
-
return TRUE;
-
}
-
}
-
return FALSE;
- }
24.找到链表环的入口
在判断了链表是否有环后,我们需要取判断链表环的入口节点,这个直接进行判断是非常困难的,我们需要把问题进行转换。
转换的方式如下图所示:
链表环转换为相交问题
点击(此处)折叠或打开
-
List_node *find_circle_first_node2(List *list) //找到带环链表的环入口节点
-
{
-
List_node *intersect = NULL;
-
List_node *list2_head = NULL;
-
List_node *list1_head = NULL;
-
List_node *p_node = NULL;
-
List_node *q_node = NULL;
-
int list1_len = 1;
-
int list2_len = 1;
-
int dis = 0 ;
-
-
if(list == NULL || list->count < TWO
-
|| has_circle(list, &intersect) == FALSE){
-
return NULL;
-
}
-
-
//相交点的下一个点为list2的头结点,把求环入口点
-
//问题转化为求两个相交链表的第一个交点
-
p_node = list1_head = list->head;
-
q_node = list2_head = intersect->next;
-
-
-
//计算两个链表的长度
-
while(p_node != intersect){
-
list1_len++;
-
p_node = p_node->next;
-
}
-
-
while(q_node != intersect){
-
list2_len++;
-
q_node = q_node->next;
-
}
-
-
p_node = list1_head;
-
q_node = list2_head;
-
-
//让较长的链表先移动长度差值的步数
-
if(list1_len > list2_len){
-
dis = list1_len - list2_len;
-
while(dis--){
-
p_node = p_node->next;
-
}
-
}else{
-
dis = list2_len - list1_len;
-
while(dis--){
-
q_node = q_node->next;
-
}
-
}
-
-
while(p_node != q_node){
-
p_node = p_node->next;
-
q_node = q_node->next;
-
}
-
return p_node;
- }
接下来我们编写测试代码:
点击(此处)折叠或打开
-
//main.c
-
int main(int argc, char **argv)
-
{
-
List *list1 = NULL;
-
List *list2 = NULL;
-
List *list3 = NULL;
-
List_node *mid = NULL;
-
-
int i = 0;
-
-
list1 = init_list(); //链表的初始化(创建控制信息)
-
-
for(i = 0; i < 10; ++i){
-
push_front(list1, rand() % 100);
-
}
-
-
list2 = init_list(); //链表的初始化(创建控制信息)
-
-
for(i = 0; i < 10; ++i){
-
push_front(list2, rand() % 100);
-
}
-
-
printf("before sort:\n");
-
show_list(list1);
-
show_list(list2);
-
-
sort_list_ascend(list1);
-
sort_list_ascend(list2);
-
-
printf("after sort:\n");
-
show_list(list1);
-
show_list(list2);
-
-
printf("merge result:\n");
-
//list3 = merge_two_lists(list1, list2);
-
list3 = merge_two_lists_recure(list1, list2);
-
show_list(list3);
-
-
mid = find_mid_node(list1);
-
if(mid != NULL){
-
printf("list1 mid node value:%d\n", mid->data);
-
}
-
-
list3 = reverse_list(list3);
-
show_list(list3);
-
-
reverse_print_list(list3);
-
-
destroy_list(&list1); //链表的销毁
-
destroy_list(&list2);
-
destroy_list(&list3);
-
return 0;
- }
上述的22个面试题基本涵盖了链表的常见操作,希望可以给大家的笔试和面试带来帮助,另外欢迎大家能够给出更好的方案,可以共同探讨进步。