前言
介绍几种常用的排序算法,自己写了部分代码
冒泡排序
介绍
冒泡排序是比较基础的排序算法之一,其思想是相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。
冒泡排序的步骤:、
1>比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2>每趟从第一对相邻元素开始,对每一对相邻元素作同样的工作,直到最后一对。
3>针对所有的元素重复以上的步骤,除了已排序过的元素(每趟排序后的最后一个元素),直到没有任何一对数字需要比较。
具体代码如下:
//冒泡排序
void Bubble_Sort(vector<int>& nums, int len) {
for (int i = 0; i < len; ++i) {
for (int j = 1; j < len - i; ++j) {
if (nums[j] < nums[j - 1]) {
swap(nums[j], nums[j - 1]);
}
}
Read_Vec(nums);
}
}
int main(){
vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
Bubble_Sort(nums, nums.size());
}
可以看一下执行过程,每次循环之后用Read_Vec()
函数进行打印输出:
优化
可以加入一个bool类型的判断标记,进行优化,如果已经有序,不需要进行循环:
//冒泡排序
void Bubble_Sort(vector<int>& nums, int len) {
for (int i = 0; i < len; ++i) {
for (int j = 1; j < len - i; ++j) {
bool flag = true;
if (nums[j] < nums[j - 1]) {
swap(nums[j], nums[j - 1]);
flag = false;
}
}
Read_Vec(nums);
if(flag){
break;
}
}
}
int main(){
vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
Bubble_Sort(nums, nums.size());
}
选择排序
介绍
从第一个元素开始跟剩余的所有元素比较,找出剩余元素中最小(最大)的元素位置,然后再交换开始元素位置与最小(最大)元素位置的值。(不断地选择剩余元素中的最小者)
选择排序的步骤:
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始(结尾)位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始(结尾)位置。
3.重复第二步,直到排序完毕。
void Select_Sort(vector<int>& nums, int len) {
int mid;
for (int i = 0; i < len - 1; ++i) {
mid = i;
for (int j = i + 1; j < len; ++j) {
if (nums[j] < nums[mid]) {
mid = j;
}
}
swap(nums[mid], nums[i]);
Read_Vec(nums);
}
}
int main() {
vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
Select_Sort(nums, nums.size());
}
同样看一下执行过程:
优化
可以同时一次找出最大值和最小值,这样就可以减少一半的遍历次数,
void Select_Sort(vector<int>& nums, int len) {
int Min, Max;
int right = len - 1;
for (int left = 0; left <= right; ++left, --right) {
Min = left, Max = left;
for (int j = left + 1; j <= right; ++j) {
if (nums[j] < nums[Min]) {
Min = j;
}
if (nums[j] > nums[Max]) {
Max = j;
}
}
if (Min == right && Max == left) {
swap(nums[Min], nums[Max]); //这时只需要交换一次,交换两次则没变化
}
else {
swap(nums[left], nums[Min]);
swap(nums[right], nums[Max]);
}
Read_Vec(nums);
}
}
插入排序
将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。算法适用于少量数据的排序。
类比打扑克牌,我们已经整理好手中的牌,每拿到一张新的牌,我们会把它插到适当的位置,使得整个序列仍然有序。
代码实现:
//插入排序
void Insert_Sort(vector<int>& nums, int len) {
for (int i = 1; i < len; ++i) {
for (int j = i; j > 0 && nums[j - 1] > nums[j]; --j)
swap(nums[j - 1], nums[j]);
Read_Vec(nums);
}
}
int main() {
vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
Insert_Sort(nums, nums.size());
}
具体过程:
希尔排序
希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
插入排序每次调整的步数为1,Shell排序步数则是从一个大的数字逐渐减小,调高效率:
具体代码:
//希尔排序
void Shell_Sort(vector<int>& nums, int len) {
int step = len / 2;
while (step > 0) {
for (int i = step; i < len; ++i) {
for (int j = i; j - step >= 0 && nums[j - step] > nums[j]; j -= step)
swap(nums[j], nums[j - step]);
Read_Vec(nums);
}
step /= 2;
}
}
int main() {
vector<int> nums = { 8,13,436,7,54,35,32,65,123,54,12};
Shell_Sort(nums, nums.size());
}
执行过程:
堆排序
堆排序视频讲解:
堆排序(heapsort)
讲解的十分细致到位,一看就懂!
代码实现:
void Heapify(vector<int>& nums, int n, int i) {
if (i >= n)
return;
int c1 = 2 * i + 1; //左孩子下标
int c2 = 2 * i + 2; //右孩子下标
int mid = i;
if (c1 < n && nums[c1] > nums[mid]) {
mid = c1;
}
if (c2 < n && nums[c2] > nums[mid]) {
mid = c2;
}
if (mid != i) {
swap(nums[mid], nums[i]);
Heapify(nums, n, mid);
}
}
void Build_Heap(vector<int>& nums, int len) {
int parent = (len - 1) / 2;
for (int i = parent - 1; i >= 0; --i) {
Heapify(nums, len, i);
}
}
void Heap_Sort(vector<int>& nums, int len) {
Build_Heap(nums, len);
cout << "建立完成大顶堆后的数字顺序:" << endl;
Read_Vec(nums);
cout << endl << "调整顺序:" << endl;
for (int i = len - 1; i >= 0; --i) {
swap(nums[i], nums[0]);
Heapify(nums, i, 0);
Read_Vec(nums);
}
}
测试相同的数据,观察结果:
快速排序
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
快速排序使用分治的思想,从待排序序列中选取一个记录的关键字为key,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字不大于key,另一部分记录的关键字不小于key,之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
快速排序算法的基本步骤为(从小到大):
1.选择关键字:从待排序序列中,按照某种方式选出一个元素作为 key 作为关键字(也叫基准)。
2.置 key 分割序列:通过某种方式将关键字置于一个特殊的位置,把序列分成两个子序列。此时,在关键字 key 左侧的元素小于或等于 key,右侧的元素大于 key(这个过程称为一趟快速排序)。
3.递归上述过程,直至排序结束
代码实现:
//快速排序
void Quick_Sort(vector<int>& nums, int left, int right) {
if (left >= right)
return;
int base = nums[left];
int i = left, j = right;
while (true) {
while (nums[j] >= base && i < j)
--j;
while (nums[i] <= base && i < j)
++i;
if (i >= j)
break;
swap(nums[i], nums[j]);
}
swap(nums[left], nums[i]);
Quick_Sort(nums, left, j - 1);
Quick_Sort(nums, j + 1, right);
}
测试过程:
基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
基数排序法是属于稳定性的排序,基数排序法是效率高的稳定性排序法。
基数排序(Radix Sort)是桶排序的扩展,它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体实现如下:
//基数排序
void Radix_Sort(vector<int>& nums, int len) {
int MaxData = INT_MIN;
for (int i = 0; i < len; ++i) {
MaxData = max(MaxData, nums[i]);
}
int radix = 1;
vector<int> temp(len, 0);
while (MaxData / radix > 0) {
int bucket[10] = {0};
for (int i = 0; i < len; ++i) {
bucket[nums[i] / radix % 10]++;
}
for (int i = 1; i < 10; ++i)
bucket[i] += bucket[i - 1];
for (int i = len - 1; i >= 0; --i) {
temp[bucket[nums[i] / radix % 10] - 1] = nums[i];
bucket[nums[i] / radix % 10]--;
}
for (int i = 0; i < len; ++i) {
nums[i] = temp[i];
}
radix *= 10;
Read_Vec(nums);
}
}
测试同上的数据,执行过程如下:
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
简单的来说,归并排序主要分为三步,一是对数组的划分,二是对数组的排序,三是对数组的合并。划分的大小是可以随自己的想法而设置,但是一般都是以2为单位,这样最小的一组的排序就比较方便。
具体代码实现:
void Merge(vector<int>& nums, vector<int>& temp,
int left_begin, int right_begin, int right_end) {
int left_end = right_begin - 1;
int len = right_end - left_begin + 1;
int i = left_begin;
while (left_begin <= left_end && right_begin <= right_end) {
if (nums[left_begin] <= nums[right_begin]) {
temp[i++] = nums[left_begin++];
}
else {
temp[i++] = nums[right_begin++];
}
}
while (left_begin <= left_end)
temp[i++] = nums[left_begin++];
while (right_begin <= right_end)
temp[i++] = nums[right_begin++];
for (int i = 0; i < len; ++i, right_end--) {
nums[right_end] = temp[right_end];
}
}
void M_Sort(vector<int>& nums, vector<int>& temp, int left, int right) {
int mid;
if (left < right) {
mid = (right - left) / 2 + left;
M_Sort(nums, temp, left, mid);
M_Sort(nums, temp, mid + 1, right);
Merge(nums, temp, left, mid + 1, right);
Read_Vec(nums);
}
}
void Merge_Sort(vector<int>& nums, int len) {
vector<int> temp(nums.size(), 0);
M_Sort(nums, temp, 0, len - 1);
}
测试过程:
总结
常用排序算法小结,各个时间复杂度,空间复杂度如图,在不同的场景选择最合适的排序方式。