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].data
和 nodes[nodes[current].next].data
的类型是什么,整个表达式的结果都将是一个 double
类型的浮点数
②result = (nodes[current].data + nodes[nodes[current].next].data) / 2;
这里除以 2
,表示进行了整数除法。
如果 nodes[current].data
和 nodes[nodes[current].next].data
都是整数类型(如 int
或 long
),则整数除法会导致结果截断为整数。例如,如果被除数的和是整数 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;
}