写在开头的话
经过昨天的学习,想必大家对排序算法有了基本的认识,那么今天我们就开始进阶排序算法的学习吧。
第一节
知识点:(1)内部排序(2)外部排序
内部排序
内部排序定义
内部排序是指在计算机内存中对数据进行排序的过程,即所有数据都可以一次性加载到内存中进行排序。
常见算法
冒泡排序(Bubble Sort) :逐个比较相邻元素并交换,直到整个序列有序。
选择排序(Selection Sort) :从未排序部分选择最小(或最大)元素放到已排序部分的末尾。
插入排序(Insertion Sort) :将未排序部分的元素逐个插入到已排序部分的合适位置。
快速排序(Quick Sort) :通过选取一个基准元素,将序列划分为两个子序列,然后递归地对子序列进行排序。
适用场景
- 小规模数据排序:内部排序适用于小规模数据集的排序需求,如数组、列表等。
- 实时排序:某些内部排序算法具有较高的执行速度,适用于实时排序场景,如实时数据流处理。
- 系统排序:内部排序算法通常被集成到操作系统和编程语言的库中,用于对系统资源进行排序,如文件列表、进程列表等。
总的来说,内部排序通常用于处理小规模数据集,其排序过程不涉及外部存储(如硬盘)的读写操作。
外部排序
外部排序定义
外部排序是指在处理大规模数据集时,由于数据量过大无法一次性加载到内存中进行排序,需要借助外部存储(如硬盘)进行排序的过程。
多路并归
多路并归定义
将大数据将大数据集分成多个块,分别在内存中排序,然后利用多路归并的方式将各块合并成一个有序序列。
图示

代码实现
C++代码实现
cpp
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#define MAXNUM 2000
int filenum;
int filenumtemp;
int filenumend;
void CreatFile()
{
FILE *f;
f = fopen("test.txt", "w+");
srand((unsigned)time(NULL));
for (int i = 0; i < 10000; ++i)
{
int temp = rand() % 100;
fprintf(f, "%d\n", temp);
}
fclose(f);
}
void merge(int num[], int start, int mid, int end)
{
int n1 = mid - start + 1;
int n2 = end - mid;
int *left, *right;
left = (int*)malloc(n1 * sizeof(int));
right = (int*)malloc(n2 * sizeof(int));
int i, j, k;
for (i = 0; i < n1; i++)
left[i] = num[start + i];
for (j = 0; j < n2; j++)
right[j] = num[mid + 1 + j];
i = j = 0;
k = start;
while (i < n1 && j < n2)
if (left[i] < right[j])
num[k++] = left[i++];
else
num[k++] = right[j++];
while (i < n1)
num[k++] = left[i++];
while (j < n2)
num[k++] = right[j++];
free(left);
free(right);
}
void merge_sort(int num[], int start, int end)
{
int mid;
if (start < end)
{
mid = (start + end) / 2;
merge_sort(num, start, mid);
merge_sort(num, mid + 1, end);
merge(num, start, mid, end);
}
}
void MergeFile()
{
filenumtemp = 0;
while (filenum != 1)
{
while (filenumtemp < filenum)
{
if ((filenum - filenumtemp) == 1)
{
FILE *f1, *f;
char filename1[10] = { "" };
filenumtemp++;
filename1[0] = filenumtemp + 48;
strcat(filename1, ".txt");
f1 = fopen(filename1, "r");
filenumend++;
char filename[10] = { "" };
filename[0] = filenumend + 48;
strcat(filename, "temp.txt");
f = fopen(filename, "w+");
int num1;
while (fscanf(f1, "%d", &num1) != EOF)
{
fprintf(f, "%d\n", num1);
}
fclose(f1);
fclose(f);
}
else
{
FILE *f1, *f2, *f;
char filename1[10] = { "" };
filenumtemp++;
filename1[0] = filenumtemp + 48;
strcat(filename1, ".txt");
f1 = fopen(filename1, "r");
char filename2[10] = { "" };
filenumtemp++;
filename2[0] = filenumtemp + 48;
strcat(filename2, ".txt");
f2 = fopen(filename2, "r");
filenumend++;
char filename[10] = { "" };
filename[0] = filenumend + 48;
strcat(filename, "temp.txt");
f = fopen(filename, "w+");
int temp;
int count = 0;
int num1, num2;
fscanf(f1, "%d", &num1);
fscanf(f2, "%d", &num2);
while (true)
{
if (num1 < num2)
{
fprintf(f, "%d\n", num1);
if (fscanf(f1, "%d", &num1) == EOF)
{
fprintf(f, "%d\n", num2);
while (fscanf(f2, "%d", &num2) != EOF)
{
fprintf(f, "%d\n", num2);
}
break;
}
}
else
{
fprintf(f, "%d\n", num2);
if (fscanf(f2, "%d", &num2) == EOF)
{
fprintf(f, "%d\n", num1);
while (fscanf(f1, "%d", &num1) != EOF)
{
fprintf(f, "%d\n", num1);
}
break;
}
}
}
fclose(f1);
fclose(f2);
fclose(f);
}
char filename1[10] = { "" };
char filename2[10] = { "" };
filename1[0] = filenumend + 48;
filename2[0] = filenumend + 48;
strcat(filename1, "temp.txt");
strcat(filename2, ".txt");
char filename3[10] = { "" };
char filename4[10] = { "" };
filename3[0] = filenumend * 2 - 1 + 48;
filename4[0] = filenumend * 2 + 48;
strcat(filename3, ".txt");
strcat(filename4, ".txt");
int r1 = remove(filename3);
int r2 = remove(filename4);
printf("r1=%d r2=%d\n", r1, r2);
rename(filename1, filename2);
if (filenumtemp == filenum&&filenum != 1)
{
filenum = filenumend;
filenumtemp = 0;
filenumend = 0;
}
}
}
char filename1[20] = { "" };
char filename2[20] = { "" };
filename1[0] = 1 + 48;
strcat(filename1, ".txt");
strcat(filename2, "test_sort.txt");
rename(filename1, filename2);
printf("排序完成,有序序列保存在:test_sort.txt文件中\n");
}
void CreatTempFile(int temp[], int count)
{
FILE *f;
char filename[10] = { "" };
filename[0] = filenum + 48;
strcat(filename, ".txt");
f = fopen(filename, "w+");
for (int i = 0; i < count; ++i)
{
fprintf(f, "%d\n", temp[i]);
}
fclose(f);
}
void SortFile()
{
FILE *f;
f = fopen("test.txt", "r");
int *temp;
temp = (int *)malloc(MAXNUM * sizeof(int));
char tempchar;
int count = 0;
while (fscanf(f, "%d", &temp[count])!=EOF)
{
count++;
if (count == MAXNUM)
{
filenum++;
merge_sort(temp, 0, count - 1);
CreatTempFile(temp, count);
count = 0;
}
}
if (count != 0)
{
filenum++;
CreatTempFile(temp, count);
count = 0;
}
fclose(f);
free(temp);
}
int main()
{
CreatFile(); //生成10000个随机数存储在test.txt文件中
SortFile(); //初次切割并排序为有序文件
MergeFile(); //对文件进行归并排序
return 0;
}
Java代码实现
java
import java.io.*;
import java.util.*;
public class ExternalSort {
private static final int MAXNUM = 2000;
private static int filenum = 0;
private static int filenumtemp = 0;
private static int filenumend = 0;
public static void main(String[] args) throws IOException {
createFile();
sortFile();
mergeFile();
}
public static void createFile() throws IOException {
Random rand = new Random();
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter("test.txt")))) {
for (int i = 0; i < 10000; ++i) {
int temp = rand.nextInt(100);
writer.println(temp);
}
}
}
public static void merge(int[] num, int start, int mid, int end) {
int n1 = mid - start + 1;
int n2 = end - mid;
int[] left = new int[n1];
int[] right = new int[n2];
System.arraycopy(num, start, left, 0, n1);
System.arraycopy(num, mid + 1, right, 0, n2);
int i = 0, j = 0, k = start;
while (i < n1 && j < n2) {
if (left[i] <= right[j]) {
num[k++] = left[i++];
} else {
num[k++] = right[j++];
}
}
while (i < n1) {
num[k++] = left[i++];
}
while (j < n2) {
num[k++] = right[j++];
}
}
public static void mergeSort(int[] num, int start, int end) {
if (start < end) {
int mid = (start + end) / 2;
mergeSort(num, start, mid);
mergeSort(num, mid + 1, end);
merge(num, start, mid, end);
}
}
public static void mergeFile() throws IOException {
filenumtemp = 0;
while (filenum != 1) {
while (filenumtemp < filenum) {
if ((filenum - filenumtemp) == 1) {
mergeSingleFile();
} else {
mergeTwoFiles();
}
cleanup();
}
}
finalizeMerge();
}
private static void mergeSingleFile() throws IOException {
String filename1 = filenumtemp + ".txt";
filenumtemp++;
String filename = filenumend + "temp.txt";
filenumend++;
try (BufferedReader reader = new BufferedReader(new FileReader(filename1));
PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
String line;
while ((line = reader.readLine()) != null) {
writer.println(line);
}
}
}
private static void mergeTwoFiles() throws IOException {
String filename1 = filenumtemp + ".txt";
filenumtemp++;
String filename2 = filenumtemp + ".txt";
filenumtemp++;
String filename = filenumend + "temp.txt";
filenumend++;
try (BufferedReader reader1 = new BufferedReader(new FileReader(filename1));
BufferedReader reader2 = new BufferedReader(new FileReader(filename2));
PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
mergeReaders(reader1, reader2, writer);
}
}
private static void mergeReaders(BufferedReader reader1, BufferedReader reader2, PrintWriter writer) throws IOException {
String line1 = reader1.readLine();
String line2 = reader2.readLine();
while (line1 != null && line2 != null) {
if (Integer.parseInt(line1) <= Integer.parseInt(line2)) {
writer.println(line1);
line1 = reader1.readLine();
} else {
writer.println(line2);
line2 = reader2.readLine();
}
}
while (line1 != null) {
writer.println(line1);
line1 = reader1.readLine();
}
while (line2 != null) {
writer.println(line2);
line2 = reader2.readLine();
}
}
private static void cleanup() throws IOException {
String filename1 = filenumend + "temp.txt";
String filename2 = filenumend + ".txt";
new File(filename1).renameTo(new File(filename2));
if (filenumtemp == filenum && filenum != 1) {
filenum = filenumend;
filenumtemp = 0;
filenumend = 0;
}
}
private static void finalizeMerge() throws IOException {
String filename1 = "1.txt";
String filename2 = "test_sort.txt";
new File(filename1).renameTo(new File(filename2));
System.out.println("排序完成,有序序列保存在:test_sort.txt文件中");
}
public static void createTempFile(int[] temp, int count) throws IOException {
String filename = filenum + ".txt";
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
for (int i = 0; i < count; ++i) {
writer.println(temp[i]);
}
}
}
public static void sortFile() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
int[] temp = new int[MAXNUM];
int count = 0;
String line;
while ((line = reader.readLine()) != null) {
temp[count++] = Integer.parseInt(line);
if (count == MAXNUM) {
filenum++;
mergeSort(temp, 0, count - 1);
createTempFile(temp, count);
count = 0;
}
}
if (count > 0) {
filenum++;
mergeSort(temp, 0, count - 1);
createTempFile(temp, count);
}
}
}
}
Python代码实现
python
import os
import random
MAXNUM = 2000
filenum = 0
filenumtemp = 0
filenumend = 0
def create_file():
with open("test.txt", "w") as f:
for _ in range(10000):
temp = random.randint(0, 99)
f.write(f"{temp}\n")
def merge(num, start, mid, end):
n1 = mid - start + 1
n2 = end - mid
left = num[start:mid + 1]
right = num[mid + 1:end + 1]
i = j = 0
k = start
while i < n1 and j < n2:
if left[i] <= right[j]:
num[k] = left[i]
i += 1
else:
num[k] = right[j]
j += 1
k += 1
while i < n1:
num[k] = left[i]
i += 1
k += 1
while j < n2:
num[k] = right[j]
j += 1
k += 1
def merge_sort(num, start, end):
if start < end:
mid = (start + end) // 2
merge_sort(num, start, mid)
merge_sort(num, mid + 1, end)
merge(num, start, mid, end)
def merge_file():
global filenumtemp, filenum, filenumend
filenumtemp = 0
while filenum != 1:
while filenumtemp < filenum:
if (filenum - filenumtemp) == 1:
merge_single_file()
else:
merge_two_files()
cleanup()
finalize_merge()
def merge_single_file():
global filenumtemp, filenumend
filename1 = f"{filenumtemp + 1}.txt"
filenumtemp += 1
filename = f"{filenumend + 1}temp.txt"
filenumend += 1
with open(filename1, "r") as f1, open(filename, "w") as f:
for line in f1:
f.write(line)
def merge_two_files():
global filenumtemp, filenumend
filename1 = f"{filenumtemp + 1}.txt"
filenumtemp += 1
filename2 = f"{filenumtemp + 1}.txt"
filenumtemp += 1
filename = f"{filenumend + 1}temp.txt"
filenumend += 1
with open(filename1, "r") as f1, open(filename2, "r") as f2, open(filename, "w") as f:
num1 = f1.readline()
num2 = f2.readline()
while num1 and num2:
if int(num1) < int(num2):
f.write(num1)
num1 = f1.readline()
else:
f.write(num2)
num2 = f2.readline()
while num1:
f.write(num1)
num1 = f1.readline()
while num2:
f.write(num2)
num2 = f2.readline()
def cleanup():
global filenumtemp, filenum, filenumend
filename1 = f"{filenumend}temp.txt"
filename2 = f"{filenumend}.txt"
os.rename(filename1, filename2)
if filenumtemp == filenum and filenum != 1:
filenum = filenumend
filenumtemp = 0
filenumend = 0
def finalize_merge():
os.rename("1.txt", "test_sort.txt")
print("排序完成,有序序列保存在:test_sort.txt文件中")
def create_temp_file(temp, count):
global filenum
filename = f"{filenum}.txt"
with open(filename, "w") as f:
for i in range(count):
f.write(f"{temp[i]}\n")
def sort_file():
global filenum
temp = []
with open("test.txt", "r") as f:
for line in f:
temp.append(int(line))
if len(temp) == MAXNUM:
filenum += 1
merge_sort(temp, 0, len(temp) - 1)
create_temp_file(temp, len(temp))
temp = []
if temp:
filenum += 1
merge_sort(temp, 0, len(temp) - 1)
create_temp_file(temp, len(temp))
if __name__ == "__main__":
create_file()
sort_file()
merge_file()
适用场景
- 大型数据库排序:外部排序常用于对大型数据库中的数据进行排序,以提高查询效率和优化数据库性能。
- 大数据分析:在大数据处理和分析场景中,外部排序可用于对大规模数据集进行排序和预处理,以便后续分析和挖掘。
- 磁盘排序:外部排序算法可以应用于对磁盘上的大文件进行排序,以实现对文件内容的快速访问和检索。
总的来说,外部排序算法通常涉及到多次读写外部存储,并且需要考虑数据分块、多路归并等策略。
简单总结
本节简单的让大家了解了一下内部排序和外部排序的概念、区别以及各自的适用场景。
第二节
本节旨在介绍两种常见的排序算法:希尔排序(Shell Sort)和桶排序(Bucket Sort)。通过实验,我们将学习这两种排序算法的原理、实现方法以及应用场景。
希尔排序(Shell Sort) 是插入排序的一种改进版本,也称为"缩小增量排序"。它通过将待排序的数据按一定间隔分组,然后分组进行插入排序,最后逐步减小间隔,直到间隔为 1,完成最后一次插入排序。希尔排序的时间复杂度介于 O(n) 和 O() 之间,取决于间隔序列的选择,通常优于插入排序。
桶排序(Bucket Sort) 是一种分布排序算法,适用于元素均匀分布在一个范围内的情况。它的基本思想是将待排序的元素分配到有限数量的桶中,然后对每个桶中的元素进行排序,最后按照桶的顺序合并所有元素。桶排序的时间复杂度为 O(n+k),其中 k 是桶的数量。
在本实验中,我们将编写希尔排序和桶排序的代码,并进行性能测试,以便了解它们的实际应用效果和性能表现。同时,我们还将讨论它们的优缺点以及适用场景,以便更好地理解和应用这两种排序算法。
知识点
(1)希尔排序(2)桶排序
希尔排序
希尔排序定义
希尔排序(Shell Sort) 是插入排序的一种改进版本,也称为"缩小增量排序"。它通过将待排序的数据按一定间隔分组,然后分组进行插入排序,最后逐步减小间隔,直到间隔为 11,完成最后一次插入排序。希尔排序的时间复杂度介于 O(n) 和 O() 之间,取决于间隔序列的选择,通常优于插入排序。
希尔排序的步骤
希尔排序是一种改进的插入排序算法,其主要思想是将原始的待排序序列分成若干个子序列,然后对每个子序列进行插入排序。希尔排序的关键在于如何选择间隔序列(gap sequence),通过不同的间隔序列可以得到不同的希尔排序算法,但通常情况下使用的是希尔原始序列(1, 2, 4, ..., n/2)。下面是希尔排序的详细步骤:
-
确定间隔序列:选择一个合适的间隔序列,通常采用希尔原始序列。
-
分组插入排序:根据间隔序列将待排序的数据分成若干个子序列,然后对每个子序列进行插入排序。即对每个子序列进行直接插入排序,不断缩小间隔直至 1。
-
逐步缩小间隔:重复上述步骤,逐步减小间隔直至 1。在每次缩小间隔的过程中,对于间隔为 gap 的子序列,使用插入排序算法进行排序。
希尔排序通过逐步减小间隔,先使得数组局部有序,然后逐步扩大局部有序区域,最终完成整体排序。这种分组插入排序的思想能够在一定程度上提高插入排序的效率,尤其是对于大规模数据集合。
图示
希尔排序演示
希尔排序样例模拟
我们对上图再额外进行一个补充说明:
以 [9,1,2,5,7,4,8,6,3,5] 为例。
-
第一轮,我们取 gap 为 5,也就是取相隔距离为 5 的两个元素分为了一组,分为 5 组,对每组进行插入排序。
(9,4),(1,8),(2,6),(5,3),(7,5)\]。
-
第二轮,我们的序列为 [4,1,2,3,5,9,8,6,5,7],gap 折半变成 2,相隔距离为 2 的元素为一组。
(4,2,5,8,5),(1,3,9,6,7)
注意,这里只代表了两个元素为一组,实际在序列中元素位置并未改变。
- 第三轮,我们的序列为 [2,1,4,3,5,6,5,7,8,9],gap 折半变成 1,此时执行插入排序。
序列变为 [1,2,3,4,5,5,6,7,8,9]。
代码实现
C++代码实现
cpp
#include <iostream>
#include <vector>
void shellSort(std::vector<int>& arr) {
int n = arr.size();
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; ++i) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
int main() {
std::vector<int> arr = {12, 34, 54, 2, 3};
shellSort(arr);
std::cout << "Sorted array: ";
for (int x : arr) {
std::cout << x << " ";
}
return 0;
}
Java代码实现
java
import java.util.Arrays;
public class ShellSort {
public static void shellSort(int[] arr) {
int n = arr.length;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
public static void main(String[] args) {
int[] arr = {12, 34, 54, 2, 3};
shellSort(arr);
System.out.println("Sorted array: " + Arrays.toString(arr));
}
}
Python代码实现
python
def shellSort(arr):
n = len(arr)
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
gap //= 2
arr = [12, 34, 54, 2, 3]
shellSort(arr)
print("Sorted array:", arr)
运行结果

桶排序
桶排序定义
桶排序是一种分布排序算法,适用于元素均匀分布在一个范围内的情况。它的基本思想是将待排序的元素分配到有限数量的桶中,然后对每个桶中的元素进行排序,最后按照桶的顺序合并所有元素。桶排序的时间复杂度为 O(n+k),其中 k 是桶的数量。
桶排序的步骤
其实现步骤如下:
-
确定桶的数量:根据待排序数据的范围和分布情况,确定合适数量的桶。
-
将数据分配到桶中:遍历待排序的数据,根据数据的大小将其分配到对应的桶中。
-
对每个桶进行排序:对每个桶中的数据进行排序,可以选择不同的排序算法,如插入排序或快速排序等。
-
合并桶中数据:将各个桶中的数据按照顺序合并起来,得到最终的排序结果。
桶排序的核心思想是利用空间换时间的思想,通过将数据分配到多个桶中,降低了排序的时间复杂度。
图示
代码实现
C++代码实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
void bucketSort(std::vector<int>& arr) {
int max_val = *max_element(arr.begin(), arr.end());
int min_val = *min_element(arr.begin(), arr.end());
int bucket_count = (max_val - min_val) / arr.size() + 1;
std::vector<std::vector<int>> buckets(bucket_count);
for (int num : arr) {
int index = (num - min_val) / arr.size();
buckets[index].push_back(num);
}
for (auto& bucket : buckets) {
std::sort(bucket.begin(), bucket.end());
}
int index = 0;
for (auto& bucket : buckets) {
for (int num : bucket) {
arr[index++] = num;
}
}
}
int main() {
std::vector<int> arr = {12, 34, 54, 2, 3};
bucketSort(arr);
std::cout << "Sorted array: ";
for (int x : arr) {
std::cout << x << " ";
}
return 0;
}
Java代码实现
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BucketSort {
public static void bucketSort(int[] arr) {
int maxVal = Integer.MIN_VALUE;
int minVal = Integer.MAX_VALUE;
for (int num : arr) {
maxVal = Math.max(maxVal, num);
minVal = Math.min(minVal, num);
}
int bucketCount = (maxVal - minVal) / arr.length + 1;
List<List<Integer>> buckets = new ArrayList<>(bucketCount);
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
for (int num : arr) {
int index = (num - minVal) / arr.length;
buckets.get(index).add(num);
}
for (List<Integer> bucket : buckets) {
Collections.sort(bucket);
}
int index = 0;
for (List<Integer> bucket : buckets) {
for (int num : bucket) {
arr[index++] = num;
}
}
}
public static void main(String[] args) {
int[] arr = {12, 34, 54, 2, 3};
bucketSort(arr);
System.out.print("Sorted array: ");
for (int x : arr) {
System.out.print(x + " ");
}
}
}
Python代码实现
python
def bucketSort(arr):
max_val = max(arr)
min_val = min(arr)
bucket_count = (max_val - min_val) // len(arr) + 1
buckets = [[] for _ in range(bucket_count)]
for num in arr:
index = (num - min_val) // len(arr)
buckets[index].append(num)
for bucket in buckets:
bucket.sort()
sorted_arr = []
for bucket in buckets:
sorted_arr.extend(bucket)
return sorted_arr
arr = [12, 34, 54, 2, 3]
sorted_arr = bucketSort(arr)
print("Sorted array:", sorted_arr)
运行结果

简单总结
通过本节,我们学习了两种不同的排序算法:希尔排序和桶排序。
希尔排序:
- 希尔排序是插入排序的改进版,通过将数组分割成多个子序列来排序,然后逐步缩小子序列的间隔,最终实现排序。
- 它的时间复杂度为 O(
),相比普通的插入排序有较大的改进。
桶排序:
- 桶排序是一种分配排序,它将数据分散到有限数量的桶中,然后对每个桶中的数据进行排序,最后合并桶中的数据得到排序结果。
- 桶排序适用于数据均匀分布的情况,时间复杂度为 O(n+k),其中 n 是数据量,k 是桶的数量。
在节中,我们编写了三种语言(C++、Java、Python)的代码来实现这两种排序算法,并对给定的数组进行了排序。我们深入理解了希尔排序和桶排序的实现原理和算法思想,并了解了它们在不同情况下的应用场景和性能表现。
第三节
知识点:
(1)归并排序(2)分治算法
并归排序
并归排序定义
归并排序是一种经典的分治算法,它将待排序的数组不断地分割成两部分,直到每个部分只有一个元素,然后再将这些部分合并在一起,通过比较元素的大小来进行排序。
归并排序具体步骤
分割:
- 将数组递归地分割为左右两个子数组,直到每个子数组只有一个元素。
- 通过递归实现二分分割,得到多个长度为 1 的子数组。
合并:
- 从最小的子数组开始,依次将相邻的两个子数组合并。
- 在合并的过程中,比较左右两个子数组的元素大小,按顺序将较小(或较大)的元素放入临时数组中。
- 如果某个子数组已全部合并完,而另一个子数组还有元素未合并,则直接将剩余元素复制到临时数组中。
复制回原数组:
- 将合并后的结果复制回原数组对应位置。
图示

时间复杂度分析
归并排序的时间复杂度为 O(nlogn) ,其中 n 为待排序数组的长度。它是一种稳定的排序算法,适用于处理大规模数据集的排序问题。虽然归并排序需要额外的空间来存储临时数组,但由于其时间复杂度较稳定,常被用于需要稳定排序算法的场景中。
并归排序优缺点
优点
- 稳定性: 归并排序是一种稳定的排序算法,即相同元素的相对位置在排序前后不会改变。这在某些情况下非常重要,特别是在需要保持原始数据顺序的情况下。
- 时间复杂度: 归并排序的时间复杂度为 O(n\log n)O(nlogn),其中 nn 为待排序数组的长度。这使得它在处理大规模数据集时具有较高的效率,是一种高效的排序算法。
- 分治思想: 归并排序通过分治思想将排序问题分解为若干子问题,降低了问题的复杂度,易于理解和实现。这种分而治之的策略使得归并排序在算法设计上具有灵活性和可扩展性。
- 对链表排序效果更好: 归并排序在对链表进行排序时,只需要修改指针的指向,不需要额外的空间开销。这使得它在对链表进行排序时效果更好。
缺点
- 额外空间开销: 归并排序需要额外的空间来存储临时数组,其空间复杂度为 O(n)O(n)。在空间受限的情况下,这可能导致不适用于某些场景。
- 非原地排序: 归并排序属于非原地排序算法,无法在原数组上直接进行排序,需要额外的空间来存储中间结果。这使得它在某些情况下可能不太适用,尤其是对于内存受限的环境。
- 对小规模数据排序效率较低: 在排序规模较小的情况下,归并排序的性能可能不如一些其他排序算法,如插入排序或快速排序等。这是因为归并排序需要递归地分割数组,对于小规模数据可能会产生较多的递归开销和额外的复制操作,影响了排序效率。
适用场景
-
大规模数据集的排序: 由于归并排序的时间复杂度为 O(n\log n)O(nlogn),在处理大规模数据集时具有较高的效率。因此,当需要对大量数据进行排序时,归并排序是一个很好的选择。
-
需要稳定排序算法的场景: 归并排序是一种稳定的排序算法,即相同元素的相对位置在排序前后不会改变。在需要保持相同元素相对位置的情况下,归并排序是一个理想的选择。
-
链表排序: 对链表进行排序时,归并排序的效果更好。因为在链表中,归并排序只需要修改指针的指向,不需要额外的空间开销,相比于数组更加高效。
-
对内存占用不敏感的情况: 归并排序虽然需要额外的空间来存储临时数组,但在内存占用不是主要考虑因素的情况下,归并排序仍然是一个很好的选择。因为它的时间复杂度相对较低,可以快速地对大规模数据进行排序。
总的来说,归并排序适用于对大规模数据集进行稳定排序,并且在内存占用不敏感的情况下效果更佳。
易错点
-
边界条件错误:在递归的实现中,需要正确处理递归结束的边界条件,如数组长度小于等于1时不需要进行排序。
-
中间位置计算错误 :在确定中间位置时,应该使用
(low + high) / 2而不是简单的low + (high - low) / 2,以避免整数溢出和取整误差。 -
合并过程出错:在合并两个有序数组时,需确保左右两部分都是有序的,并正确地将元素按顺序合并到原数组中。
-
截取子数组错误:在拆分数组时,需要正确地截取子数组,确保左右子数组的边界正确,并不包含多余或缺失的元素。
-
数组索引越界:在合并或操作数组时,需要注意数组索引是否越界,特别是在迭代子数组时,确保不会超出数组的范围。
-
复制元素错误:如果在合并阶段采用临时数组存储子数组元素,需要确保元素的复制和填入原数组的过程正确,避免遗漏或重复复制元素。
-
递归调用出错:确保递归调用的参数传递正确,包括起始位置、终止位置等,以保证每次递归调用都能正确处理对应的子数组。
-
返回值错误:如果队递归函数有返回值,需要确保递归函数的返回值在合适的地方进行处理和传递,以确保排序结果正确。
通过仔细检查上述易错点,可以帮助避免在实现归并排序时产生常见的错误,并提高代码的准确性和稳定性。
代码实现
C++ 代码实现(利用STL实现归并排序)
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 合并两个有序数组
void merge(vector<int>& arr, int low, int mid, int high) {
int n1 = mid - low + 1; // 左半部分数组的长度
int n2 = high - mid; // 右半部分数组的长度
// 创建临时数组存储左右两部分的元素
vector<int> left(n1);
vector<int> right(n2);
for (int i = 0; i < n1; i++) {
left[i] = arr[low + i]; // 将左半部分元素复制到临时数组
}
for (int j = 0; j < n2; j++) {
right[j] = arr[mid + 1 + j]; // 将右半部分元素复制到临时数组
}
int i = 0, j = 0, k = low;
// 合并两个临时数组到原始数组中
while (i < n1 && j < n2) {
if (left[i] <= right[j]) {
arr[k] = left[i];
i++;
} else {
arr[k] = right[j];
j++;
}
k++;
}
// 将剩余的元素复制到原始数组中
while (i < n1) {
arr[k] = left[i];
i++;
k++;
}
while (j < n2) {
arr[k] = right[j];
j++;
k++;
}
}
// 递归实现归并排序
void mergeSort(vector<int>& arr, int low, int high) {
if (low < high) {
int mid = low + (high - low) / 2; // 计算中间位置
mergeSort(arr, low, mid); // 对左半部分递归排序
mergeSort(arr, mid + 1, high); // 对右半部分递归排序
merge(arr, low, mid, high); // 合并两个有序数组
}
}
int main() {
vector<int> arr = {12, 11, 13, 5, 6, 7}; // 待排序数组
int n = arr.size();
mergeSort(arr, 0, n - 1); // 调用归并排序函数
cout << "排序后的数组: ";
for (int num : arr) { // 输出排序后的数组
cout << num << " ";
}
cout << endl;
return 0;
}
Java代码实现
java
import java.util.Arrays;
public class MergeSort {
// 合并两个有序数组
private static void merge(int[] arr, int low, int mid, int high) {
int n1 = mid - low + 1;
int n2 = high - mid;
// 创建临时数组存储左右两部分的元素
int[] left = Arrays.copyOfRange(arr, low, mid + 1);
int[] right = Arrays.copyOfRange(arr, mid + 1, high + 1);
int i = 0, j = 0, k = low;
// 合并两个临时数组到原始数组中
while (i < n1 && j < n2) {
if (left[i] <= right[j]) {
arr[k] = left[i];
i++;
} else {
arr[k] = right[j];
j++;
}
k++;
}
// 将剩余的元素复制到原始数组中
while (i < n1) {
arr[k] = left[i];
i++;
k++;
}
while (j < n2) {
arr[k] = right[j];
j++;
k++;
}
}
// 递归实现归并排序
private static void mergeSort(int[] arr, int low, int high) {
if (low < high) {
int mid = low + (high - low) / 2; // 计算中间位置
mergeSort(arr, low, mid); // 对左半部分递归排序
mergeSort(arr, mid + 1, high); // 对右半部分递归排序
merge(arr, low, mid, high); // 合并两个有序数组
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6, 7}; // 待排序数组
mergeSort(arr, 0, arr.length - 1); // 调用归并排序函数
System.out.print("排序后的数组: ");
for (int num : arr) { // 输出排序后的数组
System.out.print(num + " ");
}
System.out.println();
}
}
Python代码实现
python
def merge(arr, low, mid, high):
n1 = mid - low + 1
n2 = high - mid
left = arr[low:mid+1]
right = arr[mid+1:high+1]
i = j = 0
k = low
while i < n1 and j < n2:
if left[i] <= right[j]:
arr[k] = left[i]
i += 1
else:
arr[k] = right[j]
j += 1
k += 1
while i < n1:
arr[k] = left[i]
i += 1
k += 1
while j < n2:
arr[k] = right[j]
j += 1
k += 1
def merge_sort(arr, low, high):
if low < high:
mid = (low + high) // 2
merge_sort(arr, low, mid)
merge_sort(arr, mid + 1, high)
merge(arr, low, mid, high)
arr = [12, 11, 13, 5, 6, 7] # 待排序数组
merge_sort(arr, 0, len(arr) - 1)
print("排序后的数组:", arr)
运行结果

简单总结
在本节中,我们学习了归并排序的定义,步骤和时间复杂度。通过学习这些排序的实现原理,我们可以对这些排序的理解进一步加深,从而更好的使用这些排序。