单链表经典面试题

来源:互联网 发布:老九门 网络销售价格 编辑:程序博客网 时间:2024/06/07 11:01

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

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;}





0 0
原创粉丝点击