数据结构(C语言版)线性表-单链表的拓展及应用

单链表的应用

练习1---用快慢指针找倒数第 k 个节点

(1)算法的基本设计思想

双指针:快慢指针

定义两个指针变量 fast 和 slow ,初始时均指向头结点的下一个结点(链表的第一个结点)。 fast指针沿链表移动:当 fast 指针移动到第 k 个结点时, fast 指针开始与 slow 指针同步移动:当 fast 指针移动到最后一个结点时, slow 指针所指示结点为倒数第 k 个结点。以上过程对链表仅进行一遍扫描。

(2)算法的详细实现步骤

①fast 和 slow 指向链表表头结点的下一个结点; ②快指针提前走 k 步; ③快慢指针同步移动至快指针到末尾; ④ 输出该结点的 data 域的值,返回; ⑤算法结束

(3)关键算法:

c 复制代码
int findNodeFS(Node*L,int k) {

	Node* fast = L->link;
	Node* slow = L->link;

	for (int i = 0; i < k; i++)
	{
		fast = fast->link;
	}
	while (fast != NULL)
	{
		fast = fast->link;
		slow = slow->link;
	}
	printf("倒数第%d个节点值为%d\n", k, slow->data);
	return 1;
}

完整代码:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;

typedef struct node {
	ElemType data;
	struct node* link;
}Node;


//初始化
Node* initList()
{
	Node* head = (Node*)malloc(sizeof(Node));
	head->data = 0;
	head->link = NULL;
	return head;
}



int findNodeFS(Node*L,int k) {

	Node* fast = L->link;
	Node* slow = L->link;

	for (int i = 0; i < k; i++)
	{
		fast = fast->link;
	}
	while (fast != NULL)
	{
		fast = fast->link;
		slow = slow->link;
	}
	printf("倒数第%d个节点值为%d\n", k, slow->data);
	return 1;
}

//尾插法
Node* get_tail(Node* L) {
	Node* p = L;
	while (p->link != NULL) {
		p = p->link;
	}
	return p;
}

int insertTail(Node* L, ElemType e) {
	Node* tail=get_tail(L);
	Node* p = (Node*)malloc(sizeof(Node));

	//赋值并连接节点
	p->data = e;
	tail->link = p;
	p->link = NULL;
	
	return 1;
}

//遍历
void PrintNode(Node*L)
{
	Node* p = L->link;
		while (p != NULL) {
			printf("%d ", p->data);
			p = p->link;
		}
		printf("\n");
}
//释放链表
void freeList(Node* L) {
	Node* p = L->link;
	Node* q;

	while (p != NULL) {
		q = p->link;
		free(p);
		p = q;
	}
	L->link = NULL;
}

int main() {
	Node* list = initList();

	insertTail(list, 10);
	insertTail(list, 20);
	insertTail(list, 30);
	insertTail(list, 40);
	insertTail(list, 50);
	insertTail(list, 60);
	insertTail(list, 70);
	PrintNode(list);

	findNodeFS(list, 3);

	freeList(list);

	return 0;
}

运行结果:

10 20 30 40 50 60 70

倒数第3个节点值为50

练习2---两链表公共后缀

(1)算法基本设计思路

第一步:分别求出两个链表的长度m和n

第二步:Fast指针指向较长的链表,先走m-n或n-m步。

第三步:同步移动指针,判断是否指向同一个节点。

完整代码:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

typedef char ElemType;

typedef struct node {
	ElemType data;
	struct node* next;
}Node;


//初始化
Node* initList()
{
	Node* head = (Node*)malloc(sizeof(Node));
	head->data = 0;
	head->next = NULL;
	return head;
}
//初始化节点(带节点数据参数)
Node* initListWithElem(ElemType e) {
	Node* node = (Node*)malloc(sizeof(Node));
	node->data = e;
	node->next = NULL;
	return node;
}

//获取链表长度
int getLength(Node* L) {
	Node* p = L->next;
	int len = 0;
	while (p != NULL) {
		p = p->next;
		len++;
	}
	return len;
}

//
Node* findCommonSuff(Node* str1, Node* str2)
{
	if (str1 == NULL || str2 == NULL)
	{
		return NULL;
	}

	int m = getLength(str1);
	int n = getLength(str2);

	Node* longList = str1->next;
	Node* shortList = str2->next;

	if (n > m) {
		longList = str2->next;
		shortList = str1->next;
	}

	int diff = abs(m - n);//m n的差值
	for (int i = 0; i < diff; i++)
	{
		if (longList == NULL) break;
		longList = longList->next;
	}

	while (longList!=NULL&&shortList!=NULL)
	{
		if (longList == shortList)
		{
			return longList;
		}
		longList = longList->next;
		shortList = shortList->next;
		
	}
	return NULL;
}

//尾插法
Node* get_tail(Node* L)
{
	Node* p = L;
	while (p->next != NULL)
	{
		p = p->next;
	}
	return p;
}

Node* TailList(Node* L, ElemType e)
{
	Node* tail = get_tail(L);
	Node* p = (Node*)malloc(sizeof(Node));
	p->data = e;
	tail->next = p;
	p->next = NULL;
	return p;
}

//尾插法(节点)
Node* insertTailWithNode(Node* L, Node* node) {
	Node* tail = get_tail(L);
	tail->next = node;
	node->next = NULL;
	return node;
}

//遍历
void PrintList(Node* L)
{
	Node* p = L->next;
	while (p != NULL) {
		printf("%c ", p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
	Node* str1 = initList();
	Node* str2 = initList();

	TailList(str1, 'l');
	TailList(str1, 'o');
	TailList(str1, 'a');
	TailList(str1, 'd');


	TailList(str2, 'b');
	TailList(str2, 'e');

	//创建带数据域的新节点
	Node* node1 = initListWithElem('i');
	Node* node2 = initListWithElem('n');
	Node* node3 = initListWithElem('g');

	//让str1和str2都指向 i n g
	insertTailWithNode(str1, node1);
	insertTailWithNode(str1, node2);
	insertTailWithNode(str1, node3);

	insertTailWithNode(str2, node1);
	insertTailWithNode(str2, node2);
	insertTailWithNode(str2, node3);
    
    //遍历
	PrintList(str1);
	PrintList(str2);
    
	Node*com=findCommonSuff(str1, str2);
	printf("%c ", com->data);


}

运行结果:

l o a d i n g

b e i n g

i

(3)时间复杂度:O(m+n)

练习3---用辅助数组删除绝对值重复节点

(1)算法基本设计思想

利用空间换时间的策略,借助辅助数组记录链表中已出现的节点绝对值:

  • 由于|data| ≤ n,创建大小为n+1的辅助数组(下标对应节点绝对值),初始值为 0;

  • 遍历链表,对每个节点取其data的绝对值,检查辅助数组对应下标的值:

    • 若为 0,说明该绝对值首次出现,保留节点并将数组对应值设为 1;
    • 若为 1,说明该绝对值已出现过,删除当前节点。

(2) 单链表结点的数据类型定义

c 复制代码
typedef struct node {
    int data;          // 数据域
    struct node* link; // 指针域,指向下一个结点
} Node;

(3)关键算法:

c 复制代码
void removeNode(Node* L, int n) 
{
	Node* p = L; // p指向当前节点的前驱(从头结点开始,方便删除操作)
	int index;   //作为数组下标
	int* q = (int*)malloc(sizeof(int) * (n + 1));//给数组分配空间

	//遍历数组,初始化值为0
	for (int i = 0; i < n + 1; i++)
	{
		*(q + i) = 0;
	}

	while (p->next != NULL)
	{
		index = abs(p->next->data);//获取绝对值
		if (*(q + index) == 0)
		{
			*(q + index) = 1;
			p = p->next;
		}
		else  // 重复出现,删除p的后继节点
		{
			Node* temp = p->next;  // 保存待删除节点
			p->next = temp->next;  // 跳过待删除节点
			free(temp);
			//此处p不后移,因为新的p->next需要重新检查
		}
	}
	free(q);//释放数组
}

完整代码:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;

typedef struct node {
	ElemType data;
	struct node* next;
}Node;

//初始化
Node* initList()
{
	Node* head = (Node*)malloc(sizeof(Node));
	head->data = 0;
	head->next = NULL;
	return head;
}

void removeNode(Node* L, int n) 
{
	Node* p = L; // p指向当前节点的前驱(从头结点开始,方便删除操作)
	int index;   //作为数组下标
	int* q = (int*)malloc(sizeof(int) * (n + 1));//给数组分配空间

	//遍历数组,初始化值为0
	for (int i = 0; i < n + 1; i++)
	{
		*(q + i) = 0;
	}

	while (p->next != NULL)
	{
		index = abs(p->next->data);//获取绝对值
		if (*(q + index) == 0)
		{
			*(q + index) = 1;
			p = p->next;
		}
		else  // 重复出现,删除p的后继节点
		{
			Node* temp = p->next;  // 保存待删除节点
			p->next = temp->next;  // 跳过待删除节点
			free(temp);
			//此处p不后移,因为新的p->next需要重新检查
		}
	}
	free(q);//释放数组
}

//尾插法
Node* get_tail(Node* L)
{
	Node* p = L;
	while (p->next != NULL)
	{
		p = p->next;
	}
	return p;
}

Node* TailList(Node* L, ElemType e)
{
	Node* tail = get_tail(L);
	Node* p = (Node*)malloc(sizeof(Node));
	p->data = e;
	tail->next = p;
	p->next = NULL;
	return p;
}

//遍历
void PrintList(Node* L)
{
	Node* p = L->next;
	while (p != NULL)
	{		
		
		printf("%d ",p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
	Node* list = initList();

	TailList(list, 21);
	TailList(list, -15);
	TailList(list, -15);
	TailList(list, 7);
	TailList(list, 15);
	PrintList(list);

	removeNode(list, 21);

	PrintList(list);
}

运行结果:

21 -15 -15 7 15

21 -15 7

反转链表

核心思路(迭代法,最优

通过 3 个指针(first/second/third)逐步反转节点指向:

  1. 初始化first=NULL(反转后的尾节点)、second=head->next(当前待反转节点,跳过头结点);
  2. 遍历链表,每次先保存second的后继节点(third=second->next);
  3. secondnext指向first(反转指向);
  4. firstsecond分别后移(first=secondsecond=third);
  5. 遍历结束后,将头结点hd->next指向first(新的链表头)。

最后一次情况如下图:

头节点指向fiest head->next = first;

c 复制代码
Node* reverseList(Node* head)
{
	Node* first = NULL;
	Node* second = head->next;
	Node* third;

	while (second != NULL) {
		third = second->next;
		second->next = first;
		first = second;
		second = third;
	}
	head->next = first;
}

完整代码:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;

typedef struct node {
	ElemType data;
	struct node* next;
}Node;

//初始化
Node* initList()
{
	Node* head = (Node*)malloc(sizeof(Node));
	head->data = 0;
	head->next = NULL;
	return head;
}

//尾插法
Node* get_tail(Node* L)
{
	Node* p = L;
	while (p->next != NULL)
	{
		p = p->next;
	}
	return p;
}

Node* TailList(Node* L, ElemType e)
{
	Node* tail = get_tail(L);
	Node* p = (Node*)malloc(sizeof(Node));
	p->data = e;
	tail->next = p;
	p->next = NULL;
	return p;
}

//反转链表
Node* reverseList(Node* head)
{
	Node* first = NULL;
	Node* second = head->next;
	Node* third;

	while (second != NULL) {
		third = second->next;
		second->next = first;
		first = second;
		second = third;
	}
	head->next = first;
	return head;
}


//遍历
void PrintList(Node* L)
{
	Node* p = L->next;
	while (p != NULL)
	{		
		
		printf("%d ",p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
	Node* list = initList();

	TailList(list, 1);
	TailList(list, 2);
	TailList(list, 3);
	TailList(list, 4);
	TailList(list, 5);
    TailList(list, 6);
	PrintList(list);

	Node* reserve=reverseList(list);
	PrintList(reserve);
}

运行结果:

1 2 3 4 5 6

6 5 4 3 2 1

删除链表的中间节点

用快慢指针:

  • 慢指针(slow) :每次走 1 步,最终指向中间节点的前驱(方便删除)。
  • 快指针(fast):每次走 2 步,用于控制慢指针的停止位置。

假设链表有效节点数为 n

  • n 为奇数(如 n=5):中间节点是第 (n+1)/2 个(第 3 个),快指针走到尾节点时,慢指针到中间节点前驱;
  • n 为偶数(如 n=4):中间节点通常取第 n/2 个(第 2 个),快指针走到 NULL 时,慢指针到中间节点前驱。
c 复制代码
//删除中间节点
int delMiddleNode(Node* head)
{
	Node* fast = head->next;
	Node* slow = head;

	while (fast != NULL && fast->next!=NULL)
	{
		fast = fast->next->next;//fast移动两步
		slow = slow->next;//slow移动一步
	}
	Node* q = slow->next;//q记录被删除的节点
	slow->next = q->next;//slow指向被删除的下一个节点
	free(q);
	return 1;
}

完整代码:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;

typedef struct node {
	ElemType data;
	struct node* next;
}Node;

//初始化
Node* initList()
{
	Node* head = (Node*)malloc(sizeof(Node));
	head->data = 0;
	head->next = NULL;
	return head;
}

//尾插法
Node* get_tail(Node* L)
{
	Node* p = L;
	while (p->next != NULL)
	{
		p = p->next;
	}
	return p;
}

Node* TailList(Node* L, ElemType e)
{
	Node* tail = get_tail(L);
	Node* p = (Node*)malloc(sizeof(Node));
	p->data = e;
	tail->next = p;
	p->next = NULL;
	return p;
}

//删除中间节点
int delMiddleNode(Node* head)
{
	Node* fast = head->next;
	Node* slow = head;

	while (fast != NULL && fast->next!=NULL)
	{
		fast = fast->next->next;//fast移动两步
		slow = slow->next;//slow移动一步
	}
	Node* q = slow->next;//q记录被删除的节点
	slow->next = q->next;//slow指向被删除的下一个节点
	free(q);
	return 1;
}


//遍历
void PrintList(Node* L)
{
	Node* p = L->next;
	while (p != NULL)
	{		
		
		printf("%d ",p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
	Node* list = initList();

	TailList(list, 1);
	TailList(list, 2);
	TailList(list, 3);
	TailList(list, 4);
	TailList(list, 5);
	TailList(list, 6);
	TailList(list, 7);
	PrintList(list);

	delMiddleNode(list);
	PrintList(list);
}

运行结果:

1 2 3 4 5 6 7

1 2 3 5 6 7

练习---拆分、反转后半段再交替合并的重排序方法

核心逻辑分为三步:

  1. 快慢指针找链表中点,将链表拆分为前半段和后半段;
  2. 反转后半段链表;
  3. 将前半段和反转后的后半段交替合并。
  4. 追加剩余节点(将中间节点接入链表末尾)

关键算法:

c 复制代码
//链表重新排序
void reOrderList(Node* head)
{
	Node* fast = head->next;
	Node* slow = head;
	while (fast != NULL && fast->next != NULL)
	{
		fast = fast->next->next;
		slow = slow->next;
	}

	Node* first = NULL;
	Node* second = slow->next;  //记录中间节点
	slow->next = NULL;          //从中间将链表打断
	Node* third = NULL;

	while (second != NULL)
	{
		third = second->next;
		second->next = first;
		first = second;
		second = third;
	}

	Node* p1 = head->next; // p1:前半段指针
	Node* q1 = first;      // q1:反转后后半段指针
	Node* p2, *q2;         // 临时变量,保存p1/q1的后继(避免合并时丢失)

	while (p1 != NULL && q1 != NULL)  // 只要两段都有节点,就交替合并
	{
		p2 = p1->next;  //保存p1的下一个节点
		q2 = q1->next;  //保存q1的下一个节点

		p1->next = q1; //交替合并
		q1->next = p2;

		p1 = p2;       
		q1 = q2;
	}

	//追加剩余节点,若后半段还有未处理的剩余节点(如链表个数为奇数时)
	if (q1 != NULL)
	{
		// 找到当前链表的末尾节点
		Node* tail = head;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = q1;    // 将剩余节点(4)接入末尾
	}

}

完整代码:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;

typedef struct node {
	ElemType data;
	struct node* next;
}Node;

//初始化
Node* initList()
{
	Node* head = (Node*)malloc(sizeof(Node));
	head->data = 0;
	head->next = NULL;
	return head;
}

//尾插法
Node* get_tail(Node* L)
{
	Node* p = L;
	while (p->next != NULL)
	{
		p = p->next;
	}
	return p;
}

Node* TailList(Node* L, ElemType e)
{
	Node* tail = get_tail(L);
	Node* p = (Node*)malloc(sizeof(Node));
	p->data = e;
	tail->next = p;
	p->next = NULL;
	return p;
}

//链表重新排序
void reOrderList(Node* head)
{
	Node* fast = head->next;
	Node* slow = head;
	while (fast != NULL && fast->next != NULL)
	{
		fast = fast->next->next;
		slow = slow->next;
	}

	Node* first = NULL;
	Node* second = slow->next;  //记录中间节点
	slow->next = NULL;          //从中间将链表打断
	Node* third = NULL;

	while (second != NULL)
	{
		third = second->next;
		second->next = first;
		first = second;
		second = third;
	}

	Node* p1 = head->next; // p1:前半段指针
	Node* q1 = first;      // q1:反转后后半段指针
	Node* p2, *q2;         // 临时变量,保存p1/q1的后继(避免合并时丢失)

	while (p1 != NULL && q1 != NULL)  // 只要两段都有节点,就交替合并
	{
		p2 = p1->next;  //保存p1的下一个节点
		q2 = q1->next;  //保存q1的下一个节点

		p1->next = q1; //交替合并
		q1->next = p2;

		p1 = p2;       
		q1 = q2;
	}

	//追加剩余节点,若后半段还有未处理的剩余节点(如链表个数为奇数时)
	if (q1 != NULL)
	{
		// 找到当前链表的末尾节点
		Node* tail = head;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = q1;    // 将剩余节点(4)接入末尾
	}

}


//遍历
void PrintList(Node* L)
{
	Node* p = L->next;
	while (p != NULL)
	{		
		
		printf("%d ",p->data);
		p = p->next;
	}
	printf("\n");
}

int main() {
	Node* list = initList();

	TailList(list, 1);
	TailList(list, 2);
	TailList(list, 3);
	TailList(list, 4);
	TailList(list, 5);
	TailList(list, 6);
	TailList(list, 7);
	PrintList(list);

	reOrderList(list);
	PrintList(list);
}

运行结果:

1 2 3 4 5 6 7

1 7 2 6 3 5 4

单链表有哪些局限?------不能回头

相关推荐
xiaozi41202 小时前
Ruey S. Tsay《时间序列分析》Python实现笔记:综合与应用
开发语言·笔记·python·机器学习
d111111111d2 小时前
STM32低功耗学习-停止模式-(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
@游子2 小时前
Python学习笔记-Day5
笔记·python·学习
摇滚侠2 小时前
2025最新 SpringCloud 教程,网关功能、创建网关,笔记51、笔记52
java·笔记·spring cloud
浓墨染彩霞3 小时前
Java-----多线路
java·经验分享·笔记
浩瀚地学3 小时前
【Java】String
java·开发语言·经验分享·笔记·学习
摇滚侠4 小时前
2025最新 SpringCloud 教程,Seata-原理-二阶提交协议,笔记70
笔记·spring·spring cloud
im_AMBER4 小时前
Leetcode 67 长度为 K 子数组中的最大和 | 可获得的最大点数
数据结构·笔记·学习·算法·leetcode
爱打代码的小林5 小时前
numpy库数组笔记
笔记·python·numpy