单向链表的原地排序、去重、求交并集


2019/1/15 数据结构考试结束


本文在各类链表的创建及遍历(可参考四类链表创建及衍生关系)基础上介绍单向链表的操作,主要包含以下3类常见操作

  • 原地插入排序(不多申请空间)
  • 删除有序序列中重复元素
  • 求两个有序序列的交、并集

本文链表均带头结点

数据结构

typedef struct LNode
{
	int data;
	LNode * next;
}*LinkList,LNode;

原地插入排序

思路:假设原链表为L1,新链表为L2,每次摘下L1的第一个元素,在L2中寻找合适的插入位置,直至L1为空表。

为了实现原地插入排序,即不申请新空间,思路同上,具体操作为

  • 1,将L的后继保存,构成无头结点的L1;接着将L头结点摘下,构成空表L2
  • 2,摘下L1的首个元素,在L2中寻找合适插入位置,找到后插入
  • 3,若L1非空,重复步骤2

tips一般来说,链表适合插入删除,而单链表的插入删除均需要当前指针,前驱指针。而此处L1的摘下操作并不完全等价于删除,相对于L1它是删除了,可实际上它被插入L2,所以需要一个后继保存L1下一个节点,防止断链。

//原地插入排序 
void InsertSort(LinkList &L)
{
	if(!Valid(L))return;//有效性验证 
	//非空表 
	LNode *qcur,*qnext;//L1:原链表的当前(即将)要插入到新表的指针,记录当前节点的下一个节点(防止原链表断链) 
	LNode *pcur,*ppre;//L2:新(排序后)链表的用来查找插入位置(一个指针即可)并且插入qcur的两个指针(必须两个指针,无法访问前一个节点) 

	qcur = L->next;//防止断链
	L->next = NULL;//摘下头结点
	//对原链表所有节点依次处理 
	while(qcur != NULL){
		//每次从新链表头部开始查找插入的合适位置 
		ppre = L;
		pcur = ppre->next;
		while(pcur != NULL && qcur->data > pcur->data){//新链表未走完且插入值大于当前值 
			ppre = pcur;
			pcur = pcur->next;
		}
		qnext = qcur->next;//保留原链表下一个节点,防止断链
		//插入新链表 
		qcur->next = pcur;
		ppre->next = qcur;
		qcur = qnext;//更新为下一个节点 
	} 
} 

删除有序序列中重复元素

排好序后删除重复点,只保留第一次出现的值 ==化简为集合:每个数只出现一次
思路与动态数组类似:排好序后,当前元素只与其前驱相比,若相等,删除;若不等后移

void Set_Sorted(LinkList &L)
{
	if(!Valid(L))return;//有效性检验 
	LNode *pcur,*ppre;
	ppre = L;
	pcur = ppre->next;
	L->data = -11111;//头节点赋为不可能出现的值 
	while(pcur){
		if(ppre->data != pcur->data){//当前值不等于前驱,后移 
			ppre = pcur;
			pcur = pcur->next;
		}
		else{//当前值等于前驱删除当前点 
			ppre->next = pcur->next;
			free(pcur);
			pcur = ppre->next;
		}
	}
}

求两个有序序列的交、并集

并集

已经升序排列的两个序列合并依旧有序,并接到L1
方案一:可先将两个序列拼接,接着排序,最后去重
方案二:分别对两个序列扫描,假设L1,L2当前首元素分别为a,b

  • 1.1,b < a,b直接插入a前,b后移
  • 1.2,b = a,删除b,b后移
  • 1.3,b > a,a后移
  • 2,当L1,L2均未走到头 执行步骤1
  • 3,当L1先到头时,L2剩余部分直接接到L1尾部
void Merge_Sorted(LinkList &L1,LinkList L2)
{
	//有效性验证 
	if(!Valid(L1) || !Valid(L2)){
		if(L1->next == NULL)L1 = L2;
	}
	LNode *qcur,*qpre,*pcur,*pnext;
	qpre = L1;
	qcur = qpre->next;
	pcur = L2->next;
	while(qcur != NULL && pcur != NULL){//两序列均未走完 
		if(pcur->data < qcur->data){//L2元素插入L1 
			pnext = pcur->next;//防止L2断链
			//连接到L1 
			pcur->next = qcur;
			qpre->next = pcur;
			pcur = pnext;//L2后移一位 
		}
		else if(pcur->data == qcur->data){//相等删除L2中元素 
			pnext = pcur->next;
			free(pcur);
			pcur = pnext;	
		}
		else{//大于L1往后走 
			qpre = qcur;
			qcur = qcur->next;
		}
	}
	//L1若先走完,将剩余L2直接接到末尾 
	if(qcur == NULL){
		qpre->next = pcur;
	}
	L2->next = NULL;//求交集后L2必为空表 
}

交集

求交集:取出L1中每个元素与L2比较,假设L1,L2首个元素为a,b

  • 1.1,a<b,直接删除a,a后移
  • 1.2,a=b,保留a,a后移
  • 1.3,a<b,b后移
  • 2,当L1,L2均未走完时,执行步骤1
  • 3,当L2比L1先走完时,L1剩余部分直接删除
void Intersect_Sorted(LinkList &L1,LinkList L2)
{
	if(!Valid(L1) || !Valid(L2)){
		cout<<"L1,L2无交集!"<<endl;
		return; 
	}
	
	LNode *qcur,*qpre,*pcur;
	qpre = L1;
	qcur = qpre->next;
	pcur = L2->next;
	//L1,L2均未走完
	while(qcur != NULL && pcur != NULL){
		if(qcur->data > pcur->data){
			pcur = pcur->next;
		}
		else if(qcur->data == pcur->data){
			qpre = qcur;
			qcur = qcur->next;
		}
		else{
			qpre->next = qcur->next;
			free(qcur);
			qcur = qpre->next;
		}
	}
	//当L1未走完,将剩余节点全部释放 
	while(qcur != NULL){
		qpre->next = qcur->next;
		free(qcur);
		qcur = qpre->next;
	}
} 

测试

测试文件链表测试.txt内容

3 5 0 19 8 5 6 4 12 7

测试文件链表测试2.txt内容

8 8 8 3 0 2 512 6 7 123 0 99

测试结果
在这里插入图片描述

小感想

  • 今天刚考完数据结构笔试,觉得还行,但作为一名计算机专业学生,不能仅仅以考试来要求自己。够取的不错的分数固然是好事,但是否也会因此自我麻痹,飘飘乎?实际上自己并没有掌握得多好,得认清自己。课程虽然结束了,但是我和它的征程才刚开始,学以致用才是最终目的。很多东西学了没有付诸实践,相当于你只是把大脑借给人家当了中转站,走了时候他还留下一片狼藉,又得花时间清理,耗时耗力呀。今后得养成阶段性复习的习惯,知识真是温故而知新,前人智慧真是奇妙无穷~
  • 学一样东西记得多问几个为什么,清楚自己的目的,有目标的吸取知识,构建体系
  • 本想在考前将所有知识点整理一遍呢,结果好像拖延症有些严重。今天收到一个小可爱的感谢,瞬时成就感爆棚,觉得自己做的事很有意义,所以我会坚持把数据结构的知识更新完,加油小伙子~

完整Code

#include<iostream>
using namespace std;
#include"stdlib.h"
#include<fstream>

int count=0; 
typedef struct LNode
{
	int data;
	LNode * next;
}*LinkList,LNode;

//尾插法 
void CreateLinkList(LinkList &L)
{
	//初始化:空表,仅有头结点 
	L=(LinkList)malloc(sizeof(LNode));
	L->next = NULL;
	
	LNode *p,*prear;//分别代表申请的节点,当前链表的最后一个节点指针 
	int tmp;//存储文件输入的中间值 
	count++;
	fstream inFile;
	if(count == 1)inFile.open("链表测试.txt",ios::in);
	else inFile.open("链表测试2.txt",ios::in);
	
	if(!inFile)cout<<"文件打开失败!"<<endl;
	
	prear=L;//尾 
	while(true)
	{
		inFile>>tmp;
		if(!inFile)break;
		
		p=(LNode*)malloc(sizeof(LNode));
		p->data=tmp;
		p->next=NULL;
		//连接部分 
		prear->next=p;
		prear=p;
	}
	inFile.close(); 
}
//输入有效性验证 
bool Valid(LinkList L)
{
	//销毁或是不存在 
	if(L==NULL){
		cout<<"表已销毁!"<<endl;
		return false;
	}
	//空表判断 
	if(L->next==NULL){
		cout<<"空表!"<<endl;
		return false;
	}
	return true;
}
//遍历链表 
void TraverseLinkList(LinkList L)
{
	if(!Valid(L))return; 
	//非空表 
	LNode *pcur;
	pcur=L->next;
	while(pcur)
	{
		cout<<pcur->data<<" ";
		pcur=pcur->next;	
	} 
	cout<<endl;
} 
//原地插入排序 
void InsertSort(LinkList &L)
{
	if(!Valid(L))return;//有效性验证 
	//非空表 
	LNode *qcur,*qnext;//原链表的当前(即将)要插入到新表的指针,记录当前节点的下一个节点(防止原链表断链) 
	LNode *pcur,*ppre;//新(排序后)链表的用来查找插入位置(一个指针即可)并且插入qcur的两个指针(必须两个指针,无法访问前一个节点) 

	qcur = L->next;//防止断链
	L->next = NULL;//摘下头结点
	//对原链表所有节点依次处理 
	while(qcur != NULL){
		//每次从新链表头部开始查找插入的合适位置 
		ppre = L;
		pcur = ppre->next;
		while(pcur != NULL && qcur->data > pcur->data){//新链表未走完且插入值大于当前值 
			ppre = pcur;
			pcur = pcur->next;
		}
		
		qnext = qcur->next;//保留原链表下一个节点,防止断链
		//插入新链表 
		qcur->next = pcur;
		ppre->next = qcur;
		qcur = qnext;//更新为下一个节点 
	} 
} 
//排好序后删除重复点,只保留第一次出现的值 
//化简为集合:每个数只出现一次
//思路与动态数组类似:排好序后,当前元素只与其前驱相比,若相等,删除;若不等后移 
void Set_Sorted(LinkList &L)
{
	if(!Valid(L))return;//有效性检验 
	LNode *pcur,*ppre;
	ppre = L;
	pcur = ppre->next;
	L->data = -11111;//头节点赋为不可能出现的值 
	while(pcur){
		if(ppre->data != pcur->data){//当前值不等于前驱,后移 
			ppre = pcur;
			pcur = pcur->next;
		}
		else{//当前值等于前驱删除当前点 
			ppre->next = pcur->next;
			free(pcur);
			pcur = ppre->next;
		}
	}
}
//已经升序排列的两个序列合并依旧有序,并接到L1 
//方案一:可先将两个序列拼接,接着排序,最后去重
//方案二:分别对两个序列扫描,假设L1,L2当前首元素分别为a,b
//1.1,b < a,b直接插入a前,b后移
//1.2,b = a,删除b,b后移
//1.3,b > a,a后移 
//2,当L1,L2均未走到头 执行步骤1
//当L1先到头时,L2剩余部分直接接到L1尾部 
void Merge_Sorted(LinkList &L1,LinkList L2)
{
	//有效性验证 
	if(!Valid(L1) || !Valid(L2)){
		if(L1->next == NULL)L1 = L2;
	}
	LNode *qcur,*qpre,*pcur,*pnext;
	qpre = L1;
	qcur = qpre->next;
	pcur = L2->next;
	while(qcur != NULL && pcur != NULL){//两序列均未走完 
		if(pcur->data < qcur->data){//L2元素插入L1 
			pnext = pcur->next;//防止L2断链
			//连接到L1 
			pcur->next = qcur;
			qpre->next = pcur;
			pcur = pnext;//L2后移一位 
		}
		else if(pcur->data == qcur->data){//相等删除L2中元素 
			pnext = pcur->next;
			free(pcur);
			pcur = pnext;	
		}
		else{//大于L1往后走 
			qpre = qcur;
			qcur = qcur->next;
		}
	}
	//L1若先走完,将剩余L2直接接到末尾 
	if(qcur == NULL){
		qpre->next = pcur;
	}
	L2->next = NULL;//求交集后L2必为空表 
}
//求交集:取出L1中每个元素与L2比较,假设L1,L2首个元素为a,b 
//1.1,a<b,直接删除a,a后移
//1.2,a=b,保留a,a后移
//1.3,a<b,b后移 
//2,当L1,L2均未走完时,执行步骤1
//3,当L2比L1先走完时,L1剩余部分直接删除 
void Intersect_Sorted(LinkList &L1,LinkList L2)
{
	if(!Valid(L1) || !Valid(L2)){
		cout<<"L1,L2无交集!"<<endl;
		return; 
	}
	
	LNode *qcur,*qpre,*pcur;
	qpre = L1;
	qcur = qpre->next;
	pcur = L2->next;
	
	while(qcur != NULL && pcur != NULL){
		if(qcur->data > pcur->data){
			pcur = pcur->next;
		}
		else if(qcur->data == pcur->data){
			qpre = qcur;
			qcur = qcur->next;
		}
		else{
			qpre->next = qcur->next;
			free(qcur);
			qcur = qpre->next;
		}
	}
	//当L1未走完,将剩余节点全部释放 
	while(qcur != NULL){
		qpre->next = qcur->next;
		free(qcur);
		qcur = qpre->next;
	}
} 
//求交集:先把任意表排好序,删除重复元素至保留一个;思路与数组一致
//一开始是使用直接在L1上找到比当前点小就删除,忽略了相离时Amin > Bmax,导致输出多余的交集
//解决方案1:在循环结束后,添加一个判断,若是L2先于L1到尾,则从L1的当前点开始释放

//解决方案2:一开始就将L1头结点断开,作为存储交集的空表
//存在缺点:对于L1最后一个元素必须单独判断一次,是因为pcur,pnext指针只可用pcur判断,
//使用相对位置靠前的指针一般会存在最后元素指向空问题 ;还需单独释放多余节点 

//交集为空: 
//A(空,非空) B(空,非空)排列组合为四种交集为空情况
//相离:Amin > Bmax ; Amax < Bmin 

//交集非空: 
//相包:A (- B; B (- A
//相交: 
void IntersectSetList_Link(LinkList &L1, LinkList &L2)
{
	if(L1 == NULL || L2 == NULL || L1->next == NULL || L2->next == NULL)	
	{
		cout<<"无交集!"<<endl;
		return;
	}
	
	//确保两个表示升序的集合 
//	InsertSortList_Link(L1);
//	ConvertToSet_Link(L1);
	
//	InsertSortList_Link(L2);
//	ConvertToSet_Link(L2);
	
	//取交操作 
	LNode *ppre, *pcur, *qcur;
	
	ppre = L1;
	pcur = ppre->next;
	
	qcur = L2->next;
	
	//求出交集,但L1可能包含非交集元素,所以循环结束后将释放多余元素 
	while(pcur && qcur)
	{
		//L1的当前元素<L2的当前元素,由于L2升序且为集合,说明pcur->data不会在L2中出现,所以直接删除pcur即可 
		if(pcur->data < qcur->data)
		{
			ppre->next = pcur->next;//删除 
			pcur = ppre->next;//更新当前点 
		}
		//相等则保留,继续遍历L1 
		else if(pcur->data == qcur->data)
		{
			ppre = pcur; 
			pcur = pcur->next;
			
			qcur = qcur->next;//L2也移动一位 
		}
		//大于则移动L2,再次用循环判断 
		else 
		{
			qcur = qcur->next;
		}
	}
	
	//判断L1是否有多余元素 
	if(qcur == NULL)
	{
		while(pcur != NULL)
		{
			ppre->next = pcur->next;
			
			free(pcur);
			
			pcur = ppre->next;
	
		}
	}
			
} 
int main()
{
	LinkList L1,L2;
	int n;
	
	CreateLinkList(L1);
	cout<<"创建L1:";TraverseLinkList(L1);
	InsertSort(L1);
	cout<<endl<<"升序排列:";TraverseLinkList(L1);
	Set_Sorted(L1); 
	cout<<endl<<"去重排列:";TraverseLinkList(L1);
	
	CreateLinkList(L2);
	cout<<endl<<"创建L2:";TraverseLinkList(L2);
	InsertSort(L2);
	cout<<endl<<"升序排列:";TraverseLinkList(L2); 
	
//	Merge_Sorted(L1,L2);
//	cout<<endl<<"合并后:";TraverseLinkList(L1);
//	cout<<endl<<"L2:";TraverseLinkList(L2); 
	Intersect_Sorted(L1,L2); 
	cout<<endl<<"求交集后:";TraverseLinkList(L1);
	cout<<endl<<"L2:";TraverseLinkList(L2); 
	return 0;
 } 
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值