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;
}