链表是一种很常见的存储数据方式,链表有单向的、双向的、循环的,有头结点的,无头结点的等等,一般是看自己的需求去设计和实现这个链表。很多时候针对链表这种数据结构也会出现形形色色的面试题。比如常见的单链表反转,怎么判断两条单链表是否相交,怎么判断单链表是否有环存在,怎么快速取到单链表的倒数第n个节点等等这种。
链表的基本数据结构就不多说了。讲一下常见的两条链表判断是否相交的情况。
针对单链表特性要想知道链表中的某一个节点可能就得从表头开始遍历这样。这种特性使得有些时候的查找变得效率低下。通常的一些做法无非就是双指针,三指针,快慢指针这样的操作等等。题设两个单链表,长度分别为m和n,怎么快速判断这两个链表是否相交?
来一个最常见的单链表节点的数据结构:
typedef struct Node
{
int data;
struct Node *next;
}Node, *List;
一个数据成员变量,一个下一节点指针就构成了一个最基本的链表节点数据结构。可能还有一个单独的链表头,用来存储链表的一些特性数据等,如最基本的链表长度等等。那表头可能就会把数据的部分该位链表长度就可以了。那么针对这种数据结构,我们来设计一个简单的带头结点的链表再来说一下怎么去判断两个链表是否相交。
#include
#include
#include
typedef struct Node
{
int data;
struct Node *next;
}Node;
typedef struct head
{
int list_len;
struct Node* next;
}Head;
/*
尾插法创建带头链表
入参:链表头结点
返回值:带头结点的链表
*/
Head *tail_insert_create(Head *hd)
{
Head *tmp_hd = hd;
if ( !hd )
{
printf("hd is NULL\n");
return NULL;
}
int x,i;
Node *node = (Node*)malloc(sizeof(Node));
if(node == NULL)
{
printf("alloc fail\n");
return NULL;
}
Node *r = node;
scanf("%d", &x);
node->data = x;
node->next = NULL;
tmp_hd->next = node;
tmp_hd->list_len += 1;
for(i = 0; i < 4; i++)
{
scanf("%d", &x);
Node *p = (Node*)malloc(sizeof(Node));
p->data = x;
p->next = NULL;
r->next = p;
r = p;
tmp_hd->list_len += 1;
}
return hd;
}
void print_list(Head *hd)
{
Head *tmp = hd;
int i = 0;
if (hd == NULL)
{
printf("head node is NULL\n");
return ;
}
printf("list len = %d\n", tmp->list_len);
i = hd->list_len;
Node *tmp_node = hd->next;
for(i = 0; i < tmp->list_len; i++)
{
printf("%d\t", tmp_node->data);
tmp_node = tmp_node->next;
}
printf("\n");
}
int main(int argc, char **argv)
{
int i = 0;
Head *hd = NULL;
hd = (Head *)malloc(sizeof(Head));
if(hd == NULL)
{
printf("malloc fail\n");
return -1;
}
hd->list_len = 0;
hd->next = NULL;
tail_insert_create(hd);
print_list(hd);
return 0;
}
编译执行看结果:
因为尾插法更简单所以为了方便直接采用尾插法建立链表来测试。针对以上代码,功能ok。这是建立一条链表,增删查改可以自己去实现一下,道理相同。这样两条链表我们是可以创建出来,那么我们怎么去判断长度分表为m和n的链表是否相交呢?我们来看下这个链表的节点数据结构:
typedef struct Node
{
int data;
struct Node *next;
}Node;
一个数据变量,一个next指针。假设两条链表不等长且是有相交点的,把可能出现的情况列出来,列出来前首先明白一点,这两条链表绝对不可能出现“X”形状,next指针只有一个,同一个节点不可能指向两个不同的下一节点,所以如果相交,这两个单链表一定有一个共同的结尾,否则就会出现异常,也就是说最终一定会出现“Y”型(排除有环存在的情况)。
那么怎么快速找到这两个链表的相交的地方呢,两层循环去遍历肯定是可以的,但是时间复杂度肯定是有点高的。能不能假设这两个链表相交,那么相交的话情况肯定就确定了,尾部肯定是重合的部分,那么差异是不是就在头部了。那么突破点就找到了。两个指针分别指向两个链表的头节点,长度已知的情况下,让长度更长的那个指针先走|m-n|步,然后再判断下一节点和另外一个指针的下一节点是否相当即可得到结论。这种解法同样适用与求单链表的倒是第n个节点这种题目,两个指针指向头节点,其中一个先走n步,然后两个一起走,先走的走到尾部了,另外一个节点就是倒数第n个节点了。说回来,解法有了,来写个代码。
#include
#include
#include
typedef struct Node
{
int data;
struct Node *next;
}Node;
typedef struct head
{
int list_len;
struct Node* next;
}Head;
/*
尾插法创建带头链表
入参:链表头结点
返回值:带头结点的链表
*/
Head *tail_insert_create(Head *hd)
{
Head *tmp_hd = hd;
if ( !hd )
{
printf("hd is NULL\n");
return NULL;
}
int x,i;
Node *node = (Node*)malloc(sizeof(Node));
if(node == NULL)
{
printf("alloc fail\n");
return NULL;
}
Node *r = node;
scanf("%d", &x);
node->data = x;
node->next = NULL;
tmp_hd->next = node;
tmp_hd->list_len += 1;
for(i = 0; i < 4; i++)
{
scanf("%d", &x);
Node *p = (Node*)malloc(sizeof(Node));
p->data = x;
p->next = NULL;
r->next = p;
r = p;
tmp_hd->list_len += 1;
}
return hd;
}
/*
判断两链表是否相交
参数,a b链表表头以及对应的链表长度
返回值:0无相交 1相交
*/
int list_intersection_or_not(Head *a, int a_len, Head *b, int b_len)
{
if( !a || !b)
{
printf("list is NULL, not intersection\n");
return 0;
}
int i = 0;
int difference_value = (a_len > b_len) ? (a_len - b_len) : (b_len - a_len);
Node *tmpa = a->next, *tmpb = b->next;
if(difference_value == 0)
{
return !memcmp(tmpa, tmpb, sizeof(Node));
}
for(i = 0; i < difference_value; i++)
{
if (a_len > b_len)
{
tmpa = tmpa->next;
}
else
{
tmpb = tmpb->next;
}
}
while (tmpa && tmpb)
{
if ( 0 == memcmp(tmpa, tmpb, sizeof(Node)))
{
return 1;
}
tmpa = tmpa->next;
tmpb= tmpb->next;
}
return 0;
}
void print_list(Head *hd)
{
Head *tmp = hd;
int i = 0;
if (hd == NULL)
{
printf("head node is NULL\n");
return ;
}
printf("list len = %d\n", tmp->list_len);
i = hd->list_len;
Node *tmp_node = hd->next;
for(i = 0; i < tmp->list_len; i++)
{
printf("%d\t", tmp_node->data);
tmp_node = tmp_node->next;
}
printf("\n");
}
int main(int argc, char **argv)
{
int i = 0;
Head *hd1 = NULL;
hd1 = (Head *)malloc(sizeof(Head));
if(hd1 == NULL)
{
printf("malloc fail\n");
return -1;
}
hd1->list_len = 0;
hd1->next = NULL;
Head *hd2 = NULL;
hd2 = (Head *)malloc(sizeof(Head));
if(hd2 == NULL)
{
printf("malloc fail\n");
return -1;
}
hd2->list_len = 0;
hd2->next = NULL;
tail_insert_create(hd1);
tail_insert_create(hd2);
print_list(hd1);
print_list(hd2);
printf("ret = %d\n", list_intersection_or_not(hd1, hd1->list_len, hd2, hd2->list_len));
return 0;
}
测试一下,建立两条链表,肯定是不相交的,这里即使我输入的data是相同的也是不相交的:
结果是如此的,因为这两条链表创建的时候都是重新申请的内存,所以肯定不存在相交的地方的,那我们就手动构造一个相交再来测试吧
#include
#include
#include
typedef struct Node
{
int data;
struct Node *next;
}Node;
typedef struct head
{
int list_len;
struct Node* next;
}Head;
/*
尾插法创建带头链表
入参:链表头结点
返回值:带头结点的链表
*/
Head *tail_insert_create(Head *hd)
{
Head *tmp_hd = hd;
if ( !hd )
{
printf("hd is NULL\n");
return NULL;
}
int x,i;
Node *node = (Node*)malloc(sizeof(Node));
if(node == NULL)
{
printf("alloc fail\n");
return NULL;
}
Node *r = node;
scanf("%d", &x);
node->data = x;
node->next = NULL;
tmp_hd->next = node;
tmp_hd->list_len += 1;
for(i = 0; i < 4; i++)
{
scanf("%d", &x);
Node *p = (Node*)malloc(sizeof(Node));
p->data = x;
p->next = NULL;
r->next = p;
r = p;
tmp_hd->list_len += 1;
}
return hd;
}
/*
判断两链表是否相交
参数,a b链表表头以及对应的链表长度
返回值:0无相交 1相交
*/
int list_intersection_or_not(Head *a, int a_len, Head *b, int b_len)
{
if( !a || !b)
{
printf("list is NULL, not intersection\n");
return 0;
}
int i = 0;
int difference_value = (a_len > b_len) ? (a_len - b_len) : (b_len - a_len);
Node *tmpa = a->next, *tmpb = b->next;
if(difference_value == 0)
{
return !memcmp(tmpa, tmpb, sizeof(Node));
}
for(i = 0; i < difference_value; i++)
{
if (a_len > b_len)
{
tmpa = tmpa->next;
}
else
{
tmpb = tmpb->next;
}
}
return !memcmp(tmpa, tmpb, sizeof(Node));
}
void print_list(Head *hd)
{
Head *tmp = hd;
int i = 0;
if (hd == NULL)
{
printf("head node is NULL\n");
return ;
}
printf("list len = %d\n", tmp->list_len);
i = hd->list_len;
Node *tmp_node = hd->next;
for(i = 0; i < tmp->list_len; i++)
{
printf("%d\t", tmp_node->data);
tmp_node = tmp_node->next;
}
printf("\n");
}
int main(int argc, char **argv)
{
int i = 0;
Head *hd1 = NULL;
hd1 = (Head *)malloc(sizeof(Head));
if(hd1 == NULL)
{
printf("malloc fail\n");
return -1;
}
hd1->list_len = 0;
hd1->next = NULL;
Head *hd2 = NULL;
hd2 = (Head *)malloc(sizeof(Head));
if(hd2 == NULL)
{
printf("malloc fail\n");
return -1;
}
hd2->list_len = 0;
hd2->next = NULL;
tail_insert_create(hd1);
Node *tmp = hd1->next; //tmp指向hd1的第一个数据节点
tmp = tmp->next; //第二个数据节点
hd2->next = tmp->next; //hd2的下一个节点指向hd1的第3个节点
hd2->list_len = 3; //已知hd1长度为5的情况下,手动计算hd2长度为3
print_list(hd1);
print_list(hd2);
printf("ret = %d\n", list_intersection_or_not(hd1, hd1->list_len, hd2, hd2->list_len));
return 0;
}
编译执行:
上述代码将第二链表指向了第一个创建好的链表的第三个数据节点位置,所以二者肯定是有相交节点的。
执行结果返回值为1,得出结果,所以针对这种“Y”型的两条单链表就可以这么去判断是否相交,时间复杂度只有O(n)。
以上内容如果错误还请指出,共同学习,共同进步!