前言:给xdm一点参考吧,有问题可以私聊交流。
实验题目:单链表和二叉排序树综合实现集合的有关操作
一、概述
应用需求:实现大规模数据集合创建、显示,元素查找、插入、排序、删除以及集合的交、并、差、补,求势,求top-k问题。
设计目标:①能够实现应用需求中的基本功能
②能够生成大规模随机数据集
③拥有性能监测模块,测量各类算法在不同场景下的时空效能
④能对其进行时空复杂度分析,和其他方案对比,得出有效结论。
设计方案:
1.存储方案设计:由于实验应用要求生成大规模随机数据集,不容易预测储存规模,为了提高空间利用率,我选择采用链式存储结构,主要的框架采用单链表,利于提高插入、删除等算法的时间效率,降低时间复杂度。在排序部分,采用二叉排序树,以简化逻辑关系,利于开发。
2.函数设计:为了方便程序后期的修改和调试,采用了模块化开发函数,分为若干个小的函数模块,以便重复调用,再用主函数将其链接调用。
3.数据测试设计:①采用rand函数随机生成不同规模的数据集,多次进行常规实验测试。②手动规定小规模特殊数据测试。
关键算法://创建一个大规模无重复元素的集合(单链表)
Node* createGather(Node* L);
//利用已有链表创建二叉排序树
TreeNode* createTree(Node* L);
//利用二叉排序树中序遍历 排序单链表L(目标集合)
Node* sortList(Node* L);
//顺序查找集合中的元素
Node* findNode(Node* L, int data);
//采用头插法向集合中插入元素
void headInsert(Node* L, int data);
二、问题分析
要实现大规模数据集合创建、显示,元素查找、插入、排序、删除以及集合的交、并、差、补,求势以及求top-k问题,即数据对象为大规模数据集合。根据集合结构的特点,其数据关系为:数据元素之间除了“属于同一集合”的关系外,别无其他关系。可以看成若干个毫不相干的数据。需要实现的主要操作:对单个集合中元素的查找、排序、插入、删除操作,以及对两个数据集合之间的求交集、并集、差集、补集、以及求势、求Top-k问题。
对于单个集合的主要操作中,在不同的数据结构下,问题规模对各个操作的影响不同。例如:采用顺序表时,查找和排序比较方便,但是数据规模一大,就会导致插入删除的时空复杂度非常大;采用链表时,插入删除比较方便,但是排序成了比较麻烦的事情。对于集合之间的交、并、差、补等操作,需要不断的插入、删除,所以采用链表时,问题规模对于主要操作的时间复杂度影响优于顺序表。
在算法设计方面,由于主体框架采用链表,部分功能采用二叉排序树的数据结构方案,所以查找算法采用顺序查找,时间复杂度为O(n),插入、删除操作为O(1)。对于单链表的排序操作,很难有一种高效率的排序算法,典型的就是归并排序和直接插入排序,但是对于大数据集合,这两种算法效率并不可观,所以我打算创新地使用二叉排序树的性质,对链表进行排序。在集合间的操作时,首先创建一个新链表,判断两个集合中谁的元素比较多,在进行遍历比较,利用尾插法向新链表插入元素,最后进行去重,以此运用集合的交并差的性质完成算法。
三、设计和开发
1. ADT设计
根据需求分析,设计抽象数据类型,确定数据对象、数据关系以及操作。
ADT List{
数据对象:D={ai|ai∈ElemSet,i=1,2,···,n,n=RANDMAX}
数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2,···,n}
基本操作:
IniList(&L)
操作结果:构造一个空的线性表L。
CreateGather(Node* L)
初始条件:线性表L已初始化。
操作结果:创建一个大规模无重复元素的集合。
has(Node* L, int data)
初始条件:线性表L已存在。
操作结果:判断data是否在单链表L中。
headInsert(Node* L, int data)
初始条件:线性表L已存在。
操作结果:采用头插法向集合中插入元素。
tailInsert(Node* L, int data)
初始条件:线性表L已存在。
操作结果:采用尾插法向集合中插入元素。
delete(Node* L, int data)
初始条件:线性表L已存在。
操作结果:删除单链表中的元素(按值删除)。
sortList(Node* L)
初始条件:线性表L已存在。
操作结果:利用二叉排序树中序遍历 排序单链表。
findNode(Node* L, int data)
初始条件:线性表L已存在。
操作结果:顺序查找集合中的元素。
removeDuplicate(Node* L)
初始条件:线性表L已存在。
操作结果:去除集合中的重复元素。
Union(Node* L1, Node* L2)
初始条件:线性表L1已存在,线性表L2已存在。
操作结果:对两个集合求并集。
Intersection(Node* L1, Node* L2)
初始条件:线性表L1已存在,线性表L2已存在。
操作结果:对两个集合求交集。
Difference(Node* L1, Node* L2)
初始条件:线性表L1已存在,线性表L2已存在。
操作结果:对两个集合求差集。
isSubSet(Node* l1, Node* l2)
初始条件:线性表L1已存在,线性表L2已存在。
操作结果:判断L1是否是L2的子集。
complement(Node* L1, Node* L2)
初始条件:线性表L1已存在,线性表L2已存在。
操作结果:对两个集合求补集。
card(Node* L)
初始条件:线性表L已存在。
操作结果:求集合的势。
topK(Node* L, int k)
初始条件:线性表L已存在。
操作结果:top-k问题。
printList(Node* L)
初始条件:线性表L已存在。
操作结果:打印输出链表中的所有元素。
inOrder(TreeNode* T, Node* L)
初始条件:线性表L已存在,排序二叉树T已存在。
操作结果:中序遍历二叉排序树。
bstInsert(TreeNode** T, int data)
初始条件:线性表L已存在,排序二叉树T已存在。
操作结果:插入元素到二叉排序树排序树。
reateTree(Node* L)
初始条件:线性表L已存在,排序二叉树T已初始化。
操作结果:利用已有链表创建二叉排序树。
}ADT List
2.存储方案
分析不同存储方案的优势和不足,根据应用需要,选择恰当的存储结构
对顺序存储和链式存储两种存储方案分别从空间性能和时间性能进行比较。
【空间性能】
(1)存储空间的分配
顺序存储的存储空间必须预先分配,元素个数扩充受限制,容易造成空间浪费或空间溢出。而链式存储不需要为其预先分配空间,只要内存空间允许,链表中的元素个数就没有限制。由此可见,当线性表的长度变化较大,难以预估储存规模时,宜采用链表作为存储结构。
(2)存储密度的大小
链式存储除设置数据域外,还需额外设置指针域,存储密度低,空间利用率低;若不考虑顺序表中的空闲区域,顺序存储几乎可以做到空间利用率100%,但是链表只有50%。由此可见,当线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序存储作为存储结构。
【时间性能】
(1)存取元素的效率
顺序存储时由数组实现,随机存取,取值操作效率高;而链式存储是一种顺序存取结构,按位置访问时,依次遍历,时间复杂度为O(n),取值操作效率低。由此可见,当线性表的主要操作是和元素位置紧密相关的取值操作,很少做插入删除时,宜采用顺序存储作为存储结构。
(2)插入和删除操作的效率
对于链式存储,在确定插入和删除操作的位置后,无需移动数据,只需要修改指针,时间复杂度为O(1);而对于顺序存储,进行删除插入操作时,需要移动将近一半的结点,时间复杂度为O(n)。由此可见,对于频繁进行插入删除操作的线性表,宜采用链表作为存储结构。
综上所述,结合本次实验的具体应用需求,一方面需要随机生成大规模数据集合,其元素个数无法事先预测,且变化较大;另一方面在集合的交、并、差、补的操作中,需要对集合中的元素频繁的进行插入删除操作。所以最终采用链式存储作为存储方案。
3.关键算法
选用伪代码或流程图对ADT中的操作进行描述,并从理论上分析算法的性能
给出关键算法实现代码(注意不要不加选择地黏贴所有代码)。
(一)单个集合的操作
(1)创建一个大规模无重复元素的集合
【算法实现】
Node* createGather(Node* L) {
start = clock();//算法性能测量模块 ①
int randnum;
srand((unsigned)time(0));//srand初始化随机数
int maxNum = rand() % RAND_MAX;//调用rand在0~32767随机生成集合大小
for (int i = 0; i < maxNum; i++) {
randnum = rand() % RAND_MAX;//随机生成一个数据元素
headInsert(L, randnum);//采用头插法插入集合
}
over = clock();//算法性能测量模块 ②
double during = (double)(over - start) / CLOCKS_PER_SEC;//算法性能测量模块 ③
printf("\n创建集合操作用时 %lf 秒!\n", during);//算法性能测量模块 ④
return L;
}
【算法性能】
该算法拥有完备的算法性能测量模块(见上述代码备注),主要是利用rand函数获取随机时间种子来模拟生成一些随机数,再利用头插法,将其插入到已经初始化的链表L中。在头插法中有判断元素是否重复的函数,不再赘述。对于时间复杂度,主要受问题规模即数据元素的个数影响,由于仅仅使用一个for循环,理论算法时间复杂度为O(n)。
(2)插入算法(包括头插法和尾插法,性能相似,这里以头插法进行分析)
【算法实现】
void headInsert(Node* L, int data) {
if (has(L, data)) return;//判断data在已有的集合中是否已经存在的函数
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = L->next;
L->next = node;
L->data++;
}
【算法性能】
该算法首先通过has函数判断data在已有的集合中是否已经存在,存在则直接返回;不存在进行头部插入,关键算法语句为4~6行,再利用头节点的data域存放表中元素个数,每成功插入一个元素,则自增一次。不受问题规模的影响,尾插法相似;故而算法的时间复杂度为O(1)。
(3)删除算法
【算法实现】
int delete(Node* L, int data)
{
while (node) {
if (node->data == data) {
preNode->next = node->next;
free(node);
L->data--;
return TRUE;
}
preNode = node;
node = node->next;
}
return FALSE;
}
【算法性能】
该算法首先通过while循环和if判断找到与data相同的元素,再通过preNode->next = node->next操作,断开要删除元素的结点,再通过free释放内存。该操作关键算法不受问题规模的影响,与插入类似,故而时间复杂度为O(1)。
(4)查找算法
【算法实现】
Node* findNode(Node* L, int data) {
if (!has(L, data)) return NULL;
else {
while (L->data != data) {
L = L->next;
}
}
return L;
}
【算法性能】
对于查找算法,我采用的是顺序查找,关键在于while循环,一个个遍历的方式查找与data值相同的元素,并返回一个指向他的指针,一遍后面对他进行各种操作。该算法与集合中的元素个数有关,并且只有一个循环,循环中有一个与问题规模无关的语句,故而是线性阶的,其时间复杂度为O(n)。
(5)排序算法(采用将单链表转化成二叉排序树,进行中序遍历)
【算法实现】
//利用已有链表创建二叉排序树
TreeNode* createTree(Node* L) {
TreeNode* T = NULL;
while (L) {
bstInsert(&T, L->data);
L = L->next;
}
return T;
}
//利用链表构造二叉排序树,中序遍历二叉排序树得到升序
Node* sortList(Node* L){
TreeNode* T = createTree(L);
Node* nodes = initList();
inOrder(T, nodes);
return nodes;
}
//中序遍历二叉排序树
void inOrder(TreeNode* T, Node* L) {
if (T) {
inOrder(T->lchild, L);
tailInsert(L, T->data);
inOrder(T->rchild, L);
}
}
【算法性能】
对于排序算法,首先通过已有的创建二叉排序树的算法,通过已有的单链表构造出一个二叉排序树,根据二叉排序树的性质:二叉排序树的中序遍历即为数据元素的顺序序列,因此采用中序遍历即可将原有的链表排好序,在中序遍历的过程中,在利用尾插法将将原来的集合元素升序排列成新的链表,并利用原来的头结点指向。如果有n个元素,则需要递归输出n次,故时间复杂度为O(n)。该算法用到辅助栈,故空间复杂度为O(n)。
(6)求集合的势
【算法实现】
int card(Node* L) {
return L->data;
}
【算法性能】
集合的势就是集合中元素的个数,由于创建集合的时候,令头节点的data域存放集合中元素的个数,然后每插入一个元素,data域自增一次,所以直接返回头节点的data域的值即可。则时间复杂度为O(1)。
(7)求top-K问题
【算法实现】
int topK(Node* L, int k) {
int length = L->data;
L = sortList(L);
printf("length = %d\n", L->data);
int target = length - k+1;
printf("%d\n", target);
for (int i = 0; i < target; i++) {
L = L->next;
}
return L->data;
}
【算法性能】
该算法首先获取集合中的元素个数,再对集合进行排序,然后由于target = length - k+1;遍历寻找target位置的元素并返回即可。时间复杂度为O(n)。
(二)集合间的操作
(1)求两个集合之间的交集
【算法实现】
Node* Intersection(Node* L1, Node* L2) {
Node* L3 = initList();
if (L1->data < L2->data) {
for (int i = 0; i < L1->data; i++) {
if (has(L2, L1->data))
tailInsert(L3, L1->data);
L1 = L1->next;
}
}
else {
for (int i = 0; i < L2->data; i++) {
if (has(L1, L2->data))
tailInsert(L3, L2->data);
L2 = L2->next;
}
}
return L3;
}
【算法性能】
该算法首先重新初始化一个链表,再判断两个集合谁的元素比较多,再对较少的元素的集合和较多元素的集合中的元素比较,若是有相同的元素,则通过尾插法插入到新建的集合中,再进行下一轮比较。该算法主要是应用一个for循环,影响效率的主要原因是问题规模。故而时间复杂度为O(n)。由于需要重新建立一个新表存放筛选出来的集合元素,故而空间复杂度也为O(n)。
(2)求两个集合之间的并集
【算法实现】
Node* Union(Node* L1, Node* L2) {
Node* L3 = initList();
while (L1) {
tailInsert(L3, L1->data);
L1 = L1->next;
}
while (L2) {
tailInsert(L3, L2->data);
L2 = L2->next;
}
return removeDuplicate(L3);
}
【算法性能】
该算法首先重新初始化一个链表,再依次将两个集合中的元素插入到新创建的集合中,再利用已有的去除集合中的重复元素的函数,对重复元素进行去重操作,最后得到的就是两个集合的并集。该算法主要是应用两个独立的while循环,影响效率的主要原因是问题规模。故而时间复杂度为O(n)。由于需要重新建立一个新表存放筛选出来的集合元素,故而空间复杂度也为O(n)。
(3)求两个集合之间的差集
【算法实现】
Node* Difference(Node* L1, Node* L2) {
Node* L3 = initList();
if (L1->data > L2->data) {
for (int i = 0; i < L1->data; i++) {
if (!has(L2, L1->data))
tailInsert(L3, L1->data);
L1 = L1->next;
}
}
else {
for (int i = 0; i < L2->data; i++) {
if (!has(L1, L2->data))
tailInsert(L3, L2->data);
L2 = L2->next;
}
}
return removeDuplicate(L3);
}
【算法性能】
该算法与求交集的流程类似,首先重新初始化一个链表,再判断两个集合谁的元素比较多,再对较少的元素的集合和较多元素的集合中的元素比较,若是不同的元素,则通过尾插法插入到新建的集合中,再进行下一轮比较。该算法主要是应用一个for循环,影响效率的主要原因是问题规模。故而时间复杂度为O(n)。由于需要重新建立一个新表存放筛选出来的集合元素,故而空间复杂度也为O(n)。
(4)求两个集合之间的补集
【算法实现】
Node* complement(Node* L1, Node* L2) {
Node* L3 = initList();
if (isSubSet(L1, L2)) {
L2 = L2->next;
while (L2) {
if (!has(L1, L2->data)) {
tailInsert(L3, L2->data);
}
L2 = L2->next;
}
}
else if (isSubSet(L2, L1)) {
L1 = L1->next;
while (L1) {
if (!has(L2, L1->data)) {
tailInsert(L3, L1->data);
}
L1 = L1->next;
}
}
else {
return NULL;
}
return L3;
}
【算法性能】
该算法首先重新初始化一个链表,再判断两个集合是不是有子集的关系,若是没有,直接返回空集;若是有,判断谁是谁的子集,再对子集的元素的集合和较多元素的集合中的元素比较,若是不同的元素,则通过尾插法插入到新建的集合中,再进行下一轮比较。该算法主要是应用一个while循环,影响效率的主要原因是问题规模。故而时间复杂度为O(n)。由于需要重新建立一个新表存放筛选出来的集合元素,故而空间复杂度也为O(n)。
4.实现总结
总结关键难点以及实现和调试过程中遇到的问题、解决思路和办法,给出部分系统实现和调试界面
(1)在实验调试功能环节,求并集的操作,进行多次的测试,依然是两个集合的元素个数相加,后来发现是缺少去除重复元素的环节。例如:
经过加入去重的操作,得到正确的调试结果。如下:
(2)在实现排序功能中,将链表转化成二叉排序树时,将元素插入到二叉排序树操作里,功能没办法实现。后来发现时因为传参是要传入二级指针,对树进行操作,一级指针可以运行但是功能无法实现。
修改之后,功能完成。
四、实验研究
1.实验目的
①研究问题规模即数据元素数量,对集合间插入、删除、排序、查找算法的时间效率的影响
②研究问题规模即数据元素数量,对集合的交、并、差、补算法的时间效率的影响
2. 实验数据
实验数据采用两组规模不同的随机生成的数据集合。
第一组:规模有限的两个数据集合。(便于验证规律,形成对比)
第二组:规模较大的两个数据集合。(便于研究时间效率影响)
3. 实验方案及实验结果
第一组:规模有限的两个数据集合。(便于验证规律,形成对比)
L1元素:
L2元素:
(1)插入算法
(2)删除算法
(3)排序算法
(4)查找算法
(5)并集算法(差集、补集、交集类似,不做赘述)
第二组:规模较大的两个数据集合。(便于研究时间效率影响)
L1元素:
L2元素:
(1)插入算法
(2)删除算法
(3)排序算法(由于数据量太大,所以截取一段表示)
(4)查找算法
(5)并集算法(差集、补集、交集类似,不做赘述)
4. 实验结果评价分析
根据对实验方案的测试,当问题规模越大,耗时越长。但是在插入、删除操作中,时间复杂度为O(1),问题规模对时间效率影响不大,这是由于单链表的性质决定的。在排序操作中,小规模几乎显示为0,但是大规模数据,时间测量模块显示0.377s,影响较大。由于需要重新创建排序二叉树,又要对二叉树重新遍历,转化成链表,所以导致时间复杂影响比较大。查找采用顺序查找,时间复杂度为O(n),由于是线性阶,所以在上万个数据下,无法明显看出影响,但是大致是随着问题规模变大而变大的。
在集合间的操作中,主要以并集操作为代表研究集合间操作的问题规模影响算法的时空效率。在20个数据元素下,并集操作用时将近0s,但是在两个上万的集合操作时,用时0.853s。和插入查找比较,并集操作时间复杂度还是比较高的。
总体来说,①问题规模对集合间插入、删除、查找算法的时间效率的影响不大,但是对于二叉排序树的排序算法,影响较大。
②问题规模对集合的交、并、差、补算法的时间效率的影响较大。
五、总结与优化
(1)现有方案不足:去重操作单独作为一个函数,仅在并集、补集中调用,重用度不高,而且算法时间效率不高,并不是一个好的处理方法。
改进方案:在插入的时候之间做判断,若集合中已经存在该元素,直接返回;若是不存在元素,则插入。这样就避免存在重复元素的问题。
(2)现有方案不足:经过测试之后发现,利用二叉排序树进行排序,算法时间性能并没有特别高。
改进方案:可以采用归并排序或者直接插入排序,经过分析,在大规模的数据集合中,归并排序时间效率优于直接插入排序。可以将基于二叉排序树的排序算法改成归并排序。
六、心得和体会
通过对本次实验的研究,自己首次独立、完整地成功开发出了一个能够实现基本功能的程序。对单链表和二叉排序树的性质和操作重新熟悉和时间,温故而知新。在本次实验中,我发现单链表的插入删除操作效率极其高,二叉排序树的逻辑关系虽然明显,但是占用递归栈,空间复杂度不好,而且时间效率一般。这次试验也检验了我编程方面的很多短板,在以后学习中,我应该不断提高自己的编程能力,学习能力,应用实践能力。
下面是完整源码:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define TRUE 1
#define FALSE 0
#define CLOCKS_PER_SEC ((clock_t)1000)
typedef struct Node {
int data;
struct Node* next;
}Node;
typedef struct TreeNode {
int data;
struct TreeNode* lchild;
struct TreeNode* rchild;
}TreeNode;
//算法时间性能测量模块
clock_t clock(void);//定义函数
clock_t start, over;//定义全局变量
//功能菜单
void menu();
//初始化单链表
Node* initList();
//创建一个大规模无重复元素的集合(单链表)
Node* createGather(Node* L);
//判断data是否在单链表L中
int has(Node* L, int data);
//采用头插法向集合中插入元素
void headInsert(Node* L, int data);
//采用尾插法向集合中插入元素
void tailInsert(Node* L, int data);
//删除单链表中的元素(按值删除)
int delete(Node* L, int data);
//利用二叉排序树中序遍历 排序单链表L(目标集合)
Node* sortList(Node* L);
//顺序查找集合中的元素
Node* findNode(Node* L, int data);
//去除集合中的重复元素(集合并集补集等操作中调用)
Node* removeDuplicate(Node* L);
//对两个集合求并集
Node* Union(Node* L1, Node* L2);
//对两个集合求交集
Node* Intersection(Node* L1, Node* L2);
//对两个集合求差集
Node* Difference(Node* L1, Node* L2);
//判断L1是否是L2的子集(对两个集合求补集中调用)
int isSubSet(Node* l1, Node* l2);
//对两个集合求补集
Node* complement(Node* L1, Node* L2);
//求集合的势
int card(Node* L);
//top-k问题
int topK(Node* L, int k);
//输出链表中的所有元素
void printList(Node* L);
//中序遍历二叉排序树
void inOrder(TreeNode* T, Node* L);
//插入元素到二叉排序树排序树
void bstInsert(TreeNode** T, int data);
//利用已有链表创建二叉排序树
TreeNode* createTree(Node* L);
int main() {
Node* L1 = initList();
Node* L2 = initList();
Node* L3 = initList();
Node* L4 = initList();
Node* L5 = initList();
Node* L6 = initList();
Node* findnode = NULL;//存放查找函数的结果
int choice;//定义操作的集合为此
int option;//定义功能选择数为此
int data;//定义插入的数据为此
int k;// top - k的k值为此
while (1) {
menu();
scanf("%d", &option);
switch (option) {
case 0:
exit(0);
break;
case 1:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2 。):\n");
scanf("%d", &choice);
if (choice == 1) { createGather(L1); printf("创建集合L1成功!\n"); }
else if (choice == 2) {
createGather(L2); printf("创建集合L2成功!\n");
}
else return printf("输入错误!\t (输入样例:1 对应 L1 ,2 对应 L2 。)");
break;
case 2:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2 。):\n ");
scanf("%d", &choice);
switch (choice) {
case 1:
printList(L1);
printf("集合L1显示完毕!\n");
break;
case 2:
printList(L2);
printf("集合L2显示完毕!\n");
break;
case 3:
printList(L3);
printf("集合L3显示完毕!\n");
break;
case 4:
printList(L4);
printf("集合L4显示完毕!\n");
break;
case 5:
printList(L5);
printf("集合L5显示完毕!\n");
break;
case 6:
printList(L6);
printf("集合L6显示完毕!\n");
break;
default:
printf("无效显示操作!");
}
break;
case 3:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2 。):\n ");
scanf("%d", &choice);
printf("请输入插入元素值:");
scanf("%d", &data);
switch (choice) {
case 1:
headInsert(L1, data);
printf("元素插入集合L1完毕!\n");
break;
case 2:
headInsert(L2, data);
printf("元素插入集合L2完毕!\n");
break;
case 3:
headInsert(L3, data);
printf("元素插入集合L3完毕!\n");
break;
case 4:
headInsert(L4, data);
printf("元素插入集合L4完毕!\n");
break;
case 5:
headInsert(L5, data);
printf("元素插入集合L5完毕!\n");
break;
case 6:
headInsert(L6, data);
printf("元素插入集合L6完毕!\n");
break;
default:
printf("无效插入操作!");
}
break;
case 4:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2 。):\n ");
scanf("%d", &choice);
printf("请输入删除元素值:");
scanf("%d", &data);
switch (choice) {
case 1:
delete(L1, data);
printf("集合L1中目标元素删除完毕!\n");
break;
case 2:
delete(L2, data);
printf("集合L2中目标元素删除完毕!\n");
break;
case 3:
delete(L3, data);
printf("集合L3中目标元素删除完毕!\n");
break;
case 4:
delete(L4, data);
printf("集合L4中目标元素删除完毕!\n");
break;
case 5:
delete(L5, data);
printf("集合L5中目标元素删除完毕!\n");
break;
case 6:
delete(L6, data);
printf("集合L6中目标元素删除完毕!\n");
break;
default:
printf("无效删除操作!");
}
break;
case 5:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2 。):\n ");
scanf("%d", &choice);
switch (choice) {
case 1:
L1= sortList(L1);
printf("集合L1排序完毕!\n");
break;
case 2:
L2=sortList(L2);
printf("集合L2排序完毕!\n");
break;
case 3:
L3=sortList(L3);
printf("集合L3排序完毕!\n");
break;
case 4:
L4=sortList(L4);
printf("集合L4排序完毕!\n");
break;
case 5:
L5 = sortList(L5);
printf("集合L5排序完毕!\n");
break;
case 6:
L6=sortList(L6);
printf("集合L6排序完毕!\n");
break;
default:
printf("无效排序操作!");
}
break;
case 6:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2 。):\n ");
scanf("%d", &choice);
printf("请输入查找元素值:");
scanf("%d", &data);
switch (choice) {
case 1:
findnode = findNode(L1, data);
printf("集合L1中目标元素查找完毕!(使用findnode保存地址)\n");
break;
case 2:
findnode = findNode(L2, data);
printf("集合L2中目标元素查找完毕!(使用findnode保存地址)\n");
break;
case 3:
findnode = findNode(L3, data);
printf("集合L3中目标元素查找完毕!(使用findnode保存地址)\n");
break;
case 4:
findnode = findNode(L4, data);
printf("集合L4中目标元素查找完毕!(使用findnode保存地址)\n");
break;
case 5:
findnode = findNode(L5, data);
printf("集合L5中目标元素查找完毕!(使用findnode保存地址)\n");
break;
case 6:
findnode = findNode(L6, data);
printf("集合L6中目标元素查找完毕!(使用findnode保存地址)\n");
break;
default:
printf("无效查找操作!");
}
break;
case 7:
L3 = Union(L1, L2);
printf("求并集完成!(已存入L3中)\n");
break;
case 8:
L4 = Intersection(L1, L2);
printf("求交集完成!(已存入L4中)\n");
break;
case 9:
L5 = Difference(L1, L2);
printf("求差集完成!(已存入L5中)\n");
break;
case 10:
L6 = complement(L1, L2);
printf("求补集完成!(已存入L6中)\n");
break;
case 11:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2...6 对应 L6):\n ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("集合L1的势为 : %d\n", card(L1));
break;
case 2:
printf("集合L2的势为 : %d\n", card(L2));
break;
case 3:
printf("集合L2的势为 : %d\n", card(L3));
break;
case 4:
printf("集合L2的势为 : %d\n", card(L4));
break;
case 5:
printf("集合L2的势为 : %d\n", card(L5));
break;
case 6:
printf("集合L2的势为 : %d\n", card(L6));
break;
default:
printf("无效求集合L%d操作!", choice);
}
break;
case 12:
printf("请输入操作对象(输入样例:1 对应 L1 ,2 对应 L2...6 对应 L6):\n ");
scanf("%d", &choice);
printf("请输入k的值:\n ");
scanf("%d", &k);
switch (choice) {
case 1:
printf("集合L1的top-%d为 : %d\n", k, topK(L1, k));
break;
case 2:
printf("集合L2的top-%d为 : %d\n", k, topK(L2, k));
break;
case 3:
printf("集合L3的top-%d为 : %d\n", k, topK(L3, k));
break;
case 4:
printf("集合L4的top-%d为 : %d\n", k, topK(L4, k));
break;
case 5:
printf("集合L5的top-%d为 : %d\n", k, topK(L5, k));
break;
case 6:
printf("集合L6的top-%d为 : %d\n", k, topK(L6, k));
break;
default:
printf("无效求top-k操作!\n");
}
break;
default:
printf("无效操作!\n");
}
}
}
//初始化单链表
Node* initList() {
Node* L = (Node*)malloc(sizeof(Node));
L->data = 0;
L->next = NULL;
return L;
}
//随机生成一个不同规模的数据集(线性表) 0~32767
Node* createGather(Node* L) {
start = clock();
int randnum;
srand((unsigned)time(0));
int maxNum = rand() % RAND_MAX;
for (int i = 0; i < maxNum; i++) {
randnum = rand() % RAND_MAX;
headInsert(L, randnum);
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n创建集合操作用时 %lf 秒!\n", during);
return L;
}
//判断元素在集合中是否已经存在
int has(Node* L, int data) {
while (L) {
if (L->data == data) {
return 1;
}
L = L->next;
}
return 0;
}
//头插法插入元素
void headInsert(Node* L, int data) {
if (has(L, data)) return;
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = L->next;
L->next = node;
L->data++;
}
//尾插法插入元素
void tailInsert(Node* L, int data) {
if (has(L, data)) return;
Node* node = L;
for (int i = 0; i < L->data; i++) {
node = node->next;
}
Node* n = (Node*)malloc(sizeof(Node));
n->data = data;
n->next = NULL;
node->next = n;
L->data++;
}
//删除集合中的元素(按值删除)
int delete(Node* L, int data) {
start = clock();
Node* preNode = L;
Node* node = L->next;
while (node) {
if (node->data == data) {
preNode->next = node->next;
free(node);
L->data--;
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n删除操作用时 %lf 秒!\n", during);
return TRUE;
}
preNode = node;
node = node->next;
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n删除操作用时 %lf 秒!\n", during);
return FALSE;
}
//查找(顺序查找)返回位置
Node* findNode(Node* L, int data) {
start = clock();
if (!has(L, data)) {
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n查找操作用时 %lf 秒!\n", during);
return NULL;
}
else {
while (L->data != data) {
L = L->next;
}
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n查找操作用时 %lf 秒!\n", during);
return L;
}
//去重
Node* removeDuplicate(Node* L) {
Node* cur = L;
Node* pre = NULL;
Node* next = NULL;
while (cur) {
pre = cur;
next = cur->next;
while (next) {
if (cur->data == next->data) {
pre->next = next->next;
L->data--;
}
else {
pre = next;
}
next = next->next;
}
cur = cur->next;
}
return L;
}
//并集
Node* Union(Node* L1, Node* L2) {
start = clock();
Node* L3 = initList();
while (L1) {
tailInsert(L3, L1->data);
L1 = L1->next;
}
while (L2) {
tailInsert(L3, L2->data);
L2 = L2->next;
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n并集操作用时 %lf 秒!\n", during);
return removeDuplicate(L3);
}
//交集
Node* Intersection(Node* L1, Node* L2) {
start = clock();
Node* L3 = initList();
if (L1->data < L2->data) {
for (int i = 0; i < L1->data; i++) {
if (has(L2, L1->data))
tailInsert(L3, L1->data);
L1 = L1->next;
}
}
else {
for (int i = 0; i < L2->data; i++) {
if (has(L1, L2->data))
tailInsert(L3, L2->data);
L2 = L2->next;
}
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n差集操作用时 %lf 秒!\n", during);
return L3;
}
//差集
Node* Difference(Node* L1, Node* L2) {
start = clock();
Node* L3 = initList();
if (L1->data > L2->data) {
for (int i = 0; i < L1->data; i++) {
if (!has(L2, L1->data))
tailInsert(L3, L1->data);
L1 = L1->next;
}
}
else {
for (int i = 0; i < L2->data; i++) {
if (!has(L1, L2->data))
tailInsert(L3, L2->data);
L2 = L2->next;
}
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n差集操作用时 %lf 秒!\n", during);
return removeDuplicate(L3);
}
//判断L1,是否为L2 的子集
int isSubSet(Node* l1, Node* l2) {
while (l1) {
if (!has(l2, l1->data)) return FALSE;
l1 = l1->next;
}
return TRUE;
}
// 补集
Node* complement(Node* L1, Node* L2) {
start = clock();
Node* L3 = initList();
if (isSubSet(L1, L2)) {
L2 = L2->next;
while (L2) {
if (!has(L1, L2->data)) {
tailInsert(L3, L2->data);
}
L2 = L2->next;
}
}
else if (isSubSet(L2, L1)) {
L1 = L1->next;
while (L1) {
if (!has(L2, L1->data)) {
tailInsert(L3, L1->data);
}
L1 = L1->next;
}
}
else {
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n补集操作用时 %lf 秒!\n", during);
return NULL;
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n补集操作用时 %lf 秒!\n", during);
return L3;
}
// 势
int card(Node* L) {
start = clock();
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n集合求势操作用时 %lf 秒!\n", during);
return L->data;
}
//top-k
int topK(Node* L, int k) {
start = clock();
int length = L->data;
L = sortList(L);
printf("length = %d\n", L->data);
int target = length - k+1;
printf("%d\n", target);
for (int i = 0; i < target; i++) {
L = L->next;
}
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\ntop-k问题操作用时 %lf 秒!\n", during);
return L->data;
}
//遍历输出所有元素
void printList(Node* L) {
if (!L) { printf("集合为空!\n"); return; }
Node* node = L->next;
int n = 0;
while (node) {
printf("node %d = %d\t", ++n, node->data);if ((n) % 10 == 0 && n != 0)printf("\n");
node = node->next;
}
}
//菜单
void menu()
{
printf("************单链表集合运算************\n\n");
printf("\t1.创建集合\n\n");
printf("\t2.集合数据显示\n\n");
printf("\t3.向集合中插入元素\n\n");
printf("\t4.删除集合中的元素\n\n");
printf("\t5.对集合进行排序\n\n");
printf("\t6.查找集合中的元素\n\n");
printf("\t7.求集合L1和集合L2的并集\n\n");
printf("\t8.求集合L1和集合L2的交集\n\n");
printf("\t9.求集合L1和集合L2的差集\n\n");
printf("\t10.求集合L1和集合L2的补集\n\n");
printf("\t11.求集合的势\n\n");
printf("\t12.求top-k问题\n\n");
printf("\t0.退出系统\n\n");
printf("************单链表集合运算************\n");
printf("请输入选项:");
}
//插入元素到二叉排序树排序树
void bstInsert(TreeNode** T, int data) {
if (*T == NULL) {
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = data;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
}
else if (data == (*T)->data) {
return;
}
else if (data < (*T)->data) {
bstInsert(&((*T)->lchild), data);
}
else {
bstInsert(&((*T)->rchild), data);
}
}
//利用已有链表创建二叉排序树
TreeNode* createTree(Node* L) {
TreeNode* T = NULL;
while (L) {
bstInsert(&T, L->data);
L = L->next;
}
return T;
}
//中序遍历二叉排序树
void inOrder(TreeNode* T, Node* L) {
if (T) {
inOrder(T->lchild, L);
tailInsert(L, T->data);
inOrder(T->rchild, L);
}
}
//利用链表构造二叉排序树,中序遍历二叉排序树得到升序
Node* sortList(Node* L) {
start = clock();
TreeNode* T = createTree(L);
Node* nodes = initList();
inOrder(T, nodes);
over = clock();
double during = (double)(over - start) / CLOCKS_PER_SEC;
printf("\n排序操作用时 %lf 秒!\n", during);
return nodes;
}