链表的几个问题

1.链表的遍历

代码如下:
 

#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int current =first;

	while (current!=-1)
	{
		printf("%d %d %d\n", current, nodes[current].data, nodes[current].next);
		current = nodes[current].next;
	}

	return 0;
}

2.链表结点个数

#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int count=0,current = first;
	while (current != -1) {
		count++;
		current = nodes[current].next;
	}

	printf("%d", count);

	return 0;
}

3.链表头插法

代码如下:

#include<cstdio>

const int MAXN = 1124;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int m;

	scanf("%d", &m);
	for (int i = 0; i < m; i++)
	{
		scanf("%d",&id);
		scanf("%d", &nodes[id].data);
		nodes[id].next = first;
		first = id;
	}

	int current = first;
	while (current!=-1)
	{
		printf("%d %d %d\n", current, nodes[current].data, nodes[current].next);
		current = nodes[current].next;
	}

	return 0;
}

这道题需要注意MAXN大小的设置

4.链表删除元素

代码如下:

#include<cstdio>

const int MAXN = 1124;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, k, id;

	scanf("%d%d%d", &n, &first,&k);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int current = first, last = 0;//last是current序号所在结点的上一个结点的序号

	while (current!=-1)
	{
		if (nodes[current].data == k) {
			if (current == first){//第一个结点特殊性使其需要特别处理
				first = nodes[current].next;
			}
			nodes[last].next = nodes[current].next;//删除结点,将data=k结点a的上一个结点指向a所连接的下一个结点
			current = nodes[last].next;//由于上面last已经连接了下一个结点,故可用这种方法移动current所在结点到下一个结点
		}
		else
		{
			last = current;
			current = nodes[current].next;
		}
	}

	current = first;
	while (current!=-1)
	{
		printf("%d %d %d\n", current, nodes[current].data, nodes[current].next);
		current = nodes[current].next;
	}

	return 0;
}

为了删除结点时链没有“断掉”,故遍历时需要保存上一个结点的序号

另外,注意第一个结点的处理

------------提高题-------------

5.链表反转

代码如下:

#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int current = first, last = -1;

	while (current!=-1)
	{
		int next = nodes[current].next;
		nodes[current].next = last;
		last = current;
		current = next;
	}

	current = last;//此时current=-1,说明last所指向的结点为原链表最后一个结点
	while (current!=-1)
	{
		printf("%d %d %d\n", current, nodes[current].data, nodes[current].next);
		current = nodes[current].next;
	}

	return 0;
}

反转的核心代码如下:

    int current = first, last = -1;//①

    while (current!=-1)
    {
        int next = nodes[current].next;//②
        nodes[current].next = last;//③
        last = current;//④
        current = next;//⑤
    }

接下来我根据①,②,...,⑤这里我画图逐步加以解释:

① int current = first, last = -1;

 

②int next = nodes[current].next; 

 

 ③nodes[current].next = last;

④last = current;

⑤current = next;

总而言之就是把前面的结点按顺序一个接一个放到NULL前面(可能描述得有点不准确)

6.链表删除重复元素

代码如下:

#include<cstdio>

const int MAXN = 100;
const int MAXV = 1000+1;

struct Node {
	int data, next;
}nodes[MAXN];

bool needDelete[MAXV] = { false };
//由于数据域有范围且范围不是很大[1,1000],故可通过数组来标志重复的数据域

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int current = first, last = -1;

	while (current!=-1)
	{
		//判断结点数据是否出现过,删除数据重复的结点并移动到下一结点
		if (needDelete[nodes[current].data]) {
			nodes[last].next = nodes[current].next;
			current=nodes[current].next;
		}
		else//如果数据没有出现过,则对该数据进行标记并移动到下一结点
		{
			needDelete[nodes[current].data] = true;
			last = current;
			current = nodes[current].next;
		}
	}

	current = first;
	while (current!=-1)
	{
		printf("%d %d %d\n", current, nodes[current].data, nodes[current].next);
		current = nodes[current].next;
	}

	return 0;
}

关键代码是中间那部分:

	int current = first, last = -1;

	while (current!=-1)
	{
		//判断结点数据是否出现过,删除数据重复的结点并移动到下一结点
		if (needDelete[nodes[current].data]) {
			nodes[last].next = nodes[current].next;
			current=nodes[current].next;
		}
		else//如果数据没有出现过,则对该数据进行标记并移动到下一结点
		{
			needDelete[nodes[current].data] = true;
			last = current;
			current = nodes[current].next;
		}
	}

7.升序链表中位数

方法一:

结合问题2.链表结点个数,判断个数奇偶来得出中位数

代码如下:

#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int count = 0, current = first;
	while (current != -1) {
		count++;
		current = nodes[current].next;
	}

	double result;

	//如果结点个数是偶数,中位数中间两个结点数据域的平均值
	if (count % 2 == 0) {
		int flag = 1;
		current = first;
		while (flag!=count/2)
		{
			flag++;
			current = nodes[current].next;
		}

		result = (nodes[current].data + nodes[nodes[current].next].data) / 2.0;
	}
	else//结点个数是奇数
	{
		int flag = 1;
		current = first;
		while (flag != (count+1) / 2)
		{
			flag++;
			current = nodes[current].next;
		}
		result=(double) nodes[current].data;
	}

	printf("%.1f", result);

	return 0;
}

值得注意的是:

①result = (nodes[current].data + nodes[nodes[current].next].data) / 2.0;

这里除以 2.0,表示进行了浮点数除法。

无论 nodes[current].datanodes[nodes[current].next].data 的类型是什么,整个表达式的结果都将是一个 double 类型的浮点数

②result = (nodes[current].data + nodes[nodes[current].next].data) / 2;

这里除以 2,表示进行了整数除法。

如果 nodes[current].datanodes[nodes[current].next].data 都是整数类型(如 intlong),则整数除法会导致结果截断为整数。例如,如果被除数的和是整数 5,整数除以 2 的结果将是 2 而不是 2.5

由于希望得到一个精确的小数结果,故我们选择①式子

方法二

答案的方法很妙,它设置了两个纸指针:fast和slow。

fast的移动速度是slow的两倍,而且遍历过程总是能保证slow指针所指向的结点始终在first和fast所指向结点的中间,或者是first和fast中间两个结点的前一个结点。

代码如下:

#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int fast = first, slow = first;
	while (nodes[fast].next!=-1&&nodes[nodes[fast].next].next!=-1)
	{
		slow = nodes[slow].next;
		fast = nodes[fast].next;
		fast = nodes[fast].next;
	}

	if (nodes[fast].next = -1) {
		printf("%.1f", (double)nodes[slow].data);
	}
	else
	{
		printf("%.1f", (nodes[slow].data + nodes[nodes[slow].next].data) / 2.0);
	}

	return 0;
}

我们可以画图来理解核心过程:

int fast = first, slow = first;//①
while (nodes[fast].next!=-1&&nodes[nodes[fast].next].next!=-1)
{
    slow = nodes[slow].next;//②
    fast = nodes[fast].next;//③
    fast = nodes[fast].next;//④
}

 当结点个数是偶数时:

这种情况下,最后用以下代码计算中位数:

printf("%.1f", (nodes[slow].data + nodes[nodes[slow].next].data) / 2.0); 

 当结点个数是奇数时:

这种情况下,最后用以下代码计算中位数:

if (nodes[fast].next = -1) {
    printf("%.1f", (double)nodes[slow].data);
}

8.链表倒数第k个结点

代码如下:

#include <cstdio>

const int MAXN = 100;

struct Node {
    int data, next;
} nodes[MAXN];

int main() {
    int n, first, k, id;
    scanf("%d%d%d", &n, &first, &k);
    for (int i = 0; i < n; i++) {
        scanf("%d", &id);
        scanf("%d%d", &nodes[id].data, &nodes[id].next);
    }
    int fast = first;
    while (k--) {
        fast = nodes[fast].next;
    }
    int slow = first;
    while (fast != -1) {
        slow = nodes[slow].next;
        fast = nodes[fast].next;
    }
    printf("%d %d %d", slow, nodes[slow].data, nodes[slow].next);
    return 0;
}

这道题我第一反应是遍历链表得到结点个数n,再用n-k得到倒数第k个结点前的结点个数,再从头遍历;

后面看到了答案的做法感觉好妙,它设置了两个指针,slow在前,fast在后,先让fast比slow多走k个节点,然后slow和fast以相同的速度向前移动,当fast指到NULL时,slow就恰好在第k个结点了。

核心代码如下:

int fast = first;
    while (k--) {
        fast = nodes[fast].next;
    }//①
    int slow = first;//②
    while (fast != -1) {
        slow = nodes[slow].next;
        fast = nodes[fast].next;
    }//③

9.回文链表

方法一:

我第一反应还是先求出链表的结点数,后面觉得自己的方法要进步一些,就参考了7.升序链表中的中位数中的思想,把slow指针遍历过的结点的数据域存储进数组里方便后面进行对比。

但是要注意一种特殊情况,就是只有一个结点的链表是特殊的回文链表

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

int a[MAXN] = { 0 };

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int fast = first, slow = first, num=1;
	while (nodes[fast].next!=-1&&nodes[nodes[fast].next].next!=-1)
	{
		a[num] = nodes[slow].data;
		num++;
		slow = nodes[slow].next;
		fast = nodes[fast].next;
		fast = nodes[fast].next;
	}

	bool flag = true;//用来标志链表是否是回文链表
	
	if (num == 1&& nodes[fast].next == -1);//只有一个结点

	else 
	{
		//此时结点个数是奇数,上面的步骤中slow最后位于中点时并没有把中点的数据录入数组中
		if (nodes[fast].next == -1)
		{
			num--;//但鉴于每次录入数据后都会+1,所以这里要把1减掉
			while (num)
			{
				slow = nodes[slow].next;
				if (nodes[slow].data != a[num]) {
					flag = false;
					break;
				}
				num--;
			}
		}

		else//此时结点个数是偶数,则slow可以从下一个结点开始对比遍历
		{
			a[num] = nodes[slow].data;//因为上面的步骤中slow最后位于中点时并没有把中点的数据录入数组中
			while (num)
			{
				slow = nodes[slow].next;
				if (nodes[slow].data != a[num]) {
					flag = false;
					break;
				}
				num--;
			}
		}
	}

	if (flag == true)
		printf("Yes");
	else
		printf("No");

	return 0;
}

方法二:

该方法将输入链表的前1/2进行反转(定位还是用到了7.升序链表中位数的思想),把 5.链表反转 核心代码封装成函数,在这里进行调用。

反转前1/2链表后将两链表结点依次进行对比

#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>

const int MAXN = 100;

struct Node {
	int data, next;
}nodes[MAXN];

//实现链表翻转的函数
int reverseList(int first) {
	int current = first, last = -1;
	while (current!=-1)
	{
		int next = nodes[current].next;
		nodes[current].next = last;
		last = current;
		current = next;
	}

	return last;
}

bool judgePalindrome(int head1, int head2) {
	while (head2 != -1)//由于只对链表前1/2部分进行了翻转,
    //故head2所指向的链表比head1短,因此用head2是否等于 -1 进行判断
	{
		if (nodes[head1].data != nodes[head2].data) {
			return false;
		}
		head1 = nodes[head1].next;
		head2 = nodes[head2].next;
	}
	return true;
}

int main()
{
	int n, first, id;

	scanf("%d%d", &n, &first);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &id);
		scanf("%d%d", &nodes[id].data, &nodes[id].next);
	}

	int fast = first, slow = first;
	while (nodes[fast].next!=-1&&nodes[nodes[fast].next].next!=-1)
	{
		slow = nodes[slow].next;
		fast = nodes[fast].next;
		fast = nodes[fast].next;
	}

	//↑找到链表串前1/2的位置,↓将前1/2链表串反转
	int headOfSecondPart = reverseList(nodes[slow].next);
	bool isPalindrome = judgePalindrome(first, headOfSecondPart);
	
	printf(isPalindrome ? "Yes" : "No");

	return 0;
}

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值