链表相关问题

求有环单链表中的环长、环起点、链表长

1.判断单链表是否有环

  使用两个slow, fast指针从头开始扫描链表。指针slow 每次走1步,指针fast每次走2步。如果存在环,则指针slow、fast会相遇;如果不存在环,指针fast遇到NULL退出。

  就是所谓的追击相遇问题:

    

2.求有环单链表的环长

   在环上相遇后,记录第一次相遇点为Pos,之后指针slow继续每次走1步,fast每次走2步。在下次相遇的时候fast比slow正好又多走了一圈,也就是多走的距离等于环长。

  设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2*len步,相遇时多走了一圈:

    环长=2*len-len。

3.求有环单链表的环连接点位置

  第一次碰撞点Pos到连接点Join的距离=头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。

     

  在环上相遇后,记录第一次相遇点为Pos,连接点为Join,假设头结点到连接点的长度为LenA,连接点到第一次相遇点的长度为x,环长为R

    第一次相遇时,slow走的长度 S = LenA + x;

    第一次相遇时,fast走的长度 2S = LenA + n*x;

    所以可以知道,LenA + x =  n*R;  LenA = n*R -x;

4.求有环单链表的链表长

   上述2中求出了环的长度;3中求出了连接点的位置,就可以求出头结点到连接点的长度。两者相加就是链表的长度。

 

编程实现:

  下面是代码中的例子:

  

  具体代码如下:

#include <stdio.h>
#include <stdlib.h>
typedef struct node{
    int value;
    struct node *next;
}LinkNode,*Linklist;

/// 创建链表(链表长度,环节点起始位置)
Linklist createList(){
    Linklist head = NULL;
    LinkNode *preNode = head;
    LinkNode *FifthNode = NULL;
    for(int i=0;i<6;i++){
        LinkNode *tt = (LinkNode*)malloc(sizeof(LinkNode));
        tt->value = i;
        tt->next = NULL;
        if(preNode == NULL){
            head = tt;
            preNode = head;
        }
        else{
            preNode->next =tt;
            preNode = tt;
        }

        if(i == 3)
            FifthNode = tt;
    }
    preNode->next = FifthNode;
    return head;
}

///判断链表是否有环
LinkNode* judgeRing(Linklist list){
    LinkNode *fast = list;
    LinkNode *slow = list;

    if(list == NULL)
        return NULL;

    while(true){
        if(slow->next != NULL && fast->next != NULL && fast->next->next != NULL){
            slow = slow->next;
            fast = fast->next->next;
        }
        else
            return NULL;

        if(fast == slow)
            return fast;
    }
}

///获取链表环长
int getRingLength(LinkNode *meetNode){
    int RingLength=0;
    LinkNode *fast = meetNode;
    LinkNode *slow = meetNode;
    for(;;){
        fast = fast->next->next;
        slow = slow->next;
        RingLength++;
        if(fast == slow)
            break;
    }
    return RingLength;
}

///获取链表头到环连接点的长度
int getLenA(Linklist list,LinkNode *meetNode){
    int lenA=0;
    LinkNode *fast = list;
    LinkNode *slow = meetNode;
    for(;;){
        fast = fast->next;
        slow = slow->next;
        lenA++;
        if(fast == slow)
            break;
    }
    return lenA;
}

///释放空间
int freeMalloc(Linklist list){
    LinkNode *nextnode = NULL;
    while(list != NULL){
        nextnode = list->next;
        free(list);
        list = nextnode;
    }
}

int main(){
    Linklist list = NULL;
    LinkNode *meetNode = NULL;
    int RingLength = 0;
    int LenA = 0;

    list = createList();
    meetNode = judgeRing(list);

    if(meetNode == NULL)
        printf("No Ring\n");
    else{
        printf("Have Ring\n");
        RingLength = getRingLength(meetNode);
        LenA = getLenA(list,meetNode);

        printf("RingLength:%d\n",RingLength);
        printf("LenA:%d\n",LenA);
        printf("listLength=%d\n",RingLength+LenA);

        freeMalloc(list);
    }
    return 0;
}



执行结果:

http://www.cnblogs.com/xudong-bupt/p/3667729.html

http://blog.csdn.net/doufei_ccst/article/details/10578315

5.如果存在环,求出环上距离任意一个节点最远的点(对面节点);

问题5是,求出环上距离任意一个节点最远的点(对面节点)


如下图所示,点1和4、点2和5、点3和6分别互为”对面节点“ ,也就是换上最远的点,我们的要求是怎么求出换上任意一个点的最远点。



对于换上任意的一个点ptr0, 我们要找到它的”对面点“,可以这样思考:同样使用上面的快慢指针的方法,让slow和fast都指向ptr0,每一步都执行与上面相同的操作(slow每次跳一步,fast每次跳两步),

当fast = ptr0或者fast = prt0->next的时候slow所指向的节点就是ptr0的”对面节点“。

为什么是这样呢?我们可以这样分析:



如上图,我们想像一下,把环从ptro处展开,展开后可以是无限长的(如上在6后重复前面的内容)如上图。

现在问题就简单了,由于slow移动的距离永远是fast的一般,因此当fast遍历玩整个环长度r个节点的时候slow正好遍历了r/2个节点,

也就是说,此时正好指向距离ptr0最远的点。



https://github.com/hit9/oldblog/blob/gh-pages/blog-src/blog/C/posts/25.mkd#9%E6%89%BE%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4%E7%9A%84%E4%BA%A4%E7%82%B9


这里处理的全部是单链表:

typedef struct node {
    char *data; 
    struct node *next; 
} node_t;

我们约定一个打印链表的函数:

void list_display(node_t *head)
{
    for (; head; head = head->next)
        printf("%s ", head->data);
    printf("\n");
}

下面是几个常见的链表笔面问题:

[TOC]

1.计算链表长度

很简单:(复杂度O(n))

int list_len(node_t *head)
{
    int i; 
    for (i = 0; head; head = head->next, i++); 
    return i; 
}

测试:

int main(int argc, const char *argv[])
{
    node_t d = {"d", 0}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
    printf("%d\n", list_len(&a));//4
    return 0;
}

2.反转链表

我们多用几个指针就可以在O(n)完成反转任务:

算法:t遍历链表, q记录t的上一个结点, p是一个临时变量用来缓存t的值

void reverse(node_t *head)
{
    node_t *p = 0, *q = 0, *t = 0; 
    for (t = head; t; p = t, t = t->next, p->next = q, q = p); 
}

测试:

node_t d = {"d", 0}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
list_display(&a); 
reverse(&a); 
list_display(&d); 

3.查找倒数第k个元素(尾结点记为倒数第0个)

算法:2个指针p, q初始化指向头结点.p先跑到k结点处, 然后q再开始跑, 当p跑到最后跑到尾巴时, q正好到达倒数第k个.复杂度O(n)

node_t *_kth(node_t *head, int k)
{
    int i = 0; 
    node_t *p = head, *q = head; 
    for (; p && i < k; p = p->next, i++); 
    if (i < k) return 0;
    for (; p->next; p = p->next, q = q->next); 
    return q; 
}

测试:

node_t d = {"d", 0}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
printf("_0 :%s _1: %s _2:%s _3:%s\n", _kth(&a, 0)->data, _kth(&a, 1)->data, _kth(&a, 2)->data, _kth(&a, 3)->data);

输出:

_0 :d _1: c _2:b _3:a

4.查找中间结点

找出中间的那个结点

算法:设两个初始化指向头结点的指针p, q.p每次前进两个结点, q每次前进一个结点, 这样当p到达链表尾巴的时候, q到达了中间.复杂度O(n)

node_t *middle(node_t *head)
{
    node_t *p, *q; 
    for (p = q = head; p->next; p = p->next, q = q->next){
        p = p->next; 
        if (!(p->next)) break; 
    }
    return q; 
}

测试:

node_t e = {"e", 0}, d = {"d", &e}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
printf("%s\n", middle(&a)->data);

5.逆序打印链表

给你链表的头结点, 逆序打印这个链表.使用递归(即让系统使用栈), 时间复杂度O(n)

void r_display(node_t *t)
{
    if (t){
        r_display(t->next); 
        printf("%s", t->data);
    }
}

6.判断一个链表是否有环

如果一个链表有环, 那么它肯定只有一个环.(一个相交结点)

算法:设两个指针p, q, 初始化指向头.p以步长2的速度向前跑, q的步长是1.这样, 如果链表不存在环, p和q肯定不会相遇.如果存在环, p和q一定会相遇.(就像两个速度不同的汽车在一个环上跑绝对会相遇).复杂度O(n)

int any_ring(node_t *head)
{
    node_t *p, *q; 
    for (p = q = head;p; p = p->next, q = q->next){
        p = p->next; 
        if (!p) break; 
        if (p == q) return 1; //yes
    }
    return 0; //fail find
}

测试:

node_t e = {"e", 0}, d = {"d", &e}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
e.next = &a; 
printf("%d\n", any_ring(&a));

7.找出链表中环的入口结点

还是使用俩指针p和q, p扫描的步长为1, q扫描的步长为2.它们的相遇点为图中meet处(在环上).

假设头指针head到入口点entry之间的距离是K.则当q入环的时候, p已经领先了q为: d = K%n(n为环的周长).

我们设meet处相对entry的距离(沿行进方向)为x, 则有

(n-d)+x = 2x (p行进的路程是q的两倍)

解得x = n-d

那么当p和q在meet处相遇的时候, 从head处再发出一个步长为1的指针r, 可以知道, r和q会在entry处相遇!

算法就是:

初始化三个指针p, q, r全部指向head. 然后p以2的速度行进, q以1的速度行进.当p和q相遇的时候, 发出r指针并以1的速度行进, 当p和r相遇返回这个结点.复杂度O(n)

代码:

node_t *find_entry(node_t *head)
{
    node_t *p, *q, *r; 

    for (p = q = head; p; p = p->next, q = q->next){
        p = p->next; 
        if (!p) break; 
        if (p == q) break; 
    }

    if (!p) return 0; //no ring in list

    for (r = head, q = q->next; q != r; r = r->next, q = q->next); 

    return r; 
}

测试:

node_t e = {"e", 0}, d = {"d", &e}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
e.next = &d; 
printf("%s\n", find_entry(&a)->data);

8.判断两个单链表是否相交

算法:两个指针遍历这两个链表,如果他们的尾结点相同,则必定相交.复杂度O(m+n)

代码实现:

int is_intersect(node_t *a, node_t *b)
{
    if (!a || !b) return -1; //a or b is NULL
    for (; a->next; a = a->next); 
    for (; b->next; b = b->next); 
    return a == b?1:0; //return 1 for yes, 0 for no
}

测试代码 :

node_t e = {"e", 0}, d = {"d", &e}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
node_t z = {"z", &c}, y = {"y", &z}, x = {"x", &y}; 
printf("%d\n", is_intersect(&a, &x));

9.找两个链表相交的交点

假设两个链表a,b.a比b长k个结点(k>=0).

那么当a_ptr,b_ptr两个指针同时分别遍历a,b的时候, 必然b_ptr先到达结尾(NULL),而此时a_ptr落后a的尾巴k个结点.

如果此时再从a的头发出一个指针t,继续和a_ptr 一起走,当a_ptr达到结尾(NULL)时,t恰好走了k个结点.此时从b的头发一个指针s, s和t一起走,因为a比b长k个结点,所以,t和s会一起到达交点.

算法便是:

p,q分别遍历链表a,b,假设q先到达NULL,此时从a的头发出一个指针t,当p到达NULL时,从b的头发出s,当s==t的时候即交点.

代码实现: (注,当a,b不相交,函数返回0,即相交在NULL)

node_t *intersect_point(node_t *a, node_t *b)
{
    node_t *p, *q, *k, *t, *s; 
    for (p = a, q = b; p && q; p = p->next, q = q->next); 

    k = (p == 0)?q:p; //k record the pointer not NULL
    t = (p == 0)?b:a; //if p arrive at tail first, t = b ; else p = a
    s = (p == 0)?a:b; 
    for (; k; k = k->next, t = t->next); 
    for (; t != s; t = t->next, s = s->next); 
    return t; 
}

测试

node_t e = {"e", 0}, d = {"d", &e}, c = {"c", &d}, b = {"b", &c}, a = {"a", &b}; 
node_t z = {"z", &b}, y = {"y", &z}, x = {"x", &y}; 
printf("%s\n", intersect_point(&a, &x)->data);

10.O(1)删除结点(不给头结点)

其实我很反对这个做法.

不给头结点的时候怎么删除一个结点d:

把d的下一个结点e的数据拷贝到d中,然后删除e

我认为这是个伪删除,并且这个算法无法处理d是最后一个结点的情况

代码实现:

node_t *delete(node_t *d) 
{
    node_t *e = d->next; 
    d->data = e->data; 
    d->next = e->next; 
}

11.两个链表右对齐打印

为了打印的整齐性,我们把结点存储的数据类型改为int(我们存放数字)

比如两个链表1->2->3->4->56->7->8,我们想要打印这种效果:

1 2 3 4 5 
    6 7 8 

算法:

p和q两个指针分别遍历链表a和b,假如q先到达0(即a比b长),此时由a头发出t,打印完链表a.
p继续移动到0,并打印空格.
从b头发出指针s打印链表b

代码:

void foo(node_t *a, node_t *b)
{
    node_t *p, *q, *k, *t, *s; 
    for (p = a, q = b; p && q; p = p->next, q = q->next); 

    k = p?p:q; 
    t = p?a:b; 
    s = p?b:a; 

    for (; t; printf("%d ", t->data), t = t->next); 
    printf("\n");
    for (; k; printf("  "), k = k->next); 
    for (; s; printf("%d ", s->data), s = s->next); 
}

测试:

node_t e = {5, 0}, d = {4, &e}, c = {3, &d}, b = {2, &c}, a = {1, &b}; 
node_t  o = {8, 0}, n = {7, &o}, m = {6, &n}; 
foo(&a, &m); 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值