链表相关问题

来源:互联网 发布:身知内功 编辑:程序博客网 时间:2024/05/02 04:43

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

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

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 江西高考二本差5分上线怎么办 湖南文科考生二本上线差两分怎么办 在商场试鞋自己鞋子被偷怎么办 网易账号忘记密码更换手机了怎么办 一人在外地钱花光了怎么办 银行卡被公安冻结卡里的钱怎么办 在店铺不上班了押工资了怎么办? 在银座的押金拿不出来怎么办 唯品会退货时快递单号填错了怎么办 天猫店铺快递已发到不了怎么办 中邮消费贷没审核通过怎么办 房贷扣款日忘存钱了怎么办 农业银行卡输密码次数超限载怎么办 农业银行卡丢了不知道卡号怎么办 银行卡短信扣费失败怎么恢复怎么办 歌华有线遥控器点了tvav怎么办 一级注册结构延续注册晚了怎么办 在京东买的黄金项链买贵了怎么办 京东上面买的商品不符合实际怎么办 在京东买东西卖家迟迟不发货怎么办 淘宝商家买的货一直不到怎么办 国美买的格力空调发票丢了怎么办 国美实体店发票丢了怎么办 我在苏宁买的冰箱发票丢了怎么办 信用卡家庭电单位电话换了怎么办 常住户囗和实际住址没房子怎么办 退货淘宝极速退款 卖家拒收怎么办 淘宝退货极速退款后卖家拒收怎么办 蘑菇街付款成功怎么申请退款怎么办 淘宝上拍错地址联系卖家不管怎么办 收到货想退款卖家不同意怎么办 小米小店复合通过qq群怎么办 买了火车票在订单上查不到怎么办 用支付宝付款扣款没成功怎么办 在京东买东西只收到空盒子怎么办 魅族移动数据是灰色的怎么办? 魅族手机账号密码忘记了怎么办视频 淘宝购物退货把单号写错了怎么办 天猫想退货商家不给退货地址怎么办 天天爱消除四星宠物等级已满怎么办 店不干了店里面的财神怎么办