1. 实验内容及上机实验所用平台
1.1 实验内容
【问题描述】
假设不带头结点的单链表结点类型如下:
struct ListNode
{ int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){}
};
给定一个含n+1个结点的单链表L:L0→L1→…→Ln-1→Ln,将其重新排列后变为:L0→Ln→L1→Ln-1→L2→Ln-2→…。你不能只是单纯的改变结点内部的值,而是需要实际的进行结点交换。示例1,给定链表为1->2->3->4,重新排列为1->4->2->3。示例2,给定链表为1->2->3->4->5,重新排列为1->5->2->4->3。你需要设计的reorderList函数。
class Solution {
public:
void reorderList(ListNode * head)
{ … }
};
【输入形式】
输入含有n个元素的单链表。
【输出形式】
输出重排后的单链表中的元素。
1.2 设计思路
先找到链表的中间点或者靠左的中间点,接着依靠中间点进行分割,再将第二条链表逆置,最后通过两个指针依次合并成一条单链表,如下图。
1.3 实验平台软件
Dev-C++.
2. 数据结构
非空单链表,有指向后继结点的next指针,尾结点的next域置为NULL,且不带头结点。
3. 设计描述与分析
3.1 伪码
输入:数组num。
输出:链表重排后的新链表。
由于要详细描述伪码长度过长,且指针过多,因此只用中文描述每个主要步骤。
// 将数组num创建为单链表,first为单链表的首结点
if first!=NULL and first→next!=NULL and first→next→next!=NULL
// 寻找中点middle,根据中点middle分割链表,即middle → next = NULL
// middle指向第2条链表,q为空指针
while middle != NULL
// 逆置第2条链表
end while
while (first != NULL && q != NULL)
// 按照约定合并链表
end while
3.2 流程图
3.3 源代码
需要先在源代码目录下新建 in.txt
文件,在此文件下输入要测试的数据。
#include <iostream>
using namespace std;
struct LinkNode { // 不带头结点的单链表结点类型
int val;
LinkNode *next;
LinkNode(int x): val(x), next(NULL) {}
};
class LinkList {
LinkNode *first; // 循环单链表的首结点
public:
void CreateList(int a[], int n) { // 建立单链表
first = new LinkNode(a[0]);
LinkNode *s, *r = first; // r为尾结点指针
for (int i = 1; i < n; i++) {
s = new LinkNode(a[i]); // 创建数据结点s
r -> next = s; // 将 s 结点链接到末尾
r = s;
}
r -> next = NULL; // 将尾结点的 next域置为 NULL
}
void reorderList() { // 重排链表
if (first != NULL && first -> next != NULL && first -> next -> next != NULL) {
// 获取链表的中点,或者靠左的中点
LinkNode *middle = first, *last = first -> next; // 初始化 middle 指向首结点,last 指向首结点的后继结点
while (last != NULL && last -> next != NULL) {
middle = middle -> next; // 指针 middle 后移一位
last = last -> next -> next; // 指针 last 后移两位
}
// 将第二条链表逆置
LinkNode *p = middle -> next, *q = NULL; // p 指向 middle 的后继结点,q 为空指针
middle -> next = NULL; // 分割链表
middle = p; // middle 指向第二条链表的首结点
while (middle != NULL) {
p = middle -> next; // p 指向 middle 的后继结点
middle -> next = q; // 将当前第2条链表的首元素提取出来
q = middle; // 让 q 指向逆置新链表的尾结点 (第2条链表)
middle = p; // middle 继续指向要逆置的链表的首结点
}
// 合并两条链表
middle = first; // middle 设为第一条链表的首结点
while (first != NULL && q != NULL) {
p = middle -> next; // p 指向 middle 的后继结点
middle -> next = q; // 第1条链表与第2条链表的首结点进行链接
q = q -> next; // q 指向第2条链表的首结点
middle -> next -> next = p; // 再次链接第1条链表的后一个元素
middle = p; // middle 设为要合并第1条链表(第一条链表剩余元素组成的链表)的首结点
}
}
}
void DispList() {
while (first != NULL) {
cout << first -> val << " ";
first = first -> next; // first 移向下一个结点
}
cout << endl;
}
};
int main() {
cout << "\t\t第1题 - 重排链表\n\n";
cout << "---------------------------------------------------\n\n";
freopen("in.txt", "r", stdin);
int num[1001] = {0}, cnt = 0;
while (cin >> num[cnt++]); // 从 in.txt 中读取所有数据
cnt--; // 因为 在读取数据时 cnt++ 多运行了一次,所以减 1 得到所有数据的长度
cout << "样例输入:";
for (int i = 0; i < cnt; i++) cout << num[i] << " ";
cout << "\n\n样例输出:";
LinkList L;
L.CreateList(num, cnt); // 创建循环单链表
L.reorderList();
L.DispList();
cout << "\n\n";
freopen("CON", "r", stdin); // 为了可直接查看exe可执行文件,需要将权限返回键盘
system("pause");
return 0;
}
4. 调试过程
-
测试1
a) 测试数据用例:1 2 3 4 5 6
b) 测试数据用例1的特点:链表结点个数为偶数。
c) 测试结果:
-
测试2
a) 测试数据用例:6 5 4 3 2
b) 测试数据用例2的特点:链表结点个数为奇数。
c) 测试结果:
-
测试3
a) 测试数据用例:6 5
b) 测试数据用例3的特点:只有两个结点数。
c) 测试结果:
5. 实验总结
本次实验由于题目要求用链表做,其时间复杂度为O(n),空间复杂度为O(1)。本来是打算用数组来做,比较简单,但是分析了一下发现用数组来做的时间复杂度为O(n),空间复杂度为O(n)。经过分析发现,链表明显优于数组,这也给我很大的启示。