您的位置:首页 > 职场人生

单链表经典面试题

2015-10-13 21:57 441 查看

    面试官通过考察应聘者的编程语言、数据结构和算法来判断应聘者是否具备扎实的基础知识。而单链表又是数据结构中最基本的知识。

1
单链表总结

单链表的使用总是围绕着增、删、改、查结点等。其中,增加结点是最常用到的。归纳一下,常见有下面几种情况:
a.在首结点之前插入新结点
b.在尾结点之前插入新结点
c.在某个中间结点之前插入新结点
d.在某个中间结点之后插入新结点
/*链表结构定义*/
typedef struct list_node
{
int data;
struct list_node *next;
}list_node_t, link_list_t;1.1在首结点p之前插入s(头插法),且p一直作为首结点:(结点p作为一种游标)

操作:
s->next = p;
p = s;


示例:采用头插法创建不带头结点的单链表。
int link_list_create(link_list_t **link_list, int n)
{
int i = 0;
list_node_t *node = NULL;/*新生成的结点*/
list_node_t *cursor = NULL;/*游标结点*/

/*分配第一个结点*/
cursor = (list_node_t *)malloc(sizeof(list_node_t));
if (cursor == NULL) return -1;

cursor->next = NULL;

/*让*link_list指向第一个结点*/
*link_list = cursor;

/*分配第2个结点至第n个结点*/
for (i = 1; i < n; i++)
{
node = (list_node_t *)malloc(sizeof(list_node_t));
if (node == NULL) return -1;

/*头插法*/
node->next = cursor;
cursor = node;
}

return 0;
}

1.2在尾结点p之后插入s(尾插法),且p一直作为尾结点:(结点p作为一种游标)
操作:
p->next = s;
p = s;

示例:采用尾插法创建不带头结点的单链表。
int link_list_create(link_list_t **link_list, int n)
{
int i = 0;
list_node_t *node = NULL;/*新生成的结点*/
list_node_t *cursor = NULL;/*游标结点*/

/*分配第一个结点*/
cursor = (list_node_t *)malloc(sizeof(list_node_t));
if (cursor == NULL) return -1;

cursor->next = NULL;

/*让*link_list指向第一个结点*/
*link_list = cursor;

/*分配第2个结点至第n个结点*/
for (i = 1; i < n; i++)
{
node = (list_node_t *)malloc(sizeof(list_node_t));
if (node == NULL) return -1;
node->next = NULL;
/*尾插法*/
cursor->next = node;
cursor = node;
}

return 0;
}

1.3在结点p之后插入s:(结点p只是链表中的任意一个结点)
操作:
s->next = p->next;
p->next = s;

示例:在第i个位置上插入新结点,即在第i-1位置之后插入新结点
/*在第i个位置上插入新结点,i的范围为[1, list_len+1]*/
int link_list_insert(link_list_t **link_list, int i, int data)
{
int j = 1;
list_node_t *s = NULL;
list_node_t *p = *link_list;

if (*link_list == NULL || i < 0)
{
return -1;
}

/*
*在第1个位置插入新结点,使得该结点成为第1个位置的结点
*对插入到第一个位置的结点需要特殊处理,这是不带头结点的缺点
*/
if (i == 1)
{
s = (list_node_t *)malloc(sizeof(list_node_t));
if (s == NULL) return -1;

s->data = data;
/*插入到第1个位置*/
s->next = *link_list;
*link_list = s;
}
else
{
/*找到第i-1个位置上的结点p*/
while (p && j < i - 1)
{
p = p->next;
j++;
}

/*i大于链表的长度+1*/
if (!p)
{
return -1;
}

s = (list_node_t *)malloc(sizeof(list_node_t));
if (s == NULL) return -1;

s->data = data;
s->next = NULL;

/*在结点p之后插入新结点s*/
s->next = p->next;
p->next = s;
}

return 0;
}


1.4在节点p之前插入s:(结点p只是链表中的任意一个结点)
操作:
s->next = p;/*s的下一个结点为p*/
*last = s; /*修改q->next的值为s,即q的下一个结点为s*/
说明:
P = q->next;/*假设p的前一个结点为q*/
node_t **last;/*声明二级指针last,指向p的前一个结点q的指针域next的地址*/
last = &q->next;


示例:
/*在第i个位置之前插入新结点,i的范围为[1, list_len]*/
int link_list_insert1(link_list_t **link_list, int i, int data)
{
int j = 0;
list_node_t *s = NULL;
list_node_t *p = NULL;
list_node_t **last = NULL;

if (*link_list == NULL || i < 0)
{
return -1;
}

last = link_list;
p = *last;

/*找到第i个位置上的结点p*/
while (p && j < i - 1)
{
last = &p->next;/*last为p的前一个结点的指针域的地址*/
p = p->next;
j++;
}

/*i大于链表的长度*/
if (!p)
{
return -1;
}

/*生成新的结点s*/
s = (list_node_t *)malloc(sizeof(list_node_t));
if (s == NULL) return -1;

s->data = data;

/*在第i个位置之前插入s*/
s->next = p;
*last = s;

return 0;
}


2
单链表面试题

2.1求单链表中结点的个数
int list_node_num(link_list_t *link_list)
{
int num = 0;
list_node_t *node = link_list;

while (node)
{
++num;
node = node->next;
}

return num;
}

2.2将单链表反转(即逆序排列)
/*功能:将单链表逆序
*说明:
* 遍历原单链表,每遍历一个结点,则将其摘除,并
* 用头插法的方式,插入到新链表的头部。
*注意: 链表为空或只有一个结点的情况
*/
link_list_t *link_list_reverse(link_list_t *link_list)
{
list_node_t *next = NULL;
link_list_t *link_rev_head = NULL;/*逆序链表的首结点*/
list_node_t *node = link_list; /*原链表的第一个结点*/

/*包含了原链表为空或只有一个结点的处理*/
while (node)
{
next = node->next;/*摘除结点node*/
node->next = link_rev_head;/*头插法,插入到新链表的头部*/
link_rev_head = node;
node = next; /*下一个结点*/
}

return link_rev_head;
}

2.3查找单链表中倒数第K个结点(K>0)
/*功能:查找单链表中倒数第K个结点(k > 0)
*
*思路:
* 使用两个指针,第一个指针先走K-1步,然后两个指针一起走。
* 当第一个指针走到尾结点的时候,第二个指针指向的就是倒数
* 第K个结点。
* 即: 正数第K个结点到末尾结点的距离等于
* 第1个结点到倒数第K个结点的距离。
*
*注意: 链表为空的情况、K小于1或大于链表长度的情况
*
*/
list_node_t *link_list_find_backward(link_list_t *link_list, int k)
{
int i = 0;
list_node_t *node = link_list;
list_node_t *k_node = link_list;

/*输入错误处理*/
if (link_list == NULL)
{
return NULL;
}

/*查找链表第K个结点*/
while (node && --k)
{
node = node->next;
}

/*k大于链表长度或小于1*/
if (!node || k < 0)
{
return NULL;
}

/*查找链表倒数第K个结点*/
while (node->next)
{
node = node->next;
k_node = k_node->next;
}

return k_node;
}


2.4查找单链表的中间结点
/*功能: 查找单链表的中间结点
*思路: 与查找倒数第K个结点类似,两个问题一起问时,考察学习迁移能力。
* 使用两个指针,快指针每次走两步,慢指针每次走一步。
* 当快指针走到尾结点时,慢指针刚好走到中间结点
*
*注意: 链表为空或只有一个或两个结点的情况
*/
list_node_t *link_list_find_middle(link_list_t *link_list)
{
list_node_t *fast_node = link_list;/*快指针,走两步*/
list_node_t *slow_node = link_list;/*慢指针,走一步*/

/*没有结点或只有一个结点*/
if (link_list == NULL || link_list->next == NULL)
{
return link_list;
}

/*保证fast_node是最后那个结点*/
while (fast_node->next)
{
/*fast_node走两步*/
fast_node = fast_node->next;
if (fast_node->next)
{
fast_node = fast_node->next;
}
/*slow_node走一步*/
slow_node = slow_node->next;
}

return slow_node;
}


2.5从尾到头打印单链表
/*功能:从尾到头打印单链表
*思路:
* 颠倒顺序的问题,一般利用递归,让系统使用栈。
*
*/
void link_list_print_reverse(link_list_t *link_list)
{
if (link_list == NULL)
{
return ;
}
else
{
link_list_print_reverse(link_list->next);
printf("%d ", link_list->data);
}

return ;
}

2.6将有序的两个单链表合并成一个新的依然有序的链表
link_list_t *link_list_merge(link_list_t *a_link_list, link_list_t *b_link_list)
{
link_list_t *link_list = NULL;/*新链表*/
list_node_t *node = NULL;/*游标结点*/

/*对新链表的第一个结点的特殊处理*/
if (a_link_list->data <= b_link_list->data)
{
link_list = a_link_list;
a_link_list = a_link_list->next;
}
else
{
link_list = b_link_list;
b_link_list = b_link_list->next;
}

node = link_list;
node->next = NULL;

while (a_link_list && b_link_list)
{
if (a_link_list->data <= b_link_list->data)
{
node->next = a_link_list;
a_link_list = a_link_list->next;
}
else
{
node->next = b_link_list;
b_link_list = b_link_list->next;
}
node = node->next;
node->next = NULL;
}

/*插入剩余的链表段*/
node->next = (a_link_list ? a_link_list : b_link_list);

return link_list;
}


2.7判断一个单链表是否有环,若有环,求出进入环中的第一个结点
/*功能: 单链表是否存在环,若存在,则求进入环的第一个结点
*思路:
*1 判断是否有环:
* 使用两个指针,一个走两步,一个走一步。如果有环,
* 则将在环中的的某个结点相遇。
*2 求进入环的第一个结点:
* 碰撞点到连接点的距离等于第一个结点到连接点的距离。
*
*注意:链表为空或只有一个结点(有环或没环)的情况,
*/
list_node_t *link_list_loop_node(link_list_t *link_list)
{
list_node_t *fast_node = link_list;
list_node_t *slow_node = link_list;

/*链表为空*/
if (link_list == NULL)
{
return NULL;
}

/*判断是否有环*/
while (fast_node && fast_node->next)
{
fast_node = fast_node->next->next;
slow_node = slow_node->next;
if (fast_node == slow_node)
{
/*存在环,此时fast_node为碰撞点*/
break;
}
}

/*不存在环*/
if (fast_node == NULL || fast_node->next == NULL)
{
return NULL;
}

/*求出进入环的第一个结点*/
slow_node = link_list;
while (slow_node != fast_node)
{
slow_node = slow_node->next;
fast_node = fast_node->next;
}

return slow_node;
}


2.8判断两个单链表是否相交,若相交,求出相交的第一个结点
/*功能:判断两个单链表是否相交,若相交,则求相交的第一个结点
*思路:
*1 判断是否相交: 两个单链表相交,则最后的结点相同
*2 求相交的第一个结点:
* 计算链表1的长度LEN1,链表2的长度LEN2,计算长度差LEN = |LEN1 - LEN2|
* 长度较长的链表先遍历LEN个结点,接着两个链表到第一个结点的长度
* 就相同了,再同时遍历。
*/
list_node_t *link_list_intersect(link_list_t *a_link_list, link_list_t *b_link_list)
{
int len = 0;
int a_len = 0;
int b_len = 0;
list_node_t *node = NULL;
list_node_t *a_node = a_link_list;
list_node_t *b_node = b_link_list;

if (a_link_list == NULL || b_link_list == NULL)
{
return NULL;
}

while (a_node->next)
{
a_node = a_node->next;
a_len++;
}

while (b_node->next)
{
b_node = b_node->next;
b_len++;
}

/*链表不相交则直接返回*/
if (a_node != b_node)
{
return NULL;
}

a_node = a_link_list;
b_node = b_link_list;

/*长度较长的链表先遍历到与较短的链表等长*/
if (a_len > b_len)
{
len = a_len - b_len;
while (len--)
{
a_node = a_node->next;
}
}
else
{
len = b_len - a_len;
while (len--)
{
b_node = b_node->next;
}
}

/*等长情况下,两个链表同时遍历,结点相同处便是相交点*/
while (a_node != b_node)
{
a_node = a_node->next;
b_node = b_node->next;
}

return a_node;
}




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: