数据结构第三节——链表(2)

数据结构第三节——链表(2)

今天(嘶,昨天忘记发了)继续链表,嘤嘤嘤…

今天尝试加入“分析”栏帮助梳理思路。


二、合并链表

先复习一下昨天那令人悲伤的例2 T_T( 不抄题了阿 )

分析

首先读入两个链表。

使用Merge函数将两个链表中的数据合并成一个链表,且保证新链表从小到大依次排列。

为此使用归并方法定义Merge()函数。

最后输出合并后的链表。

提交
#include <bits/stdc++.h>
using namespace std;
const int N = 100000;
struct Node {
    int value;
    Node *next;
} a[N+10], b[N+10] ,*h1, *h2, *t1, *t2;
int n,m;

Node* Merge( Node *h1, Node *h2 ){
    Node *h = NULL;
    Node *t = NULL;
    while( h1 || h2 ){
        if ( h1 && ( h2 == NULL || ( h1 -> value < h2 -> value )) ){ //保证h1存在
            if ( h == NULL ){
                h = h1;
                t = h1;
            }else{
                t -> next = h1;
                t = h1;
            }
            h1 = h1 -> next;               //每次存入后记得 h1 —> next
        }else{
            if ( h == NULL ){
                h = h2;
                t = h2;
            }else{
                t -> next = h2;
                t = h2;
            }
            h2 = h2 -> next;
        }

    }
    return h;
}
int main(){
    scanf("%d %d", &n, &m);
    for ( int i = 1; i <= n; i++ ){
        scanf("%d", &a[i].value );
        if( h1 == NULL ){
            h1 = &a[i];
            t1 = &a[i];
        }else{
            t1 -> next = &a[i];
            t1 = &a[i];
        }
    }
    for ( int i = 1; i <= m; i++ ){
        scanf("%d", &b[i].value );
        if( h2 == NULL ){
            h2 = &b[i];
            t2 = &b[i];
        }else{
            t2 -> next = &b[i];
            t2 = &b[i];
        }
    }

    Node *p = Merge( h1, h2 );

    for( ; p ; p = p -> next ){
        printf("%d ", p->value);
    }

    return 0;
}

三、链表中间的元素

给定 n个数字,保证 n 是偶数,请问其中第 n/2 个数字是几?

请用链表实现。

输入格式

第一行一个整数 n。

接下来一行 n 个整数 a1,a2,…,an.

输出格式

输出第 n/2 个数字。

样例输入
4
1 2 3 4
样例输出
2
数据规模

对于所有数据,保证 1≤n,ai≤100000。

分析

由于保证n是偶数,则始终存在n/2。

先将n个整数读入链表。

再定义两个指针p1, p2 。

p1每次后移一个节点,p2每次后移两个节点,当p2到达链表末尾(即 p2 -> next = NULL )时,

p1刚好到达链表中央,输出此时p1处的value即可。

代码实现
#include <bits/stdc++.h>
using namespace std;
struct Node{
    int value;
    Node *next;
}*h, *t, *p1,*p2,a[100010];
int n;
int main()
{
    scanf("%d", &n);
    for( int i = 1; i <= n; i++ ){
        scanf("%d", &a[i].value);
        if( h == NULL ){
            h = &a[i];
            t = &a[i];
        }else{
            t -> next = &a[i];
            t = &a[i];
        }
    }
    p1 = h;
    p2 = h;
    while( p2 -> next ){
        if( p2 == h ){
            p2 = p2 -> next;
        }else{
            p1 = p1 -> next;
            p2 = p2 -> next -> next;
        }
    }

    printf("%d\n", p1 -> value);

    return 0;
}

四、n个学生的成绩

输入 n 个学生的成绩,要求将这些成绩倒过来输出,即第一个输入的数最后一个输出,第二个输入的数倒数第二个输出……以此类推。

输入格式

第一行一个正整数 n。

第二行,一共 n 个整数,每个整数均在 0 到 100 之间(包含 0 和 100),表示学生的成绩。

输出格式

n 行,每行一个整数表示答案。

样例输入
10
99 98 97 12 45 78 56 43 66 89
样例输出
89
66
43
56
78
45
12
97
98
99
数据范围

对于 100% 的数据,保证学生成绩在 0 到 100 之间(包含 0 和 100),保证 1≤n≤1000。

分析

先读入有n个节点的链表。

要将链表反向输出,只需要调换两个节点直接的指针方向,

但鉴于简单的调换两个指针方向会影响到后续节点的指向,为此我们考虑三个相邻的节点。

先将第三个节点的指针方向确立,再将前两个节点之间的指针方向调转。

代码实现
#include <bits/stdc++.h>
using namespace std;
struct Node{
    int value;
    Node *next;
}a[1010], *h, *t;
int n;

int main(){
    scanf("%d", &n);
    for ( int i = 1; i <= n; i++ ) {
        scanf("%d", &a[i].value );
        if ( h == NULL ){
            h = t = &a[i];
        }else{
            t -> next = &a[i];
            t = &a[i];
        }
    }

    Node *x =h; Node *y = x -> next; Node *z;
    h -> next = NULL; //保证反转后的tail指向NULL
    while( y ){
        z = y -> next; //在保证y存在的前提下,定义z
        y -> next = x;
        x = y; //每次调换指针方向后更新x,y
        y = z;
    }

    for (Node *p = x ; p ; p = p -> next ){
        printf("%d\n", p->value);
    }

}

​ 前面的例子向我们展示了单向链表的用法,他可以通过next的指向找出每个节点的下一个元素。那么有没有一种链表可以找到该节点前面的一个元素呢?

​ 下面的双向列表,通过添加了一个新的指针*prev来将整个链表前后连通,使得我们可以得到某个节点的前后元素。同样的,我们采用例题的形式加以理解。

五、翻转序列

给你一个1..n这n个数按顺序组成的有序链表。

需要对这个链表做m次翻转操作,每个翻转操作会给出两个数l, r,表示从链表的第l个元素到第r个元素进行翻转。

现在问你这些操作结束完的链表序列长什么样。

输入格式

第一行读入n和m。

后面m行每行给出一组(l,r),表示要翻转的区间。

输出格式

输出最后处理完成的序列。

样例输入
5 2
1 5
3 4
样例输出
5 4 2 3 1
数据规模

对于所有数据,保证1≤n,m≤2000,1≤l≤r≤n。

分析

本题的关键在于将给定区间的数据进行反转,由于是双向链表,要注意考虑prve的情况。

另外,在对于给定区间的数据进行反转时,可采取和单向链表类似的方法,

但值得注意的是,如果所给区间处在链表头或链表尾,则需要考虑next或prev指向NULL的情况

代码实现
#include <bits/stdc++.h>
using namespace std;
struct Node{
    int value;
    Node *next;
    Node *prev;
}a[2010], *h, *t;

int n,l,r,m;

int main()
{
    scanf("%d %d", &n, &m);              //读入n

    for ( int i = 1; i<=n; i++ ){
        a[i].value = i;          //读入n个数

        if ( h == NULL ){        //将n个数存入列表
            h = t = &a[i];
        }else{
            t -> next = &a[i];
            a[i].prev = t;       //双向链表,注意prev
            t = &a[i];
        }
    }

for ( int i = 1; i <= m; i++ ) {

    scanf("%d %d", &l, &r);

    if( l == r ) continue; //如果反转区间为 1 ,则无需反转

    Node *pl = h;
    Node *pr = h; //定义两个边界指针

    //内部循环,注意用 j 不能用 i
    for (int j = 1; j < l; j++) pl = pl->next; //找到两个边界的位置
    for (int j = 1; j < r; j++) pr = pr->next; //由于 i 从 1 开始,因此只需要next r - 1 次

    //为了将反转后的链表串联起来(反转时头尾断开了)
    //所以需要求边界l的前一个(pl->prev)和r的后一个(pr->next)
    Node *v = pl->prev;
    Node *u = pr->next;

    //对于中间相邻元素,仅需要将相邻节点间的指针反向调换即可,
    Node *x = pl;
    Node *y = x->next;
    Node *z;

    while (x != pr) {
        z = y->next;
        y->next = x;
        x->prev = y;
        x = y;
        y = z;
    }

    //当边界pl或pr位于链表头、尾时
    //首先将反转区间的首尾互换,
    //再处理头(尾)部元素,(考虑是否需要指向NULL)
    if (h == pl) {
        h = pr;
        pr->prev = NULL;
    } else {
        Node *p = v;
        p->next = pr;
        pr->prev = p;
    }

    if (t == pr) {
        t = pl;
        pl->next = NULL;
    }else{
        Node *p = u;
        p->prev = pl;
        pl->next = p;
    }

}
    //最后将反转后的链表输出即可
    for ( Node *p = h; p ; p = p -> next ){
        printf("%d ",p -> value);
    }

    return 0;
}

循环链表

对于一些特定的题目,我们可以巧妙的构建循环链表解决.

即将链表的 tail 和 head 连接起来形成闭环,(如果是双向注意prev)

比如先前用队列解决的"约瑟夫问题"就可以通过这种方式解答.

总结:

阿巴阿巴…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值