【数据结构与算法——C语言】“链表操作与算法”之“重排链表”

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. 测试1
    a) 测试数据用例:1 2 3 4 5 6
    b) 测试数据用例1的特点:链表结点个数为偶数。
    c) 测试结果:
    测试1

  2. 测试2
    a) 测试数据用例:6 5 4 3 2
    b) 测试数据用例2的特点:链表结点个数为奇数。
    c) 测试结果:
    在这里插入图片描述

  3. 测试3
    a) 测试数据用例:6 5
    b) 测试数据用例3的特点:只有两个结点数。
    c) 测试结果:
    在这里插入图片描述

在这里插入图片描述

5. 实验总结

本次实验由于题目要求用链表做,其时间复杂度为O(n),空间复杂度为O(1)。本来是打算用数组来做,比较简单,但是分析了一下发现用数组来做的时间复杂度为O(n),空间复杂度为O(n)。经过分析发现,链表明显优于数组,这也给我很大的启示。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值