C++实现排序 - 03 计数排序、桶排序和基数排序

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

写在前面:
今天我们继续来整理与 O(n+k) 有关的三个排序算法,即计数排序、桶排序和基数排序。

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
计数排序O(n+k)O(n+k)O(n+k)O(k)稳定
桶排序O(n+k)O(n+k)O(n2)O(n+k)稳定
基数排序O(n×k)O(n×k)O(n×k)O(n+k)稳定

计数排序

计数排序是去统计每个值在数组中的数量,然后依次放在它们应该在的位置,所以这个算法不适合数组中的值过大,因为这是用空间来换时间。并且,计数排序也不适合按照字符串进行排序。算法步骤如下:

  1. 找到数组中最大的值,进行初始化操作。
  2. 统计数组中个数值的个数。
  3. 对所有的计数进行累加(等于求前缀和)。
  4. 将每个元素放到新的数组中去。

直接上图:

我们对数组先进行遍历,统计每个数值的个数。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

统计完数量后,计算前缀和:

在这里插入图片描述

然后对每个元素的位置进行推断,可能会有小伙伴比较疑惑为什么要弄的这么麻烦,直接从前往后根据下标输出不就好了吗。这是因为每个元素都是一个个体,如果遇到相同元素为了保证稳定性,即排序后相同元素前后次序和原数组中的前后顺序要保持一致。

为了避免数据混乱即保持稳定性,我们从后往前进行遍历。每次都将元素对应的 nums 值减 1 ,然后从后往前赋值给 dis 数组(用来保存最终元素位置)。现在可能听起来会有点晕,我们直接看图。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

至此,dis 数组更新完毕,我帮你将 dis 数组和 arr 数组放在一起。

在这里插入图片描述

现在是不是更清楚了,我们只用根据得到的 dis 数组将原数组中的元素放在 dis 数组中要求放的位置即可。

在这里插入图片描述

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

//计数排序
void countSort(int arr[], int size, int maxnum) {
	int m = maxnum + 1;
	vector<int> count(m);	//计算数据的个数
	vector<int> nums(m);	//计算每个位置与前面的数据个数总和
	vector<int> dis(size);	//记录数据位置信息
	vector<int> newarr(size);	//储存排序后的数组

	//计算数据个数
	for (int i = 0; i < size; i++)
		count[arr[i]]++;

	//计算数据总和
	nums[0] = count[0];
	for (int i = 1; i < m; i++)
		nums[i] += nums[i - 1] + count[i];

	//推断数据位置信息
	for (int i = size - 1; i >= 0; i--) {
		//从后往前查找,避免数据顺序混乱
		int x = arr[i];
		nums[x]--;
		dis[i] = nums[x];
	}

	//数据排序
	for (int i = 0; i < size; i++)
		newarr[dis[i]] = arr[i];

	//输出数组
	for (int i = 0; i < size; i++)
		cout << newarr[i] << " ";
}

int main() {
	int arr[10] = { 2, 3, 1, 8, 5, 10, 4, 3, 9, 6 };
	int size = 10, maxnum = 10;
	countSort(arr, size, maxnum);
	return 0;
}

桶排序

桶排序就是创建若干个桶,每个桶代表一个区间,然后将数组中的元素放入对应的桶中。算法步骤如下:

  1. 具体要建立多少个桶根据元素的最大和最小值而定,我们这里采用如下公式确定。
    区间大小 = (最大值 - 最小值)/(通的数量 - 1)
  2. 遍历原始数组,根据区间将元素放入对应的桶中。
  3. 将每个桶内的元素进行排序。
  4. 遍历所有的桶,将所有元素串起来输出。

不过因为桶排序不是很重要,计数排序和基数排序才是重点。我们下面实现的是最简单的方法,即直接根据元素最大值创建相应的桶,并且遍历进行计数,最后输出。由于简单的实现方法比较易懂,我就直接上代码啦~

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

//计数排序
void bucketSort(int arr[], int size, int maxnum) {
	vector<int> newarr(size);
	vector<int> bucket(maxnum + 1, 0);

	//进行计数
	for (int i = 0; i < size; i++)
		bucket[arr[i]]++;

	//进行输出
	for (int i = 0; i < maxnum; i++) {
		while (bucket[i]) {
			cout << i << " ";
			bucket[i]--;
		}
	}
}

int main() {
	int arr[10] = { 2, 3, 1, 8, 5, 10, 4, 3, 9, 6 };
	int size = 10, maxnum = 10;
	bucketSort(arr, size, maxnum);
	return 0;
}

基数排序

基数排序是一种多关键字的排序算法,算法步骤如下:

  1. 找到数组中最大的元素,并获得其位数。
  2. 从个位开始到最大元素最高位,对每个数根据每个位进行排序。
  3. 每次排序完之后,都从对每个位排好序的数组中取出来连成一串,最终得到有序的序列。

直接上图:

假设初始数组为 { 10 , 278 , 109 , 63 , 930 , 589 , 184 , 505 , 269 , 8 , 83 } 。

第一步:获得数组元素中最大的元素即 930 ,计算其位数为 3 。

在这里插入图片描述

第二步:对每个元素的个数进行排序,下面遇到相同位置的元素我们用链表串起来,并且用尾插法进行插入。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第三步:个位数排序已完成,将数组中元素依次输出,连成一串。

在这里插入图片描述

第四步:对十位数进行排序。

在这里插入图片描述

第五步:十位数排序已完成,将数组中元素依次输出,连成一串。

在这里插入图片描述

第六步:对百位数进行排序。

在这里插入图片描述

第七步:百位数排序已完成,将数组中元素依次输出,连成一串。

在这里插入图片描述

至此,排序位数已经是最大数字的最大位数即 3 ,故排序结束,输出数组。

下面代码部分我们加入了对负数进行排序的操作,如果数组中存在负数,则将数组中所有元素先加上数组中最小负数的绝对值再进行基数排序(因为基数排序用的数组下标都必须大于零),最后输出的时候再减去最小负数的绝对值。

另外,我们对每一趟排序都进行了输出方便大家观察结果。

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

struct Node {
	int val = INT_MAX;
	Node *next = NULL;
};

int res, minx;
void Sort(int a[], Node *arr[]) {
	int cnt = 0;
	//将下标数组中的元素取出排成一列
	for (int i = 0; i < 10; i++) {
		Node *temp = arr[i]->next;
		while (temp != NULL) {
			a[cnt++] = temp->val;
			temp = temp->next;
		}
	}
	//将下标数组初始化
	for (int i = 0; i < 10; i++) {
		arr[i]->next = NULL;
	}
	//如果原数组中存在负数,则还原数组
	if (minx < 0) {
		cout << a[0] - res;
		for (int i = 1; i < cnt; i++)
			cout << " " << a[i] - res;
	} else {
		cout << a[0];
		for (int i = 1; i < cnt; i++)
			cout << " " << a[i];
	}
	cout << endl;
}

//桶排序
void BarrelSort(int a[], Node *arr[], int size, int flag) {
	int x = 1;
	//判断是对哪个位置进行排序
	while (flag != 1) {
		x *= 10;
		flag--;
	}
	//进行对应位置的排序
	for (int i = 0; i < size; i++) {
		Node *node = new Node;
		node->val = a[i];
		Node *temp;
		temp = arr[a[i] / x % 10];
		while (temp->next != NULL) {
			temp = temp->next;
		}
		node->next = temp->next;
		temp->next = node;
	}
	//输出排好序的桶
	for (int i = 0; i < 10; i++) {
		cout << i << ":";
		if (arr[i]->next == NULL) {
			cout << "NULL" << endl;
			continue;
		}
		Node *temp = arr[i]->next;
		while (temp != NULL) {
			cout << "->" << temp->val;
			temp = temp->next;
		}
		cout << "->^" << endl;
	}
	Sort(a, arr);
}

//基数排序
void cardinalSort(int a[], int size, int k) {
	Node *arr[10];	//根据下标大小存放节点
	//初始化数组
	for (int i = 0; i < 10; i++) {
		Node *node = new Node;
		arr[i] = node;
	}
	//对数字的每一位进行桶排序
	for (int i = 1; i <= k; i++)
		BarrelSort(a, arr, size, i);
}

int main() {
	int t;
	cin >> t;	//输入组数
	while (t--) {
		int n, k = 1;
		cin >> n;	//输出元素数量
		int *a = new int[n];
		minx = 0;
		//找到数组中最小的数
		for (int i = 0; i < n; i++) {
			cin >> a[i];
			if (a[i] < minx)
				minx = a[i];
		}
		//如果数组中最小的数为负数,则所有数都加上该最小数的绝对值,为了能够后续的基数排序
		if (minx < 0) {
			res = abs(minx);
			for (int i = 0; i < n; i++)
				a[i] = a[i] + res;
		}
		//找到数组中长度最大的数
		for (int i = 0; i < n; i++) {
			int temp = a[i], cnt = 0;
			while (temp) {
				cnt++;
				temp = temp / 10;
			}
			k = max(k, cnt);
		}
		cardinalSort(a, n, k);
		cout << endl;
	}
	return 0;
}

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

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我可以为您解答这个问题。 首先,堆排序、快速排序、归并排序计数排序都是常见的排序算法,它们的实现方式各不相同。下面分别介绍它们的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++实现,希望能对您有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值