当前位置:   article > 正文

数据结构从入门到精通——排序的概念及运用

数据结构从入门到精通——排序的概念及运用


前言

排序是将数据按照一定规则重新排列的过程,常见规则有升序、降序等。排序算法如冒泡排序、快速排序等,广泛用于数据库、搜索引擎等场景,提高数据检索效率。此外,排序也应用于统计分析、机器学习等领域,以获取有序数据集或发现数据间的关联。

排序是一种将一组数据按照一定的规则重新排列的操作。通常,排序的目标是将数据按照某种顺序进行排列,比如按照升序或降序排列。排序算法是对数据进行排序的具体步骤和方法。

排序算法在计算机科学和数据结构中具有广泛的应用。在实际生活中,排序也随处可见。比如在图书馆中,图书按照作者姓名或图书编号进行排序,使得读者能够更方便地查找所需的图书。在金融领域,股票交易价格也需要按照时间顺序进行排序。

排序算法的选择根据数据规模和性质的不同而有所差异。常见的排序算法包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。这些算法在时间复杂度和空间复杂度上有各种差异,因此在实际应用中需要根据具体情况选择适合的算法。

排序算法的效率对于处理大量数据的程序非常重要。在一些场景下,排序算法的效率甚至可以决定整个程序的性能。因此,研究和优化排序算法是计算机科学领域的重要课题之一。


一、排序的概念

排序

排序的概念,简而言之,就是对一组数据元素按照某种特定的顺序进行重新排列的过程。这种顺序可以是升序、降序,或者是根据某种特定的规则进行排序。排序算法是计算机科学中的一个重要分支,广泛应用于数据处理、搜索引擎、数据库管理等领域。

在日常生活和工作中,排序的概念也无处不在。例如,在超市购物时,商品按照价格或类别进行排序,方便顾客快速找到所需商品;在图书馆中,书籍按照书名或作者进行排序,便于读者查找;在学校的成绩管理中,学生的成绩按照分数从高到低或从低到高进行排序,方便教师进行评估和比较。

在计算机科学中,排序算法的种类繁多,各有其优缺点。常见的排序算法有冒泡排序、选择排序、插入排序、归并排序、快速排序等。冒泡排序通过相邻元素之间的比较和交换,逐步将最大的元素“冒”到序列的末尾;选择排序则通过每次选择剩余元素中的最小(或最大)元素,将其放到序列的起始位置;插入排序则是将每个元素插入到已排序的序列中的适当位置,从而保持整个序列的有序性。归并排序和快速排序则采用了分治的思想,将待排序的序列划分为若干个子序列,分别对子序列进行排序,然后将排序结果合并起来,得到最终的有序序列。

不同的排序算法在不同的应用场景下具有不同的性能表现。例如,对于小规模的数据集,冒泡排序和选择排序等简单算法可能更加适用,因为它们的时间复杂度相对较低;而对于大规模的数据集,归并排序和快速排序等高效算法则更具优势,因为它们可以在更短的时间内完成排序任务。

除了算法本身的性能外,排序的效果还受到数据特性、排序规则等多种因素的影响。例如,对于已经部分有序的数据集,插入排序等算法可能具有更好的性能表现;而对于包含大量重复元素的数据集,桶排序等算法则可能更加高效。

总之,排序的概念在日常生活和计算机科学中都具有广泛的应用。通过选择合适的排序算法和考虑各种影响因素,我们可以实现高效、准确的数据排序,为数据分析和处理提供有力支持。同时,对排序算法的研究和改进也是计算机科学领域的一个重要课题,有助于推动相关技术的发展和创新。

稳定性

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序

数据元素全部放在内存中的排序。

内部排序是数据处理过程中的重要环节,它指的是在没有外部存储设备辅助的情况下,仅依靠计算机的内存对数据进行排序的过程。这一过程对于提高数据处理效率、优化数据结构、以及实现高效算法都至关重要。

内部排序算法的选择依赖于数据的特性,如数据量的大小、数据类型的复杂性以及排序要求的严格程度等。常见的内部排序算法有冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序等。这些算法各有优缺点,适用于不同的场景。

例如,对于数据量较小的情况,冒泡排序和插入排序等简单算法可能就足够高效;而对于大数据量的处理,快速排序、归并排序等更为复杂的算法则可能更加适合。此外,对于某些特定类型的数据,如已经部分排序的数据或具有特殊分布规律的数据,还可以采用更为高效的特定算法。

在实际应用中,内部排序算法的选择还需要考虑内存使用的效率。因为内部排序完全依赖于内存,如果算法的内存使用效率不高,即使排序速度再快,也可能因为内存占用过多而导致整体性能下降。因此,在实际应用中,我们需要根据具体情况综合考虑算法的时间复杂度和空间复杂度,选择最合适的排序算法。

总的来说,内部排序是数据处理中不可或缺的一环。了解和掌握各种内部排序算法的特点和适用场景,对于提高数据处理效率、优化数据结构、以及实现高效算法都具有重要的意义。在未来的数据处理工作中,我们需要不断学习和研究新的排序算法和技术,以适应不断变化的数据处理需求。

外部排序

数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

外部排序,指的是当待排序的数据量过大,无法一次性装入内存时,需要使用外部存储设备如磁盘等进行排序的过程。这种排序方法通常涉及数据的分块、部分排序、归并等步骤,以适应大数据量的处理需求。

在现代数据处理的场景中,外部排序的应用非常广泛。例如,在处理海量日志文件、数据库查询结果、大数据分析等任务时,由于数据量庞大,无法一次性加载到内存中进行排序,因此需要使用外部排序算法。

外部排序的一个典型算法是k路归并排序。首先,将数据分割成若干个小块,每块的大小刚好能够装入内存。然后,使用内部排序算法(如快速排序、归并排序等)对每块数据进行排序,并将排序后的数据写回磁盘。接下来,使用多路归并算法将这些已排序的数据块合并成一个有序的大文件。在归并过程中,每次从各个数据块中读取一定数量的数据到内存中,进行比较和合并,然后将合并后的数据写回磁盘。这个过程不断重复,直到所有的数据块都被合并成一个有序的文件。

外部排序不仅需要考虑排序算法的效率,还需要考虑磁盘I/O操作、内存使用等因素。为了提高排序速度,可以采用一些优化策略,如增加归并路数、使用缓冲区来减少磁盘I/O次数、利用并行计算等。

此外,随着技术的发展,外部排序算法也在不断演进。例如,利用分布式存储和计算框架(如Hadoop、Spark等)进行外部排序,可以处理更大规模的数据集,并且提高排序的并行度和效率。

总的来说,外部排序是处理大数据量排序问题的重要手段。通过合理的算法设计和优化,可以在有限的内存和磁盘空间下,实现高效、稳定的数据排序。

二、排序运用

在这里插入图片描述

在这里插入图片描述

三、常见的排序算法

在这里插入图片描述
直接插入排序

希尔排序

选择排序

堆排序

冒泡排序

快速排序

归并排序

四、排序性能检测代码

排序性能检测代码是用于评估不同排序算法性能的代码。它通常包括生成随机数据集、实现多种排序算法、计时每种算法的执行时间,并比较它们的性能。这种代码可以帮助开发者选择最适合特定应用场景的排序算法。

总结:排序性能检测代码通过生成随机数据、实现多种排序算法并比较它们的执行时间,来评估不同排序算法的性能,帮助开发者选择最佳算法。

// 排序实现的接口
 
// 插入排序
	void InsertSort(int* a, int n);
 
// 希尔排序
	void ShellSort(int* a, int n);
 
// 选择排序
	void SelectSort(int* a, int n);
 
// 堆排序
	void AdjustDwon(int* a, int n, int root);
	void HeapSort(int* a, int n);
 
// 冒泡排序
	void BubbleSort(int* a, int n)
 
// 快速排序递归实现
// 快速排序hoare版本
	int PartSort1(int* a, int left, int right);
// 快速排序挖坑法
	int PartSort2(int* a, int left, int right);
// 快速排序前后指针法
	int PartSort3(int* a, int left, int right);
	void QuickSort(int* a, int left, int right);
 
// 快速排序 非递归实现
	void QuickSortNonR(int* a, int left, int right)
 
// 归并排序递归实现
	void MergeSort(int* a, int n)
// 归并排序非递归实现
	void MergeSortNonR(int* a, int n)
 
// 计数排序
	void CountSort(int* a, int n)
 
// 测试排序的性能对比
	void TestOP()
	{
	 srand(time(0));
	 const int N = 100000;
	 int* a1 = (int*)malloc(sizeof(int)*N);
	 int* a2 = (int*)malloc(sizeof(int)*N);
	 int* a3 = (int*)malloc(sizeof(int)*N);
	 int* a4 = (int*)malloc(sizeof(int)*N);
	 int* a5 = (int*)malloc(sizeof(int)*N);
	 int* a6 = (int*)malloc(sizeof(int)*N);
	 int* a7 = (int*)malloc(sizeof(int)*N);
 
	 for (int i = 0; i < N; ++i)
	 {
	 	a1[i] = rand();
	 	a2[i] = a1[i];
	 	a3[i] = a1[i];
	 	a4[i] = a1[i];
	 	a5[i] = a1[i];
	 	a6[i] = a1[i];
	 	a7[i] = a1[i];
 	} 
	int begin1 = clock();// 插入排序
	InsertSort(a1, N);
	int end1 = clock();

	int begin7 = clock();// 冒泡排序
	BubbleSort(a7, N);
	int end7 = clock();

	int begin2 = clock();// 希尔排序
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();// 选择排序
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();// 堆排序
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();// 快速排序前后指针法
	QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();// 归并排序递归实现
	MergeSort(a6, N);
	int end6 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("BubbleSort:%d\n", end7 - begin7);

	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

下面是对该代码的详细解释:

函数定义:void TestOP() 是一个函数,它没有返回值(void)并且不接受任何参数。

  1. 初始化随机数生成器:
    srand(time(0));:使用当前时间作为种子初始化随机数生成器。这样每次运行程序时,生成的随机数序列都会不同。

  2. 内存分配:
    代码为7个整数数组(a1 到 a7)分配了内存,每个数组都有 N(100,000)个整数。这些数组用于存储要排序的数据。

  3. 填充数组:
    在一个for循环中,所有数组(除了 a7)都被填充了随机数。a7 数组没有被正确初始化,这是一个错误。

  4. 排序和计时:
    代码使用 clock() 函数来测量每种排序算法的执行时间。clock() 函数返回程序执行的时钟周期数。

每种排序算法(插入排序、冒泡排序、希尔排序、选择排序、堆排序、快速排序和归并排序)都被调用一次,并计算其执行时间。

  1. 输出结果:
    使用 printf 函数输出每种排序算法的执行时间。

  2. 释放内存:
    使用 free() 函数释放之前为数组分配的内存。

注意
由于所有数组(除了 a7)都使用了相同的随机数序列进行初始化,因此这些排序算法的比较是公平的。

clock() 函数的结果可能受到多种因素的影响,包括CPU速度、负载和其他正在运行的程序。因此,这些结果只能作为大致的性能比较,而不是精确的性能指标。

srand()

srand()是C语言中的一个函数,用于设置随机数生成器的种子。它的原型是:

void srand(unsigned int seed);
  • 1

其中,seed是一个整数作为种子。通过设置种子,srand()函数可以改变随机数序列的起点。每次调用srand()函数,生成的随机数序列将会根据种子重新开始。

一般情况下,我们可以使用系统时间作为种子,以使生成的随机数序列每次都不一样,例如:

srand(time(NULL));
  • 1

在调用srand()函数之后,我们可以使用rand()函数来生成随机数。每次调用rand()函数,都会返回一个伪随机数,这个数的取值范围通常是0RAND_MAX。需要注意的是,生成的随机数是伪随机数,其实质是通过算法计算得到的,并非真正意义上的随机数。

总结来说,srand()函数用于设置随机数生成器的种子,以改变随机数序列的起点;而rand()函数用于生成伪随机数序列。

clock()

clock() 函数是C和C++中的一个标准库函数,用于返回程序运行的CPU时间。它的原型如下所示:

#include <time.h>

clock_t clock(void);
  • 1
  • 2
  • 3

clock() 函数返回一个 clock_t 类型的值,表示自程序开始执行以来所经过的时钟周期数。时钟周期的长度取决于系统和编译器的实现。

需要注意的是,clock() 函数返回的是CPU时间,而不是实际的墙钟时间(即现实世界中的时间)。CPU时间是指程序运行所消耗的CPU时间,包括程序执行和系统调用等。因此,如果程序在多个CPU核心上运行,clock() 函数返回的值可能会大于墙钟时间。

使用 clock() 函数可以用来测量程序的执行时间,例如用于性能分析和优化。可以通过在程序的开始和结束处调用 clock() 函数并计算差值来获得程序的CPU时间消耗。

需要注意的是,clock() 函数返回的值是一个CPU时间计数器的累积值,因此不能直接用于表示程序执行的实际时间。如果需要测量实际时间,可以使用其他函数和方法,如 time() 函数和 chrono 库中的类。

五、oj排序测试代码

排序OJ(可使用各种排序跑这个OJ)


本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号