三、数据结构第三节——链表(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)
比如先前用队列解决的"约瑟夫问题"就可以通过这种方式解答.
总结:
阿巴阿巴...
标签:Node,prev,第三节,int,h1,next,链表,数据结构 From: https://www.cnblogs.com/XTian258/p/17009264.html