数据结构加餐:三路划分、自省排序、文件归并排序

数据结构加餐1

1.快排之三路划分

在之前完成的快排仍然存在这一些问题,当重复数据较多时,快速排序选择的基值也会较不恰当,比如在一组全为2的数组中,选择的基值也会是2,但是这样就会触发快排的最坏情况,导致时间的增加,下面我们要介绍一个方法来解决这种情况
我们先实现基本的快排(Hoare 版本):

c 复制代码
//快速排序--Hoare版本
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//寻找基值
int Quick_Sort(int* arr, int left, int right)
{
	//假设第一个值为基值
	int mid = left;
	left++;

	while (left <= right)
	{
		//left负责寻找比基值大的元素
		while (left <= right && arr[left] < arr[mid])
		{
			left++;
		}
		//right负责寻找比基值小的元素
		while (left <= right && arr[right] > arr[mid])
		{
			right--;
		}
		//将左右数据交换,大数据在右边,小数据在左边
		if(left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}

	Swap(&arr[mid], &arr[right]);
	return mid;
}

void QuickSort(int* arr, int left, int right)
{
	//快速排序需要递归寻找基值
	//递归结束条件
	if (left >= right)
	{
		return;
	}
	
	//寻找基值
	int mid = Quick_Sort(arr, left, right);

	//递归区间
	//左区间:[left, mid-1], 右区间:[mid+1, right] 
	QuickSort(arr, left, mid-1);
	QuickSort(arr, mid+1, right);
}

void Print(int* arr, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = { 1, 22, 3, 43, 66, 27, 38, 49 };
	QuickSort(arr, 0, 7);

	Print(arr, 8);
	return 0;
}

更改后的代码为:

c 复制代码
void QuickSort(int* arr, int left, int right)
{
	//递归结束条件
	if (left >= right)
	{
		return;
	}

	//寻找基值
	int begin = left;
	int end = right;

	int cur = left + 1;
	int key = arr[left];
	while (cur <= right)
	{
		if (arr[cur] < key)
		{
			Swap(&arr[cur], &arr[left]);
			cur++;
			left++;
		}
		else if (arr[cur] > key)
		{
			Swap(&arr[cur], &arr[right]);
			right--;
		}
		else
		{
			cur++;
		}
	}

	QuickSort(arr, begin, left-1);
	QuickSort(arr, right+1, end);
}

int main()
{
	int arr[] = { 1, 22, 3, 43, 66, 27, 38, 49 };
	QuickSort(arr, 0, 7);

	Print(arr, 8);
	return 0;
}

2.快排之自省排序

自省排序值的就是在排序函数之中加上一个检查的功能,使得排序算法能够针对不同的环境做出不同的反应,这一部分较为简单,因为都是已经学过的内容,所以我们直接看改变即可

c 复制代码
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{
	if (left >= right)
		return;

	// 数组⻓度⼩于16的⼩数组,换为插⼊排序,简单递归次数
	if (right - left + 1 < 16)
	{
		//此时使用插入排序
		InsertSort(a + left, right - left + 1);
		return;
	}
	// 当深度超过2*logN时改⽤堆排序
	if (depth > defaultDepth)
	{
		HeapSort(a + left, right - left + 1);
		return;
	}

	depth++;//每次完成一次递归,将递归深度++

	int begin = left;
	int end = right;
	// 随机选key
	int randi = left + (rand() % (right - left + 1));
	Swap(&a[left], &a[randi]);
	int prev = left;
	int cur = prev + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	// [begin, keyi-1] keyi [keyi+1, end]
	IntroSort(a, begin, keyi - 1, depth, defaultDepth);
	IntroSort(a, keyi + 1, end, depth, defaultDepth);
}

void QuickSort(int* a, int left, int right)
{
	int depth = 0;
	int logn = 0;
	int N = right - left + 1;
	//计算logN的值
	for (int i = 1; i < N; i *= 2)
	{
		logn++;
	}
	// introspective sort -- ⾃省排序
	// 一般来说,当深度大于2倍的logN时,最合理,但也不是必须要使用2倍当作结点
	IntroSort(a, left, right, depth, logn * 2);
}

int* sortArray(int* nums, int numsSize, int* returnSize) {
	srand(time(0));
	QuickSort(nums, 0, numsSize - 1);
	*returnSize = numsSize;
	return nums;
}

3.文件归并排序

3.1外排序

外排序是指能够处理大量数据的排序算法,比如说10G的数据,外排序的数据不能够一次性地写入内存,只能放在读写比较慢的硬盘上,也就是写入文件中,通常会采用"排序-归并"的思想。先读取能够放在内存中的数据量,将这些数据排成有序后放在一个临时文件中,然后在归并阶段将一个个的小文件归并成一个文件即可

3.2归并排序的实现

3.2.1归并排序思想

先将一个一个的思想列出,最后有一个总截图:



3.2.2文件归并排序代码实现

c 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <time.h>
#include <stdlib.h>


//创建随机数,并将数据写到文件中
void CreateNDate()
{
    //造数据
    int n = 100;
    srand(time(0));
    const char* file = "data.txt";
    FILE* fin = fopen(file, "w");
    if (fin == NULL)
    {
        perror("fopen error");
        return;
    }

    for (int i = 0; i < n; ++i)
    {
        int x = rand() + i;
        fprintf(fin, "%d\n", x);
    }

    fclose(fin);
}

//qsort函数比较方法
int compare(const void* a, const void* b)
{
    return (*(int*)a - *(int*)b);
}

//读取数据,将数据排序后写入文件中
int ReadDataSortToFile(FILE* fout, int m, const char* file2)    
{
    //这个函数为将排序后的数据写到文件中,所以肯定要先进行排序
    //排序思路为先创建出一个数组,存储m个数据,对数组中的数据进行排序
    int* a = (int*)malloc(m * sizeof(int));
    if (a == NULL)
    {
        perror("malloc fail");
        return 0;
    }

    int x = 0;
    int j = 0;
    for (int i = 0; i < m; i++)
    {
        //这里要对最后一组数据读取进行特殊处理(最后一组数据读取可能读取不了m个数据):
        //如果读取不到数据了,就停止读取数据,也就是说实际上读取的数据个数其实只有j个
        if (fscanf(fout, "%d", &x) == EOF)
            break;
        a[j++] = x;
    }
    if (j == 0)//当没有数据读取时,直接返回即可
    {
        free(a);
        return 0;
    }

    //有了数组之后,就可以对数组中的数据进行排序
    qsort(a, j, sizeof(int), compare);//既然读取了j个数据,那么就对j个数据进行排序

    //排序完成,将数据写入文件中,这时只需要创建一个文件,然后写入数据即可
    FILE* fin = fopen(file2, "w");
    if (fin == NULL)
    {
        //注意要释放空间
        free(a);
        perror("fopen fail");
        return 0;
    }
    for (int i = 0; i < j; i++)
    {
        fprintf(fin, "%d\n", a[i]);//这里写入时,需要加上\n
    }

    //最后注意要释放空间
    free(a);
    a = NULL;
    fclose(fin);
    return j;
}

//将file1和file2文件中的数据排序着写入mfile文件中
void MergeFile(const char* file1, const char* file2, const char* mfile)
{
    //对于两个文件的排序,只需要一个数据一个数据地读取,然后将读取的数据进行比较,小的数据写入mfile文件即可
    //先打开三个文件
    FILE* fout1 = fopen(file1, "r");
    if (fout1 == NULL)
    {
        perror("fopen error");
        return;
    }
    FILE* fout2 = fopen(file2, "r");
    if (fout2 == NULL)
    {
        perror("fopen error");
        return;
    }
    FILE* mfin = fopen(mfile, "w");
    if (mfin == NULL)
    {
        perror("fopen error");
        return;
    }

    //然后是排序写入文件过程
    int x1 = 0;
    int x2 = 0;
    int ret1 = fscanf(fout1, "%d", &x1);//fscanf函数,如果读取失败或已经没有数据读取了,返回EOF
    int ret2 = fscanf(fout2, "%d", &x2);
    while (ret1 != EOF && ret2 != EOF)
    {
        if (x1 < x2)
        {
            fprintf(mfin, "%d\n", x1);
            ret1 = fscanf(fout1, "%d", &x1);
        }
        else
        {
            fprintf(mfin, "%d\n", x2);
            ret2 = fscanf(fout2, "%d", &x2);
        }
    }
    while (ret1 != EOF)
    {
        fprintf(mfin, "%d\n", x1);
        ret1 = fscanf(fout1, "%d", &x1);
    }

    while (ret2 != EOF)
    {
        fprintf(mfin, "%d\n", x2);
        ret2 = fscanf(fout2, "%d", &x2);
    }

    //最后要注意关闭文件
    fclose(fout1);
    fclose(fout2);
    fclose(mfin);
}

int main()
{
    //创建数据函数,也就是创建file数据文件
    //CreateNDate();

    //创建file1和file2文件,并且读取数据进行排序后写入file1和file2文件中
    //既然要多次使用一个方法,那么我们就将这个方法写成一个函数
    const char* file1 = "file1.txt";
    const char* file2 = "file2.txt";
    const char* mfile = "mfile.txt";

    FILE* fout = fopen("data.txt", "r");//fopen函数返回一个指向打开文件的指针,错误时返回NULL
    if (fout == NULL)
    {
        perror("fopen fail");
        return 0;
    }

    int m = 10;
    //函数参数的意思是:向fout文件中读取m个数据排序后写入file1文件中
    ReadDataSortToFile(fout, m, file1);
    ReadDataSortToFile(fout, m, file2);

    //将file1和file2文件中的数据排序着放到mfile文件中
    while (1)
    {
        //文件写入函数
        MergeFile(file1, file2, mfile);

        //关闭file1和file2文件
        remove(file1);//remove函数作用为删除一个文件,成功返回0,不成功返回-1,并设置错误原因
        remove(file2);
    
        //将mfile文件名称改为file1
        rename(mfile, file1);//remane函数:将文件重命名,成功时,返回0,失败时,返回非0

        //读取m个数据写到file2文件中
        //当读取不出数据时,也就是文件中的数据读取完了之后,就结束循环
        int n = 0;
        if ((n = ReadDataSortToFile(fout, m, file2)) == 0)
            break;
    }

	return 0;
}
相关推荐
xmh-sxh-131413 分钟前
常用的前端框架有哪些
java
权^17 分钟前
list的介绍(详解)
数据结构·list
老马啸西风18 分钟前
NLP 中文拼写检测纠正论文 A Hybrid Approach to Automatic Corpus Generation 代码实现
java
小蒜学长19 分钟前
基于Spring Boot的宠物领养系统的设计与实现(代码+数据库+LW)
java·前端·数据库·spring boot·后端·旅游·宠物
L.S.V.19 分钟前
Java 溯本求源之基础(三十一)——泛型
java·开发语言
Redamancy_Xun26 分钟前
开源软件兼容性可信量化分析
java·开发语言·程序人生·网络安全·测试用例·可信计算技术
IDRSolutions_CN33 分钟前
(教程)用 Java 从 PDF 中提取嵌入的文件
java·经验分享·pdf·软件工程·团队开发
海波东36 分钟前
单例模式懒汉式、饿汉式(线程安全)
java·安全·单例模式
ZLRRLZ39 分钟前
【C++】多态
开发语言·c++
lwprain1 小时前
解决tomcat双击startup.bat乱码的几种方法
java·tomcat