【10种排序算法 (C++实现)】


前言

算法学习中, 这里给出了常用的10种排序算法的C++代码.


一、冒泡排序

辅助使用的函数以及头文件::

#include <iostream>
#include <vector>
#include <algorithm>

void show(const std::vector<int> vec) {
	for (auto x : vec) {
		std::cout << x << " ";
	}
	std::cout << std::endl;
}

代码如下:

/*
* 1. 冒泡排序(Bubble-Sort)
* 是一种很简单的排序算法, 重复走访排列对象, 每次进行两个元素的比较, 如果顺序错误, 则交换两个元素的位置. 
* 指导没有可进行交换的两个元素, 表示排序完成. 类比大小气泡的上浮与下沉.
* 算法描述:
* a. 比较相邻的两个元素, 如果前一个比后一个大, 就交换这两个元素的位置;
* b. 对每一对相邻元素作同样的操作, 从最开始的一对到结尾的一对, 这样操作之后, 最后的一个元素一定是最大的元素.
* c. 去除最后一个元素, 对剩余所有元素重复以上步骤.
* d. 直到排序完成.
*/
class BubbleSort {
public:
	void operate(std::vector<int>& vec) {
		int len = vec.size();
		int count = 0;
		for (int i = 0; i < len - 1; ++i) {
			for (int j = 0; j < len - 1 - i; ++j) {
				if (vec[j] > vec[j + 1]) {
					std::swap(vec[j], vec[j + 1]);
					count++;
					std::cout << "第 " << count <<"\t 次操作后: ";
					show(vec);
				}
			}
		}
	}
};

二、选择排序

/*
* 2. 选择排序(Selection-Sort)
* 是一种简单直观的排序算法. 工作原理: 首先对排序对象进行遍历, 寻找其中最小的元素, 然后将最小元素与序列开始的元素交换位置.
* 这时开始位置的元素已经确定, 剔除该元素, 重复之前的寻找最小元素的操作, 直到序列长度变为1.
* 需进行n-1次遍历.
* 算法分析: 表现最稳定的算法之一, 因为无论处理什么序列都是O(n^2)的时间复杂度, 数据规模越小用起来越方便, 并且不会占用额外的内存空间.
*/
class SelectionSort {
public:
	void operate(std::vector<int>& vec) {
		int len = vec.size();
		int count = 0;
		for (int i = 0; i < len; ++i) {
			int minIndex = i;
			for (int j = i + 1; j < len; ++j) {
				if (vec[j] < vec[minIndex]) {
					minIndex = j;
				}
			}
			std::swap(vec[minIndex], vec[i]);
			count++;
			std::cout << "第 " << count << "\t 次操作后: ";
			show(vec);
		}
	}
};

三、插入排序

/*
* 3. 插入排序(Insertion-Sort)
* 描述是一种简单直观的排序算法. 原理是通过构建有序序列, 对于未排序的元素, 在已排序序列中从后向前扫描, 找到相应位置并插入.
* 算法描述:
* 从第一个元素开始, 该元素可以认为已经被排序;
* 取出下一个元素, 在已经排序好的元素序列中从后向前扫描;
* 如果该元素(已排序)大于新元素, 将该元素移至下一位置;
* 重复上一步骤, 知道找到已排序的元素小于或者等于新元素的位置;
* 将新元素插入到该位置之后;
* 重复上述步骤.
*/
class InsertionSort {
public:
	void operate(std::vector<int>& vec) {
		int len = vec.size();
		int count = 0;
		int preIndex = 0, current = 0;
		for (int i = 0; i < len; ++i) {
			preIndex = i - 1;
			current = vec[i];
			while (preIndex >= 0 && vec[preIndex] > current) {
				vec[preIndex + 1] = vec[preIndex];
				preIndex--;
			}
			vec[preIndex + 1] = current;
			count++;
			std::cout << "第 " << count << "\t 次操作后: ";
			show(vec);
		}
	}
};

四、希尔排序

/*
* 4. 希尔排序(Shell-Sort)
* 1959年Shell发明, 第一个突破O(n^2)的排序算法, 是简单插入排序的改进版.
* 与插入排序的不同之处在于, 它会优先比较距离较远的元素. 
* 又称缩小增量排序.
*/
class ShellSort {
public:
	void operate(std::vector<int>& vec, int increment) {
		int len = vec.size();
		int count = 0;
		int preIndex = 0, current = 0;
		int _increment = increment;
		while (_increment != 0) {
			for (int i = 0; i < _increment; ++i) {
				for (int j = i; j < len; j += _increment) {
					preIndex = j - _increment;
					current = vec[j];
					while (preIndex >= 0 && vec[preIndex] > current) {
						vec[preIndex + _increment] = vec[preIndex];
						preIndex -= _increment;
					}
					vec[preIndex + _increment] = current;
					count++;
					std::cout << "第 " << count << "\t 次操作后: ";
					show(vec);
				}
			}
			_increment /= 2;
		}
	}
};

五、归并排序

/*
* 5. 归并排序(Merge-Sort)
* 归并排序是建立在归并操作上的一种有效的排序算法. 该算法是采用分治法(Divide and Conquer) 的一个非常典型的应用. 
* 将已有序的子序列合并, 得到完全有序的序列; 即先使每个子序列有序, 再使子序列段间有序. 
* 若将两个有序表合并成一个有序表, 称为2-路归并.
* 算法描述:
* 把长度为n的输入序列分成2个长度为n/2的子序列.
* 对这两个子序列分别采用归并排序.
* 将两个排序好的子序列合并成一个最终的排序序列.
* 算法分析:
* 归并排序是一种稳定的排序方法. 和选择排序一样, 归并排序的性能不受输入数据的影响,
* 但表现比选择排序好得多, 因为时钟都是O(nlog2(n))的时间复杂度. 代价是需要额外的内存空间.
*/
class MergeSort {
public:
	int count = 0;
	void operate(std::vector<int>& vec) {
		int len = vec.size();
		this->sort(vec, 0, len - 1);
	}
private:
	void sort(std::vector<int>& vec, int left, int right) {
		if (left < right) {
			int mid = left + (right - left) / 2;
			this->sort(vec, left, mid);
			this->sort(vec, mid + 1, right);
			this->merge(vec, left, mid, right);
		}
	}
	void merge(std::vector<int>& vec, int left, int mid, int right) {
		std::vector<int> temp(right - left + 1);
		int i = left, j = mid + 1, k = 0;
		while (i <= mid && j <= right) {
			temp[k++] = vec[i] <= vec[j] ? vec[i++] : vec[j++];
		}
		while (i <= mid)
			temp[k++] = vec[i++];

		while (j <= right)
			temp[k++] = vec[j++];

		for (k = 0, i = left; i <= right; ++i, ++k)
			vec[i] = temp[k];
		count++;
		std::cout << "第 " << count << "\t 次操作后: ";
		show(vec);
	}
};

六、快速排序

/*
* 6. 快速排序(Quick-Sort)
* 基本思想: 通过一趟排序将待排记录分割成独立的两部分, 其中一部分记录的关键字均比另一部分的关键字小,
* 则可分别对这两部分记录继续进行排序, 以达到整个序列有序.
* 算法描述:
* 快速排序使用分治法来把一个串(list)分成两个子串(sub-lists). 具体做法如下:
* 从数列中挑出一个元素, 称为"基准"(pivot)。
* 重新排序数列, 所有元素比基准值小的摆放在基准前面, 所有元素比基准值大的摆放在基准的后面.
* 相同的元素无所谓. 
* 在这个分区退出之后, 该基准就处于数列的中间位置. 这个称为分区(partition)操作.
* 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序.
*/
class QuickSort {
public:
	int count = 0;
	void operate(std::vector<int>& vec) {
		int len = vec.size();
		this->quickSort(vec, 0, len - 1);
	}
private:
	void quickSort(std::vector<int>& vec, int start, int end) {
		if (start >= end) return;
		int mid = partition(vec, start, end);

		this->quickSort(vec, start, mid - 1);
		this->quickSort(vec, mid + 1, end);
	}
	int partition(std::vector<int>& vec, int start, int end) {
		int pivot = vec[end]; // 选择最右边的元素作为基准值
		int left = start - 1;
		for (int i = start; i <= end - 1; ++i) {
			// 如果当前元素小于或者等于基准值
			if (vec[i] <= pivot) {
				++left;
				std::swap(vec[left], vec[i]);
			}
		}
		std::swap(vec[left + 1], vec[end]);

		count++;
		std::cout << "第 " << count << "\t 次操作后: ";
		show(vec);

		return left + 1;
	}
};

七、堆排序

/*
* 7. 堆排序(Heap-Sort)
* 堆排序是指利用堆这种数据结构所设计的一种排序算法. 
* 堆积是一个近似完全二叉树的结构, 并同时满足堆积的性质:
* 子节点的键值或索引总是小于或大于它的父节点.
*/
class HeapSort {
public:
	int count = 0;
	void operate(std::vector<int>& vec) {
		int heapSize = vec.size();
		this->buildHeap(vec, heapSize);

		for (int i = vec.size() - 1; i > 0; --i) {
			std::swap(vec[0], vec[i]);
			heapSize--;
			this->heapify(vec, 0, heapSize);
		}
	}
private:
	void buildHeap(std::vector<int>& vec, int heapSize) {
		for (int i = heapSize / 2 - 1; i >= 0; --i) {
			this->heapify(vec, i, heapSize);
		}
	}
	void heapify(std::vector<int>& vec, int idx, int heapSize) {
		int largest = idx;
		int leftChild = 2 * idx + 1;
		int rightChild = 2 * idx + 2;
		if (leftChild < heapSize && vec[leftChild] > vec[largest]) {
			largest = leftChild;
		}
		if (rightChild < heapSize && vec[rightChild] > vec[largest]) {
			largest = rightChild;
		}
		if (largest != idx) {
			std::swap(vec[idx], vec[largest]);
			this->heapify(vec, largest, heapSize);
		}

		count++;
		std::cout << "第 " << count << "\t 次操作后: ";
		show(vec);
	}
};

八、计数排序

/*
* 8. 计数排序(Counting-Sort)
* 计数排序不是基于比较的排序算法, 其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中.
* 作为一种线性时间复杂度的排序, 计数排序要求输入的数据必须是有确定范围的整数.
* 算法描述:
* 找出待排序的数组中的最大元素和最小元素.
* 统计数组中每个值为i的元素出现的次数, 存入数组C的第i项.
* 对所有的计数累加(从C中的第一个元素开始, 每一项和前一项相加).
* 反向填充目标数组: 将每个元素i放在新数组的第C(i)项, 每放一个元素就将C(i)减去1.
* 计数排序是一个稳定的排序算法. 
* 当输入的元素是n个 0-k 之间的整数时, 时间复杂度是O(n+k), 空间复杂度也是O(n+k),
* 其排序速度快于任何比较排序算法. 适用于k不是很大并且序列元素较为集中的情况.
*/
class CountingSort {
public:
	int opCount = 0;
	void operate(std::vector<int>& vec) {
		// 寻找最大最小元素
		int max = *std::max_element(vec.begin(), vec.end());
		int min = *std::min_element(vec.begin(), vec.end());

		int range = max - min + 1;

		std::vector<int> count(range, 0), output(vec.size(), 0);

		std::vector<int>::iterator it;

		for (int i = 0; i < vec.size(); ++i) {
			count[vec[i] - min]++;
		}
		int index = 0;
		for (int i = 0; i < count.size(); ++i) {
			while (count[i] > 0) {
				vec[index++] = i + min;

				this->opCount++;
				std::cout << "第 " << this->opCount << "\t 次操作后: ";
				show(vec);

				count[i]--;
			}
		}
	}
};

九、桶排序

/*
* 9. 桶排序(Bucket-Sort)
* 桶排序是计数排序的升级版. 
* 它利用了函数的映射关系, 高效与否的关键就在于这个映射函数的确定.
* 桶排序的工作原理:
* 假设输入数据服从均匀分布, 将数据分到有限数量的桶里, 每个桶再分别排序.
* 对每个桶的排序可以使用任意的排序算法, 亦或是仍然使用桶排序(递归).
* 算法描述:
* 设置一个定量的数组当作空桶.
* 遍历输入数据, 并且把数据一个一个放入对应的桶中.
* 对非空的桶分别进行排序.
* 将每个已排序好的非空桶中的数据拼接.
*/
class BucketSort {
public:
	void operate(std::vector<int>& vec, int r) {
		if (vec.empty() || r < 1) return;
		
		int minValue = *std::min_element(vec.begin(), vec.end());
		int maxValue = *std::max_element(vec.begin(), vec.end());

		int numBuckets = (maxValue - minValue) / r + 1;
		std::vector<std::vector<int>> buckets(numBuckets, std::vector<int>(0));

		for (auto i : vec) {
			int bucketIndex = (i - minValue) / r;
			buckets[bucketIndex].push_back(i);
		}

		for (auto& bucket : buckets) {
			std::sort(bucket.begin(), bucket.end());
		}

		vec.clear();
		for (const std::vector<int>& bucket : buckets) {
			for (int value : bucket) {
				vec.push_back(value);
			}
		}
	}
};

十、基数排序

/*
* 10. 基数排序(Radix-Sort)
* 基数排序是按照低位先排序, 然后收集.
* 再按照高位排序, 然后收集, 依次类推, 直到最高位.
* 算法描述:
* 取得数组中的最大数, 并取得位数.
* 原始数组, 从最低位开始取每个位组成radix数组.
* 对radix进行计数排序(利用计数排序适用于小范围数的特点).
*/
class RadixSort {
public:
	void operate(std::vector<int>& vec) {
		int minValue = *std::min_element(vec.begin(), vec.end());
		int maxValue = *std::max_element(vec.begin(), vec.end());
		int len = vec.size();

		int d = 0;
		while (maxValue > 0) {
			maxValue /= 10;
			++d;
		}

		std::vector<std::vector<int>> q(10, std::vector<int>(len, 0));
		std::vector<int> count(len, 0);

		for (int i = 0; i < d; ++i) {
			for (int j = 0; j < len; ++j) {
				int key = vec[j] % static_cast<int>(std::pow(10, i + 1)) / static_cast<int>(std::pow(10, i));
				q[key][count[key]++] = vec[j];
			}

			int c = 0;
			for (int j = 0; j < 10; ++j) {
				for (int l = 0; l < count[j]; ++l) {
					vec[c++] = q[j][l];
					q[j][l] = 0;
				}
				count[j] = 0;
			}
		}

	}
};

使用案例

#include <iostream>
#include <vector>
#include <algorithm>

// 这里定义你需要排序的序列
std::vector<int> vec = {
	4,2,5,6,3,1,4,6,9,8,4,6,2
};

int main(int argc, char** argv)
{
	std::cout << "排序对象: ";
	show(vec);

	// 这里处理排序

	// 以快速排序为例
	QuickSort* sort = new QuickSort();
	sort->operate(vec);

	std::cout << "排序后的结果: ";
	show(vec);

	return 0;
}

总结

/*
* 排序算法可以归为两大类: 比较排序和非比较排序
* 1. 比较排序: 
* 交换排序(冒泡排序+快速排序)
* 插入排序(简单插入排序+希尔排序)
* 选择排序(简单选择排序+堆排序)
* 归并排序(二路归并排序+多路归并排序)
* 2. 非比较排序:
* 计数排序+桶排序+基数排序
* 不同排序的算法复杂度
* 1. 插入排序: 平均时间复杂度O(n^2), 最坏时间复杂度O(n^2), 最好时间复杂度O(n), 空间复杂度O(1), 稳定
* 2. 希尔排序: 平均时间复杂度O(n^1.3), 最坏时间复杂度O(n^2), 最好时间复杂度O(n), 空间复杂度O(1), 不稳定
* 3. 选择排序: 平均时间复杂度O(n^2), 最坏时间复杂度O(n^2), 最好时间复杂度O(n), 空间复杂度O(1), 不稳定
* 4. 堆排序: 平均时间复杂度O(n*log2(n)), 最坏时间复杂度O(n*log2(n)), 最好时间复杂度O(n*log2(n)), 空间复杂度O(1), 不稳定
* 5. 冒泡排序: 平均时间复杂度O(n^2), 最坏时间复杂度O(n^2), 最好时间复杂度O(n), 空间复杂度O(1), 稳定
* 6. 快速排序: 平均时间复杂度O(n*log2(n)), 最坏时间复杂度O(n^2), 最好时间复杂度O(n*log2(n)), 空间复杂度O(nlog2(n)), 不稳定
* 7. 归并排序: 平均时间复杂度O(n*log2(n)), 最坏时间复杂度O(n*log2(n)), 最好时间复杂度O(n*log2(n)), 空间复杂度O(n), 稳定
* 8. 计数排序: 平均时间复杂度O(n+k), 最坏时间复杂度O(n+k), 最好时间复杂度O(n+k), 空间复杂度O(n+k), 稳定
* 9. 桶排序: 平均时间复杂度O(n+k), 最坏时间复杂度O(n^2), 最好时间复杂度O(n), 空间复杂度O(n+k), 稳定
* 10. 基数排序: 平均时间复杂度O(n*k), 最坏时间复杂度O(n*k), 最好时间复杂度O(n*k), 空间复杂度O(n+k), 稳定
* 相关概念:
* 稳定表示排序前相等元素的相对位置不会在排序之后调换, 不稳定反之.
* 时间复杂度: 对排序数据的总的操作次数, 反映操作量随n的变化趋势.
* 空间复杂度: 算法完成任务时需要开计算机开辟内存的度量, 也是n的函数.
*/
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值