单链表及笔试题

目录

SList.h

SList.c

Text.c

练习:

链表的中间结点

移除链表元素

思路一:

思路二:

链表分割

反转链表

思路一:

倒数第k个结点

思路:

原理:

相交链表

思路一:

思路二:

环形链表

思路:

plus:

思路一:

结论:

思路二:


SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


typedef int SLNDataType;
//Single List
typedef struct SListNode {
	SLNDataType val;
	struct SListNode* next;//指向下一个结点的指针
}SLNode;

//打印
void SLPrint(SLNode* phead);

//尾插
void SLPushBack(SLNode** pphead, SLNDataType);

//头插
void SLPushFront(SLNode** pphead, SLNDataType);

//尾删
void SLPopBack(SLNode** pphead);

//头删
void SLPopFront(SLNode** pphead);

//查找
SLNode* SLFind(SLNode* phead, SLNDataType x);

//在pos前插入
void SLInsert(SLNode** pphead,SLNode* pos, SLNDataType x);
//pos可以通过SLFind找到,或者写成int pos

//删除pos位置
void SLErase(SLNode** pphead, SLNode* pos);

//清空单链表
void SLDestory(SLNode** pphead);

//在pos后插入
void SLAfterInsert(SLNode* pos, SLNDataType x);
//不用pphead,因为是在后面操作plist不会发生变动

//删除pos位置
void SLAfterErase(SLNode* pos);

SList.c

#define  _CRT_SECURE_NO_WARNINGS
#include"SList.h"

void SLPrint(SLNode* phead) {

	SLNode* cur = phead;
	while (cur != NULL) {
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}

SLNode* CreateNode(SLNDataType x) {
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL) {
		perror("malloc fail");
		exit(-1);
		//要是发生错误直接结束程序
		//0才是正常终止
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

但是phead newnode都是临时变量,出了函数就会被销毁
主函数上是SLPushBack(plist, 1);
相当于phead是plist的拷贝,没有被修改的权利
空间还在,但是找不到,也使用不了
//void SLPushBack(SLNode* phead, SLNDataType x) {
//	SLNode* newnode = CreateNode(x);
//	if (phead == NULL) {
//		phead = newnode;
//	}
//	else {
//		SLNode* tail = phead;
//		while (tail->next != NULL) {
//			tail = tail->next;//循环
//		}
//		//SLNode* newnode = CreateNode(x);
//		tail->next = newnode;
//	}
//}

//此处传进来的是SLPushBack(&plist, 1);也就是plist的地址
//用SLNode**接收,pphead就是plist的地址,*pphead就是根据地址找到plist
//此时就可以使用对指针进行修改
void SLPushBack(SLNode** pphead, SLNDataType x) {
	assert(pphead);
	SLNode* newnode = CreateNode(x);
	//这里用的是二级指针
	if (*pphead == NULL) {
		*pphead = newnode;
	}
	else {
		SLNode* tail = *pphead;
		while (tail->next != NULL) {
			tail = tail->next;//循环
		}
		//SLNode* newnode = CreateNode(x);
		tail->next = newnode;
		//这里用的是一级指针
			//改变指向结构体的指针Node*,要用二级指针
			// 也就是改变结构体指针的位置
			//改变结构体内部的东西Node,比如指针,要用一级指针
			// 也就是改变指针的内容
	}
}

void SLPushFront(SLNode** pphead, SLNDataType x) {
	//此处不用管链表是否为空,并无影响
	assert(pphead);

	SLNode* newnode = CreateNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
	newnode->val = x;
}

void SLPopBack(SLNode** pphead) {
	assert(pphead);

	温柔检查
	//if (*pphead == NULL)
	//	return;
	// 
	//暴力检查
	assert(*pphead);

	//如果删除完链表为空,就要将指针置为空,否则就成为野指针
	//free掉一个结点之后,要将上一个节点的next指针置为空
	//所以此处可以采用两个指针完成
	//一个结点
	SLNode* tail = *pphead;
	if (tail->next == NULL) {
		free(tail);
		tail = NULL;
		*pphead = NULL;
	}
	//多个结点
	else {
		SLNode* prev = NULL;
		while (tail->next != NULL) {
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		//其实此处tail可以不置空,因为出了作用域也销毁了
		prev->next = NULL;
	}
}

void SLPopFront(SLNode** pphead) {
	assert(pphead);

	assert(*pphead);
	//一个结点
	if ((*pphead)->next == NULL) {
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else {
		SLNode* tail = *pphead;
		*pphead = (*pphead)->next;
		free(tail);
		tail = NULL;
	}
}

SLNode* SLFind(SLNode* phead, SLNDataType x) {
	SLNode* cur = phead;
	while (cur) {
		if (cur->val == x) {
			return cur;
		}
		else {
			cur = cur->next;
		}
	}
	printf("未找到\n");
	return NULL;
}

void SLInsert(SLNode** pphead, SLNode* pos, SLNDataType x) {
	assert(pphead);
	//pphead是一个指向plist的指针。万一是头插,则需要改plist,就需要pphead
	//需要断言

	//assert(*pphead);
	//*pphead相当于是plist
	//可以不断言,链表为空,相当于空链表的尾插,此时pos也为空


	assert((pos== NULL && *pphead == NULL)||(pos && *pphead));
	//前者是都为空
	//后者是都不为空

	//使用assert是为了把以下这种情况检查出来
	//SLNode* pos = SLFind(plist, 1000);
	//SLInsert(&plist, pos, 100);

	if (*pphead == pos) {
		//头插
		SLPushFront(pphead, x);
	}
	else {
		//找前一个
		SLNode* prev = *pphead;
		while (prev->next != pos) {
			prev = prev->next;
		}
		//插入
		SLNode* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

void SLErase(SLNode** pphead, SLNode* pos) {
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//找前一个
	SLNode* prev = *pphead;

	//pos在首结点
	if (*pphead == pos) {
		//头删
		SLPopFront(pphead);
	}
	else {
		//pos不在首结点
		while (prev->next != pos) {
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SLDestory(SLNode** pphead) {
	assert(pphead);
	SLNode* cur = *pphead;
	while (cur) {
		SLNode* tmp = (*pphead)->next;
		*pphead = tmp;
		free(cur);
		cur = *pphead;
	}
}

void SLAfterInsert(SLNode* pos, SLNDataType x) {
	assert(pos);
	SLNode* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode->next;
}

void SLAfterErase(SLNode* pos) {
	assert(pos);
	assert(pos->next);
	SLNode* cur = NULL;
	cur = pos->next;
	pos->next = pos->next->next;
	free(cur);
	//free空不会报错
	cur = NULL;
}

Text.c

#define  _CRT_SECURE_NO_WARNINGS
#include"SList.h"
void Text1() {
	SLNode* plist = NULL;

	//SLPushBack(plist, 1);
	//SLPushBack(plist, 2);
	//SLPushBack(plist, 3);

	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);

	SLPrint(plist);

	SLPopBack(&plist);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

	SLFind(plist, 3);
}

void Text2() {
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);
	SLPrint(plist);

	SLNode* pos = SLFind(plist, 2);
	SLInsert(&plist, pos, 100);
	SLPrint(plist);

	SLErase(&plist, pos);
	SLPrint(plist);

}

void Text3() {
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);
	SLPrint(plist);

	SLDestory(&plist);
	SLPrint(plist);

}

int main() {
	//Text1();
	//Text2();
	Text3();
	return 0;
}

练习:

链表的中间结点

移除链表元素

思路一:

删除结点

思路二:

组成新的单链表,但并不是复制到新的结点,只是使用新的指针

tail用来指向新的单链表的当前节点

最后一个if语句的作用是在遍历链表的过程中,当最后一个元素是需要移除的val时,防止tail->next指向空导致后续访问出错。如果链表的尾部元素是需要删除的val,那么在删除之后,tailnext应该设置为NULL,表示链表已经结束。这样做是为了保持链表结构的完整性,并在返回新的头节点newhead时,确保链表的尾部状态被正确更新

链表分割

eg:3 6 1 8 4 6 9   x=5

结果为:3 1 4 6 8 6 9

思路:新建两个链表,一个存放小于x的值,另一个存放大于x的值,然后拼接

要是两个链表其中一个为空,拼接的时候要考虑两个链表谁为空,然后返回不为空的,有点麻烦,所以最好选择带哨兵位(首元结点)的链表,使得都不为空

反转链表

思路一:

改变箭头方向

思路二:

头插

倒数第k个结点

思路:

相对距离:fast先走k步,然后fast和slow同时走,直到fast为空

原理:

slow和fast之间相差k-1步,之间距离就是k

当fast走到尾了,说明slow走到了倒数第k个

相交链表

思路一:

把a链表的所有节点地址和b去比对

思路二:

分别找到尾节点,要是地址相同,就说明相交

如果a、b依次走再判断结点是否为同一个,就有可能完美错过

所以先算出a、b长度,长的先走长度差,然后再a、b同时走

环形链表

也有可能自己指向自己

思路:

快慢指针的追及问题

plus:

思路一:

结论:

一个指针从相遇点开始走,一个指针从头开始走,他们会在环入口点相遇,以此可以求出环入口点

思路二:

合并两个有序链表

不建议把list2插入到list1的合适位置,很难控制,涉及头插尾插以及头指针的变化等等,吃力不讨好

链表的回文结构

思路:

找到中间结点,逆制后半段链表,和前半段比较看是否相同

随机链表的复制

思路一:

找random是原装链表的第i个,然后定义copy链表里的第i个为random,此时合计为O(N^2)

思路二:

要求优化到O(N):

1、复制一个结点在原节点的后面

2、复制结点的random就等于前一个节点的random所在结点(原结点)的后一个结点(复制结点)

3、然后拆下来复制节点,再尾插

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值