C++实现排序 - 02 归并排序、快速排序和堆排序

数据结构与算法专栏 —— C++实现

写在前面:
今天我们继续来整理平均时间复杂度为 O(nlogn) 的三个排序算法,即归并排序、堆排序和快速排序。

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(n2)O(nlogn)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定

归并排序

归并排序采用了分治法的思想,不断将两个有序的序列进行合并从而最终得到整个有序的序列,算法步骤如下:

  1. 把当前长度为 n 的序列分为两个长度为 n / 2 的子序列,对子序列进行操作。
  2. 同样对两个子序列进行划分,再分别对其子序列进行划分操作,直至子序列中只有一个元素为止就不再进行划分,而是进行回溯。
  3. 通过回溯的子序列进行排序操作,我们额外创建一个数组出来,将回溯的两个子序列进行合并操作。
  4. 一直往上回溯,这样每次得到的两个回溯的子序列都是有序的,可以方便我们进行合并操作,合并的序列也会是有序的。

直接上图:

我们先对序列进行划分操作:

在这里插入图片描述

然后对各个子序列进行回溯:

在这里插入图片描述

通过合并操作,得到最终的子序列。

#include <bits/stdc++.h>
using namespace std;

int n, q[100010], temp[100010];

void merge_sort(int q[], int l, int r) {
	//如果当前序列只有一个元素,则不用排序直接返回
	if (l >= r)
		return;

	//对序列进行划分,然后对其子序列进行递归操作
	int mid = l + r >> 1;
	merge_sort(q, l, mid), merge_sort(q, mid + 1, r);

	//合并操作
	int k = 0, i = l, j = mid + 1;
	while (i <= mid && j <= r) {
		//遍历两个子序列,将更小的元素取出放入临时数组temp
		if (q[i] <= q[j])
			temp[k++] = q[i++];
		else
			temp[k++] = q[j++];
	}

	//如果还有元素没有取出,将剩余元素放入临时数组temp
	while (i <= mid)
		temp[k++] = q[i++];
	while (j <= r)
		temp[k++] = q[j++];

	//将原数组进行更新,更新上面合并的区间
	for (int i = l, j = 0; i <= r; i++, j++)
		q[i] = temp[j];
}

int main() {
	/*
	5
	2 3 1 8 5
	*/
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", &q[i]);
	}
	merge_sort(q, 0, n - 1);
	for (int i = 0; i < n; i++) {
		printf("%d ", q[i]);
	}
}

快速排序

快速排序也是利用了递归思想,算法步骤如下:

  1. 选取第一个的数作为基准数。
  2. 将小于基准数的元素换到前面,将大于基准数的元素换到后面。
  3. 重复上述操作,直到区间只有一个数为止。

直接上图:

第一步:选取第一个数作为基准数。

在这里插入图片描述

第二步:从后往前找一个比基准数小的数放到前面。

在这里插入图片描述

在这里插入图片描述

第三步:从前往后找到一个比基准数大的树放到后面。

在这里插入图片描述

第四步:继续从后往前查找,发现 first 和 last 重合,本次遍历结束,并将基准数放到指针所指地方。
在这里插入图片描述

第五步:将该序列分成两半,左半部分是该序列左边界至 first -1 ,右半部分是 first +1 至右边界。

第六步:对左半部分即下标为 0 ~ 1 区间进行递归排序,由于已经是有序我们就直接跳过这里。

第七步:对右半部分即下标为 3 ~ 6 区间进行递归排序。

在这里插入图片描述

第八步:从后往前找到一个比基准数小的数放到前面。

在这里插入图片描述

第九步:从前往后找一个比基准数大的数放到后面,但是发现指针最终重合,故此次遍历结束,将基准数放到指针所指位置。

在这里插入图片描述

第十步:与上面相同,对该序列进行左右区间划分,再分别遍历。由于右半部分已经没有元素了,我们直接对左半部分即下标为 3 ~ 5 区间进行递归排序。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

由于该序列已经有序,后续递归操作我们就不再展示了,直接来看代码。

#include <bits/stdc++.h>
using namespace std;

int q[1000010];

void quick_sort(int q[], int l, int r) {
	if (l >= r)
		return;
	int first = l, last = r, key = q[first];
	while (first < last) {
		//将比第一个小的移到前面
		while (first < last && q[last] >= key)
			last--;
		if (first < last)
			q[first++] = q[last];

		//将比第一个大的数移到后面
		while (first < last && q[first] <= key)
			first++;
		if (first < last)
			q[last--] = q[first];
	}
	q[first] = key;
	quick_sort(q, l, first - 1), quick_sort(q, first + 1, r);
}

int main() {
	/*
	7
	2 3 1 8 5 11 4
	*/
	int size = 0;
	scanf("%d", &size);
	for (int i = 0; i < size; i++) {
		scanf("%d", &q[i]);
	}
	quick_sort(q, 0, size - 1);
	for (int i = 0; i < size; i++) {
		printf("%d ", q[i]);
	}
	return 0;
}

堆排序

在了解堆排序之前,我们先来看看什么是堆。

堆就是一个类似于完全二叉树的结构,同时它分为大顶堆和小顶堆,它们的性质分别是每个结点的值都大于(或小于)它的孩子结点。

下面就是一个大顶堆:

在这里插入图片描述

下面就是一个小顶堆:

在这里插入图片描述

那么我们如何利用堆的性质进行排序呢,算法步骤如下(这里讲解升序操作):

  1. 先将初始化的数组序列构建成大顶堆。
  2. 将堆顶元素和最后一个元素进行交换,此时最后一个元素的位置就是该序列的最大值了。然后再对其前面的所有元素进行堆更新,即从堆顶元素往下进行堆操作,但是这里已经排好序的最后一个元素不参与对操作。
  3. 重复上述步骤,每次都从堆顶取剩余元素的最大值放到堆即剩余元素的最后一个,然后从堆顶往下进行对操作,直至所有元素都已经操作完。

可能会有小伙伴比较有疑惑,我们该如何确定已经排好序和没有排好序的元素呢。这就需要用到完全二叉树的性质:

在下标从 0 开始存储元素的顺序存储的数组当中,结点的左孩子下标等于该结点下标乘以 2 加 1 ,结点的右孩子下标等于该结点下标乘以 2 加 2 。

这样我们可以通过数组下标进行操作,先对前 n 个元素构建大顶堆。然后交换堆顶和最后一个元素,确定数组中第 n 个元素的序列,再对前 n - 1 个元素进行堆操作。

直接上图:

先来看看如何构建出大顶堆,我们要从第一个非叶子结点开始往上构建。因为如果从第一个结点开始往下构建的话,可能无法将最大的元素放到堆顶。

假设我们初始的序列为 { 21 , 343 , 122 , 84 , 5 , 117 , 4 } ,从第一个非叶子结点开始往上构建。

在这里插入图片描述

第一步:第一个非叶子结点为 122 ,每次都去找到该结点与其孩子结点中的最大值并进行交换,发现自身印记是最大值了,故找到上一个非叶子结点。

在这里插入图片描述

第二步:非叶子结点 343 已经是其和其孩子结点中的最大值,故不用交换。
在这里插入图片描述

第三步:非叶子结点 21 与其孩子结点进行比较,发现其孩子结点 343 更大,故进行交换。

在这里插入图片描述

在这里插入图片描述

第四步:交换后的结点不是叶子结点,故继续向下找到最大值。

在这里插入图片描述

在这里插入图片描述

至此,大顶堆已经构建完成,现在进行排序操作。

在这里插入图片描述

第一步:交换堆顶和最后一个元素。

在这里插入图片描述

第二步:对堆顶元素向下进行对操作,注意已经排好序的 343 不再参与到对操作之中。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第三步:交换堆顶和最后一个元素,并对堆顶元素向下进行堆操作。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第四步:交换堆顶和最后一个元素,并对堆顶元素向下进行堆操作。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第五步:交换堆顶和最后一个元素,并对堆顶元素向下进行堆操作。

在这里插入图片描述

第六步:交换堆顶和最后一个元素,并对堆顶元素向下进行堆操作。

在这里插入图片描述

第七步:交换堆顶和最后一个元素,并对堆顶元素向下进行堆操作。

在这里插入图片描述

第八步:交换堆顶和最后一个元素,并对堆顶元素向下进行堆操作。

在这里插入图片描述

至此,所有元素都排好序了,直接输出即可。

#include <bits/stdc++.h>
using namespace std;

//用递归方式来调整为大顶堆
void adjust(int a[], int len, int index) {
	int right = index * 2 + 1;	//index的右结点
	int left = index * 2 + 2;	//index的左结点

	//找到三个结点中最大的一个,使其在index位置上
	int maxIdx = index;
	if (right < len && a[right] > a[maxIdx])
		maxIdx = right;
	if (left < len && a[left] > a[maxIdx])
		maxIdx = left;

	if (maxIdx != index) {
		swap(a[maxIdx], a[index]);
		adjust(a, len, maxIdx);	//变换结点后如果还有子结点,则继续进行排序
	}
}

//堆排序
void heapSort(int a[], int size) {
	//从最后一个非叶子结点开始构建大顶推
	for (int i = size / 2 - 1; i >= 0; i--) {
		adjust(a, size, i);
	}

	for (int i = size - 1; i > 0; i--) {
		swap(a[i], a[0]);	//将大顶堆的根结点与最后一个结点交换,就得到了最大值
		adjust(a, i, 0);	//交换完后,继续对除交换到最后的元素之外的前面所有元素进行堆排序
	}
}

int main() {
	int a[10] = { 21, 343, 122, 84, 5, 117, 4, 35, 90, 666 };
	int size = 10;
	heapSort(a, size);
	for (int i = 0; i < size; i++) {
		cout << a[i] << " ";
	}
	return 0;
}

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

【上一讲】C++实现排序 - 01 归并排序、快速排序和堆排序

【下一讲】C++实现排序 - 03 计数排序、桶排序和基数排序

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您解答这个问题。 首先,堆排序快速排序归并排序和计数排序都是常见的排序算法,它们的实现方式各不相同。下面分别介绍它们的C++实现。 1. 堆排序(Heap Sort) 堆排序是一种选择排序,它利用堆的性质进行排序。堆是一个完全二叉树,其中每个节点的值都大于或等于其子节点的值(最大堆),或者小于或等于其子节点的值(最小堆)。在堆排序中,我们将待排序数组构建成一个最大堆,然后将堆顶元素(即最大值)与数组最后一个元素交换,然后重新调整堆,重复执行此操作,直到整个数组有序。 下面是堆排序C++实现: ```c++ void heapify(int arr[], int n, int i) { int largest = i; // 初始化最大值为根节点 int l = 2 * i + 1; // 左子节点索引 int r = 2 * i + 2; // 右子节点索引 // 如果左子节点比最大值大,则更新最大值索引 if (l < n && arr[l] > arr[largest]) largest = l; // 如果右子节点比最大值大,则更新最大值索引 if (r < n && arr[r] > arr[largest]) largest = r; // 如果最大值不是根节点,则交换根节点和最大值,然后递归调整堆 if (largest != i) { swap(arr[i], arr[largest]); heapify(arr, n, largest); } } void heapSort(int arr[], int n) { // 构建最大堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 依次取出堆顶元素,放到数组末尾,并重新调整堆 for (int i = n - 1; i >= 0; i--) { swap(arr[0], arr[i]); heapify(arr, i, 0); } } ``` 2. 快速排序(Quick Sort) 快速排序是一种分治排序,它利用分治的思想将大问题分解成小问题,然后递归地解决小问题。在快速排序中,我们选择一个枢轴元素(一般选择第一个元素),将数组分为两个部分,左边部分的元素均小于枢轴元素,右边部分的元素均大于枢轴元素。然后分别对左右两部分递归执行此操作,直到整个数组有序。 下面是快速排序C++实现: ```c++ int partition(int arr[], int low, int high) { int pivot = arr[low]; // 枢轴元素 int i = low, j = high; while (i < j) { // 从右往左找第一个小于枢轴元素的位置 while (i < j && arr[j] >= pivot) j--; // 从左往右找第一个大于枢轴元素的位置 while (i < j && arr[i] <= pivot) i++; // 交换左右两个元素 if (i < j) swap(arr[i], arr[j]); } // 将枢轴元素放到正确的位置上 arr[low] = arr[i]; arr[i] = pivot; return i; } void quickSort(int arr[], int low, int high) { if (low < high) { int pivot = partition(arr, low, high); quickSort(arr, low, pivot - 1); quickSort(arr, pivot + 1, high); } } ``` 3. 归并排序(Merge Sort) 归并排序也是一种分治排序,它将待排序数组分成两部分,分别排序,然后合并成一个有序数组。在归并排序中,我们先将数组分为两半,分别对左半部分和右半部分递归执行归并排序,然后将两个有序子数组合并成一个有序数组。 下面是归并排序C++实现: ```c++ void merge(int arr[], int l, int m, int r) { int n1 = m - l + 1; int n2 = r - m; // 创建临时数组 int L[n1], R[n2]; // 将左半部分复制到临时数组 L for (int i = 0; i < n1; i++) L[i] = arr[l + i]; // 将右半部分复制到临时数组 R for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i]; // 合并左右两个有序子数组 int i = 0, j = 0, k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 将剩余的元素复制到数组中 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int arr[], int l, int r) { if (l < r) { int m = l + (r - l) / 2; mergeSort(arr, l, m); mergeSort(arr, m + 1, r); merge(arr, l, m, r); } } ``` 4. 计数排序(Counting Sort) 计数排序是一种非比较排序,它利用元素出现的次数来确定每个元素在有序数组中的位置。在计数排序中,我们先计每个元素出现的次数,然后计算每个元素在有序数组中的位置,最后将元素放到正确的位置上。 下面是计数排序C++实现: ```c++ void countingSort(int arr[], int n) { int max_val = 0; for (int i = 0; i < n; i++) { if (arr[i] > max_val) max_val = arr[i]; } // 创建计数数组 int count[max_val + 1] = {0}; // 计每个元素出现的次数 for (int i = 0; i < n; i++) count[arr[i]]++; // 计算每个元素在有序数组中的位置 for (int i = 1; i <= max_val; i++) count[i] += count[i - 1]; // 将元素放到正确的位置上 int output[n]; for (int i = n - 1; i >= 0; i--) { output[count[arr[i]] - 1] = arr[i]; count[arr[i]]--; } // 将有序数组复制到原数组中 for (int i = 0; i < n; i++) arr[i] = output[i]; } ``` 以上就是堆排序快速排序归并排序和计数排序C++实现,希望能对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值