
【春招】十大经典排序算法
发布日期:2021-05-10 06:33:46
浏览次数:27
分类:原创文章
本文共 7501 字,大约阅读时间需要 25 分钟。
文章目录
1、冒泡排序
1.1 算法思路
- 比较相邻的元素,如果第一个比第二个大,那么就交换这两个元素。
- 从第一对到最后一对的比较,那么最后的元素就是最大的,重复上述的步骤。
1.2 代码实现
// 冒泡排序void BubbleSort(vector<int> & nums){ int len = nums.size(); for (int i = 0; i < len - 1; ++i) { for (int j = 0; j < len - 1 - i; ++j) { if (nums[j] > nums[j + 1]) { swap(nums[j], nums[j + 1]); } } }}
1.3 冒泡排序优化
1.4 复杂度
- 时间复杂度
- 最坏:O(n²)
- 最好:O(n)
- 空间复杂度
- O(1)
1.5 优缺点
- 优点
- 简单,稳定
- 缺点
- 时间复杂度过大
2、选择排序
2.1 算法思路
- 首先在序列中找到最大(小)的元素,放到排序序列的起始位置。
- 在从剩余未排序的元素中寻找最大(小)的元素放到已排序序列的末尾
2.2 代码实现
// 选择排序void SelectSort(vector<int>& nums){ int len = nums.size(); for (int i = 0; i < len - 1; i++) { int maxPos = 0; // 找到的最大值的位置 for (int j = 0; j < len - i; j++) { if (nums[j] > nums[maxPos]) maxPos = j; } if (maxPos != len - 1 - i) swap(nums[len - i - 1], nums[maxPos]); }}
2.3 算法分析
- 无论什么数据进去时间复杂度都是O(n²),所以用它的时候数据规模越小越好。
- 唯一的好处是不占用额外的空间
时间复杂度:
- 最坏 / 优:O(n²)
空间复杂度:
- O(1)
2.4 选择排序优化
3、插入排序
3.1 算法思路
- 从第一个元素开始,第一个元素被认为是排序好的
- 取出下一个元素,从已经排好序的序列中从前开始比较
- 如果大于该元素大于新元素,将该元素移动到下一个位置,否则重复这个过程,直到找到小于等于新元素的位置
- 然后将新元素插入到该位置
3.2 代码实现
// 插入排序void InsertSort(vector<int>& nums){ int len = nums.size(); for (int i = 1; i < len; ++i) { int tmp = nums[i]; int end = i - 1; while (end >= 0 && tmp < nums[end]) { nums[end + 1] = nums[end]; end--; } nums[end + 1] = tmp; }}
3.3 算法分析
插入排序在实现上,空间复杂度O(1),在向前扫描的过程中,需要反复把排序元素向后搬移。
3.4 链接
4、希尔排序
4.1 算法思路
- 将整个待排序的序列分割成若干个子序列分别进行直接插入排序
- 每趟排序,将待排序分割成若干长度为m的子序列,分别对各子表进行直接插入排序。
4.2 代码实现
// 希尔排序void ShellSort(vector<int>& nums){ int gap = 3; int len = nums.size(); while (gap >= 1) { for (int i = gap; i < len; ++i) { int tmp = nums[i]; int end = i - gap; while (end >= 0 && tmp < nums[end]) { nums[end + gap] = nums[end]; end -= gap; } nums[end + gap] = tmp; } gap--; }}
4.3 算法分析
希尔排序的核心在于间隔序列的设定。既可以提前设置间隔序列,也可以动态的定义间隔序列。
4.4 链接
5、归并排序
5.1 算法思路
- 将已有序的子序列合并,得到完全有序的序列。
- 先将每个子序列有序,再使子序列段间有序
- 若将两个有序表合并成一个有序表,那么称之为二路归并
5.2 代码实现
// 归并排序void MergeData(int *nums, int left, int mid, int right, int* temp){ // 先将数组分为两个部分,一个从头开始,一个从中间开始 int begin1 = left, end1 = mid; int begin2 = mid, end2 = right; // index表示temp的下标 int index = left; // 循环将小的放到temp中 while (begin1 < end1 && begin2 < end2) { if (nums[begin1] > nums[begin2]) temp[index++] = nums[begin2++]; else temp[index++] = nums[begin1++]; } // 处理还没有排序的数字 while (begin1 < end1) temp[index++] = nums[begin1++]; while (begin2 < end2) temp[index++] = nums[begin2++];}void _MergeSort(int* nums, int left, int right, int* temp){ int mid = left + ((right - left) >> 1); // 采用递归的方式 if (right - left > 1) { _MergeSort(nums, left, mid, temp); _MergeSort(nums, mid, right, temp); MergeData(nums, left, mid, right, temp); // 这里一定要加上left,因为你排了左边,还有右边要排,右边的下标也得加上left memcpy(nums + left, temp + left, (right - left)*sizeof(nums[0])); }}void MergeSort(int* nums, int size){ // 开辟一个辅助空间 int* temp = (int*)malloc(sizeof(nums[0] * size)); // 空间检测是否开辟成功 if (NULL == temp) { assert(0); return; } _MergeSort(nums, 0, size, temp); //free(temp);}
5.3 算法分析
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好得多,因此时间复杂度始终是O(nlogn)。需要大量的额外的空间。
5.4 链接
6、快速排序
6.1 算法思路
- 通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键比另一部分的关键字小,然后对这两部分记录继续进行排序
6.2 代码实现
int Partion1(int* array, int left, int right){ int begin = left; int end = right - 1; //设置基准值,取数组最右侧的元素 int key = array[end]; while (begin < end) { //寻找大于基准值的数 while (begin < end && array[begin] <= key) begin++; while (begin < end && array[end] >= key) end--; if (begin < end) Swap(&array[begin], &array[end]); } //这里将基准值和begin(end)交换 if (begin != right - 1) Swap(&array[begin], &array[right - 1]); return begin;//此时应该返回begin,key只是值(这里是下标操作)}
6.3 快速排序优化
6.4 快速排序特点
优点:
- 速度快,平均性能好,O(nlogn)
缺点:
- 不稳定,初始序列有序或基本有序,O(n²)
空间复杂度:
- 递归:O(nlogn)
7、堆排序
7.1 算法思路
- 利用堆是一个近似完全二叉树的结构
- 将堆顶元素和最后一个元素交换,然后对于违反堆的性质的区域进行调整
7.2 代码实现
// 堆排序void Adjust(vector<int> & nums, int len, int index){ int left = 2 * index + 1; int right = 2 * index + 2; int maxIdx = index; if (left < len && nums[left] > nums[maxIdx]) maxIdx = left; if (right < len && nums[right] > nums[maxIdx]) maxIdx = right; if (maxIdx != index) { swap(nums[maxIdx], nums[index]); Adjust(nums, len, maxIdx); }}void HeapSort(vector<int>& nums){ int len = nums.size(); // 从最后一个非叶子节点开始向上调整 for (int i = len / 2 - 1; i >= 0; --i) { Adjust(nums, len, i); } // 调整大堆 for (int i = len - 1; i >= 1; --i) { swap(nums[0], nums[i]); // 调整违反堆性质的区域 Adjust(nums, i, 0); }}
7.3 堆排序优缺点
优点:
- 堆排序效率稳定,不像快排在极端情况下是O(n²),不管有序还是不有序,时间复杂度都是O(nlogn)
- 堆排序,快速排序,归并排序都达到了比较排序算法的峰值
- 高效并且节省空间,空间复杂度O(1)
缺点:
- 堆排序最大的缺点就是堆的维护问题。
- 当数据是频繁的发生变化时,每次都要更新待排序序列,我们做一遍堆的维护,才能保证它的特性。
7.4 堆的实现
8、计数排序
8.1 算法思路
- 将输入的数据转化成键存储在额外开辟的数组空间。
- 找出待排序列的数组中最大和最小的元素
- 统计每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
8.2 代码实现
void countSort(int* nums, int size){ // 1.找到最大的值 int max = nums[0]; for (int i = 0; i < size; ++i) { if (nums[i] > max) max = nums[i]; } // 2.确定数组的长度并进行初始化 int* countData = new int[max + 1]; for (int i = 0; i <= max; ++i) { countData[i] = 0; } // 3.遍历数组,统计每个数字的次数 for (int i = 0; i < size; ++i) ++countData[nums[i]]; // 4.累计输出数字 int index = 0; for (int i = 0; i <= max; ++i) { for (int j = 0; j < countData[i]; ++j) { nums[index++] = i; } }}
8.3 算法分析
计数排序是一个稳定的排序算法。当输入的元素是n个0到k之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何排序算法。当k不是很大且序列比较集中时,计数排序是一个很有效的排序算法。
对于计数排序的空间复杂度,为了不污染原数组,可以开辟新的数组来保存最后排序的结果,所以空间复杂度是O(n+k),k表示开辟的辅助空间的大小。
9、桶排序
9.1 算法思路
- 桶排序是计数排序的升级版。
- 计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素。
- 通过映射函数,将待排序数组中的元素映射到各个对应的桶中。
- 对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中。
9.2 代码实现
#include <iostream>#include <vector>#include <limits.h>#include <algorithm>void BucketSort(std::vector<int>& vec){ int len = vec.size(); int Max = INT_MIN; int Min = INT_MAX; // 1.计算出最大最小值 for(int i = 0; i < len; ++i) { if(vec[i] > Max){ Max = vec[i]; } if(vec[i] < Min){ Min = vec[i]; } } // 2. 计算出桶的数量 int bucketnum = (Max - Min) / len + 1; std::vector<std::vector<int>> temp(bucketnum); // 3. 将元素放入桶内 for(int i = 0; i < len; ++i) { // 计算出该数字在哪个桶中 int num = (vec[i] - Min) / bucketnum; temp[num].push_back(vec[i]); } // 4. 对桶内的元素进行排序 for(int i = 0; i < bucketnum; ++i){ sort(temp[i].begin(), temp[i].end()); } // 5. 将桶中的元素赋值给原序列 int index = 0; for(int i = 0; i < bucketnum; ++i) { int knum = temp[i].size(); int j = 0; while(j < knum) { vec[index++] = temp[i][j++]; } }}
9.3 算法分析
桶排序最好情况下使用线形时间O(n),桶排序的时间复杂度取决于各个桶之间数据进行排序的时间复杂度,因此其他部分的时间复杂度都为O(n)。很显然,桶划分越小,各个桶之间的数据越少,排序所用的时间也就越少。
10、基数排序
10.1 算法思路
- 基数排序是按照低位优先,然后收集,在按照高位排序,然后在收集,以此类推,知道最高位。
10.2 代码实现
// 基数排序int maxbit(int data[], int n){ int d = 1; //保存最大的位数 int p = 10; for (int i = 0; i < n; ++i) { while (data[i] >= p) { p *= 10; ++d; } } return d;}void radixsort(int data[], int n) //基数排序{ int d = maxbit(data, n); int tmp[n]; int count[10]; //计数器 int i, j, k; int radix = 1; for (i = 1; i <= d; i++) //进行d次排序 { for (j = 0; j < 10; j++) count[j] = 0; //每次分配前清空计数器 for (j = 0; j < n; j++) { k = (data[j] / radix) % 10; //统计每个桶中的记录数 count[k]++; } for (j = 1; j < 10; j++) count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶 for (j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中 { k = (data[j] / radix) % 10; tmp[count[k] - 1] = data[j]; count[k]--; } for (j = 0; j < n; j++) //将临时数组的内容复制到data中 data[j] = tmp[j]; radix = radix * 10; }}
10.3算法分析
基数排序基于分别排序,分别收集,所以是最稳定的。但基数排序的性能略差,每一次关键字都需要O(n)的时间复杂度,而且新的分配之后也需要O(n)。时间复杂度O(n+k),其中k为桶的数量。
发表评论
最新留言
网站不错 人气很旺了 加油
[***.192.178.218]2025年05月08日 17时08分04秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
二改广告横幅在线制作源码 美化版
2019-03-14
炫彩文字404动画页面源码
2019-03-14
服饰贴图定制小程序V1.2.4安装更新一体包+小程序前端
2019-03-14
一款好看新颖的404页面源码
2019-03-14
创意沙雕黑色蝙蝠侠/小丑动态404页面源码
2019-03-14
使用Mac OS X如何开启和配置防火墙
2019-03-14
格式化Mac硬盘---DoYourData Super Eraser安全、快速
2019-03-14
MacOS磁盘分区出错的解决办法
2019-03-14
MacOS 应对系统无响应的方法
2019-03-14
使用KeyShot调整一个场景中的照明亮度
2019-03-14
Mac隐藏辅助功能|自定义苹果Mac显示器
2019-03-14
ActivityNotFoundException异常错误
2019-03-14
socket 乱码解决
2019-03-14
elasticsearch 不能root启动
2019-03-14
git远程仓库切换
2019-03-14
国芯网国产芯片精选月刊V20190801 国产芯片 芯片选型 芯片厂家
2019-03-14
华大芯片调试问题
2019-03-14
DCMTK:存储服务类用户(C-STORE操作)
2019-03-14
带照片捕捉功能的ESP32-CAM PIR运动检测器
2019-03-15