快排优化和文件归并排序

文章目录

    • [1. 快排之三路划分](#1. 快排之三路划分)
    • [2. 快排之自省排序](#2. 快排之自省排序)
    • [3. 外排序之文件归并排序](#3. 外排序之文件归并排序)

1. 快排之三路划分

c 复制代码
void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

void QuickSort(int* a, int left, int right)
{
	//区间只有一个值或者不存在就是最小子问题
	if (left >= right)
	{
		return;
	}

	int begin = left, end = right;
	
	//选[left, right]区间中的随机数做key
	int randi = rand() % (right - left + 1);
	randi += left;
	Swap(&a[left], &a[randi]);
	
    int key = a[left];
	int cur = left + 1;

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

	//[begin, left - 1][left, right][right + 1, end]
	QuickSort(a, begin, left - 1);
	QuickSort(a, right + 1, end);
}

2. 快排之自省排序

c 复制代码
void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

void AdjustDown(int* a, int n, int parent)
{
    int child = parent * 2 + 1;

    while (child < n)
    {
        // 选出左右孩子中大的那一个
        if (child + 1 < n && a[child + 1] > a[child])
        {
            ++child;
        }

        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void HeapSort(int* a, int n)
{
    // 建堆 -- 向下调整建堆 -- O(N)
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(a, n, i);
    }
    
    // 自己先实现 -- O(N * logN)
    int end = n - 1;

    while (end > 0)
    {
        Swap(&a[end], &a[0]);
        AdjustDown(a, end, 0);
        --end;
    }
}

void InsertSort(int* a, int n)
{
    for (int i = 1; i < n; i++)
    {
        int end = i - 1;
        int tmp = a[i];

        // 将tmp插入到[0, end]区间中,保持有序
        while (end >= 0)
        {
            if (tmp < a[end])
            {
                a[end + 1] = a[end];
                --end;
            }
            else
            {
                break;
            }
        }

        a[end + 1] = tmp;
    }
}

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

    for (int i = 1; i < N; i *= 2)
    {
        logn++;
    }

    // introspective sort -- 自省排序
    IntroSort(a, left, right, depth, logn * 2);
}

3. 外排序之文件归并排序

c 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

// 创建N个随机数,写到文件中
void CreateNData()
{
	// 造数据
	int n = 10000000;
	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);
}

int compare(const void* a, const void* b)
{
	return (*(int*)a - *(int*)b);
}

// 返回实际读到的数据个数,没有数据了,返回0
int ReadNDataSortToFile(FILE* fout, int n, const char* file1)
{
	int x = 0;
	int* a = (int*)malloc(sizeof(int) * n);

	if (a == NULL)
	{
		perror("malloc error");
		return 0;
	}

	// 想读取n个数据,如果遇到文件结束,应该读到j个
	int j = 0;

	for (int i = 0; i < n; i++)
	{
		if (fscanf(fout, "%d", &x) == EOF)
		{
			break;
		}

		a[j++] = x;
	}
	
	if (j == 0)
	{
		free(a);
		return 0;
	}

	// 排序
	qsort(a, j, sizeof(int), compare);

	FILE* fin = fopen(file1, "w");

	if (fin == NULL)
	{
		free(a);
		perror("fopen error");
		return 0;
	}

	// 写回file1文件
	for (int i = 0; i < j; i++)
	{
		fprintf(fin, "%d\n", a[i]);
	}

	free(a);
	fclose(fin);

	return j;
}

void MergeFile(const char* file1, const char* file2, const char* mfile)
{
	FILE* fout1 = fopen(file1, "r");

	if (fout1 == NULL)
	{
		perror("fopen error");
		return;
	}

	FILE* fout2 = fopen(file2, "r");

	if (fout2 == NULL)
	{
		fclose(fout1);
		perror("fopen error");
		return;
	}

	FILE* mfin = fopen(mfile, "w");

	if (mfin == NULL)
	{
		fclose(fout1);
		fclose(fout2);
		perror("fopen error");
		return;
	}

	// 归并逻辑
	int x1 = 0;
	int x2 = 0;
	int ret1 = fscanf(fout1, "%d", &x1);
	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()
{
	CreateNData();

	const char* file1 = "file1.txt";
	const char* file2 = "file2.txt";
	const char* mfile = "mfile.txt";

	FILE* fout = fopen("data.txt", "r");

	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	int m = 1000000;
	ReadNDataSortToFile(fout, m, file1);
	ReadNDataSortToFile(fout, m, file2);

	while (1)
	{
		MergeFile(file1, file2, mfile);

		// 删除file1和file2
		remove(file1);
		remove(file2);

		// 重命名mfile为file1
		rename(mfile, file1);

		// 当再次读取数据,一个都读不到,说明已经没有数据了
		// 已经归并完成,归并结果在file1
		if (ReadNDataSortToFile(fout, m, file2) == 0)
		{
			break;
		}
	}

	return 0;
}